1030 lines
39 KiB
Go
1030 lines
39 KiB
Go
package commandkit
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestDiscoveryUsageRetainsEnteredPrefix(t *testing.T) {
|
|
initialized := false
|
|
inspect := NewCommand(CommandSpec[struct{}]{
|
|
Name: "inspect", NewInput: func() struct{} { initialized = true; return struct{}{} },
|
|
Run: func(Invocation, struct{}) Outcome { t.Fatal("usage failure executed a handler"); return Success() },
|
|
})
|
|
app, err := New(Identity{Name: "Demo"}, inspect, Group("box", "Box"),
|
|
Alias("list things", "inspect"), Alias("p", "q"), Alias("q", "box"), Alias("q admin pick", "inspect"),
|
|
FlagAlias("--hlep", "--help"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, prefix := range []string{"list", "p admin"} {
|
|
for _, test := range []struct {
|
|
name string
|
|
tail []string
|
|
want string
|
|
}{
|
|
{name: "bare", want: "incomplete command path"},
|
|
{name: "unknown flag", tail: []string{"--missing"}, want: "--missing"},
|
|
{name: "invalid help", tail: []string{"--hlep=bad"}, want: "for --hlep"},
|
|
} {
|
|
t.Run(prefix+"/"+test.name, func(t *testing.T) {
|
|
var output, diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), append(strings.Fields(prefix), test.tail...), Streams{Out: &output, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeUsage || outcome.Status != 2 || outcome.Presentation.Diagnostics.State != DeliveryComplete || output.Len() != 0 {
|
|
t.Fatalf("outcome = %#v, output %q", outcome, output.String())
|
|
}
|
|
for _, fragment := range []string{test.want, "Usage: Demo " + prefix} {
|
|
if !strings.Contains(diagnostics.String(), fragment) {
|
|
t.Errorf("diagnostics %q omit %q", diagnostics.String(), fragment)
|
|
}
|
|
}
|
|
if strings.Contains(diagnostics.String(), "Alias for:") {
|
|
t.Errorf("discovery was assigned a canonical identity: %q", diagnostics.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
for _, prefix := range []string{"", "inspect"} {
|
|
var diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), append(strings.Fields(prefix), "--missing"), Streams{Err: &diagnostics})
|
|
if outcome.Kind != OutcomeUsage || !strings.Contains(diagnostics.String(), "Usage: Demo "+prefix) || strings.Contains(diagnostics.String(), "Alias for:") {
|
|
t.Errorf("ordinary usage for %q = %#v, diagnostics %q", prefix, outcome, diagnostics.String())
|
|
}
|
|
}
|
|
if initialized {
|
|
t.Fatal("usage initialized command input")
|
|
}
|
|
}
|
|
|
|
func TestParserFailuresRetainAliasUsageAndFlagSpellings(t *testing.T) {
|
|
built := 0
|
|
command := NewCommand(CommandSpec[struct{}]{
|
|
Name: "inspect",
|
|
NewInput: func() struct{} { built++; return struct{}{} },
|
|
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
|
func(value string) (string, error) { return value, nil },
|
|
func(*struct{}, Supplied[string]) {})},
|
|
Run: func(Invocation, struct{}) Outcome { t.Fatal("invalid input executed"); return Success() },
|
|
})
|
|
app, err := New(Identity{Name: "Demo"}, command, Alias("i", "inspect"),
|
|
FlagAliasFor("inspect", "--fmt", "--format"),
|
|
FlagAliasFor("inspect", "-F", "--format"), FlagAlias("--hlep", "--help"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, test := range []struct {
|
|
args []string
|
|
spelling string
|
|
}{
|
|
{[]string{"i", "--fmt"}, ": --fmt"},
|
|
{[]string{"i", "-F"}, ": -F"},
|
|
{[]string{"i", "--hlep=bad", "--help=true"}, "for --hlep"},
|
|
{[]string{"i", "-h=bad"}, "for -h"},
|
|
} {
|
|
t.Run(strings.Join(test.args, " "), func(t *testing.T) {
|
|
var diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), test.args, Streams{Out: io.Discard, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeUsage || built != 0 {
|
|
t.Fatalf("outcome = %#v, inputs built = %d", outcome, built)
|
|
}
|
|
for _, fragment := range []string{test.spelling, "Usage: Demo i", "Alias for: Demo inspect"} {
|
|
if !strings.Contains(diagnostics.String(), fragment) {
|
|
t.Errorf("diagnostics %q omit %q", diagnostics.String(), fragment)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCommandContextSurvivesIncompleteAndInvalidOperandCounts(t *testing.T) {
|
|
for _, spelling := range []string{"run", "r"} {
|
|
for _, test := range []struct {
|
|
name string
|
|
args []string
|
|
help bool
|
|
usage bool
|
|
}{
|
|
{name: "interspersed alias", args: []string{"first", "--fmt=yaml", "second"}},
|
|
{name: "interspersed canonical flag", args: []string{"first", "--format=yaml", "second"}},
|
|
{name: "partial help", args: []string{"first", "--help"}, help: true},
|
|
{name: "partial help with flag alias", args: []string{"first", "--fmt=yaml", "--help"}, help: true},
|
|
{name: "excess operands", args: []string{"first", "second", "third", "--fmt=yaml"}, usage: true},
|
|
{name: "missing operands", args: []string{"first", "--fmt=yaml"}, usage: true},
|
|
} {
|
|
t.Run(spelling+"/"+test.name, func(t *testing.T) {
|
|
type input struct {
|
|
operands []string
|
|
format Supplied[string]
|
|
}
|
|
built, ran := 0, false
|
|
command := NewCommand(CommandSpec[input]{
|
|
Name: "run", NewInput: func() input { built++; return input{} },
|
|
Flags: []Flag[input]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
|
func(value string) (string, error) { return value, nil },
|
|
func(target *input, value Supplied[string]) { target.format = value })},
|
|
Arguments: []Argument[input]{RepeatArgument(ArgumentMeta{Name: "ARG"}, 2, 2,
|
|
func(value string) (string, error) { return value, nil },
|
|
func(target *input, value Supplied[[]string]) { target.operands = value.Value })},
|
|
Run: func(inv Invocation, value input) Outcome {
|
|
ran = true
|
|
if inv.EnteredPath() != spelling || inv.CanonicalPath() != "run" {
|
|
t.Errorf("invocation paths = %q / %q", inv.EnteredPath(), inv.CanonicalPath())
|
|
}
|
|
if !reflect.DeepEqual(value.operands, []string{"first", "second"}) || !value.format.Set || value.format.Value != "yaml" {
|
|
t.Errorf("input = %#v", value)
|
|
}
|
|
return Success()
|
|
},
|
|
})
|
|
app, err := New(Identity{Name: "Demo"}, command, Alias("r", "run"), FlagAliasFor("run", "--fmt", "--format"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var output, diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), append([]string{spelling}, test.args...), Streams{Out: &output, Err: &diagnostics})
|
|
if test.usage {
|
|
if outcome.Kind != OutcomeUsage || strings.Contains(diagnostics.String(), "unknown flag") || !strings.Contains(diagnostics.String(), "Demo "+spelling+" ARG") {
|
|
t.Fatalf("invalid arity outcome = %v, diagnostics %q", outcome.Kind, diagnostics.String())
|
|
}
|
|
} else if outcome.Kind != OutcomeSuccess {
|
|
t.Fatalf("outcome = %#v, diagnostics %q", outcome, diagnostics.String())
|
|
}
|
|
if test.help && (!strings.Contains(output.String(), "Demo "+spelling+" ARG") || !strings.Contains(output.String(), "--fmt → --format")) {
|
|
t.Errorf("command help = %q", output.String())
|
|
}
|
|
if test.help || test.usage {
|
|
if built != 0 || ran {
|
|
t.Fatal("help or invalid arity initialized input or ran a handler")
|
|
}
|
|
} else if built != 1 || !ran {
|
|
t.Fatal("valid invocation did not run once")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRunnableCommandCanHaveAliasOnlyDiscoveryPrefixes(t *testing.T) {
|
|
ran := false
|
|
app, err := New(Identity{Name: "Demo"},
|
|
NewCommand(CommandSpec[struct{}]{Name: "box", Run: func(Invocation, struct{}) Outcome { ran = true; return Success() }}),
|
|
simpleRunnable("inspect"), Alias("box admin pick", "inspect"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"box", "admin", "--help"}, Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != OutcomeSuccess || !strings.Contains(output.String(), "pick → inspect") || ran {
|
|
t.Fatalf("discovery outcome = %v, help %q, ran %t", outcome.Kind, output.String(), ran)
|
|
}
|
|
}
|
|
|
|
func TestExplicitHelpRequiresAnExactResolvedTarget(t *testing.T) {
|
|
initialized := false
|
|
run := NewCommand(CommandSpec[struct{}]{
|
|
Name: "run", NewInput: func() struct{} { initialized = true; return struct{}{} },
|
|
Arguments: []Argument[struct{}]{RepeatArgument(ArgumentMeta{Name: "ARG"}, 2, 2,
|
|
func(value string) (string, error) { return value, nil }, func(*struct{}, Supplied[[]string]) {})},
|
|
Run: func(Invocation, struct{}) Outcome { t.Error("help ran the command"); return Success() },
|
|
})
|
|
app, err := New(Identity{Name: "Demo"}, run, Group("box", "Box", simpleRunnable("show")),
|
|
Alias("r", "run"), Alias("b", "box"), Alias("list show", "box show"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, args := range [][]string{
|
|
{"help", "run", "first", "second"}, {"help", "r", "first"},
|
|
{"help", "r", "first", "second"}, {"help", "r", "first", "second", "third"},
|
|
} {
|
|
var output, diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), args, Streams{Out: &output, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeUsage || output.Len() != 0 {
|
|
t.Errorf("Execute(%q) = %v, output %q, diagnostics %q", args, outcome.Kind, output.String(), diagnostics.String())
|
|
}
|
|
}
|
|
for _, args := range [][]string{
|
|
{"help", "run"}, {"help", "r"}, {"help", "box"}, {"help", "b"},
|
|
{"help", "list"}, {"r", "first", "second", "--help"},
|
|
} {
|
|
var output, diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), args, Streams{Out: &output, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeSuccess || !strings.Contains(output.String(), "Usage:") {
|
|
t.Errorf("Execute(%q) = %v, output %q, diagnostics %q", args, outcome.Kind, output.String(), diagnostics.String())
|
|
}
|
|
}
|
|
if initialized {
|
|
t.Fatal("help initialized command input")
|
|
}
|
|
}
|
|
|
|
func TestDiscoveryHelpComposesGroupShortcutsAndOverrides(t *testing.T) {
|
|
for _, override := range []bool{false, true} {
|
|
t.Run(fmt.Sprint(override), func(t *testing.T) {
|
|
initialized := false
|
|
inspect := NewCommand(CommandSpec[struct{}]{
|
|
Name: "inspect", NewInput: func() struct{} { initialized = true; return struct{}{} },
|
|
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
|
func(value string) (string, error) { return value, nil }, func(*struct{}, Supplied[string]) {})},
|
|
Run: func(Invocation, struct{}) Outcome { return Success() },
|
|
})
|
|
declarations := []Declaration{Group("box", "Box"), inspect, simpleRunnable("alternate"),
|
|
Alias("pk", "box"), Alias("box admin pick", "inspect"), Alias("box admin other", "inspect"),
|
|
FlagAlias("--hlep", "--help"), FlagAliasFor("inspect", "--fmt", "--format")}
|
|
wantPick := "pick → inspect"
|
|
if override {
|
|
declarations = append(declarations, Alias("pk admin pick", "alternate"), Alias("pk admin nested choose", "inspect"))
|
|
wantPick = "pick → alternate"
|
|
}
|
|
app, err := New(Identity{Name: "Demo"}, declarations...)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, args := range [][]string{{"pk", "admin", "--help"}, {"help", "pk", "admin"}, {"pk", "admin", "--hlep"}} {
|
|
var output, diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), args, Streams{Out: &output, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeSuccess {
|
|
t.Fatalf("Execute(%q) = %v, diagnostics %q", args, outcome.Kind, diagnostics.String())
|
|
}
|
|
for _, fragment := range []string{"Demo pk admin [command]", wantPick, "other → inspect"} {
|
|
if !strings.Contains(output.String(), fragment) {
|
|
t.Errorf("help %q omits %q", output.String(), fragment)
|
|
}
|
|
}
|
|
if override && strings.Contains(output.String(), "pick → inspect") {
|
|
t.Errorf("help displays overridden target: %q", output.String())
|
|
}
|
|
if strings.Contains(output.String(), "--fmt") {
|
|
t.Errorf("discovery inherited a descendant flag: %q", output.String())
|
|
}
|
|
}
|
|
if initialized {
|
|
t.Fatal("discovery initialized application input")
|
|
}
|
|
if override {
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"help", "pk", "admin", "nested"}, Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != OutcomeSuccess || !strings.Contains(output.String(), "choose → inspect") {
|
|
t.Fatalf("entered-only discovery outcome = %v, help %q", outcome.Kind, output.String())
|
|
}
|
|
}
|
|
var diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"pk", "admin"}, Streams{Out: io.Discard, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeUsage || !strings.Contains(diagnostics.String(), "pk admin") {
|
|
t.Fatalf("bare discovery = %v, diagnostics %q", outcome.Kind, diagnostics.String())
|
|
}
|
|
outcome = app.Execute(context.Background(), []string{"pk", "admin", "--fmt=yaml", "--hlep"}, Streams{Out: io.Discard, Err: io.Discard})
|
|
if outcome.Kind != OutcomeUsage {
|
|
t.Fatal("discovery accepted descendant flag")
|
|
}
|
|
outcome = app.Execute(context.Background(), []string{"pk", "admin", "other"}, Streams{Out: io.Discard, Err: io.Discard})
|
|
if outcome.Kind != OutcomeSuccess || !initialized {
|
|
t.Fatal("complete composed route is unavailable")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelpDiscoversRoutesThroughIntermediateAliases(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
nested bool
|
|
override bool
|
|
expanded bool
|
|
}{
|
|
{name: "group"},
|
|
{name: "discovery", nested: true},
|
|
{name: "discovery override", nested: true, override: true},
|
|
{name: "expanded chain", nested: true, expanded: true},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
built, runs := 0, 0
|
|
var canonical, entered string
|
|
makeCommand := func(name string) Command {
|
|
return NewCommand(CommandSpec[struct{}]{
|
|
Name: name, NewInput: func() struct{} { built++; return struct{}{} },
|
|
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
|
func(value string) (string, error) { return value, nil }, func(*struct{}, Supplied[string]) {})},
|
|
Run: func(inv Invocation, _ struct{}) Outcome {
|
|
runs++
|
|
entered, canonical = inv.EnteredPath(), inv.CanonicalPath()
|
|
return Success()
|
|
},
|
|
})
|
|
}
|
|
group, target := Group("box", "Box"), "box"
|
|
if test.expanded {
|
|
group, target = Group("outer", "Outer", group), "outer box"
|
|
}
|
|
prefix, middle := "p", "q"
|
|
if test.nested {
|
|
prefix, middle = "p admin", "q admin"
|
|
}
|
|
declarations := []Declaration{group, makeCommand("inspect"), makeCommand("alternate"),
|
|
Alias("p", "q"), Alias("q", target), Alias(middle+" pick", "inspect"), Alias(middle+" other", "inspect"),
|
|
FlagAlias("--hlep", "--help"), FlagAliasFor("inspect", "--fmt", "--format")}
|
|
wantTarget := "inspect"
|
|
if test.override {
|
|
declarations = append(declarations, Alias(prefix+" pick", "alternate"))
|
|
wantTarget = "alternate"
|
|
}
|
|
app, err := New(Identity{Name: "Demo"}, declarations...)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, args := range [][]string{
|
|
append(strings.Fields(prefix), "--help"),
|
|
append([]string{"help"}, strings.Fields(prefix)...),
|
|
append(strings.Fields(prefix), "--hlep"),
|
|
} {
|
|
var output, diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), args, Streams{Out: &output, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeSuccess {
|
|
t.Errorf("Execute(%q) = %v, diagnostics %q", args, outcome.Kind, diagnostics.String())
|
|
}
|
|
for _, fragment := range []string{"Demo " + prefix, "pick → " + wantTarget, "other → inspect", "--hlep → --help"} {
|
|
if !strings.Contains(output.String(), fragment) {
|
|
t.Errorf("help %q omits %q", output.String(), fragment)
|
|
}
|
|
}
|
|
if strings.Contains(output.String(), "--fmt") || strings.Contains(output.String(), "--format") ||
|
|
(test.override && strings.Contains(output.String(), "pick → inspect")) {
|
|
t.Errorf("help exposes a descendant flag or overridden destination: %q", output.String())
|
|
}
|
|
}
|
|
if built != 0 || runs != 0 {
|
|
t.Fatalf("help built %d inputs and ran %d handlers", built, runs)
|
|
}
|
|
for _, suffix := range []string{"pick", "other"} {
|
|
outcome := app.Execute(context.Background(), strings.Fields(prefix+" "+suffix), Streams{Out: io.Discard, Err: io.Discard})
|
|
want := "inspect"
|
|
if suffix == "pick" {
|
|
want = wantTarget
|
|
}
|
|
if outcome.Kind != OutcomeSuccess || canonical != want || entered != prefix+" "+suffix {
|
|
t.Errorf("route %s %s: outcome %v, entered %q, canonical %q", prefix, suffix, outcome.Kind, entered, canonical)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDiscoveryHelpThroughExpandedGroupPath(t *testing.T) {
|
|
app, err := New(Identity{Name: "Demo"}, Group("outer", "Outer", Group("box", "Box")),
|
|
simpleRunnable("inspect"), Alias("pk", "outer box"), Alias("outer box admin pick", "inspect"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, args := range [][]string{{"pk", "admin", "--help"}, {"help", "pk", "admin"}} {
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), args, Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != OutcomeSuccess || !strings.Contains(output.String(), "Demo pk admin [command]") || !strings.Contains(output.String(), "pick → inspect") {
|
|
t.Fatalf("expanded discovery outcome = %v, help %q", outcome.Kind, output.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEmptyCanonicalGroupHelpListsAliasRoutes(t *testing.T) {
|
|
app, err := New(Identity{Name: "Demo"}, Group("box", "Box"), Group("quiet", "Quiet"),
|
|
simpleRunnable("inspect"), Alias("box pick", "inspect"), Alias("pk", "box"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, args := range [][]string{{"box", "--help"}, {"help", "box"}, {"pk", "--help"}, {"help", "pk"}} {
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), args, Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != OutcomeSuccess || !strings.Contains(output.String(), "pick → inspect") {
|
|
t.Errorf("Execute(%q) = %v, help %q", args, outcome.Kind, output.String())
|
|
}
|
|
}
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"quiet", "--help"}, Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != OutcomeSuccess || strings.Contains(output.String(), "Command Aliases:") {
|
|
t.Fatalf("unaliased group help = %v, %q", outcome.Kind, output.String())
|
|
}
|
|
if outcome := app.Execute(context.Background(), []string{"box", "pick"}, Streams{Out: io.Discard, Err: io.Discard}); outcome.Kind != OutcomeSuccess {
|
|
t.Fatal("alias route became unavailable")
|
|
}
|
|
}
|
|
|
|
func routeTestApp(t *testing.T, extra ...Declaration) *App {
|
|
t.Helper()
|
|
collections := simpleRunnable("collections")
|
|
list := Group("list", "List", collections)
|
|
testCommand := runnableWithOperands("test", 0, 1)
|
|
promptkit := Group("promptkit", "PromptKit", list, testCommand)
|
|
run := runnableWithOperands("run", 0, 1)
|
|
inspect := runnableWithOperands("inspect", 1, 1)
|
|
declarations := []Declaration{
|
|
promptkit,
|
|
run,
|
|
inspect,
|
|
Alias("collections", "list collections"),
|
|
Alias("list collections", "promptkit list collections"),
|
|
Alias("pk", "promptkit"),
|
|
Alias("promptkit list bundles", "promptkit list collections"),
|
|
Alias("smoke", "promptkit test"),
|
|
Alias("r", "run"),
|
|
Alias("r full", "inspect"),
|
|
}
|
|
declarations = append(declarations, extra...)
|
|
app, err := New(Identity{Name: "Example"}, declarations...)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return app
|
|
}
|
|
|
|
func TestEnteredRoutesResolveChainsAndPreserveTokenOrigins(t *testing.T) {
|
|
app := routeTestApp(t)
|
|
snapshot := app.routeSnapshot()
|
|
for _, test := range []struct {
|
|
name string
|
|
args []string
|
|
entered string
|
|
canonical string
|
|
wantArgs []string
|
|
wantKind routeKind
|
|
}{
|
|
{
|
|
name: "command alias chain",
|
|
args: []string{"collections", "--tag", "first"},
|
|
entered: "collections",
|
|
canonical: "promptkit list collections",
|
|
wantArgs: []string{"promptkit", "list", "collections", "--tag", "first"},
|
|
wantKind: routeCanonical,
|
|
},
|
|
{
|
|
name: "group alias composition",
|
|
args: []string{"pk", "list", "bundles"},
|
|
entered: "pk list bundles",
|
|
canonical: "promptkit list collections",
|
|
wantArgs: []string{"promptkit", "list", "collections"},
|
|
wantKind: routeCanonical,
|
|
},
|
|
{
|
|
name: "smoke operand",
|
|
args: []string{"smoke", "all"},
|
|
entered: "smoke",
|
|
canonical: "promptkit test",
|
|
wantArgs: []string{"promptkit", "test", "all"},
|
|
wantKind: routeCanonical,
|
|
},
|
|
{
|
|
name: "explicit longer alternate spelling",
|
|
args: []string{"r", "full"},
|
|
entered: "r full",
|
|
canonical: "inspect",
|
|
wantArgs: []string{"inspect"},
|
|
wantKind: routeCanonical,
|
|
},
|
|
{
|
|
name: "canonical runnable operand",
|
|
args: []string{"run", "full"},
|
|
entered: "run",
|
|
canonical: "run",
|
|
wantArgs: []string{"run", "full"},
|
|
wantKind: routeCanonical,
|
|
},
|
|
{
|
|
name: "single dash remains an operand",
|
|
args: []string{"run", "-"},
|
|
entered: "run",
|
|
canonical: "run",
|
|
wantArgs: []string{"run", "-"},
|
|
wantKind: routeCanonical,
|
|
},
|
|
{
|
|
name: "unknown path",
|
|
args: []string{"missing", "route"},
|
|
entered: "missing route",
|
|
wantArgs: []string{"missing", "route"},
|
|
wantKind: routeUnknown,
|
|
},
|
|
{
|
|
name: "space-containing token is not split",
|
|
args: []string{"collections list"},
|
|
entered: "collections list",
|
|
wantArgs: []string{"collections list"},
|
|
wantKind: routeUnknown,
|
|
},
|
|
{
|
|
name: "flag values and spaced tokens",
|
|
args: []string{"collections", "--message", "--hlep", "--text=two words"},
|
|
entered: "collections",
|
|
canonical: "promptkit list collections",
|
|
wantArgs: []string{"promptkit", "list", "collections", "--message", "--hlep", "--text=two words"},
|
|
wantKind: routeCanonical,
|
|
},
|
|
{
|
|
name: "terminator stops command matching",
|
|
args: []string{"collections", "--", "r", "full"},
|
|
entered: "collections",
|
|
canonical: "promptkit list collections",
|
|
wantArgs: []string{"promptkit", "list", "collections", "--", "r", "full"},
|
|
wantKind: routeCanonical,
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
got := resolveEnteredRoute(test.args, snapshot)
|
|
if got.kind != test.wantKind {
|
|
t.Fatalf("route kind = %d, want %d", got.kind, test.wantKind)
|
|
}
|
|
if got.enteredPath.String() != test.entered {
|
|
t.Errorf("entered path = %q, want %q", got.enteredPath.String(), test.entered)
|
|
}
|
|
if got.canonicalPath.String() != test.canonical {
|
|
t.Errorf("canonical path = %q, want %q", got.canonicalPath.String(), test.canonical)
|
|
}
|
|
if !reflect.DeepEqual(got.args, test.wantArgs) {
|
|
t.Errorf("routed args = %q, want %q", got.args, test.wantArgs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLongerRouteSelectionDoesNotFallBackAfterTargetParsingFails(t *testing.T) {
|
|
app := routeTestApp(t)
|
|
result := resolveEnteredRoute([]string{"r", "full"}, app.routeSnapshot())
|
|
if got, want := result.canonicalPath.String(), "inspect"; got != want {
|
|
t.Fatalf("canonical path = %q, want %q", got, want)
|
|
}
|
|
|
|
var diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"r", "full"}, Streams{Out: io.Discard, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeUsage {
|
|
t.Fatalf("outcome kind = %v, want usage; diagnostics: %s", outcome.Kind, diagnostics.String())
|
|
}
|
|
}
|
|
|
|
func TestEnteredAliasCanSelectExplicitLongerMapping(t *testing.T) {
|
|
app := routeTestApp(t, Alias("pk list bundles", "inspect"))
|
|
result := resolveEnteredRoute([]string{"pk", "list", "bundles"}, app.routeSnapshot())
|
|
if got, want := result.canonicalPath.String(), "inspect"; got != want {
|
|
t.Fatalf("canonical path = %q, want %q", got, want)
|
|
}
|
|
if got, want := result.enteredPath.String(), "pk list bundles"; got != want {
|
|
t.Fatalf("entered path = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestExecuteDispatchesEnteredAliasToCanonicalRunner(t *testing.T) {
|
|
runs := 0
|
|
command := NewCommand(CommandSpec[struct{}]{
|
|
Name: "canonical", Summary: "Canonical",
|
|
Run: func(Invocation, struct{}) Outcome {
|
|
runs++
|
|
return Success()
|
|
},
|
|
})
|
|
app, err := New(Identity{Name: "Example"}, command, Alias("shortcut", "canonical"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
outcome := app.Execute(context.Background(), []string{"shortcut"}, Streams{Out: io.Discard, Err: io.Discard})
|
|
if outcome.Kind != OutcomeSuccess || runs != 1 {
|
|
t.Fatalf("alias execution = (%v, runs %d), want success and one canonical run", outcome.Kind, runs)
|
|
}
|
|
}
|
|
|
|
func TestInvocationReportsEnteredAndCanonicalPathsPerCall(t *testing.T) {
|
|
type input struct {
|
|
label Supplied[string]
|
|
values Supplied[[]string]
|
|
}
|
|
type observed struct {
|
|
entered string
|
|
canonical string
|
|
label string
|
|
values []string
|
|
}
|
|
var calls []observed
|
|
command := NewCommand(CommandSpec[input]{
|
|
Name: "run",
|
|
Flags: []Flag[input]{ValueFlag(FlagMeta{Name: "label"}, (*string)(nil),
|
|
func(value string) (string, error) { return value, nil },
|
|
func(target *input, label Supplied[string]) { target.label = label },
|
|
)},
|
|
Arguments: []Argument[input]{RepeatArgument(ArgumentMeta{Name: "value"}, 0, -1,
|
|
func(value string) (string, error) { return value, nil },
|
|
func(target *input, values Supplied[[]string]) { target.values = values },
|
|
)},
|
|
Run: func(inv Invocation, value input) Outcome {
|
|
calls = append(calls, observed{
|
|
entered: inv.EnteredPath(),
|
|
canonical: inv.CanonicalPath(),
|
|
label: value.label.Value,
|
|
values: append([]string(nil), value.values.Value...),
|
|
})
|
|
return Success()
|
|
},
|
|
})
|
|
shortcut := Alias("shortcut", "workspace run")
|
|
app, err := New(Identity{Name: "Example"}, Group("workspace", "Workspace", command), shortcut)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, args := range [][]string{{"workspace", "run", "--label=first", "first"}, {"shortcut", "--label=second", "second"}, {"workspace", "run"}} {
|
|
if outcome := app.Execute(context.Background(), args, Streams{Out: io.Discard, Err: io.Discard}); outcome.Kind != OutcomeSuccess {
|
|
t.Fatalf("Execute(%q) = %v, want success", args, outcome.Kind)
|
|
}
|
|
}
|
|
if len(calls) != 3 {
|
|
t.Fatalf("handler calls = %d, want 3", len(calls))
|
|
}
|
|
want := []observed{
|
|
{entered: "workspace run", canonical: "workspace run", label: "first", values: []string{"first"}},
|
|
{entered: "shortcut", canonical: "workspace run", label: "second", values: []string{"second"}},
|
|
{entered: "workspace run", canonical: "workspace run"},
|
|
}
|
|
if !reflect.DeepEqual(calls, want) {
|
|
t.Fatalf("handler invocations = %#v, want %#v", calls, want)
|
|
}
|
|
|
|
secondApp, err := New(Identity{Name: "Another"}, Group("workspace", "Workspace", command), shortcut)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if outcome := secondApp.Execute(context.Background(), []string{"shortcut"}, Streams{Out: io.Discard, Err: io.Discard}); outcome.Kind != OutcomeSuccess {
|
|
t.Fatalf("reused declaration execution = %v, want success", outcome.Kind)
|
|
}
|
|
if got := calls[3]; got.entered != "shortcut" || got.canonical != "workspace run" || len(got.values) != 0 {
|
|
t.Fatalf("reused declaration invocation = %#v", got)
|
|
}
|
|
|
|
var zero Invocation
|
|
if zero.EnteredPath() != "" || zero.CanonicalPath() != "" {
|
|
t.Fatalf("zero Invocation paths = (%q, %q), want empty paths", zero.EnteredPath(), zero.CanonicalPath())
|
|
}
|
|
}
|
|
|
|
func TestPrivateCommandCallbacksReceiveResolvedInvocationPaths(t *testing.T) {
|
|
var calls []Invocation
|
|
command := Command{record: &commandRecord{
|
|
name: "inspect",
|
|
payload: typedCommand[struct{}]{
|
|
spec: CommandSpec[struct{}]{Name: "inspect"},
|
|
privateRun: func(exec commandExecution, _ struct{}) Outcome {
|
|
calls = append(calls, exec.invocation)
|
|
return Success()
|
|
},
|
|
},
|
|
}}
|
|
app, err := New(Identity{Name: "Example"}, command, Alias("shortcut", "inspect"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, args := range [][]string{{"inspect"}, {"shortcut"}} {
|
|
if outcome := app.Execute(context.Background(), args, Streams{Out: io.Discard, Err: io.Discard}); outcome.Kind != OutcomeSuccess {
|
|
t.Fatalf("Execute(%q) = %v, want success", args, outcome.Kind)
|
|
}
|
|
}
|
|
if len(calls) != 2 || calls[0].EnteredPath() != "inspect" || calls[0].CanonicalPath() != "inspect" || calls[1].EnteredPath() != "shortcut" || calls[1].CanonicalPath() != "inspect" {
|
|
t.Fatalf("private callback invocations = %#v", calls)
|
|
}
|
|
}
|
|
|
|
func TestExecuteAppliesCanonicalCommandFlagAliasesAfterRouting(t *testing.T) {
|
|
type input struct{ color string }
|
|
var got string
|
|
command := NewCommand(CommandSpec[input]{
|
|
Name: "inspect",
|
|
Flags: []Flag[input]{ValueFlag(FlagMeta{Name: "color", Short: "c"}, (*string)(nil),
|
|
func(value string) (string, error) { return value, nil },
|
|
func(target *input, supplied Supplied[string]) { target.color = supplied.Value },
|
|
)},
|
|
Run: func(_ Invocation, value input) Outcome {
|
|
got = value.color
|
|
return Success()
|
|
},
|
|
})
|
|
app, err := New(Identity{Name: "Example"}, command,
|
|
Alias("shortcut", "inspect"),
|
|
FlagAliasFor("inspect", "--colour", "-c"),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
outcome := app.Execute(context.Background(), []string{"shortcut", "--colour=blue"}, Streams{Out: io.Discard, Err: io.Discard})
|
|
if outcome.Kind != OutcomeSuccess || got != "blue" {
|
|
t.Fatalf("alias flag execution = (%v, color %q), want success and blue", outcome.Kind, got)
|
|
}
|
|
}
|
|
|
|
func TestAliasConversionErrorsRetainOccurrenceAndEnteredUsage(t *testing.T) {
|
|
colorFailure := errors.New("invalid color")
|
|
tagFailure := errors.New("invalid tag")
|
|
type input struct {
|
|
color Supplied[string]
|
|
tags Supplied[[]string]
|
|
}
|
|
command := NewCommand(CommandSpec[input]{
|
|
Name: "inspect",
|
|
Flags: []Flag[input]{
|
|
ValueFlag(FlagMeta{Name: "color"}, (*string)(nil), func(value string) (string, error) {
|
|
if value == "bad" {
|
|
return "", colorFailure
|
|
}
|
|
return value, nil
|
|
}, func(target *input, value Supplied[string]) { target.color = value }),
|
|
RepeatFlag(FlagMeta{Name: "tag"}, nil, func(value string) (string, error) {
|
|
if value == "bad" {
|
|
return "", tagFailure
|
|
}
|
|
return value, nil
|
|
}, func(target *input, value Supplied[[]string]) { target.tags = value }),
|
|
},
|
|
Run: func(Invocation, input) Outcome { return Success() },
|
|
})
|
|
app, err := New(Identity{Name: "Example"}, command,
|
|
Alias("shortcut", "inspect"),
|
|
FlagAliasFor("inspect", "--colour", "--color"),
|
|
FlagAliasFor("inspect", "--labels", "--tag"),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
args []string
|
|
cause error
|
|
binding string
|
|
spelling string
|
|
occurrence int
|
|
}{
|
|
{
|
|
name: "invalid scalar before valid overwrite",
|
|
args: []string{"shortcut", "--colour=bad", "--color=good"},
|
|
cause: colorFailure,
|
|
binding: "color",
|
|
spelling: "--colour",
|
|
occurrence: 1,
|
|
},
|
|
{
|
|
name: "invalid repeat occurrence after mixed spelling",
|
|
args: []string{"shortcut", "--labels=first", "--tag=bad"},
|
|
cause: tagFailure,
|
|
binding: "tag",
|
|
spelling: "--tag",
|
|
occurrence: 2,
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), test.args, Streams{Out: io.Discard, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeUsage {
|
|
t.Fatalf("outcome kind = %v, want usage", outcome.Kind)
|
|
}
|
|
if !errors.Is(outcome.Cause, test.cause) {
|
|
t.Fatalf("outcome cause = %v, want wrapped %v", outcome.Cause, test.cause)
|
|
}
|
|
var conversionErr *flagConversionError
|
|
if !errors.As(outcome.Cause, &conversionErr) {
|
|
t.Fatalf("outcome cause %T does not expose flag conversion context", outcome.Cause)
|
|
}
|
|
if conversionErr.binding != test.binding || conversionErr.spelling != test.spelling || conversionErr.occurrence != test.occurrence {
|
|
t.Fatalf("conversion error = %#v, want binding/spelling/occurrence for %s %d", conversionErr, test.spelling, test.occurrence)
|
|
}
|
|
for _, fragment := range []string{fmt.Sprintf("flag %s occurrence %d: %s", test.spelling, test.occurrence, test.cause), "Usage: Example shortcut", "Alias for: Example inspect"} {
|
|
if !strings.Contains(diagnostics.String(), fragment) {
|
|
t.Errorf("diagnostics %q do not contain %q", diagnostics.String(), fragment)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAliasUsageSurvivesRegistrationDuringExecution(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
register func(*App) error
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "successful registration",
|
|
register: func(app *App) error {
|
|
return app.Register(NewCommand(CommandSpec[struct{}]{Name: "late", Run: func(Invocation, struct{}) Outcome { return Success() }}))
|
|
},
|
|
},
|
|
{
|
|
name: "failed registration",
|
|
register: func(app *App) error { return app.Register(Alias("shortcut", "other")) },
|
|
wantErr: true,
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var app *App
|
|
var registrationErr error
|
|
command := NewCommand(CommandSpec[struct{}]{
|
|
Name: "inspect",
|
|
Run: func(Invocation, struct{}) Outcome {
|
|
registrationErr = test.register(app)
|
|
return Usage("operation rejected").WithCause(errors.New("operation rejected"))
|
|
},
|
|
})
|
|
var err error
|
|
app, err = New(Identity{Name: "Example"}, command, Alias("shortcut", "inspect"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"shortcut"}, Streams{Out: io.Discard, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeUsage {
|
|
t.Fatalf("outcome kind = %v, want usage", outcome.Kind)
|
|
}
|
|
if (registrationErr != nil) != test.wantErr {
|
|
t.Fatalf("registration error = %v, want error %t", registrationErr, test.wantErr)
|
|
}
|
|
for _, fragment := range []string{"operation rejected", "Usage: Example shortcut", "Alias for: Example inspect"} {
|
|
if !strings.Contains(diagnostics.String(), fragment) {
|
|
t.Errorf("diagnostics %q do not contain %q", diagnostics.String(), fragment)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUnresolvedRouteUsageHasNoCanonicalAliasAnnotation(t *testing.T) {
|
|
app := routeTestApp(t)
|
|
var diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"missing", "route"}, Streams{Out: io.Discard, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeUsage {
|
|
t.Fatalf("outcome kind = %v, want usage", outcome.Kind)
|
|
}
|
|
if strings.Contains(diagnostics.String(), "Alias for:") {
|
|
t.Fatalf("unresolved route diagnostics invented a canonical target: %q", diagnostics.String())
|
|
}
|
|
|
|
diagnostics.Reset()
|
|
outcome = app.Execute(context.Background(), []string{"r", "full", "--unknown"}, Streams{Out: io.Discard, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeUsage {
|
|
t.Fatalf("unknown flag outcome kind = %v, want usage", outcome.Kind)
|
|
}
|
|
for _, fragment := range []string{"unknown flag", "Usage: Example r full", "Alias for: Example inspect"} {
|
|
if !strings.Contains(diagnostics.String(), fragment) {
|
|
t.Errorf("unknown flag diagnostics %q do not contain %q", diagnostics.String(), fragment)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExecuteAppliesGlobalHelpAliasAtApplicationRoot(t *testing.T) {
|
|
app, err := New(Identity{Name: "Example"}, FlagAlias("--hlep", "--help"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"--hlep"}, Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != OutcomeSuccess || !strings.Contains(output.String(), "Usage:") {
|
|
t.Fatalf("root help alias outcome = %v, output %q", outcome.Kind, output.String())
|
|
}
|
|
}
|
|
|
|
func TestAliasOnlyPrefixProducesDiscoveryRoute(t *testing.T) {
|
|
app, err := New(Identity{Name: "Example"},
|
|
Group("promptkit", "PromptKit", Group("list", "List", simpleRunnable("collections"))),
|
|
Alias("list collections", "promptkit list collections"),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
result := resolveEnteredRoute([]string{"list", "--help"}, app.routeSnapshot())
|
|
if result.kind != routeDiscovery {
|
|
t.Fatalf("route kind = %d, want discovery", result.kind)
|
|
}
|
|
if got, want := result.discoveryPath.String(), "list"; got != want {
|
|
t.Fatalf("discovery path = %q, want %q", got, want)
|
|
}
|
|
if got, want := result.enteredPath.String(), "list"; got != want {
|
|
t.Fatalf("entered path = %q, want %q", got, want)
|
|
}
|
|
if !reflect.DeepEqual(result.args, []string{"--help"}) {
|
|
t.Fatalf("discovery args = %q, want [--help]", result.args)
|
|
}
|
|
}
|
|
|
|
func TestAliasHelpTargetsGroupsAndDiscoveryViews(t *testing.T) {
|
|
var inputBuilds, promptRuns int
|
|
showPrompt := NewCommand(CommandSpec[struct{}]{
|
|
Name: "prompt",
|
|
NewInput: func() struct{} {
|
|
inputBuilds++
|
|
return struct{}{}
|
|
},
|
|
Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
|
func(value string) (string, error) { return value, nil },
|
|
func(*struct{}, Supplied[string]) {},
|
|
)},
|
|
Run: func(Invocation, struct{}) Outcome {
|
|
promptRuns++
|
|
return Success()
|
|
},
|
|
})
|
|
commands := Group("promptkit", "PromptKit",
|
|
Group("list", "List", simpleRunnable("collections")),
|
|
Group("show", "Show", showPrompt),
|
|
)
|
|
app, err := New(Identity{Name: "example"}, commands,
|
|
Alias("list collections", "promptkit list collections"),
|
|
Alias("promptkit list bundles", "promptkit list collections"),
|
|
Alias("pk", "promptkit"),
|
|
Alias("pk list bundles", "promptkit show prompt"),
|
|
FlagAlias("--hlep", "--help"),
|
|
FlagAliasFor("promptkit show prompt", "--fmt", "--format"),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, test := range []struct {
|
|
name string
|
|
args []string
|
|
want []string
|
|
omit []string
|
|
}{
|
|
{
|
|
name: "explicit command help resolves entered target",
|
|
args: []string{"help", "list", "collections"},
|
|
want: []string{"Usage:\n example list collections", "Alias for: example promptkit list collections"},
|
|
},
|
|
{
|
|
name: "aliased help flag uses entered command path",
|
|
args: []string{"list", "collections", "--hlep"},
|
|
want: []string{"Usage:\n example list collections", "Alias for: example promptkit list collections", "--hlep → --help"},
|
|
},
|
|
{
|
|
name: "canonical group shows direct aliases",
|
|
args: []string{"help", "promptkit", "list"},
|
|
want: []string{"Command Aliases:", "bundles → promptkit list collections"},
|
|
},
|
|
{
|
|
name: "group shortcut shows more-specific override",
|
|
args: []string{"pk", "--help"},
|
|
want: []string{"Usage:\n example pk [flags]", "Alias for: example promptkit", "list bundles → promptkit show prompt"},
|
|
omit: []string{"list bundles → promptkit list collections"},
|
|
},
|
|
{
|
|
name: "explicit discovery target",
|
|
args: []string{"help", "list"},
|
|
want: []string{"Usage:\n example list [command]", "Available Aliases:", "collections → promptkit list collections", "--hlep → --help"},
|
|
omit: []string{"--fmt"},
|
|
},
|
|
{
|
|
name: "discovery help flag",
|
|
args: []string{"list", "--hlep"},
|
|
want: []string{"Usage:\n example list [command]", "collections → promptkit list collections"},
|
|
},
|
|
{
|
|
name: "scoped flag alias appears on canonical command help",
|
|
args: []string{"promptkit", "show", "prompt", "--hlep"},
|
|
want: []string{"--fmt → --format"},
|
|
},
|
|
{
|
|
name: "group excludes descendant scoped flag aliases",
|
|
args: []string{"promptkit", "--help"},
|
|
want: []string{"Command Aliases:"},
|
|
omit: []string{"--fmt → --format"},
|
|
},
|
|
{
|
|
name: "root lists explicit command aliases separately",
|
|
args: []string{"--help"},
|
|
want: []string{"Available Commands:", "Command Aliases:", "list collections → promptkit list collections", "pk → promptkit"},
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), test.args, Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != OutcomeSuccess {
|
|
t.Fatalf("Execute(%q) = %#v; output %q", test.args, outcome, output.String())
|
|
}
|
|
for _, fragment := range test.want {
|
|
if !strings.Contains(output.String(), fragment) {
|
|
t.Errorf("help output %q does not contain %q", output.String(), fragment)
|
|
}
|
|
}
|
|
for _, fragment := range test.omit {
|
|
if strings.Contains(output.String(), fragment) {
|
|
t.Errorf("help output %q unexpectedly contains %q", output.String(), fragment)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
var diagnostics bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"list"}, Streams{Out: io.Discard, Err: &diagnostics})
|
|
if outcome.Kind != OutcomeUsage || !strings.Contains(outcome.Explanation.Message, "--help") {
|
|
t.Fatalf("bare discovery prefix = %#v, diagnostics %q; want usage pointing to list --help", outcome, diagnostics.String())
|
|
}
|
|
if inputBuilds != 0 || promptRuns != 0 {
|
|
t.Fatalf("help built %d inputs and ran %d commands", inputBuilds, promptRuns)
|
|
}
|
|
}
|