Co-authored-by: zearlin <ruomiao.linrm@alibaba-inc.com> Co-authored-by: chichuan <30925823+haofeng0705@users.noreply.github.com>
230 lines
8.6 KiB
Go
230 lines
8.6 KiB
Go
// 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 app
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"errors"
|
||
"os"
|
||
"strings"
|
||
"testing"
|
||
|
||
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output"
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pipeline"
|
||
"github.com/spf13/cobra"
|
||
)
|
||
|
||
func TestCrossPlatformCoverageExecuteEmitsStoredUnifiedResultAtSingleRootExit(t *testing.T) {
|
||
oldNormalize := rootNormalizeProcessProfileArgs
|
||
oldExecute := rootExecuteCommand
|
||
oldNewRoot := rootNewRootCommandWithEngine
|
||
oldPreParse := rootRunPreParse
|
||
oldStop := rootStopAllStdioClients
|
||
oldArgs := os.Args
|
||
t.Cleanup(func() {
|
||
rootNormalizeProcessProfileArgs = oldNormalize
|
||
rootExecuteCommand = oldExecute
|
||
rootNewRootCommandWithEngine = oldNewRoot
|
||
rootRunPreParse = oldPreParse
|
||
rootStopAllStdioClients = oldStop
|
||
os.Args = oldArgs
|
||
})
|
||
os.Args = []string{"dws"}
|
||
rootNormalizeProcessProfileArgs = func() func() { return func() {} }
|
||
rootRunPreParse = func(*cobra.Command, *pipeline.Engine) error { return nil }
|
||
rootStopAllStdioClients = func() {}
|
||
rootNewRootCommandWithEngine = func(ctx context.Context, _ *pipeline.Engine) *cobra.Command {
|
||
cmd := &cobra.Command{Use: "dws", SilenceErrors: true, SilenceUsage: true}
|
||
cmd.SetContext(ctx)
|
||
return cmd
|
||
}
|
||
|
||
var stdout, stderr bytes.Buffer
|
||
executed := &cobra.Command{Use: "leaf"}
|
||
output.SetCommandRollout(executed, output.RolloutUnifiedActive)
|
||
executed.SetOut(&stdout)
|
||
executed.SetErr(&stderr)
|
||
rootExecuteCommand = func(root *cobra.Command) (*cobra.Command, error) {
|
||
executed.SetContext(root.Context())
|
||
if err := output.StoreResult(executed.Context(), output.Success(map[string]any{"id": "a"})); err != nil {
|
||
return executed, err
|
||
}
|
||
if _, _, err := output.EmitStoredResult(executed); err != nil {
|
||
return executed, err
|
||
}
|
||
return executed, nil
|
||
}
|
||
if code := Execute(); code != 0 {
|
||
t.Fatalf("Execute code=%d, want 0", code)
|
||
}
|
||
if stderr.Len() != 0 {
|
||
t.Fatalf("stderr=%q, want diagnostics only/empty", stderr.String())
|
||
}
|
||
if !strings.Contains(stdout.String(), `"outcome": "success"`) || strings.Contains(stdout.String(), `"contract_version"`) {
|
||
t.Fatalf("stdout does not match the unified envelope: %s", stdout.String())
|
||
}
|
||
}
|
||
|
||
// TestRootExecutionErrorToStderrOnly 是 B184 的回归断言:失败信封(JSON 错误
|
||
// 输出)恒走 stderr,stdout 严格为空(契约 §5.1:失败时 stdout 必须为空)。
|
||
// printExecutionError 把 PrintJSON/PrintHuman 都写 stderr writer,stdout
|
||
// writer 不得收到任何字节。
|
||
func TestRootExecutionErrorToStderrOnly(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
root := &cobra.Command{Use: "dws"}
|
||
root.PersistentFlags().String("format", "json", "")
|
||
_ = root.PersistentFlags().Set("format", "json")
|
||
|
||
var stdout, stderr bytes.Buffer
|
||
if err := printExecutionError(root, &stdout, &stderr, apperrors.NewAuth("token expired")); err != nil {
|
||
t.Fatalf("printExecutionError() error = %v", err)
|
||
}
|
||
if stdout.Len() != 0 {
|
||
t.Fatalf("failure must keep stdout empty, got %q", stdout.String())
|
||
}
|
||
want := "{\n \"error\": {\n \"category\": \"auth\",\n \"code\": 2,\n \"message\": \"token expired\"\n }\n}\n"
|
||
if got := stderr.String(); got != want {
|
||
t.Fatalf("legacy root error wire changed\n got: %q\nwant: %q", got, want)
|
||
}
|
||
}
|
||
|
||
// TestRootHumanErrorToStderrOnly 是 B184 的人类可读分支断言:非 JSON 模式下,
|
||
// 失败走 stderr(PrintHuman),stdout 为空。
|
||
func TestRootHumanErrorToStderrOnly(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
root := &cobra.Command{Use: "dws"}
|
||
root.PersistentFlags().String("format", "table", "")
|
||
_ = root.PersistentFlags().Set("format", "table")
|
||
|
||
var stdout, stderr bytes.Buffer
|
||
if err := printExecutionError(root, &stdout, &stderr, apperrors.NewInternal("boom")); err != nil {
|
||
t.Fatalf("printExecutionError() error = %v", err)
|
||
}
|
||
if stdout.Len() != 0 {
|
||
t.Fatalf("failure must keep stdout empty, got %q", stdout.String())
|
||
}
|
||
if !strings.Contains(stderr.String(), "Error:") {
|
||
t.Fatalf("expected human error on stderr, got %q", stderr.String())
|
||
}
|
||
}
|
||
|
||
// TestRootExecuteOutcomeToExitCode 是 B185 的 Execute 出口断言:Execute 把
|
||
// 命令返回的 error 类别映射为进程退出码(apperrors.ExitCode)。ok→0、
|
||
// confirmation/validation→3、panic 与 unrepresentable partial error→5。
|
||
func TestRootExecuteOutcomeToExitCode(t *testing.T) {
|
||
oldNormalize := rootNormalizeProcessProfileArgs
|
||
oldExecute := rootExecuteCommand
|
||
oldNewRoot := rootNewRootCommandWithEngine
|
||
oldPreParse := rootRunPreParse
|
||
oldStop := rootStopAllStdioClients
|
||
oldArgs := os.Args
|
||
t.Cleanup(func() {
|
||
rootNormalizeProcessProfileArgs = oldNormalize
|
||
rootExecuteCommand = oldExecute
|
||
rootNewRootCommandWithEngine = oldNewRoot
|
||
rootRunPreParse = oldPreParse
|
||
rootStopAllStdioClients = oldStop
|
||
os.Args = oldArgs
|
||
})
|
||
os.Args = []string{"dws"}
|
||
rootNormalizeProcessProfileArgs = func() func() { return func() {} }
|
||
rootRunPreParse = func(*cobra.Command, *pipeline.Engine) error { return nil }
|
||
rootStopAllStdioClients = func() {}
|
||
rootNewRootCommandWithEngine = func(context.Context, *pipeline.Engine) *cobra.Command {
|
||
return &cobra.Command{Use: "dws", SilenceErrors: true, SilenceUsage: true}
|
||
}
|
||
|
||
// ok / pending(信封 success/pending 语义)→ 0
|
||
rootExecuteCommand = func(*cobra.Command) (*cobra.Command, error) { return nil, nil }
|
||
if code := Execute(); code != 0 {
|
||
t.Fatalf("success Execute code = %d, want 0", code)
|
||
}
|
||
|
||
// An error cannot carry partial succeeded/failed data and fails closed.
|
||
rootExecuteCommand = func(*cobra.Command) (*cobra.Command, error) {
|
||
return nil, &apperrors.Error{Category: apperrors.CategoryPartial, Message: "partial"}
|
||
}
|
||
if code := Execute(); code != 5 {
|
||
t.Fatalf("partial error Execute code = %d, want 5", code)
|
||
}
|
||
|
||
// confirmation_required(validation 子类)→ 3
|
||
rootExecuteCommand = func(*cobra.Command) (*cobra.Command, error) {
|
||
return nil, apperrors.NewValidation("blocked", apperrors.WithReason("confirmation_required"))
|
||
}
|
||
if code := Execute(); code != 3 {
|
||
t.Fatalf("confirmation Execute code = %d, want 3", code)
|
||
}
|
||
|
||
// plain internal → 5
|
||
rootExecuteCommand = func(*cobra.Command) (*cobra.Command, error) {
|
||
return nil, errors.New("plain")
|
||
}
|
||
if code := Execute(); code != 5 {
|
||
t.Fatalf("plain Execute code = %d, want 5", code)
|
||
}
|
||
}
|
||
|
||
// TestRootSilenceErrorsAndDeferTeardown 是 B186 的断言:根命令 SilenceErrors/
|
||
// SilenceUsage 打开(Cobra 不自行打印),且 Execute 出口 defer 收尾路径
|
||
// (StopAllStdioClients)在错误路径也被调用。
|
||
func TestRootSilenceErrorsAndDeferTeardown(t *testing.T) {
|
||
oldNormalize := rootNormalizeProcessProfileArgs
|
||
oldExecute := rootExecuteCommand
|
||
oldNewRoot := rootNewRootCommandWithEngine
|
||
oldPreParse := rootRunPreParse
|
||
oldStop := rootStopAllStdioClients
|
||
oldArgs := os.Args
|
||
t.Cleanup(func() {
|
||
rootNormalizeProcessProfileArgs = oldNormalize
|
||
rootExecuteCommand = oldExecute
|
||
rootNewRootCommandWithEngine = oldNewRoot
|
||
rootRunPreParse = oldPreParse
|
||
rootStopAllStdioClients = oldStop
|
||
os.Args = oldArgs
|
||
})
|
||
os.Args = []string{"dws"}
|
||
rootNormalizeProcessProfileArgs = func() func() { return func() {} }
|
||
rootRunPreParse = func(*cobra.Command, *pipeline.Engine) error { return nil }
|
||
stopped := false
|
||
rootStopAllStdioClients = func() { stopped = true }
|
||
rootNewRootCommandWithEngine = func(context.Context, *pipeline.Engine) *cobra.Command {
|
||
return &cobra.Command{Use: "dws", SilenceErrors: true, SilenceUsage: true}
|
||
}
|
||
|
||
// 错误路径:Execute 返回非零,且 defer 收尾(StopAllStdioClients)被调用。
|
||
rootExecuteCommand = func(*cobra.Command) (*cobra.Command, error) {
|
||
return nil, apperrors.NewInternal("fail")
|
||
}
|
||
_ = Execute()
|
||
if !stopped {
|
||
t.Fatal("defer teardown (StopAllStdioClients) not called on error path")
|
||
}
|
||
}
|
||
|
||
// TestRootSilenceErrorsFlag 断言根命令的 SilenceErrors/SilenceUsage 为真,
|
||
// 保证 Cobra 不自行在错误时打印 usage/错误(错误渲染统一走 printExecutionError)。
|
||
func TestRootSilenceErrorsFlag(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
root := NewRootCommand()
|
||
if !root.SilenceErrors || !root.SilenceUsage {
|
||
t.Fatalf("root must set SilenceErrors=%v SilenceUsage=%v", root.SilenceErrors, root.SilenceUsage)
|
||
}
|
||
}
|