439 lines
20 KiB
Go
439 lines
20 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0
|
|
|
|
package pipeline
|
|
|
|
import (
|
|
stderrors "errors"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd"
|
|
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"
|
|
)
|
|
|
|
type reviewedProtectionTestHandler struct{}
|
|
|
|
func (reviewedProtectionTestHandler) Name() string { return "reviewed-protection-test" }
|
|
|
|
func (reviewedProtectionTestHandler) Phase() Phase { return PreParse }
|
|
|
|
func (reviewedProtectionTestHandler) ResolveFlagProtection(command, flag string) (FlagProtection, bool) {
|
|
if (command == "dws aisearch" || command == "dws chat") && flag == "types" {
|
|
return FlagProtectionBlocked, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func (reviewedProtectionTestHandler) Handle(ctx *Context) error {
|
|
if ctx.Command == "dws aisearch" || ctx.Command == "dws chat" {
|
|
ctx.ProtectFlag("types", FlagProtectionBlocked)
|
|
}
|
|
if ctx.Command == "dws aisearch enterprise" {
|
|
for index, argument := range ctx.Args {
|
|
if argument != "--leaf-typz" {
|
|
continue
|
|
}
|
|
ctx.Args[index] = "--leaf-type"
|
|
ctx.AddCorrection("reviewed-protection-test", PreParse, "leaf-type", argument, "--leaf-type", "test")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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) {}},
|
|
)
|
|
applyTestNavigationGroup(chat)
|
|
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"? (Run 'dws chat --help' for the full list)` {
|
|
t.Fatalf("Hint = %q", structured.Hint)
|
|
}
|
|
if len(structured.Actions) != 2 || structured.Actions[0] != "Run 'dws chat --help' for the full list" ||
|
|
structured.Actions[1] != "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)
|
|
}
|
|
if structured.Details["input"] != "+chat-mesages" {
|
|
t.Fatalf("Details.input = %#v", structured.Details["input"])
|
|
}
|
|
if suggestions, ok := structured.Details["suggestions"].([]string); !ok || len(suggestions) != 1 || suggestions[0] != "+chat-messages" {
|
|
t.Fatalf("Details.suggestions = %#v", structured.Details["suggestions"])
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageValidateUnresolvedCommandClassifiesOnlyExplicitContainers(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
dev := &cobra.Command{Use: "dev"}
|
|
app := &cobra.Command{Use: "app"}
|
|
applyTestNavigationGroup(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' for the full list" {
|
|
t.Fatalf("Actions = %#v", structured.Actions)
|
|
}
|
|
if structured.Details["input"] != "search" {
|
|
t.Fatalf("Details.input = %#v", structured.Details["input"])
|
|
}
|
|
if suggestions, ok := structured.Details["suggestions"].([]string); !ok || len(suggestions) != 0 {
|
|
t.Fatalf("Details.suggestions = %#v", structured.Details["suggestions"])
|
|
}
|
|
legacy := &cobra.Command{Use: "legacy", RunE: cmdutil.GroupRunE}
|
|
applyTestNavigationGroup(legacy)
|
|
legacy.AddCommand(&cobra.Command{Use: "list", Run: func(*cobra.Command, []string) {}})
|
|
root.AddCommand(legacy)
|
|
requireCommandResolutionError(t, validateUnresolvedCommand(legacy, []string{"lisst", "--query", "x"}), "unknown_subcommand")
|
|
|
|
positional := &cobra.Command{Use: "schema [path]", Run: func(*cobra.Command, []string) {}}
|
|
root.AddCommand(positional)
|
|
positionalWithChild := &cobra.Command{Use: "query [term]", Args: cobra.ExactArgs(1), Run: func(*cobra.Command, []string) {}}
|
|
positionalWithChild.AddCommand(&cobra.Command{Use: "list", Run: func(*cobra.Command, []string) {}})
|
|
root.AddCommand(positionalWithChild)
|
|
for name, test := range map[string]struct {
|
|
target *cobra.Command
|
|
remaining []string
|
|
}{
|
|
"cobra help command": {target: root, remaining: []string{"help", "chat"}},
|
|
"legitimate positional": {target: positional, remaining: []string{"+chat-messages"}},
|
|
"positional close to child command": {target: positionalWithChild, remaining: []string{"lis"}},
|
|
"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 TestCrossPlatformCoverageValidateUnresolvedCommandRejectsInvalidGroupPolicyMetadata(t *testing.T) {
|
|
group := &cobra.Command{Use: "broken"}
|
|
applyTestNavigationGroup(group)
|
|
for key := range group.Annotations {
|
|
group.Annotations[key] = "navigation_only|reject|unexpected"
|
|
}
|
|
|
|
err := validateUnresolvedCommand(group, []string{"typo"})
|
|
if err == nil || !strings.Contains(err.Error(), "invalid group recovery policy") {
|
|
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) {}})
|
|
applyTestNavigationGroup(chat)
|
|
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) {}})
|
|
applyTestNavigationGroup(validChat)
|
|
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 TestCrossPlatformCoverageRunPreParseArgsKeepsUnknownFlagValuesOutOfCommandResolution(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
root.PersistentFlags().Bool("yes", false, "")
|
|
aisearch := &cobra.Command{Use: "aisearch", RunE: func(*cobra.Command, []string) error { return nil }}
|
|
aisearch.Flags().String("query", "", "")
|
|
aisearch.AddCommand(&cobra.Command{Use: "enterprise", Run: func(*cobra.Command, []string) {}})
|
|
corecmd.ApplyGroupPolicy(aisearch, corecmd.GroupPolicy{
|
|
Mode: corecmd.GroupHybrid,
|
|
Positionals: corecmd.PositionalsReject,
|
|
Recovery: corecmd.RecoverySibling,
|
|
})
|
|
root.AddCommand(aisearch)
|
|
engine := NewEngine()
|
|
engine.Register(reviewedProtectionTestHandler{})
|
|
frozenParentTraversal := []string{"aisearch", "--"}
|
|
if got := argsForCommandTraversal(root, []string{"aisearch", "enterprise"}); !slices.Equal(got, []string{"aisearch", "enterprise"}) {
|
|
t.Fatalf("default command traversal = %v", got)
|
|
}
|
|
|
|
guarded := []string{"aisearch", "--types", "FIXTURE_VALUE"}
|
|
if got := argsForCommandTraversalForEngine(root, engine, guarded); !slices.Equal(got, frozenParentTraversal) {
|
|
t.Fatalf("guarded flag traversal = %v, want %v", got, frozenParentTraversal)
|
|
}
|
|
if ctx, err := RunPreParseArgs(root, engine, guarded); err != nil || ctx == nil || ctx.Command != "dws aisearch" || !ctx.IsFlagProtected("types") {
|
|
t.Fatalf("guarded flag value entered command resolution: context=%#v error=%v", ctx, err)
|
|
}
|
|
|
|
// A protected flag value can also be spelled exactly like a child. Command
|
|
// traversal must not guess which meaning the user intended.
|
|
guardedChildValue := []string{"aisearch", "--types", "enterprise"}
|
|
if got := argsForCommandTraversalForEngine(root, engine, guardedChildValue); !slices.Equal(got, frozenParentTraversal) {
|
|
t.Fatalf("guarded child-name value traversal = %v, want %v", got, frozenParentTraversal)
|
|
}
|
|
if ctx, err := RunPreParseArgs(root, engine, guardedChildValue); err != nil || ctx == nil || ctx.Command != "dws aisearch" {
|
|
t.Fatalf("guarded child-name value entered command resolution: context=%#v error=%v", ctx, err)
|
|
}
|
|
for _, guardedBooleanValue := range [][]string{
|
|
{"aisearch", "--types", "false", "enterprise"},
|
|
{"aisearch", "--types=false", "enterprise"},
|
|
} {
|
|
if got := argsForCommandTraversalForEngine(root, engine, guardedBooleanValue); !slices.Equal(got, frozenParentTraversal) {
|
|
t.Fatalf("guarded boolean-like value traversal = %v, want %v", got, frozenParentTraversal)
|
|
}
|
|
if ctx, err := RunPreParseArgs(root, engine, guardedBooleanValue); err != nil || ctx == nil || ctx.Command != "dws aisearch" {
|
|
t.Fatalf("guarded boolean-like value selected wrong command: context=%#v error=%v", ctx, err)
|
|
}
|
|
}
|
|
guardedAfterLocalValue := []string{"aisearch", "--unknown-query", "Alice", "--types", "enterprise"}
|
|
if got := argsForCommandTraversalForEngine(root, engine, guardedAfterLocalValue); !slices.Equal(got, frozenParentTraversal) {
|
|
t.Fatalf("guarded flag after local value traversal = %v, want %v", got, frozenParentTraversal)
|
|
}
|
|
if ctx, err := RunPreParseArgs(root, engine, guardedAfterLocalValue); err != nil || ctx == nil || ctx.Command != "dws aisearch" {
|
|
t.Fatalf("guarded flag after local value selected wrong command: context=%#v error=%v", ctx, err)
|
|
}
|
|
legitimateParentFlag := []string{"aisearch", "--query", "Alice", "enterprise", "--types", "document", "--leaf-typz", "value"}
|
|
if ctx, err := RunPreParseArgs(root, engine, legitimateParentFlag); err != nil || ctx == nil || ctx.Command != "dws aisearch enterprise" ||
|
|
!slices.Contains(ctx.Args, "--leaf-type") {
|
|
t.Fatalf("parent flag/value prevented leaf resolution: context=%#v error=%v", ctx, err)
|
|
}
|
|
|
|
// Leading fuzzy root flags remain useful because no command path is
|
|
// ambiguous yet. Once a group has resolved, the same fuzzy spelling is
|
|
// preserved for the semantic flag pipeline instead of being consumed here.
|
|
if got := argsForCommandTraversalForEngine(root, engine, []string{"--yess", "aisearch", "enterprise"}); !slices.Equal(got, []string{"aisearch", "enterprise"}) {
|
|
t.Fatalf("leading fuzzy boolean traversal = %v", got)
|
|
}
|
|
if got := argsForCommandTraversalForEngine(root, engine, []string{"aisearch", "--yess", "enterprise"}); !slices.Equal(got, []string{"aisearch", "enterprise"}) {
|
|
t.Fatalf("fuzzy boolean after group traversal = %v", got)
|
|
}
|
|
if got := argsForCommandTraversalForEngine(root, engine, []string{"aisearch", "--yess", "false", "enterprise"}); !slices.Equal(got, []string{"aisearch", "enterprise"}) {
|
|
t.Fatalf("fuzzy boolean value before child traversal = %v", got)
|
|
}
|
|
leafArgs := []string{"aisearch", "--yess", "enterprise", "--leaf-typz", "value"}
|
|
if ctx, err := RunPreParseArgs(root, engine, leafArgs); err != nil || ctx == nil || ctx.Command != "dws aisearch enterprise" ||
|
|
!slices.Contains(ctx.Args, "--leaf-type") {
|
|
t.Fatalf("fuzzy root flag skipped leaf handler: context=%#v error=%v", ctx, err)
|
|
}
|
|
|
|
_, err := RunPreParseArgs(root, engine, []string{"aisearch", "--yes", "enterprize"})
|
|
requireCommandResolutionError(t, err, string(cmdutil.ResolutionUnknownSubcommand))
|
|
_, err = RunPreParseArgs(root, engine, []string{"aisearch", "enterprize", "--types", "enterprise"})
|
|
structured := requireCommandResolutionError(t, err, string(cmdutil.ResolutionUnknownSubcommand))
|
|
if suggestions, ok := structured.Details["suggestions"].([]string); !ok || !slices.Equal(suggestions, []string{"enterprise"}) {
|
|
t.Fatalf("typo before protected flag suggestions = %#v", structured.Details["suggestions"])
|
|
}
|
|
_, err = RunPreParseArgs(root, engine, []string{"aisearch", "--query", "Alice", "enterprize", "--types", "enterprise"})
|
|
structured = requireCommandResolutionError(t, err, string(cmdutil.ResolutionUnknownSubcommand))
|
|
if suggestions, ok := structured.Details["suggestions"].([]string); !ok || !slices.Equal(suggestions, []string{"enterprise"}) {
|
|
t.Fatalf("typo after parent flag suggestions = %#v", structured.Details["suggestions"])
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunPreParseArgsUsesTypedDeepRecoveryPolicy(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
sheet := &cobra.Command{Use: "sheet"}
|
|
rangeGroup := &cobra.Command{Use: "range"}
|
|
rangeGroup.AddCommand(&cobra.Command{Use: "read", Run: func(*cobra.Command, []string) {}})
|
|
corecmd.ApplyGroupPolicy(rangeGroup, corecmd.GroupPolicy{
|
|
Mode: corecmd.GroupNavigationOnly,
|
|
Positionals: corecmd.PositionalsReject,
|
|
Recovery: corecmd.RecoverySibling,
|
|
})
|
|
sheet.AddCommand(
|
|
rangeGroup,
|
|
&cobra.Command{Use: "ready", Run: func(*cobra.Command, []string) {}},
|
|
)
|
|
corecmd.ApplyGroupPolicy(sheet, corecmd.GroupPolicy{
|
|
Mode: corecmd.GroupNavigationOnly,
|
|
Positionals: corecmd.PositionalsReject,
|
|
Recovery: corecmd.RecoveryDeep,
|
|
})
|
|
root.AddCommand(sheet)
|
|
|
|
ctx, err := RunPreParseArgs(root, nil, []string{"sheet", "read", "--sheet-id", "sheet-1"})
|
|
if ctx == nil || ctx.Command != "dws sheet" {
|
|
t.Fatalf("Context = %#v, want unresolved target dws sheet", ctx)
|
|
}
|
|
structured := requireCommandResolutionError(t, err, string(cmdutil.ResolutionUnknownSubcommand))
|
|
if structured.Message != `unknown subcommand "read" for "dws sheet"` {
|
|
t.Fatalf("Message = %q", structured.Message)
|
|
}
|
|
if structured.Hint != `Did you mean "dws sheet range read"? (Run 'dws sheet --help' for the full list)` {
|
|
t.Fatalf("Hint = %q", structured.Hint)
|
|
}
|
|
if !slices.Equal(structured.Actions, []string{"Run 'dws sheet --help' for the full list"}) {
|
|
t.Fatalf("Actions = %#v", structured.Actions)
|
|
}
|
|
if structured.Details["input"] != "read" {
|
|
t.Fatalf("Details.input = %#v", structured.Details["input"])
|
|
}
|
|
suggestions, ok := structured.Details["suggestions"].([]string)
|
|
if !ok || !slices.Equal(suggestions, []string{"range read"}) {
|
|
t.Fatalf("Details.suggestions = %#v", structured.Details["suggestions"])
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageCommandSuggestionHintIsBoundedAndFallsBack(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
group := &cobra.Command{Use: "demo", SuggestionsMinimumDistance: 100}
|
|
root.AddCommand(group)
|
|
for _, name := range []string{"aaa", "alpha", "alphi", "alphx"} {
|
|
group.AddCommand(&cobra.Command{Use: name, Run: func(*cobra.Command, []string) {}})
|
|
}
|
|
hint := cmdutil.FormatSubcommandSuggestionHint(group, cmdutil.SuggestSubcommands(group, "alpho"), "fallback")
|
|
if strings.Count(hint, `"dws demo `) != cmdutil.MaxCommandSuggestions || strings.Contains(hint, `"dws demo aaa"`) || !strings.Contains(hint, `"dws demo alphx"`) {
|
|
t.Fatalf("nearest bounded hint = %q", hint)
|
|
}
|
|
|
|
empty := &cobra.Command{Use: "empty"}
|
|
root.AddCommand(empty)
|
|
if got := cmdutil.FormatSubcommandSuggestionHint(empty, cmdutil.SuggestSubcommands(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 got := cmdutil.ClassifyCommandResolution(root, "+missing"); got != cmdutil.ResolutionUnknownSubcommand {
|
|
t.Fatal("root command was treated as a shortcut service")
|
|
}
|
|
if got := cmdutil.ClassifyCommandResolution(group, "+missing"); got != cmdutil.ResolutionUnknownSubcommand {
|
|
t.Fatal("nested command was treated as a top-level shortcut service")
|
|
}
|
|
service.AddCommand(&cobra.Command{Use: "+known", Aliases: []string{"+alias"}, Run: func(*cobra.Command, []string) {}})
|
|
if got := cmdutil.ClassifyCommandResolution(service, "+missing"); got != cmdutil.ResolutionUnknownShortcut {
|
|
t.Fatalf("top-level shortcut typo reason = %q", got)
|
|
}
|
|
if got := cmdutil.ClassifyCommandResolution(service, "+known"); got != cmdutil.ResolutionUnknownSubcommand {
|
|
t.Fatalf("exact shortcut child was reclassified as unresolved shortcut: %q", got)
|
|
}
|
|
if got := cmdutil.SuggestSubcommands(nil, "missing"); got != nil {
|
|
t.Fatalf("SuggestSubcommands(nil) = %#v", got)
|
|
}
|
|
|
|
service.SuggestionsMinimumDistance = 1
|
|
service.AddCommand(&cobra.Command{
|
|
Use: "canonical",
|
|
SuggestFor: []string{"invented"},
|
|
Run: func(*cobra.Command, []string) {},
|
|
})
|
|
if got := cmdutil.SuggestSubcommands(service, "invented"); len(got) != 1 || got[0] != "canonical" {
|
|
t.Fatalf("explicit SuggestFor suggestions = %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageHintSubCmdReturnsTypedRecovery(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
parent := &cobra.Command{Use: "chat"}
|
|
hint := cmdutil.HintSubCmd("send", "use: dws chat message send")
|
|
root.AddCommand(parent)
|
|
parent.AddCommand(hint)
|
|
|
|
err := hint.RunE(hint, nil)
|
|
var structured *apperrors.Error
|
|
if !stderrors.As(err, &structured) {
|
|
t.Fatalf("HintSubCmd error = %T %v", err, err)
|
|
}
|
|
if structured.Category != apperrors.CategoryValidation || structured.Reason != "unknown_subcommand" {
|
|
t.Fatalf("HintSubCmd error = %#v", structured)
|
|
}
|
|
if !strings.Contains(structured.Hint, "dws chat message send") || !strings.Contains(structured.Hint, "dws chat --help") {
|
|
t.Fatalf("HintSubCmd hint = %q", structured.Hint)
|
|
}
|
|
if !cmdutil.IsHintOnlyCommand(hint) {
|
|
t.Fatal("HintSubCmd lost hint-only identity")
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageHintSubCmdAndGroupRunEDefensiveBranches(t *testing.T) {
|
|
standaloneHint := cmdutil.HintSubCmd("send", " \t ")
|
|
structured := requireCommandResolutionError(t, standaloneHint.RunE(standaloneHint, nil), "unknown_subcommand")
|
|
if structured.Hint != "Run 'send --help' for the full list" {
|
|
t.Fatalf("standalone empty hint = %q", structured.Hint)
|
|
}
|
|
|
|
root := &cobra.Command{Use: "dws"}
|
|
group := &cobra.Command{Use: "demo"}
|
|
group.AddCommand(&cobra.Command{Use: "list", Run: func(*cobra.Command, []string) {}})
|
|
root.AddCommand(group)
|
|
structured = requireCommandResolutionError(t, cmdutil.GroupRunE(group, []string{"lisst"}), "unknown_subcommand")
|
|
if structured.Details["input"] != "lisst" {
|
|
t.Fatalf("GroupRunE() details.input = %#v", structured.Details["input"])
|
|
}
|
|
suggestions, ok := structured.Details["suggestions"].([]string)
|
|
if !ok || len(suggestions) != 1 || suggestions[0] != "list" {
|
|
t.Fatalf("GroupRunE() details.suggestions = %#v", structured.Details["suggestions"])
|
|
}
|
|
|
|
var help strings.Builder
|
|
group.SetOut(&help)
|
|
if err := cmdutil.GroupRunE(group, nil); err != nil {
|
|
t.Fatalf("GroupRunE() help error = %v", err)
|
|
}
|
|
if output := help.String(); !strings.Contains(output, "Usage:") || !strings.Contains(output, "list") {
|
|
t.Fatalf("GroupRunE() help output = %q", output)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func applyTestNavigationGroup(command *cobra.Command) {
|
|
corecmd.ApplyGroupPolicy(command, corecmd.GroupPolicy{
|
|
Mode: corecmd.GroupNavigationOnly,
|
|
Positionals: corecmd.PositionalsReject,
|
|
Recovery: corecmd.RecoverySibling,
|
|
})
|
|
}
|