Files
dingtalk-workspace-cli/internal/helpers/dev_output_unified_test.go

161 lines
5.5 KiB
Go

package helpers
import (
"bytes"
"context"
"encoding/json"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contractfinal"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output"
"github.com/spf13/cobra"
)
type countingDevUnifiedRunner struct {
calls int
}
func (r *countingDevUnifiedRunner) Run(_ context.Context, invocation executor.Invocation) (executor.Result, error) {
r.calls++
invocation.Implemented = true
return executor.Result{
Invocation: invocation,
Response: map[string]any{"content": map[string]any{
"success": true,
"result": map[string]any{"id": "dev-1", "hasMore": false},
}},
}, nil
}
func newDevUnifiedRoot(runner executor.Runner) *cobra.Command {
root := &cobra.Command{Use: "dws", SilenceErrors: true, SilenceUsage: true}
ctx, _ := output.WithResultStore(context.Background())
root.SetContext(ctx)
root.PersistentFlags().String("format", "json", "")
root.PersistentFlags().String("fields", "", "")
root.PersistentFlags().String("jq", "", "")
root.PersistentFlags().Bool("dry-run", false, "")
root.PersistentFlags().Bool("yes", false, "")
root.PersistentPostRunE = func(cmd *cobra.Command, _ []string) error {
_, _, err := output.EmitStoredResult(cmd)
return err
}
root.AddCommand(devHandler{}.Command(runner))
return root
}
func TestDevAppUnifiedActiveExecutesOnceAndReturnsFrameworkResult(t *testing.T) {
runner := &countingDevUnifiedRunner{}
root := newDevUnifiedRoot(runner)
var stdout bytes.Buffer
root.SetOut(&stdout)
root.SetArgs([]string{"dev", "app", "list"})
if err := root.Execute(); err != nil {
t.Fatal(err)
}
if runner.calls != 1 {
t.Fatalf("runner calls=%d, want exactly 1", runner.calls)
}
if !bytes.Contains(stdout.Bytes(), []byte(`"outcome": "success"`)) || !bytes.Contains(stdout.Bytes(), []byte(`"id": "dev-1"`)) {
t.Fatalf("stdout=%s", stdout.String())
}
}
func TestMigratedDevAppDefaultsToUnifiedFramework(t *testing.T) {
runner := &countingDevUnifiedRunner{}
root := newDevUnifiedRoot(runner)
var stdout bytes.Buffer
root.SetOut(&stdout)
root.SetErr(&bytes.Buffer{})
root.SetArgs([]string{"dev", "app", "list"})
if err := root.Execute(); err != nil {
t.Fatal(err)
}
if runner.calls != 1 {
t.Fatalf("runner calls=%d, want 1", runner.calls)
}
if !bytes.Contains(stdout.Bytes(), []byte(`"outcome": "success"`)) || bytes.Contains(stdout.Bytes(), []byte(`"contract_version"`)) {
t.Fatalf("migrated dev command did not use unified output by default: %s", stdout.String())
}
}
func TestDevDocSearchStaysLegacyUntilPagePaginationContractExists(t *testing.T) {
cmd := newDevDocSearchCommand(&countingDevUnifiedRunner{})
if got := output.CommandRollout(cmd); got != output.RolloutLegacyOnly {
t.Fatalf("dev doc search rollout=%s, want legacy_only until page pagination is modeled", got)
}
final, ok := contractfinal.RuntimeContractFinal(cmd)
if !ok {
t.Fatal("dev doc search is missing ContractFinal")
}
if final.Result != nil || final.Pagination != nil {
t.Fatalf("legacy dev doc search must not publish unified result/pagination schema: %#v", final)
}
}
func TestDevTerminalRolloutKeepsPublishedConnectStatusLegacy(t *testing.T) {
root := newDevUnifiedRoot(&countingDevUnifiedRunner{})
dev, _, err := root.Find([]string{"dev"})
if err != nil {
t.Fatal(err)
}
var walk func(*cobra.Command)
walk = func(cmd *cobra.Command) {
children := cmd.Commands()
if cmd.Runnable() && len(children) == 0 {
want := output.RolloutUnifiedActive
switch cmd.CommandPath() {
case "dws dev connect status", "dws dev connect list", "dws dev doc search":
want = output.RolloutLegacyOnly
}
if got := output.CommandRollout(cmd); got != want {
t.Errorf("%s rollout=%s, want %s", cmd.CommandPath(), got, want)
}
}
if cmd.CommandPath() == "dws dev connect" && output.CommandRollout(cmd) != output.RolloutLegacyOnly {
t.Errorf("%s must remain legacy until a streaming contract exists", cmd.CommandPath())
}
for _, child := range children {
walk(child)
}
}
walk(dev)
}
func TestDevConnectStatusPreservesPublishedHumanAndJSONShapes(t *testing.T) {
connectDaemonDirOverride = t.TempDir()
t.Cleanup(func() { connectDaemonDirOverride = "" })
humanRoot := newDevUnifiedRoot(&countingDevUnifiedRunner{})
var humanOut bytes.Buffer
humanRoot.SetOut(&humanOut)
humanRoot.SetArgs([]string{"dev", "connect", "status", "--robot-client-id", "missing"})
if err := humanRoot.Execute(); err != nil {
t.Fatal(err)
}
if !bytes.Contains(humanOut.Bytes(), []byte("not_running")) || bytes.Contains(humanOut.Bytes(), []byte(`"outcome"`)) {
t.Fatalf("default status output no longer uses the legacy human view: %s", humanOut.String())
}
jsonRoot := newDevUnifiedRoot(&countingDevUnifiedRunner{})
var jsonOut bytes.Buffer
jsonRoot.SetOut(&jsonOut)
jsonRoot.SetArgs([]string{"dev", "connect", "status", "--robot-client-id", "missing", "--json"})
if err := jsonRoot.Execute(); err != nil {
t.Fatal(err)
}
var payload map[string]any
if err := json.Unmarshal(jsonOut.Bytes(), &payload); err != nil {
t.Fatalf("legacy --json output is invalid: %v\n%s", err, jsonOut.String())
}
if payload["state"] != "not_running" || payload["supervised"] != false {
t.Fatalf("legacy health fields moved or changed: %#v", payload)
}
for _, forbidden := range []string{"ok", "outcome", "data"} {
if _, exists := payload[forbidden]; exists {
t.Fatalf("legacy --json unexpectedly gained unified key %q: %#v", forbidden, payload)
}
}
}