215 lines
6.7 KiB
Go
215 lines
6.7 KiB
Go
|
|
package commandkit
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"io"
|
||
|
|
"reflect"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func routeTestApp(t *testing.T, extra ...Declaration) *App {
|
||
|
|
t.Helper()
|
||
|
|
collections := simpleRunnable("collections")
|
||
|
|
list := Group("list", "List", collections)
|
||
|
|
testCommand := runnableWithOperands("test", 0, 1)
|
||
|
|
promptkit := Group("promptkit", "PromptKit", list, testCommand)
|
||
|
|
run := runnableWithOperands("run", 0, 1)
|
||
|
|
inspect := runnableWithOperands("inspect", 1, 1)
|
||
|
|
declarations := []Declaration{
|
||
|
|
promptkit,
|
||
|
|
run,
|
||
|
|
inspect,
|
||
|
|
Alias("collections", "list collections"),
|
||
|
|
Alias("list collections", "promptkit list collections"),
|
||
|
|
Alias("pk", "promptkit"),
|
||
|
|
Alias("promptkit list bundles", "promptkit list collections"),
|
||
|
|
Alias("smoke", "promptkit test"),
|
||
|
|
Alias("r", "run"),
|
||
|
|
Alias("r full", "inspect"),
|
||
|
|
}
|
||
|
|
declarations = append(declarations, extra...)
|
||
|
|
app, err := New(Identity{Name: "Example"}, declarations...)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
return app
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestEnteredRoutesResolveChainsAndPreserveTokenOrigins(t *testing.T) {
|
||
|
|
app := routeTestApp(t)
|
||
|
|
snapshot := app.routeSnapshot()
|
||
|
|
for _, test := range []struct {
|
||
|
|
name string
|
||
|
|
args []string
|
||
|
|
entered string
|
||
|
|
canonical string
|
||
|
|
wantArgs []string
|
||
|
|
wantKind routeKind
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "command alias chain",
|
||
|
|
args: []string{"collections", "--tag", "first"},
|
||
|
|
entered: "collections",
|
||
|
|
canonical: "promptkit list collections",
|
||
|
|
wantArgs: []string{"promptkit", "list", "collections", "--tag", "first"},
|
||
|
|
wantKind: routeCanonical,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "group alias composition",
|
||
|
|
args: []string{"pk", "list", "bundles"},
|
||
|
|
entered: "pk list bundles",
|
||
|
|
canonical: "promptkit list collections",
|
||
|
|
wantArgs: []string{"promptkit", "list", "collections"},
|
||
|
|
wantKind: routeCanonical,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "smoke operand",
|
||
|
|
args: []string{"smoke", "all"},
|
||
|
|
entered: "smoke",
|
||
|
|
canonical: "promptkit test",
|
||
|
|
wantArgs: []string{"promptkit", "test", "all"},
|
||
|
|
wantKind: routeCanonical,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "explicit longer alternate spelling",
|
||
|
|
args: []string{"r", "full"},
|
||
|
|
entered: "r full",
|
||
|
|
canonical: "inspect",
|
||
|
|
wantArgs: []string{"inspect"},
|
||
|
|
wantKind: routeCanonical,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "canonical runnable operand",
|
||
|
|
args: []string{"run", "full"},
|
||
|
|
entered: "run",
|
||
|
|
canonical: "run",
|
||
|
|
wantArgs: []string{"run", "full"},
|
||
|
|
wantKind: routeCanonical,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "single dash remains an operand",
|
||
|
|
args: []string{"run", "-"},
|
||
|
|
entered: "run",
|
||
|
|
canonical: "run",
|
||
|
|
wantArgs: []string{"run", "-"},
|
||
|
|
wantKind: routeCanonical,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "unknown path",
|
||
|
|
args: []string{"missing", "route"},
|
||
|
|
entered: "missing route",
|
||
|
|
wantArgs: []string{"missing", "route"},
|
||
|
|
wantKind: routeUnknown,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "space-containing token is not split",
|
||
|
|
args: []string{"collections list"},
|
||
|
|
entered: "collections list",
|
||
|
|
wantArgs: []string{"collections list"},
|
||
|
|
wantKind: routeUnknown,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "flag values and spaced tokens",
|
||
|
|
args: []string{"collections", "--message", "--hlep", "--text=two words"},
|
||
|
|
entered: "collections",
|
||
|
|
canonical: "promptkit list collections",
|
||
|
|
wantArgs: []string{"promptkit", "list", "collections", "--message", "--hlep", "--text=two words"},
|
||
|
|
wantKind: routeCanonical,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "terminator stops command matching",
|
||
|
|
args: []string{"collections", "--", "r", "full"},
|
||
|
|
entered: "collections",
|
||
|
|
canonical: "promptkit list collections",
|
||
|
|
wantArgs: []string{"promptkit", "list", "collections", "--", "r", "full"},
|
||
|
|
wantKind: routeCanonical,
|
||
|
|
},
|
||
|
|
} {
|
||
|
|
t.Run(test.name, func(t *testing.T) {
|
||
|
|
got := resolveEnteredRoute(test.args, snapshot)
|
||
|
|
if got.kind != test.wantKind {
|
||
|
|
t.Fatalf("route kind = %d, want %d", got.kind, test.wantKind)
|
||
|
|
}
|
||
|
|
if got.enteredPath.String() != test.entered {
|
||
|
|
t.Errorf("entered path = %q, want %q", got.enteredPath.String(), test.entered)
|
||
|
|
}
|
||
|
|
if got.canonicalPath.String() != test.canonical {
|
||
|
|
t.Errorf("canonical path = %q, want %q", got.canonicalPath.String(), test.canonical)
|
||
|
|
}
|
||
|
|
if !reflect.DeepEqual(got.args, test.wantArgs) {
|
||
|
|
t.Errorf("routed args = %q, want %q", got.args, test.wantArgs)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLongerRouteSelectionDoesNotFallBackAfterTargetParsingFails(t *testing.T) {
|
||
|
|
app := routeTestApp(t)
|
||
|
|
result := resolveEnteredRoute([]string{"r", "full"}, app.routeSnapshot())
|
||
|
|
if got, want := result.canonicalPath.String(), "inspect"; got != want {
|
||
|
|
t.Fatalf("canonical path = %q, want %q", got, want)
|
||
|
|
}
|
||
|
|
|
||
|
|
var diagnostics bytes.Buffer
|
||
|
|
outcome := app.Execute(context.Background(), []string{"r", "full"}, Streams{Out: io.Discard, Err: &diagnostics})
|
||
|
|
if outcome.Kind != OutcomeUsage {
|
||
|
|
t.Fatalf("outcome kind = %v, want usage; diagnostics: %s", outcome.Kind, diagnostics.String())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestEnteredAliasCanSelectExplicitLongerMapping(t *testing.T) {
|
||
|
|
app := routeTestApp(t, Alias("pk list bundles", "inspect"))
|
||
|
|
result := resolveEnteredRoute([]string{"pk", "list", "bundles"}, app.routeSnapshot())
|
||
|
|
if got, want := result.canonicalPath.String(), "inspect"; got != want {
|
||
|
|
t.Fatalf("canonical path = %q, want %q", got, want)
|
||
|
|
}
|
||
|
|
if got, want := result.enteredPath.String(), "pk list bundles"; got != want {
|
||
|
|
t.Fatalf("entered path = %q, want %q", got, want)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestExecuteDispatchesEnteredAliasToCanonicalRunner(t *testing.T) {
|
||
|
|
runs := 0
|
||
|
|
command := NewCommand(CommandSpec[struct{}]{
|
||
|
|
Name: "canonical", Summary: "Canonical",
|
||
|
|
Run: func(Invocation, struct{}) Outcome {
|
||
|
|
runs++
|
||
|
|
return Success()
|
||
|
|
},
|
||
|
|
})
|
||
|
|
app, err := New(Identity{Name: "Example"}, command, Alias("shortcut", "canonical"))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
outcome := app.Execute(context.Background(), []string{"shortcut"}, Streams{Out: io.Discard, Err: io.Discard})
|
||
|
|
if outcome.Kind != OutcomeSuccess || runs != 1 {
|
||
|
|
t.Fatalf("alias execution = (%v, runs %d), want success and one canonical run", outcome.Kind, runs)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAliasOnlyPrefixProducesDiscoveryRoute(t *testing.T) {
|
||
|
|
app, err := New(Identity{Name: "Example"},
|
||
|
|
Group("promptkit", "PromptKit", Group("list", "List", simpleRunnable("collections"))),
|
||
|
|
Alias("list collections", "promptkit list collections"),
|
||
|
|
)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
result := resolveEnteredRoute([]string{"list", "--help"}, app.routeSnapshot())
|
||
|
|
if result.kind != routeDiscovery {
|
||
|
|
t.Fatalf("route kind = %d, want discovery", result.kind)
|
||
|
|
}
|
||
|
|
if got, want := result.discoveryPath.String(), "list"; got != want {
|
||
|
|
t.Fatalf("discovery path = %q, want %q", got, want)
|
||
|
|
}
|
||
|
|
if got, want := result.enteredPath.String(), "list"; got != want {
|
||
|
|
t.Fatalf("entered path = %q, want %q", got, want)
|
||
|
|
}
|
||
|
|
if !reflect.DeepEqual(result.args, []string{"list", "--help"}) {
|
||
|
|
t.Fatalf("discovery args = %q, want [list --help]", result.args)
|
||
|
|
}
|
||
|
|
}
|