package commandkit import ( "bytes" "context" "errors" "io" "strings" "testing" internalcobra "gitea.maximumdirect.net/eric/CommandKit/internal/cobra" commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta" ) func TestCompletionQueryDispatch(t *testing.T) { var factories, converters, handlers int command := NewCommand(CommandSpec[struct{}]{ Name: "run", Summary: "Run a command.", NewInput: func() struct{} { factories++; return struct{}{} }, Flags: []Flag[struct{}]{ ValueFlag(FlagMeta{Name: "color", Short: "c", Completion: CompleteChoices(CompletionChoice{Value: "blue", Description: "cool\ncolor"}, CompletionChoice{Value: "green"}, CompletionChoice{Value: "foo=bar"})}, (*string)(nil), func(value string) (string, error) { converters++; return value, nil }, func(*struct{}, Supplied[string]) {}), ValueFlag(FlagMeta{Name: "file", Short: "f", Completion: CompleteFiles()}, (*string)(nil), func(value string) (string, error) { converters++; return value, nil }, func(*struct{}, Supplied[string]) {}), }, Run: func(Invocation, struct{}) Outcome { handlers++; return Success() }, }) app, err := New(Identity{Name: "example"}, command, Completion("example"), Alias("shortcut", "run"), FlagAliasFor("run", "--colour", "--color")) if err != nil { t.Fatal(err) } emptyReply, err := internalcobra.CompletionReply(commandmeta.CompletionResult{}) if err != nil { t.Fatal(err) } for _, tc := range []struct { name string args []string want string empty bool }{ {"empty word", []string{"__complete", ""}, "run\tRun a command.", false}, {"alias value", []string{"__complete", "shortcut", "--colour=bl"}, "blue\tcool color\n", false}, {"quiet is data", []string{"__complete", "run", "--quiet", "--color=bl"}, "blue\tcool color\n", false}, {"debug is data", []string{"__complete", "run", "--debug", "--color=bl"}, "blue\tcool color\n", false}, {"short attached choice", []string{"__complete", "run", "-cfoo=ba"}, "foo=bar\n", false}, {"unknown route", []string{"__complete", "missing", ""}, emptyReply, true}, } { t.Run(tc.name, func(t *testing.T) { var output, diagnostic bytes.Buffer outcome := app.Execute(context.Background(), tc.args, Streams{Out: &output, Err: &diagnostic}) if outcome.Status != 0 || diagnostic.Len() != 0 || (tc.empty && output.String() != tc.want) || (!tc.empty && !strings.Contains(output.String(), tc.want)) { t.Fatalf("outcome=%#v output=%q diagnostics=%q", outcome, output.String(), diagnostic.String()) } }) } fileReply, err := internalcobra.CompletionReply(commandmeta.CompletionResult{Kind: commandmeta.CompletionFiles, Prefix: "-f"}) if err != nil { t.Fatal(err) } var fileOutput bytes.Buffer fileOutcome := app.Execute(context.Background(), []string{"__complete", "run", "-f./part"}, Streams{Out: &fileOutput}) if fileOutcome.Status != 0 || fileOutput.String() != fileReply { t.Fatalf("file query outcome=%#v reply=%q", fileOutcome, fileOutput.String()) } if factories != 0 || converters != 0 || handlers != 0 { t.Fatalf("query invoked application callbacks: %d %d %d", factories, converters, handlers) } if err := app.Register(simpleRunnable("later")); err != nil { t.Fatal(err) } var output bytes.Buffer if outcome := app.Execute(context.Background(), []string{"__complete", "lat"}, Streams{Out: &output}); outcome.Status != 0 || !strings.Contains(output.String(), "later") { t.Fatalf("later command outcome=%#v reply=%q", outcome, output.String()) } output.Reset() if outcome := app.Execute(context.Background(), []string{"__complete", "later", "--qui"}, Streams{Out: &output}); outcome.Status != 0 || !strings.Contains(output.String(), "--quiet") { t.Fatalf("later command flag outcome=%#v reply=%q", outcome, output.String()) } } func TestCompletionQueryErrorsAndDelivery(t *testing.T) { app, err := New(Identity{Name: "example"}, Completion("example")) if err != nil { t.Fatal(err) } var output, diagnostics bytes.Buffer outcome := app.Execute(context.Background(), []string{"__complete"}, Streams{Out: &output, Err: &diagnostics}) if outcome.Status != 1 || outcome.Cause == nil || output.String() != internalcobra.CompletionErrorReply() || diagnostics.Len() == 0 { t.Fatalf("malformed query outcome=%#v reply=%q diagnostics=%q", outcome, output.String(), diagnostics.String()) } writeErr := errors.New("reply destination failed") writer := &completionCountingWriter{err: writeErr} outcome = app.Execute(context.Background(), []string{"__complete", ""}, Streams{Out: writer, Err: io.Discard}) if outcome.Status != 1 || !errors.Is(outcome.Presentation.Output.WriteError, writeErr) || writer.calls != 1 { t.Fatalf("reply write failure: %#v", outcome) } } type completionCountingWriter struct { err error calls int } func (writer *completionCountingWriter) Write([]byte) (int, error) { writer.calls++ return 0, writer.err } func TestCompletionQueryDiscovery(t *testing.T) { app, err := New(Identity{Name: "example"}, Completion("example"), Group("workspace", "", simpleRunnable("inspect")), Alias("legacy inspect", "workspace inspect"), FlagAlias("--silent", "--quiet")) if err != nil { t.Fatal(err) } var output bytes.Buffer outcome := app.Execute(context.Background(), []string{"__complete", "legacy", "--"}, Streams{Out: &output}) if outcome.Status != 0 || !strings.Contains(output.String(), "--quiet") || strings.Contains(output.String(), "inspect") || strings.Contains(output.String(), "--silent") { t.Fatalf("discovery outcome=%#v reply=%q", outcome, output.String()) } }