package commandkit import ( "testing" ) type registryInput struct { value string } func TestNewAndRegisterRetainCallbacksWithoutCallingThem(t *testing.T) { var factoryCalls, prepareCalls, setterCalls, checkCalls, runCalls int flag := Flag[registryInput]{binding: flagBinding[registryInput]{ meta: FlagMeta{Name: "mode"}, valid: true, prepare: func([]string) (func(*registryInput), error) { prepareCalls++ return func(*registryInput) { setterCalls++ }, nil }, }} command, err := NewCommand(CommandSpec[registryInput]{ Name: "Inspect", NewInput: func() registryInput { factoryCalls++ return registryInput{} }, Flags: []Flag[registryInput]{flag}, CheckInput: func(registryInput) error { checkCalls++ return nil }, Run: func(Invocation, registryInput) Outcome { runCalls++ return Success() }, }) if err != nil { t.Fatal(err) } group, err := Group("Tools", "tools", command) if err != nil { t.Fatal(err) } app, err := New(Identity{Name: "Example"}, group) if err != nil { t.Fatal(err) } if err := app.Register(); err != nil { t.Fatal(err) } if factoryCalls+prepareCalls+setterCalls+checkCalls+runCalls != 0 { t.Fatalf("construction invoked a callback: factory=%d prepare=%d setter=%d check=%d run=%d", factoryCalls, prepareCalls, setterCalls, checkCalls, runCalls) } if !app.commands["Tools"].command.children[0].payload.hasRun() { t.Fatal("registered command lost its handler") } } func TestAppsHaveIndependentRegistries(t *testing.T) { first, err := New(Identity{Name: "First"}) if err != nil { t.Fatal(err) } second, err := New(Identity{Name: "Second"}) if err != nil { t.Fatal(err) } command, err := NewCommand(CommandSpec[struct{}]{Name: "custom"}) if err != nil { t.Fatal(err) } if err := first.Register(command); err != nil { t.Fatal(err) } if _, exists := second.commands["custom"]; exists { t.Fatal("registration leaked to another app") } if !first.commands["help"].reserved || !first.commands["version"].reserved { t.Fatal("New did not reserve generated root commands") } } func TestCommandAndGroupNames(t *testing.T) { for _, name := range []string{"", "--", "-option", "two words", " leading", "trailing "} { if _, err := NewCommand(CommandSpec[struct{}]{Name: name}); err == nil { t.Errorf("NewCommand accepted invalid name %q", name) } } if _, err := NewCommand(CommandSpec[struct{}]{Name: "CamelCase"}); err != nil { t.Fatalf("NewCommand rejected a custom mixed-case name: %v", err) } first, err := NewCommand(CommandSpec[struct{}]{Name: "repeat"}) if err != nil { t.Fatal(err) } second, err := NewCommand(CommandSpec[struct{}]{Name: "repeat"}) if err != nil { t.Fatal(err) } if _, err := Group("parent", "", first, second); err == nil { t.Fatal("Group accepted duplicate sibling paths") } if _, err := Group("parent", "", Command{}); err == nil { t.Fatal("Group accepted an invalid child declaration") } } func TestWithChildrenRetainsRunnableParent(t *testing.T) { parent, err := NewCommand(CommandSpec[struct{}]{Name: "parent", Run: func(Invocation, struct{}) Outcome { return Success() }}) if err != nil { t.Fatal(err) } child, err := NewCommand(CommandSpec[struct{}]{Name: "child"}) if err != nil { t.Fatal(err) } composed, err := WithChildren(parent, child) if err != nil { t.Fatal(err) } if composed.payload == nil || !composed.payload.hasRun() { t.Fatal("WithChildren did not retain the runnable parent") } if len(composed.children) != 1 || composed.children[0].name != "child" { t.Fatalf("children = %#v, want relative child declaration", composed.children) } if len(parent.children) != 0 { t.Fatal("WithChildren changed the original parent declaration") } } func TestRegisterBatchIsAtomicAndRejectsReservedPaths(t *testing.T) { app, err := New(Identity{Name: "Example"}) if err != nil { t.Fatal(err) } existing, err := NewCommand(CommandSpec[struct{}]{Name: "existing"}) if err != nil { t.Fatal(err) } if err := app.Register(existing); err != nil { t.Fatal(err) } before := app.commands["existing"] added, err := NewCommand(CommandSpec[struct{}]{Name: "added"}) if err != nil { t.Fatal(err) } if err := app.Register(added, existing); err == nil { t.Fatal("Register accepted a duplicate path") } if app.commands["existing"] != before { t.Fatal("failed batch changed an existing registration") } if _, exists := app.commands["added"]; exists { t.Fatal("failed batch attached a partial registration") } if err := app.Register(added, added); err == nil { t.Fatal("Register accepted a duplicate within a batch") } if _, exists := app.commands["added"]; exists { t.Fatal("failed duplicate batch attached a registration") } for _, name := range []string{"help", "version"} { reserved, err := NewCommand(CommandSpec[struct{}]{Name: name}) if err != nil { t.Fatal(err) } if err := app.Register(reserved); err == nil { t.Errorf("Register replaced reserved command %q", name) } } } func TestNewRejectsEmptyIdentityAndInvalidBindingMetadata(t *testing.T) { if _, err := New(Identity{}); err == nil { t.Fatal("New accepted an empty identity name") } validFlag := func(name, short string) Flag[registryInput] { return Flag[registryInput]{binding: flagBinding[registryInput]{ meta: FlagMeta{Name: name, Short: short}, valid: true, prepare: func([]string) (func(*registryInput), error) { return func(*registryInput) {}, nil }, }} } for _, flags := range [][]Flag[registryInput]{ {validFlag("one", "x"), validFlag("one", "y")}, {validFlag("one", "x"), validFlag("two", "x")}, {validFlag("help", "")}, {validFlag("SomeFlag", "")}, {validFlag("some_flag", "")}, {validFlag("some--flag", "")}, {{binding: flagBinding[registryInput]{meta: FlagMeta{Name: "missing"}}}}, } { if _, err := NewCommand(CommandSpec[registryInput]{Name: "flags", Flags: flags}); err == nil { t.Errorf("NewCommand accepted invalid flag metadata: %#v", flags) } } badArgument := Argument[registryInput]{binding: argumentBinding[registryInput]{ meta: ArgumentMeta{Name: "item"}, min: 2, max: 1, valid: true, prepare: func([]string) (func(*registryInput), error) { return func(*registryInput) {}, nil }, }} if _, err := NewCommand(CommandSpec[registryInput]{Name: "args", Arguments: []Argument[registryInput]{badArgument}}); err == nil { t.Fatal("NewCommand accepted invalid argument bounds") } } func TestNewCommandCopiesBindingMetadata(t *testing.T) { flags := []Flag[registryInput]{{binding: flagBinding[registryInput]{ meta: FlagMeta{Name: "mode"}, valid: true, prepare: func([]string) (func(*registryInput), error) { return func(*registryInput) {}, nil }, }}} command, err := NewCommand(CommandSpec[registryInput]{Name: "run", Flags: flags}) if err != nil { t.Fatal(err) } flags[0].binding.meta.Name = "changed" payload := command.payload.(typedCommand[registryInput]) if got := payload.spec.Flags[0].binding.meta.Name; got != "mode" { t.Fatalf("stored flag name = %q, want %q", got, "mode") } }