Files
dingtalk-workspace-cli/internal/shortcut/dual_validate_compat_test.go

154 lines
5.6 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package shortcut
import (
"bytes"
"context"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
"github.com/spf13/cobra"
)
type dualValidateCaller struct {
format string
dryRun bool
calls int
text string
err error
}
func (c *dualValidateCaller) CallTool(context.Context, string, string, map[string]any) (*edition.ToolResult, error) {
c.calls++
if c.err != nil {
return nil, c.err
}
return &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: c.text}}}, nil
}
func TestFrameworkDualValidateBackendAndShadowErrors(t *testing.T) {
caller := &dualValidateCaller{format: "json", text: `{"id":"x"}`, err: context.DeadlineExceeded}
helpers.InitDepsForTest(t, caller)
root := &cobra.Command{Use: "dws", SilenceErrors: true, SilenceUsage: true}
root.PersistentFlags().String("format", "json", "")
cmd := mount(Shortcut{Service: "sample", Command: "+compat", OutputRollout: output.RolloutDualValidate, Execute: func(rt *RuntimeContext) error { return rt.CallMCP("get", nil) }})
root.AddCommand(cmd)
root.SetArgs([]string{cmd.Name()})
if err := root.Execute(); err == nil {
t.Fatal("backend error swallowed")
}
original := validateShadowResult
t.Cleanup(func() { validateShadowResult = original })
validateShadowResult = func(output.CommandResult) error { return context.Canceled }
direct := &cobra.Command{Use: "+direct"}
direct.SetContext(context.Background())
output.SetCommandRollout(direct, output.RolloutDualValidate)
if err := RuntimeContextForTest(direct, Shortcut{Service: "sample", Command: "+direct"}).Output(map[string]any{"id": "x"}); err == nil {
t.Fatal("Output shadow validation error swallowed")
}
caller.err = nil
root.SetArgs([]string{cmd.Name()})
if err := root.Execute(); err == nil {
t.Fatal("shadow validation error swallowed")
}
caller.dryRun = true
root.SetArgs([]string{cmd.Name(), "--dry-run"})
root.PersistentFlags().Bool("dry-run", false, "")
if err := root.Execute(); err == nil {
t.Fatal("dry-run shadow validation error swallowed")
}
}
func (c *dualValidateCaller) Format() string { return c.format }
func (c *dualValidateCaller) DryRun() bool { return c.dryRun }
func (c *dualValidateCaller) Fields() string { return "" }
func (c *dualValidateCaller) JQ() string { return "" }
func runDualValidateShortcut(t *testing.T, caller *dualValidateCaller, args ...string) (string, string) {
t.Helper()
helpers.InitDepsForTest(t, caller)
var stdout, stderr bytes.Buffer
helpers.GetFormatter().SetWriters(&stdout, &stderr)
root := &cobra.Command{Use: "dws", SilenceErrors: true, SilenceUsage: true}
root.PersistentFlags().String("format", "json", "")
root.PersistentFlags().Bool("dry-run", false, "")
cmd := mount(Shortcut{
Service: "sample",
Command: "+compat",
OutputRollout: output.RolloutDualValidate,
Execute: func(rt *RuntimeContext) error {
return rt.CallMCP("get_sample", map[string]any{"id": "1"})
},
})
cmd.SetOut(&stdout)
root.AddCommand(cmd)
root.SetArgs(append([]string{cmd.Name()}, args...))
if err := root.Execute(); err != nil {
t.Fatalf("Execute() error = %v", err)
}
return stdout.String(), stderr.String()
}
func TestDualValidatePreservesLegacyRawBytesWithOneBusinessCall(t *testing.T) {
caller := &dualValidateCaller{format: "raw", text: `{"url":"https://example.test/?a=1&b=2"}`}
got, _ := runDualValidateShortcut(t, caller, "--format", "raw")
if want := caller.text + "\n"; got != want {
t.Fatalf("raw output = %q, want legacy bytes %q", got, want)
}
if caller.calls != 1 {
t.Fatalf("business calls = %d, want 1", caller.calls)
}
}
func TestDualValidatePreservesLegacyJSONEscapingAndIndentation(t *testing.T) {
caller := &dualValidateCaller{format: "json", text: `{"url":"https://example.test/?a=1&b=2","tag":"<x>"}`}
got, _ := runDualValidateShortcut(t, caller, "--format", "json")
want := "{\n \"tag\": \"\\u003cx\\u003e\",\n \"url\": \"https://example.test/?a=1\\u0026b=2\"\n}\n"
if got != want {
t.Fatalf("json output = %q, want legacy bytes %q", got, want)
}
if caller.calls != 1 {
t.Fatalf("business calls = %d, want 1", caller.calls)
}
}
func TestDualValidatePreservesLegacyPlainTextFormat(t *testing.T) {
caller := &dualValidateCaller{format: "table", text: "legacy plain text"}
got, _ := runDualValidateShortcut(t, caller, "--format", "table")
if got != "legacy plain text\n" {
t.Fatalf("table/plain output = %q", got)
}
if caller.calls != 1 {
t.Fatalf("business calls = %d, want 1", caller.calls)
}
}
func TestDualValidateDoesNotJSONQuoteLegacyPlainText(t *testing.T) {
caller := &dualValidateCaller{format: "json", text: "legacy plain text"}
got, _ := runDualValidateShortcut(t, caller, "--format", "json")
if got != "legacy plain text\n" {
t.Fatalf("json/plain output = %q", got)
}
if caller.calls != 1 {
t.Fatalf("business calls = %d, want 1", caller.calls)
}
}
func TestDualValidatePreservesLegacyDryRunWithoutBusinessCall(t *testing.T) {
caller := &dualValidateCaller{format: "table", dryRun: true}
stdout, stderr := runDualValidateShortcut(t, caller, "--format", "table", "--dry-run")
if !strings.Contains(stdout, "Tool") || !strings.Contains(stdout, "get_sample") || !strings.Contains(stdout, "Arguments") || stderr != "" {
t.Fatalf("dry-run output lost legacy presentation: stdout=%q stderr=%q", stdout, stderr)
}
if caller.calls != 0 {
t.Fatalf("dry-run business calls = %d, want 0", caller.calls)
}
}