Keep command routing independent of operand counts
This commit is contained in:
@@ -11,6 +11,89 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommandContextSurvivesIncompleteAndInvalidOperandCounts(t *testing.T) {
|
||||
for _, spelling := range []string{"run", "r"} {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
args []string
|
||||
help bool
|
||||
usage bool
|
||||
}{
|
||||
{name: "interspersed alias", args: []string{"first", "--fmt=yaml", "second"}},
|
||||
{name: "interspersed canonical flag", args: []string{"first", "--format=yaml", "second"}},
|
||||
{name: "partial help", args: []string{"first", "--help"}, help: true},
|
||||
{name: "partial help with flag alias", args: []string{"first", "--fmt=yaml", "--help"}, help: true},
|
||||
{name: "excess operands", args: []string{"first", "second", "third", "--fmt=yaml"}, usage: true},
|
||||
{name: "missing operands", args: []string{"first", "--fmt=yaml"}, usage: true},
|
||||
} {
|
||||
t.Run(spelling+"/"+test.name, func(t *testing.T) {
|
||||
type input struct {
|
||||
operands []string
|
||||
format Supplied[string]
|
||||
}
|
||||
built, ran := 0, false
|
||||
command := NewCommand(CommandSpec[input]{
|
||||
Name: "run", NewInput: func() input { built++; return input{} },
|
||||
Flags: []Flag[input]{ValueFlag(FlagMeta{Name: "format"}, (*string)(nil),
|
||||
func(value string) (string, error) { return value, nil },
|
||||
func(target *input, value Supplied[string]) { target.format = value })},
|
||||
Arguments: []Argument[input]{RepeatArgument(ArgumentMeta{Name: "ARG"}, 2, 2,
|
||||
func(value string) (string, error) { return value, nil },
|
||||
func(target *input, value Supplied[[]string]) { target.operands = value.Value })},
|
||||
Run: func(inv Invocation, value input) Outcome {
|
||||
ran = true
|
||||
if inv.EnteredPath() != spelling || inv.CanonicalPath() != "run" {
|
||||
t.Errorf("invocation paths = %q / %q", inv.EnteredPath(), inv.CanonicalPath())
|
||||
}
|
||||
if !reflect.DeepEqual(value.operands, []string{"first", "second"}) || !value.format.Set || value.format.Value != "yaml" {
|
||||
t.Errorf("input = %#v", value)
|
||||
}
|
||||
return Success()
|
||||
},
|
||||
})
|
||||
app, err := New(Identity{Name: "Demo"}, command, Alias("r", "run"), FlagAliasFor("run", "--fmt", "--format"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var output, diagnostics bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), append([]string{spelling}, test.args...), Streams{Out: &output, Err: &diagnostics})
|
||||
if test.usage {
|
||||
if outcome.Kind != OutcomeUsage || strings.Contains(diagnostics.String(), "unknown flag") || !strings.Contains(diagnostics.String(), "Demo "+spelling+" ARG") {
|
||||
t.Fatalf("invalid arity outcome = %v, diagnostics %q", outcome.Kind, diagnostics.String())
|
||||
}
|
||||
} else if outcome.Kind != OutcomeSuccess {
|
||||
t.Fatalf("outcome = %#v, diagnostics %q", outcome, diagnostics.String())
|
||||
}
|
||||
if test.help && (!strings.Contains(output.String(), "Demo "+spelling+" ARG") || !strings.Contains(output.String(), "--fmt → --format")) {
|
||||
t.Errorf("command help = %q", output.String())
|
||||
}
|
||||
if test.help || test.usage {
|
||||
if built != 0 || ran {
|
||||
t.Fatal("help or invalid arity initialized input or ran a handler")
|
||||
}
|
||||
} else if built != 1 || !ran {
|
||||
t.Fatal("valid invocation did not run once")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnableCommandCanHaveAliasOnlyDiscoveryPrefixes(t *testing.T) {
|
||||
ran := false
|
||||
app, err := New(Identity{Name: "Demo"},
|
||||
NewCommand(CommandSpec[struct{}]{Name: "box", Run: func(Invocation, struct{}) Outcome { ran = true; return Success() }}),
|
||||
simpleRunnable("inspect"), Alias("box admin pick", "inspect"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var output bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), []string{"box", "admin", "--help"}, Streams{Out: &output, Err: io.Discard})
|
||||
if outcome.Kind != OutcomeSuccess || !strings.Contains(output.String(), "pick → inspect") || ran {
|
||||
t.Fatalf("discovery outcome = %v, help %q, ran %t", outcome.Kind, output.String(), ran)
|
||||
}
|
||||
}
|
||||
|
||||
func routeTestApp(t *testing.T, extra ...Declaration) *App {
|
||||
t.Helper()
|
||||
collections := simpleRunnable("collections")
|
||||
|
||||
Reference in New Issue
Block a user