package commandkit import ( "errors" "fmt" "reflect" "strings" "testing" ) type occurrenceInput struct{ events []string } func occurrenceFor(name string, required bool, calls *int) Flag[occurrenceInput] { return OccurrenceFlag(FlagMeta{Name: name, Required: required, Short: "x"}, func(raw string) (string, error) { *calls++ if raw == "bad" { return "", errors.New("invalid occurrence") } return raw, nil }, func(input *occurrenceInput, value Supplied[string]) { input.events = append(input.events, name+"="+value.Value) }) } func TestOccurrenceFlagsApplyInEnteredOrder(t *testing.T) { calls, factories := 0, 0 first := occurrenceFor("first", false, &calls) second := RenameFlag(occurrenceFor("second", false, &calls), "second") ordinary := ValueFlag(FlagMeta{Name: "ordinary"}, strptr("default"), func(s string) (string, error) { return s, nil }, func(i *occurrenceInput, v Supplied[string]) { i.events = append(i.events, "ordinary="+v.Value) }) arg := ValueArgument(ArgumentMeta{Name: "ARG"}, false, func(s string) (string, error) { return s, nil }, func(i *occurrenceInput, v Supplied[string]) { i.events = append(i.events, "operand="+v.Value) }) var got []string cmd := NewCommand(CommandSpec[occurrenceInput]{Name: "run", Flags: []Flag[occurrenceInput]{first, second, ordinary}, Arguments: []Argument[occurrenceInput]{arg}, NewInput: func() occurrenceInput { factories++; return occurrenceInput{} }, CheckInput: func(i occurrenceInput) error { got = append([]string(nil), i.events...); return nil }, Run: func(Invocation, occurrenceInput) Outcome { return Success() }}) app, err := New(Identity{Name: "Example"}, cmd, FlagAliasFor("run", "--other", "--second")) if err != nil { t.Fatal(err) } result := app.Execute(t.Context(), []string{"run", "--other=a", "--first=", "--second=b", "--ordinary=z", "tail"}, Streams{}) want := []string{"ordinary=z", "second=a", "first=", "second=b", "operand=tail"} if result.Status != 0 || !reflect.DeepEqual(got, want) || calls != 3 || factories != 1 { t.Fatalf("%#v events=%q calls=%d factories=%d", result, got, calls, factories) } result = app.Execute(t.Context(), []string{"run", "--first=bad"}, Streams{}) if result.Kind != OutcomeUsage || factories != 1 || !strings.Contains(result.Explanation.Message, "--first") { t.Fatalf("invalid result %#v factories=%d", result, factories) } result = app.Execute(t.Context(), []string{"run"}, Streams{}) if result.Status != 0 || !reflect.DeepEqual(got, []string{"ordinary=default", "operand="}) { t.Fatalf("fresh %#v, %q", result, got) } } type occurrenceOuter struct{ flags FlagValues[occurrenceInput] } func TestCapturedOccurrencesReplaySelectedOrder(t *testing.T) { calls := 0 a := occurrenceFor("alpha", false, &calls) b := RenameFlag(a, "beta") missing := RenameFlag(occurrenceFor("required", true, &calls), "required") originals := []Flag[occurrenceInput]{a, b, missing} captured := CaptureFlags(originals, func(o *occurrenceOuter) *FlagValues[occurrenceInput] { return &o.flags }) var stored FlagValues[occurrenceInput] cmd := NewCommand(CommandSpec[occurrenceOuter]{Name: "run", Flags: captured, Run: func(_ Invocation, i occurrenceOuter) Outcome { stored = i.flags; return Success() }}) app, err := New(Identity{Name: "Example"}, cmd, FlagAliasFor("run", "--b", "--beta")) if err != nil { t.Fatal(err) } result := app.Execute(t.Context(), []string{"run", "--b=1", "--alpha=2", "--beta=3", "--alpha=4"}, Streams{}) if result.Status != 0 || calls != 4 { t.Fatalf("%#v calls=%d", result, calls) } var got occurrenceInput if err := stored.ApplyTo(&got, a, b); err != nil { t.Fatal(err) } want := []string{"alpha=1", "alpha=2", "alpha=3", "alpha=4"} if !reflect.DeepEqual(got.events, want) { t.Fatalf("events %q want %q", got.events, want) } var subset occurrenceInput if err := stored.ApplyTo(&subset, b); err != nil { t.Fatal(err) } if !reflect.DeepEqual(subset.events, []string{"alpha=1", "alpha=3"}) || calls != 4 { t.Fatalf("subset %q calls=%d", subset.events, calls) } var repeated occurrenceInput if err := stored.ApplyTo(&repeated, a, b); err != nil { t.Fatal(err) } got.events[0] = "changed" if !reflect.DeepEqual(repeated.events, want) { t.Fatalf("shared state %q", repeated.events) } if err := stored.ApplyTo(&repeated, missing); !errors.Is(err, ErrFlagInput) { t.Fatalf("required error %v", err) } other := RenameFlag(a, "other") if err := stored.ApplyTo(&repeated, other); !errors.Is(err, ErrFlagWiring) { t.Fatalf("identity error %v", err) } } func TestRenameFlagKeepsBindingBehavior(t *testing.T) { parse := func(s string) (string, error) { return strings.ToUpper(s), nil } original := ValueFlag(FlagMeta{Name: "original", Short: "o", Required: true, Completion: CompleteChoices(CompletionChoice{Value: "one"})}, strptr("default"), parse, func(i *occurrenceInput, v Supplied[string]) { i.events = append(i.events, fmt.Sprint(v.Value, ":", v.Set)) }) renamed := RenameFlag(original, "renamed") if renamed.binding.identity == original.binding.identity || renamed.binding.meta.Short != "" || !renamed.binding.meta.Required || !reflect.DeepEqual(renamed.binding.meta.Completion, original.binding.meta.Completion) { t.Fatalf("renamed %#v", renamed.binding.meta) } value, err := renamed.binding.prepare(nil) if err != nil { t.Fatal(err) } var input occurrenceInput value.apply(&input) if !reflect.DeepEqual(input.events, []string{"default:false"}) { t.Fatalf("default %q", input.events) } if _, err := New(Identity{Name: "Example"}, NewCommand(CommandSpec[occurrenceInput]{Name: "run", Flags: []Flag[occurrenceInput]{original, RenameFlag(original, "bad name")}})); err == nil { t.Fatal("invalid name accepted") } } func TestOccurrenceRequiredAndAliasedConversion(t *testing.T) { parseErr := errors.New("cannot parse") builds := 0 flag := OccurrenceFlag(FlagMeta{Name: "item", Required: true}, func(raw string) (string, error) { if raw == "bad" { return "", parseErr } return raw, nil }, func(*occurrenceInput, Supplied[string]) {}) cmd := NewCommand(CommandSpec[occurrenceInput]{Name: "run", Flags: []Flag[occurrenceInput]{flag}, NewInput: func() occurrenceInput { builds++; return occurrenceInput{} }, Run: func(Invocation, occurrenceInput) Outcome { return Success() }}) app, err := New(Identity{Name: "Example"}, cmd, FlagAliasFor("run", "--entry", "--item")) if err != nil { t.Fatal(err) } missing := app.Execute(t.Context(), []string{"run"}, Streams{}) if missing.Kind != OutcomeUsage || builds != 0 { t.Fatalf("missing: %#v", missing) } invalid := app.Execute(t.Context(), []string{"run", "--item=ok", "--entry=bad"}, Streams{}) if invalid.Kind != OutcomeUsage || !errors.Is(invalid.Cause, parseErr) || !strings.Contains(invalid.Explanation.Message, "--entry occurrence 2") || builds != 0 { t.Fatalf("invalid: %#v", invalid) } help := app.Execute(t.Context(), []string{"run", "--help"}, Streams{}) if help.Status != 0 || builds != 0 { t.Fatalf("help: %#v", help) } }