package commandkit import ( "errors" "reflect" "strconv" "testing" ) type bindingInput struct { message Supplied[string] count Supplied[int] enabled Supplied[bool] tags Supplied[[]string] runtime string } func TestScalarBindingsKeepDefaultsSeparateFromPresence(t *testing.T) { defaultMessage := "fallback" factoryCalls := 0 spec := CommandSpec[bindingInput]{ Name: "run", NewInput: func() bindingInput { factoryCalls++ return bindingInput{runtime: "caller state"} }, Flags: []Flag[bindingInput]{ ValueFlag(FlagMeta{Name: "message"}, &defaultMessage, func(value string) (string, error) { return value, nil }, func(input *bindingInput, value Supplied[string]) { input.message = value }), ValueFlag(FlagMeta{Name: "count"}, (*int)(nil), strconv.Atoi, func(input *bindingInput, value Supplied[int]) { input.count = value }), BoolFlag(FlagMeta{Name: "enabled"}, true, func(input *bindingInput, value Supplied[bool]) { input.enabled = value }), }, } if err := validateCommandDeclaration(NewCommand(spec)); err != nil { t.Fatal(err) } defaultMessage = "changed after declaration" prepared, err := prepareInput(spec, [][]string{nil, nil, nil}, nil, true) if err != nil { t.Fatal(err) } if factoryCalls != 0 { t.Fatal("preparing typed values invoked NewInput") } input := prepared.build() if factoryCalls != 1 { t.Fatalf("NewInput calls = %d, want 1", factoryCalls) } if input.message.Value != "fallback" || input.message.Set { t.Fatalf("omitted message = %#v, want fallback with Set=false", input.message) } if input.count.Value != 0 || input.count.Set { t.Fatalf("omitted count = %#v, want zero with Set=false", input.count) } if !input.enabled.Value || input.enabled.Set { t.Fatalf("omitted enabled = %#v, want true default with Set=false", input.enabled) } if input.runtime != "caller state" { t.Fatalf("NewInput runtime value = %q, want caller value", input.runtime) } prepared, err = prepareInput(spec, [][]string{{"", "last"}, {"0"}, {"true", "false"}}, nil, true) if err != nil { t.Fatal(err) } input = prepared.build() if input.message.Value != "last" || !input.message.Set { t.Fatalf("message = %#v, want last supplied value", input.message) } if input.count.Value != 0 || !input.count.Set { t.Fatalf("count = %#v, want explicit zero", input.count) } if input.enabled.Value || !input.enabled.Set { t.Fatalf("enabled = %#v, want explicit false", input.enabled) } prepared, err = prepareInput(spec, [][]string{{""}, nil, nil}, nil, true) if err != nil { t.Fatal(err) } input = prepared.build() if input.message.Value != "" || !input.message.Set { t.Fatalf("message = %#v, want explicitly supplied empty string", input.message) } prepared, err = prepareInput(spec, [][]string{nil, nil, nil}, nil, true) if err != nil { t.Fatal(err) } input = prepared.build() if input.message.Value != "fallback" || input.message.Set || input.count.Set || input.enabled.Set { t.Fatalf("omitted values after a supplied invocation = %#v", input) } } func TestBoolFlagRejectsExplicitEmptyValue(t *testing.T) { flag := BoolFlag(FlagMeta{Name: "enabled"}, false, func(*bool, Supplied[bool]) {}) _, err := flag.binding.prepare([]string{""}) if err == nil { t.Fatal("BoolFlag accepted an explicitly empty value") } if _, err := flag.binding.prepare([]string{"true"}); err != nil { t.Fatalf("BoolFlag rejected the presence value: %v", err) } } func TestRepeatFlagUsesFreshDefaultsAndWholeOccurrenceValues(t *testing.T) { defaults := []string{"base"} var captured Supplied[[]string] flag := RepeatFlag(FlagMeta{Name: "tag"}, defaults, func(value string) (string, error) { return value, nil }, func(_ *bindingInput, value Supplied[[]string]) { captured = value }) defaults[0] = "changed after declaration" applyDefault, err := flag.binding.prepare(nil) if err != nil { t.Fatal(err) } first := &bindingInput{} applyDefault(first) second := &bindingInput{} applyDefault(second) if !reflect.DeepEqual(captured.Value, []string{"base"}) || captured.Set { t.Fatalf("omitted repeat value = %#v, want copied default with Set=false", captured) } captured.Value[0] = "changed one input" applyDefault(second) if !reflect.DeepEqual(captured.Value, []string{"base"}) { t.Fatalf("repeat default collection was reused: %#v", captured.Value) } applyValues, err := flag.binding.prepare([]string{"a,b", "c"}) if err != nil { t.Fatal(err) } applyValues(&bindingInput{}) if !reflect.DeepEqual(captured.Value, []string{"a,b", "c"}) || !captured.Set { t.Fatalf("supplied repeat values = %#v, want two ordered occurrences", captured) } } func TestSingletonParsesEveryOccurrenceAndPreservesCallerCause(t *testing.T) { parseFailure := errors.New("bad number") flag := ValueFlag[struct{}, int](FlagMeta{Name: "count"}, nil, func(value string) (int, error) { if value == "bad" { return 0, parseFailure } return strconv.Atoi(value) }, func(*struct{}, Supplied[int]) {}) _, err := flag.binding.prepare([]string{"bad", "9"}) if !errors.Is(err, parseFailure) { t.Fatalf("parse error = %v, want caller cause", err) } } func TestRepeatArgumentAllocationReservesLaterMinimums(t *testing.T) { type operandsInput struct { prefix Supplied[[]string] id Supplied[string] suffix Supplied[[]string] } spec := CommandSpec[operandsInput]{ Name: "restore", Arguments: []Argument[operandsInput]{ RepeatArgument(ArgumentMeta{Name: "prefix"}, 0, 2, func(value string) (string, error) { return value, nil }, func(input *operandsInput, value Supplied[[]string]) { input.prefix = value }), ValueArgument(ArgumentMeta{Name: "id"}, true, func(value string) (string, error) { return value, nil }, func(input *operandsInput, value Supplied[string]) { input.id = value }), RepeatArgument(ArgumentMeta{Name: "suffix"}, 1, -1, func(value string) (string, error) { return value, nil }, func(input *operandsInput, value Supplied[[]string]) { input.suffix = value }), }, } if err := validateCommandDeclaration(NewCommand(spec)); err != nil { t.Fatal(err) } prepared, err := prepareInput(spec, nil, []string{"a", "b", "id-1", "x", "y"}, true) if err != nil { t.Fatal(err) } input := prepared.build() if !reflect.DeepEqual(input.prefix.Value, []string{"a", "b"}) || !input.prefix.Set { t.Fatalf("prefix = %#v", input.prefix) } if input.id.Value != "id-1" || !input.id.Set { t.Fatalf("id = %#v", input.id) } if !reflect.DeepEqual(input.suffix.Value, []string{"x", "y"}) || !input.suffix.Set { t.Fatalf("suffix = %#v", input.suffix) } } func TestIncompletePreparationSkipsMissingRequirementsAndInputFactory(t *testing.T) { factoryCalls := 0 spec := CommandSpec[struct{}]{ Name: "show", NewInput: func() struct{} { factoryCalls++ return struct{}{} }, Flags: []Flag[struct{}]{ValueFlag(FlagMeta{Name: "required", Required: true}, (*string)(nil), func(value string) (string, error) { return value, nil }, func(*struct{}, Supplied[string]) {})}, Arguments: []Argument[struct{}]{ValueArgument(ArgumentMeta{Name: "id"}, true, func(value string) (string, error) { return value, nil }, func(*struct{}, Supplied[string]) {})}, } if err := validateCommandDeclaration(NewCommand(spec)); err != nil { t.Fatal(err) } if _, err := prepareInput(spec, [][]string{nil}, nil, true); err == nil { t.Fatal("strict preparation accepted missing requirements") } if _, err := prepareInput(spec, [][]string{nil}, nil, false); err != nil { t.Fatalf("help preparation rejected omitted execution inputs: %v", err) } if factoryCalls != 0 { t.Fatalf("preparation invoked NewInput %d times", factoryCalls) } }