Files
dingtalk-workspace-cli/internal/pipeline/command_resolution_test.go

165 lines
6.3 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0
package pipeline
import (
stderrors "errors"
"strings"
"testing"
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/cmdutil"
"github.com/spf13/cobra"
)
func TestCrossPlatformCoverageValidateUnresolvedCommandClassifiesShortcutBeforeFlags(t *testing.T) {
root := &cobra.Command{Use: "dws"}
chat := &cobra.Command{Use: "chat"}
chat.AddCommand(
&cobra.Command{Use: "+chat-messages", Run: func(*cobra.Command, []string) {}},
&cobra.Command{Use: "+messages-send", Run: func(*cobra.Command, []string) {}},
&cobra.Command{Use: "chat-mesages", Run: func(*cobra.Command, []string) {}},
)
root.AddCommand(chat)
err := validateUnresolvedCommand(chat, []string{"+chat-mesages", "--keyword", "x"})
structured := requireCommandResolutionError(t, err, "unknown_shortcut")
if structured.Message != `unknown shortcut "+chat-mesages" for "dws chat"` {
t.Fatalf("Message = %q", structured.Message)
}
if structured.Hint != `Did you mean "dws chat +chat-messages"?` {
t.Fatalf("Hint = %q", structured.Hint)
}
if len(structured.Actions) != 1 || structured.Actions[0] != "Run 'dws shortcut list --service chat --format json'" {
t.Fatalf("Actions = %#v", structured.Actions)
}
if len(structured.AvailableFlags) != 0 {
t.Fatalf("AvailableFlags = %#v, want none for command error", structured.AvailableFlags)
}
}
func TestCrossPlatformCoverageValidateUnresolvedCommandClassifiesOnlyExplicitContainers(t *testing.T) {
root := &cobra.Command{Use: "dws"}
dev := &cobra.Command{Use: "dev"}
app := &cobra.Command{Use: "app"}
cmdutil.MarkGroup(app)
app.AddCommand(&cobra.Command{Use: "list", Run: func(*cobra.Command, []string) {}})
dev.AddCommand(app)
root.AddCommand(dev)
err := validateUnresolvedCommand(app, []string{"search", "--keyword", "x"})
structured := requireCommandResolutionError(t, err, "unknown_subcommand")
if structured.Message != `unknown subcommand "search" for "dws dev app"` {
t.Fatalf("Message = %q", structured.Message)
}
if len(structured.Actions) != 1 || structured.Actions[0] != "Run 'dws dev app --help' to see available subcommands" {
t.Fatalf("Actions = %#v", structured.Actions)
}
positional := &cobra.Command{Use: "schema [path]", Run: func(*cobra.Command, []string) {}}
root.AddCommand(positional)
for name, test := range map[string]struct {
target *cobra.Command
remaining []string
}{
"legitimate positional": {target: positional, remaining: []string{"+chat-messages"}},
"flag remains a flag": {target: app, remaining: []string{"--keyword", "x"}},
"dash terminator": {target: app, remaining: []string{"--", "search"}},
"nil target": {target: nil, remaining: []string{"search"}},
"empty remaining": {target: app},
} {
t.Run(name, func(t *testing.T) {
if err := validateUnresolvedCommand(test.target, test.remaining); err != nil {
t.Fatalf("validateUnresolvedCommand() error = %v", err)
}
})
}
}
func TestCrossPlatformCoverageRunPreParseArgsValidatesCommandsWithoutHandlersAndPrimesPresentation(t *testing.T) {
root := &cobra.Command{Use: "dws"}
root.PersistentFlags().String("format", "table", "")
chat := &cobra.Command{Use: "chat"}
chat.AddCommand(&cobra.Command{Use: "+chat-messages", Run: func(*cobra.Command, []string) {}})
root.AddCommand(chat)
ctx, err := RunPreParseArgs(root, nil, []string{
"--format", "json", "chat", "+chat-mesages", "--keyword", "x",
})
if ctx == nil || ctx.Command != "dws chat" {
t.Fatalf("Context = %#v", ctx)
}
requireCommandResolutionError(t, err, "unknown_shortcut")
if format, getErr := root.PersistentFlags().GetString("format"); getErr != nil || format != "json" {
t.Fatalf("format = %q, %v; want primed json presentation", format, getErr)
}
validRoot := &cobra.Command{Use: "dws"}
validChat := &cobra.Command{Use: "chat"}
validChat.AddCommand(&cobra.Command{Use: "+chat-messages", Run: func(*cobra.Command, []string) {}})
validRoot.AddCommand(validChat)
if ctx, err := RunPreParseArgs(validRoot, nil, []string{"chat", "+chat-messages"}); ctx != nil || err != nil {
t.Fatalf("valid shortcut = %#v, %v", ctx, err)
}
}
func TestCrossPlatformCoverageCommandSuggestionHintIsBoundedAndFallsBack(t *testing.T) {
root := &cobra.Command{Use: "dws"}
group := &cobra.Command{Use: "demo", SuggestionsMinimumDistance: 100}
root.AddCommand(group)
for _, name := range []string{"alpha", "beta", "delta", "gamma"} {
group.AddCommand(&cobra.Command{Use: name, Run: func(*cobra.Command, []string) {}})
}
hint := commandSuggestionHint(group, "missing", "fallback")
if strings.Count(hint, `"dws demo `) != maxCommandSuggestions || strings.Contains(hint, "gamma") {
t.Fatalf("bounded hint = %q", hint)
}
empty := &cobra.Command{Use: "empty"}
root.AddCommand(empty)
if got := commandSuggestionHint(empty, "missing", "fallback"); got != "fallback" {
t.Fatalf("fallback hint = %q", got)
}
}
func TestCrossPlatformCoverageCommandResolutionDefensiveAndExplicitSuggestionPaths(t *testing.T) {
root := &cobra.Command{Use: "dws"}
service := &cobra.Command{Use: "demo"}
group := &cobra.Command{Use: "nested"}
service.AddCommand(group)
root.AddCommand(service)
if isExplicitShortcutCandidate(root, "+missing") {
t.Fatal("root command was treated as a shortcut service")
}
if isExplicitShortcutCandidate(group, "+missing") {
t.Fatal("nested command was treated as a top-level shortcut service")
}
if got := commandSuggestions(nil, "missing"); got != nil {
t.Fatalf("commandSuggestions(nil) = %#v", got)
}
service.SuggestionsMinimumDistance = 1
service.AddCommand(&cobra.Command{
Use: "canonical",
SuggestFor: []string{"invented"},
Run: func(*cobra.Command, []string) {},
})
if got := commandSuggestions(service, "invented"); len(got) != 1 || got[0] != "canonical" {
t.Fatalf("explicit SuggestFor suggestions = %#v", got)
}
}
func requireCommandResolutionError(t *testing.T, err error, reason string) *apperrors.Error {
t.Helper()
var structured *apperrors.Error
if !stderrors.As(err, &structured) {
t.Fatalf("error = %T %v, want *errors.Error", err, err)
}
if structured.Category != apperrors.CategoryValidation || structured.Reason != reason || structured.ExitCode() != 3 {
t.Fatalf("structured error = %#v", structured)
}
return structured
}