Files
commandkit/parser_test.go

171 lines
5.6 KiB
Go

package commandkit
import (
"bytes"
"context"
"io"
"reflect"
"strings"
"testing"
internalcobra "gitea.maximumdirect.net/eric/CommandKit/internal/cobra"
"gitea.maximumdirect.net/eric/promptkit"
)
func TestAppParseUsesRegisteredBindingsWithTheAdapter(t *testing.T) {
type input struct {
scope Supplied[string]
id Supplied[string]
}
show, err := NewCommand(CommandSpec[input]{
Name: "show",
Flags: []Flag[input]{ValueFlag(FlagMeta{Name: "scope"}, (*string)(nil), func(value string) (string, error) {
return value, nil
}, func(value *input, supplied Supplied[string]) { value.scope = supplied })},
Arguments: []Argument[input]{ValueArgument(ArgumentMeta{Name: "id"}, true, func(value string) (string, error) {
return value, nil
}, func(value *input, supplied Supplied[string]) { value.id = supplied })},
Run: func(Invocation, input) Outcome { return Success() },
})
if err != nil {
t.Fatal(err)
}
config, err := Group("config", "configuration", show)
if err != nil {
t.Fatal(err)
}
app, err := New(Identity{Name: "example"}, config)
if err != nil {
t.Fatal(err)
}
options := internalcobra.ParseOptions{Context: context.Background()}
result := app.parse([]string{"config", "show", "one", "--scope", "app"}, options)
if result.Failure != nil {
t.Fatalf("parse failed: %s", result.Failure.Message)
}
if !reflect.DeepEqual(result.Path, []string{"config", "show"}) || !reflect.DeepEqual(result.FlagValues, [][]string{{"app"}}) || !reflect.DeepEqual(result.Operands, []string{"one"}) {
t.Fatalf("parse result = %#v", result)
}
result = app.parse([]string{"config", "--scope", "app", "show"}, options)
if result.Failure == nil {
t.Fatal("accepted a child flag before the complete command path")
}
}
func TestExecutionRefreshesHelpMetadataAfterRegistration(t *testing.T) {
first, err := NewCommand(CommandSpec[struct{}]{
Name: "first",
Run: func(Invocation, struct{}) Outcome { return Success() },
})
if err != nil {
t.Fatal(err)
}
app, err := New(Identity{Name: "example"}, first)
if err != nil {
t.Fatal(err)
}
var output bytes.Buffer
current := newExecution(app, context.Background(), Streams{Out: &output, Err: io.Discard})
parsed := current.parse([]string{"first"})
if parsed.Failure != nil {
t.Fatalf("initial parse failed: %s", parsed.Failure.Message)
}
late, err := NewCommand(CommandSpec[struct{}]{
Name: "late",
Summary: "registered during execution",
Run: func(Invocation, struct{}) Outcome { return Success() },
})
if err != nil {
t.Fatal(err)
}
if err := app.Register(late); err != nil {
t.Fatal(err)
}
if got := current.renderHelp([]string{"late"}); got.Kind != OutcomeSuccess {
t.Fatalf("help after registration = %#v", got)
}
if !strings.Contains(output.String(), "registered during execution") {
t.Fatalf("help did not use registered metadata: %q", output.String())
}
if got := current.usageLine([]string{"late"}); !strings.HasPrefix(got, "Usage: example late") {
t.Fatalf("usage after registration = %q", got)
}
}
func TestBridgeTreeRefreshesAfterSuccessfulRegistration(t *testing.T) {
app, err := New(Identity{Name: "example"})
if err != nil {
t.Fatal(err)
}
var output bytes.Buffer
streams := Streams{Out: &output, Err: io.Discard}
renderRootHelp := func() string {
t.Helper()
output.Reset()
outcome := app.Execute(context.Background(), []string{"--help"}, streams)
if outcome.Kind != OutcomeSuccess {
t.Fatalf("root help outcome = %#v", outcome)
}
return output.String()
}
initial := renderRootHelp()
if strings.Contains(initial, "custom") || strings.Contains(initial, "config") || strings.Contains(initial, "promptkit") {
t.Fatalf("initial help unexpectedly lists future registrations: %q", initial)
}
custom, err := NewCommand(CommandSpec[struct{}]{Name: "custom", Run: func(Invocation, struct{}) Outcome { return Success() }})
if err != nil {
t.Fatal(err)
}
if err := app.Register(custom); err != nil {
t.Fatal(err)
}
providerCalls := 0
if err := RegisterConfig(app, ConfigSuite[struct{}]{Scopes: []ConfigScope[struct{}]{{
Name: "app",
Operations: []ConfigOperation{ConfigShow},
Acquire: func(context.Context, ConfigRequest, struct{}) (Acquired[ConfigView], error) {
providerCalls++
return Acquired[ConfigView]{}, nil
},
}}}); err != nil {
t.Fatal(err)
}
if err := RegisterPromptKit(app, PromptKitSuite[struct{}]{InspectPrompt: func(context.Context, Selection, struct{}, string, string) (Acquired[*promptkit.PromptInspection], error) {
providerCalls++
return Acquired[*promptkit.PromptInspection]{}, nil
}}); err != nil {
t.Fatal(err)
}
updated := renderRootHelp()
for _, name := range []string{"custom", "config", "promptkit"} {
if !strings.Contains(updated, name) {
t.Errorf("updated help omits %q: %q", name, updated)
}
}
if providerCalls != 0 {
t.Fatalf("help invoked inspection or configuration callbacks %d times", providerCalls)
}
late, err := NewCommand(CommandSpec[struct{}]{Name: "late"})
if err != nil {
t.Fatal(err)
}
if err := app.Register(late, custom); err == nil {
t.Fatal("Register accepted a batch with an existing-name collision")
}
if err := app.Register(); err != nil {
t.Fatalf("empty registration failed: %v", err)
}
final := renderRootHelp()
if !strings.Contains(final, "custom") || !strings.Contains(final, "config") || !strings.Contains(final, "promptkit") || strings.Contains(final, "late") {
t.Fatalf("help after failed and empty registration = %q", final)
}
if outcome := app.Execute(context.Background(), []string{"custom"}, streams); outcome.Kind != OutcomeSuccess {
t.Fatalf("new command did not parse after registration: %#v", outcome)
}
}