Add generated help and version commands
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -250,6 +251,156 @@ func TestExecuteHelpSkipsRequiredInputsAndRejectsNilContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelpRendersRegisteredMetadataAndResolvesExactPaths(t *testing.T) {
|
||||
type input struct {
|
||||
count commandkit.Supplied[int]
|
||||
tags commandkit.Supplied[[]string]
|
||||
}
|
||||
factoryCalls, runCalls := 0, 0
|
||||
show, err := commandkit.NewCommand(commandkit.CommandSpec[input]{
|
||||
Name: "show",
|
||||
Summary: "Show one item.",
|
||||
Description: "Display the selected item in detail.",
|
||||
NewInput: func() input {
|
||||
factoryCalls++
|
||||
return input{}
|
||||
},
|
||||
Flags: []commandkit.Flag[input]{
|
||||
commandkit.ValueFlag(commandkit.FlagMeta{Name: "count", Usage: "number of items", Required: true}, nil, func(value string) (int, error) {
|
||||
return strconv.Atoi(value)
|
||||
}, func(target *input, value commandkit.Supplied[int]) { target.count = value }),
|
||||
commandkit.RepeatFlag(commandkit.FlagMeta{Name: "tag", Usage: "item tag"}, []string{"base"}, func(value string) (string, error) { return value, nil }, func(target *input, value commandkit.Supplied[[]string]) { target.tags = value }),
|
||||
},
|
||||
Arguments: []commandkit.Argument[input]{commandkit.RepeatArgument(commandkit.ArgumentMeta{Name: "ID", Usage: "item identifier"}, 1, 3, func(value string) (string, error) { return value, nil }, func(*input, commandkit.Supplied[[]string]) {})},
|
||||
Run: func(commandkit.Invocation, input) commandkit.Outcome { runCalls++; return commandkit.Success() },
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config, err := commandkit.Group("config", "Inspect configuration.", show)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "now", Summary: "Deploy immediately.", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return commandkit.Success() }})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
deployParent, err := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "deploy", Summary: "Deploy artifacts.", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { runCalls++; return commandkit.Success() }})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
deploy, err := commandkit.WithChildren(deployParent, now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
app, err := commandkit.New(commandkit.Identity{Name: "example"}, config, deploy)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var out, diagnostic bytes.Buffer
|
||||
root := app.Execute(context.Background(), []string{"--help"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if root.Kind != commandkit.OutcomeSuccess || !strings.Contains(out.String(), "Available Commands:") {
|
||||
t.Fatalf("root help = %#v, output %q", root, out.String())
|
||||
}
|
||||
for _, name := range []string{"config", "help", "version"} {
|
||||
if !strings.Contains(out.String(), name) {
|
||||
t.Fatalf("root help omitted %q: %q", name, out.String())
|
||||
}
|
||||
}
|
||||
for _, earlier := range []string{"config", "deploy", "help"} {
|
||||
if strings.Index(out.String(), earlier) > strings.Index(out.String(), "version") {
|
||||
t.Fatalf("root help commands are not sorted: %q", out.String())
|
||||
}
|
||||
}
|
||||
out.Reset()
|
||||
bare := app.Execute(context.Background(), nil, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if bare.Kind != commandkit.OutcomeSuccess || !strings.Contains(out.String(), "Available Commands:") {
|
||||
t.Fatalf("bare root help = %#v, output %q", bare, out.String())
|
||||
}
|
||||
out.Reset()
|
||||
helpCommand := app.Execute(context.Background(), []string{"help"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if helpCommand.Kind != commandkit.OutcomeSuccess || !strings.Contains(out.String(), "Available Commands:") {
|
||||
t.Fatalf("root help command = %#v, output %q", helpCommand, out.String())
|
||||
}
|
||||
out.Reset()
|
||||
group := app.Execute(context.Background(), []string{"config"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if group.Kind != commandkit.OutcomeSuccess || !strings.Contains(out.String(), "show") {
|
||||
t.Fatalf("group help = %#v, output %q", group, out.String())
|
||||
}
|
||||
out.Reset()
|
||||
parentHelp := app.Execute(context.Background(), []string{"deploy", "--help"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if parentHelp.Kind != commandkit.OutcomeSuccess || !strings.Contains(out.String(), "now") || strings.Contains(out.String(), "Usage: example deploy now") {
|
||||
t.Fatalf("runnable parent help = %#v, output %q", parentHelp, out.String())
|
||||
}
|
||||
out.Reset()
|
||||
selected := app.Execute(context.Background(), []string{"help", "config", "show"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if selected.Kind != commandkit.OutcomeSuccess {
|
||||
t.Fatalf("selected help = %#v, error %q", selected, diagnostic.String())
|
||||
}
|
||||
for _, expected := range []string{"Display the selected item in detail.", "Usage:", "ID{1..3}", "Arguments:", "min 1, max 3", "--count", "required", "default: 0", "--tag", "repeatable", "default: [base]", "--help"} {
|
||||
if !strings.Contains(out.String(), expected) {
|
||||
t.Fatalf("selected help omitted %q: %q", expected, out.String())
|
||||
}
|
||||
}
|
||||
if factoryCalls != 0 || runCalls != 0 {
|
||||
t.Fatalf("help invoked execution callbacks: factories=%d runs=%d", factoryCalls, runCalls)
|
||||
}
|
||||
badValue := app.Execute(context.Background(), []string{"config", "show", "--count=bad", "--help"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if badValue.Kind != commandkit.OutcomeUsage || factoryCalls != 0 {
|
||||
t.Fatalf("help skipped conversion of supplied values: %#v", badValue)
|
||||
}
|
||||
out.Reset()
|
||||
versionOutcome := app.Execute(context.Background(), []string{"version"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if versionOutcome.Kind != commandkit.OutcomeSuccess || factoryCalls != 0 || runCalls != 0 {
|
||||
t.Fatalf("version invoked an application command: %#v calls=%d/%d", versionOutcome, factoryCalls, runCalls)
|
||||
}
|
||||
failed := app.Execute(context.Background(), []string{"config", "show", "--help"}, commandkit.Streams{Out: shortWriter{}, Err: &diagnostic})
|
||||
if failed.Kind != commandkit.OutcomeOperational || !failed.ReportIncomplete || !errors.Is(failed.DeliveryError, io.ErrShortWrite) {
|
||||
t.Fatalf("failed help delivery = %#v", failed)
|
||||
}
|
||||
out.Reset()
|
||||
unknown := app.Execute(context.Background(), []string{"help", "config", "missing"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if unknown.Kind != commandkit.OutcomeUsage || !strings.Contains(diagnostic.String(), "unknown help path") {
|
||||
t.Fatalf("unknown help path = %#v, diagnostics %q", unknown, diagnostic.String())
|
||||
}
|
||||
badFlag := app.Execute(context.Background(), []string{"config", "show", "--unknown", "--help"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if badFlag.Kind != commandkit.OutcomeUsage {
|
||||
t.Fatalf("help accepted an unknown flag: %#v", badFlag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionCommandFormatsApplicationAndCommandKitIdentity(t *testing.T) {
|
||||
app, err := commandkit.New(commandkit.Identity{Name: "ExampleApp", Revision: "abc123"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var out, diagnostic bytes.Buffer
|
||||
textOutcome := app.Execute(context.Background(), []string{"version"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if textOutcome.Kind != commandkit.OutcomeSuccess || out.String() != "ExampleApp dev (abc123)\n" {
|
||||
t.Fatalf("default version = %#v, output %q", textOutcome, out.String())
|
||||
}
|
||||
out.Reset()
|
||||
yamlOutcome := app.Execute(context.Background(), []string{"version", "--format=yaml"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if yamlOutcome.Kind != commandkit.OutcomeSuccess || out.String() != "name: ExampleApp\nversion: dev\nrevision: abc123\n" {
|
||||
t.Fatalf("YAML version = %#v, output %q", yamlOutcome, out.String())
|
||||
}
|
||||
out.Reset()
|
||||
component := app.Execute(context.Background(), []string{"version", "--scope=commandkit"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if component.Kind != commandkit.OutcomeSuccess || !strings.HasPrefix(out.String(), "CommandKit ") || !strings.HasSuffix(out.String(), "\n") {
|
||||
t.Fatalf("CommandKit version = %#v, output %q", component, out.String())
|
||||
}
|
||||
out.Reset()
|
||||
invalid := app.Execute(context.Background(), []string{"version", "--scope=unknown"}, commandkit.Streams{Out: &out, Err: &diagnostic})
|
||||
if invalid.Kind != commandkit.OutcomeUsage || out.Len() != 0 {
|
||||
t.Fatalf("invalid version scope = %#v, output %q", invalid, out.String())
|
||||
}
|
||||
failed := app.Execute(context.Background(), []string{"version"}, commandkit.Streams{Out: shortWriter{}, Err: &diagnostic})
|
||||
if failed.Kind != commandkit.OutcomeOperational || !failed.ReportIncomplete || !errors.Is(failed.DeliveryError, io.ErrShortWrite) {
|
||||
t.Fatalf("failed version delivery = %#v", failed)
|
||||
}
|
||||
}
|
||||
|
||||
type failingWriter struct{ err error }
|
||||
|
||||
func (w failingWriter) Write([]byte) (int, error) { return 0, w.err }
|
||||
|
||||
Reference in New Issue
Block a user