Add captured flag value application
This commit is contained in:
144
flag_values_test.go
Normal file
144
flag_values_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package commandkit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type capturedInput struct {
|
||||
name Supplied[string]
|
||||
tags Supplied[[]string]
|
||||
}
|
||||
|
||||
type capturedOuter struct {
|
||||
flags FlagValues[capturedInput]
|
||||
}
|
||||
|
||||
func TestCaptureFlagsAppliesOnlyOriginalBindingWithoutReconversion(t *testing.T) {
|
||||
parseCalls := 0
|
||||
original := ValueFlag(FlagMeta{Name: "name"}, nil, func(value string) (string, error) {
|
||||
parseCalls++
|
||||
return value, nil
|
||||
}, func(input *capturedInput, value Supplied[string]) { input.name = value })
|
||||
copied := original
|
||||
captured := CaptureFlags([]Flag[capturedInput]{original}, func(input *capturedOuter) *FlagValues[capturedInput] { return &input.flags })
|
||||
prepared, err := captured[0].binding.prepare([]string{"chosen"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outer := capturedOuter{}
|
||||
prepared.apply(&outer)
|
||||
var target capturedInput
|
||||
if err := outer.flags.ApplyTo(&target, copied); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if target.name.Value != "chosen" || !target.name.Set || parseCalls != 1 {
|
||||
t.Fatalf("target=%#v parse calls=%d", target, parseCalls)
|
||||
}
|
||||
lookalike := ValueFlag(FlagMeta{Name: "name"}, nil, func(value string) (string, error) { return value, nil }, func(*capturedInput, Supplied[string]) {})
|
||||
if err := outer.flags.ApplyTo(&target, lookalike); !errors.Is(err, ErrFlagWiring) {
|
||||
t.Fatalf("lookalike binding error = %v", err)
|
||||
}
|
||||
if err := outer.flags.ApplyTo(nil, copied); !errors.Is(err, ErrFlagWiring) {
|
||||
t.Fatalf("nil target error = %v", err)
|
||||
}
|
||||
if err := (FlagValues[capturedInput]{}).ApplyTo(&target); err != nil {
|
||||
t.Fatalf("empty selection from zero store: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaptureFlagsDefersRequiredCheckToSelectedBindings(t *testing.T) {
|
||||
required := ValueFlag(FlagMeta{Name: "name", Required: true}, strptr("default"), func(value string) (string, error) { return value, nil }, func(input *capturedInput, value Supplied[string]) { input.name = value })
|
||||
captured := CaptureFlags([]Flag[capturedInput]{required}, func(input *capturedOuter) *FlagValues[capturedInput] { return &input.flags })
|
||||
prepared, err := captured[0].binding.prepare(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outer := capturedOuter{}
|
||||
prepared.apply(&outer)
|
||||
var target capturedInput
|
||||
if err := outer.flags.ApplyTo(&target); err != nil {
|
||||
t.Fatalf("unselected required flag failed: %v", err)
|
||||
}
|
||||
if err := outer.flags.ApplyTo(&target, required); !errors.Is(err, ErrFlagInput) {
|
||||
t.Fatalf("selected missing required flag error = %v", err)
|
||||
}
|
||||
optional := ValueFlag(FlagMeta{Name: "name"}, strptr("default"), func(value string) (string, error) { return value, nil }, func(input *capturedInput, value Supplied[string]) { input.name = value })
|
||||
optionalCapture := CaptureFlags([]Flag[capturedInput]{optional}, func(input *capturedOuter) *FlagValues[capturedInput] { return &input.flags })
|
||||
optionalPrepared, err := optionalCapture[0].binding.prepare(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outer = capturedOuter{}
|
||||
optionalPrepared.apply(&outer)
|
||||
target = capturedInput{}
|
||||
if err := outer.flags.ApplyTo(&target, optional); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if target.name.Value != "default" || target.name.Set {
|
||||
t.Fatalf("captured default value = %#v", target.name)
|
||||
}
|
||||
|
||||
badCapture := CaptureFlags[capturedOuter]([]Flag[capturedInput]{required}, nil)
|
||||
command := NewCommand(CommandSpec[capturedOuter]{Name: "broken", Flags: badCapture})
|
||||
if _, err := New(Identity{Name: "example"}, command); err == nil {
|
||||
t.Fatal("installation accepted a nil captured-flag accessor")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaptureFlagsCopiesRepeatedValuesOnEachApplication(t *testing.T) {
|
||||
original := RepeatFlag(FlagMeta{Name: "tag"}, nil, func(value string) (string, error) { return value, nil }, func(input *capturedInput, value Supplied[[]string]) { input.tags = value })
|
||||
captured := CaptureFlags([]Flag[capturedInput]{original}, func(input *capturedOuter) *FlagValues[capturedInput] { return &input.flags })
|
||||
prepared, err := captured[0].binding.prepare([]string{"a,b", "c"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outer := capturedOuter{}
|
||||
prepared.apply(&outer)
|
||||
var first, second capturedInput
|
||||
if err := outer.flags.ApplyTo(&first, original); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := outer.flags.ApplyTo(&second, original); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
first.tags.Value[0] = "changed"
|
||||
if second.tags.Value[0] != "a,b" || second.tags.Value[1] != "c" || !second.tags.Set {
|
||||
t.Fatalf("second application shares repeat storage: %#v", second.tags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapturedFlagErrorClassificationPreservesWrappedCategories(t *testing.T) {
|
||||
if got := classifyInputError(fmt.Errorf("scope input: %w", ErrFlagWiring)); got.Kind != OutcomeOperational {
|
||||
t.Fatalf("wiring outcome = %#v", got)
|
||||
}
|
||||
if got := classifyInputError(fmt.Errorf("scope input: %w", ErrFlagInput)); got.Kind != OutcomeUsage {
|
||||
t.Fatalf("input outcome = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckInputClassifiesWrappedFlagWiringAsOperational(t *testing.T) {
|
||||
command := NewCommand(CommandSpec[struct{}]{
|
||||
Name: "run",
|
||||
CheckInput: func(struct{}) error { return fmt.Errorf("scope selector: %w", ErrFlagWiring) },
|
||||
Run: func(Invocation, struct{}) Outcome { t.Fatal("run called after wiring error"); return Success() },
|
||||
})
|
||||
app, err := New(Identity{Name: "example"}, command)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if outcome := app.Execute(t.Context(), []string{"run"}, Streams{}); outcome.Kind != OutcomeOperational || !errors.Is(outcome.Cause, ErrFlagWiring) {
|
||||
t.Fatalf("wrapped wiring outcome = %#v", outcome)
|
||||
}
|
||||
usage := NewCommand(CommandSpec[struct{}]{Name: "usage", CheckInput: func(struct{}) error { return fmt.Errorf("selected value: %w", ErrFlagInput) }, Run: func(Invocation, struct{}) Outcome { return Success() }})
|
||||
app, err = New(Identity{Name: "example"}, usage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if outcome := app.Execute(t.Context(), []string{"usage"}, Streams{}); outcome.Kind != OutcomeUsage || !errors.Is(outcome.Cause, ErrFlagInput) {
|
||||
t.Fatalf("wrapped input outcome = %#v", outcome)
|
||||
}
|
||||
}
|
||||
|
||||
func strptr(value string) *string { return &value }
|
||||
Reference in New Issue
Block a user