package commandkit import ( "errors" "testing" ) type registryInput struct{ value string } func TestCapturedDeclarationsRetainCallbacksWithoutCallingThem(t *testing.T) { var factoryCalls, prepareCalls, setterCalls, checkCalls, runCalls int identity := newBindingIdentity() flag := Flag[registryInput]{binding: flagBinding[registryInput]{ identity: identity, meta: FlagMeta{Name: "mode"}, valid: true, prepare: func([]string) (preparedValue[registryInput], error) { prepareCalls++ return preparedValue[registryInput]{identity: identity, apply: func(*registryInput) { setterCalls++ }}, nil }, }} command := 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() }, }) group := Group("Tools", "tools", command) 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 callback: factory=%d prepare=%d setter=%d check=%d run=%d", factoryCalls, prepareCalls, setterCalls, checkCalls, runCalls) } if !app.commands["Tools"].command.record.children[0].record.payload.hasRun() { t.Fatal("registered command lost its handler") } } func TestRoutingRuleFactoriesCaptureNormalizedValues(t *testing.T) { commandRule := Alias(" old\tpath ", "new path") if got, want := commandRule.record.sourcePath.String(), "old path"; got != want { t.Fatalf("source path = %q, want %q", got, want) } if got, want := commandRule.record.targetPath.String(), "new path"; got != want { t.Fatalf("target path = %q, want %q", got, want) } globalRule := FlagAlias("--colour", "-c") if globalRule.record.sourceFlag != (capturedFlagSpelling{name: "colour"}) { t.Fatalf("source spelling = %#v", globalRule.record.sourceFlag) } if globalRule.record.targetFlag != (capturedFlagSpelling{name: "c", short: true}) { t.Fatalf("target spelling = %#v", globalRule.record.targetFlag) } scopedRule := FlagAliasFor("group show", "-f", "--format") if got, want := scopedRule.record.scopePath.String(), "group show"; got != want { t.Fatalf("scope path = %q, want %q", got, want) } command := NewCommand(CommandSpec[struct{}]{Name: "new", Summary: "new"}) app, err := New(Identity{Name: "Example"}, &command, &commandRule, globalRule, scopedRule) if err != nil { t.Fatal(err) } if len(app.routingRules) != 3 { t.Fatalf("captured routing rules = %d, want 3", len(app.routingRules)) } registeredCommand := NewCommand(CommandSpec[struct{}]{Name: "registered", Summary: "registered"}) registeredRule := Alias("another", "registered") if err := app.Register(®isteredCommand, ®isteredRule); err != nil { t.Fatal(err) } if len(app.routingRules) != 4 || app.commands["registered"] == nil { t.Fatalf("pointer declarations were not normalized during Register: rules=%d command=%v", len(app.routingRules), app.commands["registered"]) } } func TestInvalidRoutingRuleValuesFailAtInstallation(t *testing.T) { for name, rule := range map[string]RoutingRule{ "empty command source": Alias(" \t ", "target"), "invalid command target": Alias("source", "--help"), "flag assignment": FlagAlias("--format=yaml", "--format"), "short cluster": FlagAlias("-xy", "--format"), "invalid scope": FlagAliasFor("", "-f", "--format"), } { t.Run(name, func(t *testing.T) { if _, err := New(Identity{Name: "Example"}, rule); err == nil { t.Fatal("New() succeeded with an invalid routing rule") } }) } } func TestNilAndZeroDeclarationsFailWithoutMutation(t *testing.T) { app, err := New(Identity{Name: "Example"}) if err != nil { t.Fatal(err) } var nilCommand *Command var nilRule *RoutingRule declarations := []Declaration{nil, nilCommand, nilRule, Command{}, RoutingRule{}} for index, declaration := range declarations { if _, err := New(Identity{Name: "Example"}, declaration); err == nil { t.Errorf("New() accepted invalid declaration %d", index) } beforeCommands := len(app.commands) beforeRules := len(app.routingRules) if err := app.Register(declaration); err == nil { t.Errorf("Register() accepted invalid declaration %d", index) } if len(app.commands) != beforeCommands || len(app.routingRules) != beforeRules { t.Errorf("invalid declaration %d mutated the app", index) } } } 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 := NewCommand(CommandSpec[struct{}]{Name: "custom"}) 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"] == nil || first.commands["help"].command.record.payload == nil || first.commands["version"] == nil || first.commands["version"].command.record.payload == nil { t.Fatal("New did not install real help and version declarations") } } func TestDeclarationIssuesAreReportedAtInstallation(t *testing.T) { for _, name := range []string{"", "--", "-option", "two words", " leading", "trailing "} { app, err := New(Identity{Name: "Example"}) if err != nil { t.Fatal(err) } if err := app.Register(NewCommand(CommandSpec[struct{}]{Name: name})); err == nil { t.Errorf("installation accepted invalid name %q", name) } } app, err := New(Identity{Name: "Example"}) if err != nil { t.Fatal(err) } if err := app.Register(NewCommand(CommandSpec[struct{}]{Name: "CamelCase"})); err != nil { t.Fatalf("mixed-case name rejected: %v", err) } duplicate := Group("parent", "", NewCommand(CommandSpec[struct{}]{Name: "repeat"}), NewCommand(CommandSpec[struct{}]{Name: "repeat"})) if err := app.Register(duplicate); err == nil { t.Fatal("installation accepted duplicate sibling names") } if err := app.Register(Group("parent", "", Command{})); err == nil { t.Fatal("installation accepted a zero child handle") } if err := app.Register(Command{}); err == nil { t.Fatal("installation accepted a zero command handle") } } func TestRunnableCommandCapturesChildrenAndLeavesOriginalUnchanged(t *testing.T) { child := NewCommand(CommandSpec[struct{}]{Name: "child"}) children := []Command{child} parent := NewCommand(CommandSpec[struct{}]{Name: "parent", Children: children, Run: func(Invocation, struct{}) Outcome { return Success() }}) children[0] = NewCommand(CommandSpec[struct{}]{Name: "changed"}) if parent.record.payload == nil || !parent.record.payload.hasRun() { t.Fatal("runnable parent lost its handler") } if len(parent.record.children) != 1 || parent.record.children[0].record.name != "child" { t.Fatalf("captured children = %#v", parent.record.children) } app, err := New(Identity{Name: "Example"}, parent) if err != nil { t.Fatalf("runnable parent with children rejected: %v", err) } if len(app.commands["parent"].command.record.children) != 1 { t.Fatal("child declaration was not installed") } } func TestGroupCapturesChildrenAndSkipsNestedAbsence(t *testing.T) { children := []Command{NewCommand(CommandSpec[struct{}]{Name: "visible"}), NoCommand()} group := Group("parent", "", children...) children[0] = NewCommand(CommandSpec[struct{}]{Name: "changed"}) if len(group.record.children) != 2 || group.record.children[0].record.name != "visible" { t.Fatalf("group did not capture child handles: %#v", group.record.children) } app, err := New(Identity{Name: "Example"}, group) if err != nil { t.Fatal(err) } if len(app.commands["parent"].command.record.children) != 2 { t.Fatal("captured child missing") } } func TestRootGroupRetainsPlacementMetadata(t *testing.T) { root := RootGroup("config", "Configuration.") if !root.record.rootOnly || root.record.absent || root.record.name != "config" { t.Fatalf("root group metadata = %#v", root.record) } copy := root if copy.record != root.record { t.Fatal("copied handle did not retain captured record") } } func TestBatchInstallationRejectsNestedRootOnlyAndKeepsExistingState(t *testing.T) { root := RootGroup("config", "Configuration.") app, err := New(Identity{Name: "Example"}) if err != nil { t.Fatal(err) } if err := app.Register(Group("outer", "", root)); err == nil { t.Fatal("installation accepted nested root-only declaration") } if _, exists := app.commands["outer"]; exists { t.Fatal("failed batch partially installed its parent") } if err := app.Register(root); err != nil { t.Fatalf("root-level declaration rejected: %v", err) } } func TestNoOpOnlyRegistrationPreservesRoutingSnapshot(t *testing.T) { app, err := New(Identity{Name: "Example"}) if err != nil { t.Fatal(err) } before := app.routeSnapshot() if err := app.Register(NoCommand()); err != nil { t.Fatal(err) } if after := app.routeSnapshot(); after != before { t.Fatal("no-op-only registration invalidated derived metadata") } } func TestCapturedDeclarationCanBeInstalledIntoMultipleApps(t *testing.T) { declaration := Group("tools", "", NewCommand(CommandSpec[struct{}]{Name: "run"})) first, err := New(Identity{Name: "First"}, declaration) if err != nil { t.Fatal(err) } second, err := New(Identity{Name: "Second"}, declaration) if err != nil { t.Fatal(err) } if len(first.commands["tools"].command.record.children) != 1 { t.Fatal("first app lost reused declaration") } if len(second.commands["tools"].command.record.children) != 1 { t.Fatal("second app lost reused declaration") } } func TestWithErrorCopiesDeclarationAndRetainsWrappedCauses(t *testing.T) { first := errors.New("first construction issue") second := errors.New("second construction issue") base := NewCommand(CommandSpec[struct{}]{Name: "run"}) withFirst := WithError(base, first) withBoth := WithError(withFirst, errors.Join(errors.New("wrapper"), second)) if len(base.record.issues) != 0 { t.Fatal("WithError mutated its source declaration") } if len(withFirst.record.issues) != 1 || len(withBoth.record.issues) != 2 { t.Fatal("WithError did not retain prior causes") } app, err := New(Identity{Name: "Example"}) if err != nil { t.Fatal(err) } err = app.Register(withBoth) if !errors.Is(err, first) || !errors.Is(err, second) { t.Fatalf("installation error %v did not preserve causes", err) } if err := app.Register(WithError(NoCommand(), first)); !errors.Is(err, first) { t.Fatalf("error on absence = %v", err) } if err := app.Register(NoCommand()); err != nil { t.Fatalf("plain absence was not skipped: %v", err) } if err := app.Register(RootGroup("empty", "")); err != nil { t.Fatalf("real empty root group rejected: %v", err) } } func TestNewRejectsEmptyIdentity(t *testing.T) { if _, err := New(Identity{}); err == nil { t.Fatal("New accepted an empty identity name") } } func TestNewCommandCopiesBindingMetadata(t *testing.T) { identity := newBindingIdentity() flags := []Flag[registryInput]{{binding: flagBinding[registryInput]{identity: identity, meta: FlagMeta{Name: "mode"}, valid: true, prepare: func([]string) (preparedValue[registryInput], error) { return preparedValue[registryInput]{identity: identity, apply: func(*registryInput) {}}, nil }}}} command := NewCommand(CommandSpec[registryInput]{Name: "run", Flags: flags}) flags[0].binding.meta.Name = "changed" payload := command.record.payload.(typedCommand[registryInput]) if got := payload.spec.Flags[0].binding.meta.Name; got != "mode" { t.Fatalf("stored flag name = %q, want mode", got) } }