package commandkit import ( "bytes" "context" "errors" "io" "strings" "testing" ) func TestCompletionDeclarationGeneratesScripts(t *testing.T) { for _, name := range []string{"app", "my-app", "tool.exe", "café"} { if !validCompletionExecutable(name) { t.Fatalf("supported executable rejected: %q", name) } } app, err := New(Identity{Name: "Human Readable Name"}, Completion("my-app")) if err != nil { t.Fatal(err) } if !app.routeSnapshot().completion { t.Fatal("completion not present in snapshot") } for _, shell := range []string{"bash", "zsh", "fish"} { var output, diagnostics bytes.Buffer outcome := app.Execute(context.Background(), []string{"completion", shell}, Streams{Out: &output, Err: &diagnostics}) if outcome.Status != 0 || !strings.Contains(output.String(), "my-app") || strings.Contains(output.String(), "Human Readable Name") { t.Fatalf("%s outcome=%#v output=%q diagnostics=%q", shell, outcome, output.String(), diagnostics.String()) } output.Reset() outcome = app.Execute(context.Background(), []string{"completion", shell, "--quiet"}, Streams{Out: &output, Err: &diagnostics}) if outcome.Status != 0 || output.Len() != 0 { t.Fatalf("quiet %s outcome=%#v output=%q", shell, outcome, output.String()) } } var help bytes.Buffer outcome := app.Execute(context.Background(), []string{"--help"}, Streams{Out: &help}) if outcome.Status != 0 || !strings.Contains(help.String(), "completion") || strings.Contains(help.String(), "__complete") { t.Fatalf("help outcome=%#v output=%q", outcome, help.String()) } writeErr := errors.New("destination failed") outcome = app.Execute(context.Background(), []string{"completion", "bash"}, Streams{Out: completionErrorWriter{writeErr}, Err: io.Discard}) if outcome.Status != 1 || !errors.Is(outcome.Presentation.Output.WriteError, writeErr) { t.Fatalf("script delivery failure: %#v", outcome) } } type completionErrorWriter struct{ err error } func (w completionErrorWriter) Write([]byte) (int, error) { return 0, w.err } func TestCompletionInstallationCollisionsAndRollback(t *testing.T) { run := NewCommand(CommandSpec[struct{}]{Name: "run", Run: func(Invocation, struct{}) Outcome { return Success() }}) query := NewCommand(CommandSpec[struct{}]{Name: "__complete", Run: func(Invocation, struct{}) Outcome { return Success() }}) for _, declarations := range [][]Declaration{ {Completion("app"), query}, {query, Completion("app")}, {Completion("app"), run, Alias("__complete nested", "run")}, {run, Alias("__complete nested", "run"), Completion("app")}, {Completion("app"), Group("completion", "collision")}, {Group("outer", "", Completion("app"))}, {Completion("dir/app")}, {Completion("two words")}, } { if _, err := New(Identity{Name: "example"}, declarations...); err == nil { t.Fatalf("accepted conflicting declarations: %#v", declarations) } } app, err := New(Identity{Name: "example"}, run) if err != nil { t.Fatal(err) } before := app.routeSnapshot() if err := app.Register(Completion("app"), query); err == nil || app.routeSnapshot() != before { t.Fatalf("failed batch changed snapshot: %v", err) } if err := app.Register(Completion("app")); err != nil { t.Fatal(err) } if app.routeSnapshot() == before || !app.routeSnapshot().completion { t.Fatal("late completion installation did not refresh snapshot") } if err := app.Register(query); err == nil { t.Fatal("query command accepted after completion installation") } if err := app.Register(Alias("__complete nested", "run")); err == nil { t.Fatal("query alias accepted after completion installation") } withoutCompletion, err := New(Identity{Name: "example"}, query) if err != nil || withoutCompletion.routeSnapshot().completion { t.Fatalf("ordinary query-named command: %v", err) } if outcome := withoutCompletion.Execute(context.Background(), []string{"__complete"}, Streams{}); outcome.Status != 0 { t.Fatalf("ordinary command outcome: %#v", outcome) } if err := withoutCompletion.Register(Completion("app")); err == nil || withoutCompletion.routeSnapshot().completion { t.Fatal("completion accepted an existing query command") } aliased, err := New(Identity{Name: "example"}, run, Alias("__complete nested", "run")) if err != nil { t.Fatal(err) } if err := aliased.Register(Completion("app")); err == nil || aliased.routeSnapshot().completion { t.Fatal("completion accepted an existing query alias") } }