feat: add unified command result framework core

This commit is contained in:
玉澜 committed 2026-08-10 12:22:46 +08:00
1 parent cf3bcb380f
commit 2dd067562e
40 files changed
+9463 -74

No files matched your search

@@ -0,0 +1,274 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
// Phase C 收尾(B41/B42;契约规范 §5.2)。落盘策略:轮8裁决⑩新文件——
// 不编辑 emitter_phase_c_test.go 既有断言。
//
// 既有覆盖对照:raw≠json 逐字节差异与 raw=writeRaw(data) 透传已由
// TestRawPassThroughEnvelopeDiffersFromJSONByteForByte 覆盖;jq 优先于
// -f table 已由 TestJQOverridesFormatAndEvaluatesFullEnvelope 覆盖。
// 本文件补全:raw 适用域(仅 stdout 数据通道)+ raw×fields / raw×jq
// 穷举断言(B41),以及 jq × 全六格式穷举 + jq 对 fields 的优先级(B42)。
import (
"bytes"
"encoding/json"
"strings"
"testing"
)
// --- B41:-f raw 透传完整语义——适用域穷举 ---
func TestRawDispatchScopeIsStdoutDataChannelOnly(t *testing.T) {
// §5.2 分发矩阵的适用域仅 stdout 数据通道(轮8裁决⑪):携带数据载荷的
// outcome(success/pending/partial_failure)在 raw 下透传 data;failure
// 信封即使被配置为 raw 也恒以完整 JSON 走 stderr、stdout 严格零字节。
cases := []struct {
name string
env *Envelope
}{
{"success", phaseCListFixture()},
{"pending", newRawPendingFixture()},
{"partial_failure", newRawPartialFixture(t)},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var stdout, stderr bytes.Buffer
em := NewEmitter(&stdout, &stderr, FormatRaw, "", "")
if err := em.Emit(c.env); err != nil {
t.Fatalf("Emit raw: %v", err)
}
if stdout.Len() == 0 {
t.Fatal("data-carrying envelope under raw must emit on stdout")
}
if stderr.Len() != 0 {
t.Fatalf("data channel must keep stderr empty, got %d bytes: %s", stderr.Len(), stderr.String())
}
// raw 输出不含信封外壳键(透传的是 data 载荷本身)。
if strings.Contains(stdout.String(), `"outcome"`) {
t.Fatalf("raw must pass through data only, no envelope wrapper: %s", stdout.String())
}
// 透传等价于 writeRaw(data)(与分发点单一渲染一致)。
var direct bytes.Buffer
if err := Write(&direct, FormatRaw, c.env.Data); err != nil {
t.Fatalf("Write(raw, data): %v", err)
}
if !bytes.Equal(stdout.Bytes(), direct.Bytes()) {
t.Fatalf("raw stdout must be verbatim writeRaw(data):\n got: %s\nwant: %s", stdout.String(), direct.String())
}
})
}
// failure 信封:raw 配置被绕过(轮8裁决⑪)——stderr 完整 JSON、stdout 零。
t.Run("failure bypasses raw", func(t *testing.T) {
failure := NewFailureEnvelope(&ErrorInfo{Type: "api", Code: 40001, Message: "boom"})
var stdout, stderr bytes.Buffer
em := NewEmitter(&stdout, &stderr, FormatRaw, "id", ".data")
if err := em.Emit(failure); err != nil {
t.Fatalf("Emit raw failure: %v", err)
}
if stdout.Len() != 0 {
t.Fatalf("failure must leave stdout empty under any format, got %d bytes", stdout.Len())
}
var decoded map[string]any
if err := json.Unmarshal(stderr.Bytes(), &decoded); err != nil {
t.Fatalf("failure under raw must stay full JSON on stderr: %v\n%s", err, stderr.String())
}
if decoded["outcome"] != "failure" || decoded["error"] == nil {
t.Fatalf("failure envelope lost wire-stable fields under raw bypass: %s", stderr.String())
}
})
// 分发点层同判:WriteEnvelopeTo(无流路由签名)下 failure + raw 也恒为
// 完整 JSON 信封——raw 的适用域不含失败通道。
t.Run("dispatch point also bypasses raw for failure", func(t *testing.T) {
failure := NewFailureEnvelope(&ErrorInfo{Type: "validation", Param: "--name"})
var buf bytes.Buffer
if err := WriteEnvelopeTo(&buf, failure, FormatRaw, "", ""); err != nil {
t.Fatalf("WriteEnvelopeTo failure raw: %v", err)
}
var decoded map[string]any
if err := json.Unmarshal(buf.Bytes(), &decoded); err != nil {
t.Fatalf("failure under raw at dispatch point must be full JSON: %v\n%s", err, buf.String())
}
if decoded["outcome"] != "failure" {
t.Fatalf("outcome = %v: %s", decoded["outcome"], buf.String())
}
})
}
func newRawPendingFixture() *Envelope {
// pending 形态携带 data 载荷:raw 适用域覆盖所有携带数据的 outcome。
env := NewPendingEnvelope(&OperationInfo{ID: "t_1", State: OperationStateProcessing, NextCommand: "dws op get t_1"})
env.Data = map[string]any{"taskId": "t_1", "accepted": true}
return env
}
func newRawPartialFixture(t *testing.T) *Envelope {
t.Helper()
pd, err := NewPartialData(2,
[]any{map[string]any{"id": "a", "messageId": "m_1"}},
[]PartialFailedEntry{{ID: "b", Error: &ErrorInfo{Type: "api", Code: 40001}}},
nil,
)
if err != nil {
t.Fatalf("NewPartialData: %v", err)
}
return NewPartialEnvelope(pd)
}
// --- B41:raw × fields / raw × jq 穷举 ---
func TestRawTimesFieldsProjectsDataBeforePassThrough(t *testing.T) {
// --fields 先投影数据载荷再按 format 分发(成功通道联动):raw 下
// fields 作用于 data 载荷本身——透传的是投影后的载荷,信封外壳仍缺席。
env := phaseCListFixture()
var buf bytes.Buffer
if err := WriteEnvelopeTo(&buf, env, FormatRaw, "id", ""); err != nil {
t.Fatalf("WriteEnvelopeTo(raw, fields=id): %v", err)
}
var rows []map[string]any
if err := json.Unmarshal(buf.Bytes(), &rows); err != nil {
t.Fatalf("raw×fields output must be decodable JSON of projected data: %v\n%s", err, buf.String())
}
if len(rows) != 3 {
t.Fatalf("rows = %d, want 3: %s", len(rows), buf.String())
}
for i, row := range rows {
if len(row) != 1 {
t.Fatalf("row %d must be projected to id only: %v", i, row)
}
if _, has := row["id"]; !has {
t.Fatalf("row %d lost id after projection: %v", i, row)
}
}
// 信封外壳键不出现(raw 恒只透传 data 通道)。
if strings.Contains(buf.String(), `"outcome"`) || strings.Contains(buf.String(), `"meta"`) {
t.Fatalf("raw×fields must not emit envelope wrapper: %s", buf.String())
}
}
func TestRawTimesJQJQWinsAndEvaluatesFullEnvelope(t *testing.T) {
// --jq 优先于 format(含 raw):对**完整信封**求值后直接输出结果——
// raw×jq 下 jq 表达式可触及信封外壳键(.ok/.outcome),证明求值对象是
// 完整信封而非 raw 的 data 载荷。
for _, tc := range []struct {
jq string
want string
}{
{".ok", "true"},
{".outcome", `"success"`},
{".meta.pagination.next_token", `"cursor_xyz"`},
} {
var buf bytes.Buffer
if err := WriteEnvelopeTo(&buf, phaseCListFixture(), FormatRaw, "", tc.jq); err != nil {
t.Fatalf("WriteEnvelopeTo(raw, jq=%q): %v", tc.jq, err)
}
if got := strings.TrimSpace(buf.String()); got != tc.want {
t.Fatalf("raw×jq %q = %q, want %q (jq must win over raw on the full envelope)", tc.jq, got, tc.want)
}
}
// raw×jq×fields 三者同设:jq 仍恒赢——输出是 jq 求值结果,fields 不得
// 先行改写求值输入(jq 优先级链在分发点入口,fields 投影只在无 jq 分支)。
var buf bytes.Buffer
if err := WriteEnvelopeTo(&buf, phaseCListFixture(), FormatRaw, "id", ".outcome"); err != nil {
t.Fatalf("WriteEnvelopeTo(raw, fields+jq): %v", err)
}
if got := strings.TrimSpace(buf.String()); got != `"success"` {
t.Fatalf("raw×fields×jq = %q, want \"success\" (jq wins over both)", got)
}
}
func TestRawStringPayloadVerbatimPassThrough(t *testing.T) {
// raw 完整语义的字符串分支:data 为字符串时 raw 输出净化后的原文本
// (writeRaw 字符串分支),不是 JSON 引号包裹——这是「透传」的第二形态。
env := NewSuccessEnvelope("hello raw world")
var rawBuf, jsonBuf bytes.Buffer
if err := WriteEnvelopeTo(&rawBuf, env, FormatRaw, "", ""); err != nil {
t.Fatalf("WriteEnvelopeTo(raw): %v", err)
}
if err := WriteEnvelopeTo(&jsonBuf, env, FormatJSON, "", ""); err != nil {
t.Fatalf("WriteEnvelopeTo(json): %v", err)
}
if got := strings.TrimSpace(rawBuf.String()); got != "hello raw world" {
t.Fatalf("raw string payload = %q, want verbatim text", got)
}
if strings.Contains(rawBuf.String(), `"hello`) {
t.Fatalf("raw string branch must not JSON-quote the payload: %s", rawBuf.String())
}
// json 形态仍是完整信封(两形态互不污染)。
if !strings.Contains(jsonBuf.String(), `"outcome"`) {
t.Fatalf("json must keep the envelope wrapper: %s", jsonBuf.String())
}
}
// --- B42:--jq 优先于 format——全六格式穷举对完整信封求值 ---
func TestJQOverridesAllSixFormatsEvaluatingFullEnvelope(t *testing.T) {
// jq 优先级对 §5.2 矩阵全部六格式成立:无论 format 是什么,输出恒为
// jq 对完整信封的求值结果——matrix 各行(table/pretty/csv/ndjson/raw/
// json)全部被 jq 短路。
formats := []Format{FormatJSON, FormatTable, FormatPretty, FormatCSV, FormatNDJSON, FormatRaw}
for _, format := range formats {
t.Run("format="+string(format), func(t *testing.T) {
var buf bytes.Buffer
if err := WriteEnvelopeTo(&buf, phaseCListFixture(), format, "", ".outcome"); err != nil {
t.Fatalf("WriteEnvelopeTo(%s, jq=.outcome): %v", format, err)
}
if got := strings.TrimSpace(buf.String()); got != `"success"` {
t.Fatalf("format %s × jq = %q, want \"success\" (jq must win over every format)", format, got)
}
})
}
// Emitter 路径同判(双向出口共用分发点):-f csv + jq 下 stdout 仍是
// jq 结果而非裸记录。
var stdout, stderr bytes.Buffer
em := NewEmitter(&stdout, &stderr, FormatCSV, "", ".meta.count")
if err := em.Emit(phaseCListFixture()); err != nil {
t.Fatalf("Emit csv+jq: %v", err)
}
if got := strings.TrimSpace(stdout.String()); got != "3" {
t.Fatalf("Emitter csv×jq = %q, want 3", got)
}
if stderr.Len() != 0 {
t.Fatalf("jq path must keep stderr empty, got %d bytes: %s", stderr.Len(), stderr.String())
}
}
func TestJQWinsOverFieldsAsWell(t *testing.T) {
// jq 与 fields 同设:jq 恒赢,fields 不得改写 jq 的求值输入。
// 求值对象是可触及 meta 的完整信封——若 fields 先行投影,.meta 将缺席。
var buf bytes.Buffer
if err := WriteEnvelopeTo(&buf, phaseCListFixture(), FormatJSON, "id", ".meta.count"); err != nil {
t.Fatalf("WriteEnvelopeTo(fields+jq): %v", err)
}
if got := strings.TrimSpace(buf.String()); got != "3" {
t.Fatalf("fields×jq = %q, want 3 (jq must win over fields on the full envelope)", got)
}
}
func TestJQInvalidExpressionFailsCleanly(t *testing.T) {
// jq 表达式非法时求值失败即报错(validation 类)——不静默降级回 format
// 输出,也不泄漏半截渲染(buffer-first)。
var buf bytes.Buffer
err := WriteEnvelopeTo(&buf, phaseCListFixture(), FormatTable, "", ".data[")
if err == nil {
t.Fatalf("invalid jq expression must fail, got output: %s", buf.String())
}
if !strings.Contains(err.Error(), "jq") {
t.Fatalf("error must name jq: %v", err)
}
if buf.Len() != 0 {
t.Fatalf("invalid jq must leave zero bytes on the target (buffer-first): %s", buf.String())
}
}