Cover PromptKit catalog command behavior

This commit is contained in:
2026-10-12 02:24:25 +00:00
parent e4cdedd6db
commit 526a033da5
2 changed files with 312 additions and 2 deletions

View File

@@ -98,6 +98,20 @@ func TestCatalogCommandTreesAreIndependentAndOmitShow(t *testing.T) {
}
})
}
mixed := Command(Suite[struct{}, struct{}]{
InspectPrompt: func(context.Context, PromptRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) {
return commandkit.Acquired[*promptkit.PromptInspection]{}, nil
},
ListPrompts: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
return commandkit.Acquired[[]promptkit.PromptIdentity]{}, nil
},
})
output := new(bytes.Buffer)
outcome := newPromptTestApp(t, mixed).Execute(context.Background(), []string{"help", "promptkit"}, commandkit.Streams{Out: output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "show") || !strings.Contains(output.String(), "list") {
t.Fatalf("mixed catalog/show help = (%#v, %q)", outcome, output.String())
}
}
func TestCatalogListUsesPreparedInputAndPreflightsFormat(t *testing.T) {
@@ -140,11 +154,287 @@ func TestCatalogListUsesPreparedInputAndPreflightsFormat(t *testing.T) {
if outcome.Kind != commandkit.OutcomeSuccess || newInputCalls != 1 || newStateCalls != 1 || providerCalls != 1 {
t.Fatalf("catalog invocation = %#v, factories/providers=%d/%d/%d output=%q", outcome, newInputCalls, newStateCalls, providerCalls, output.String())
}
if !received.campaign.Set || received.campaign.Value != "spring" || !receivedSelection.Config.Set || receivedSelection.Config.Value != "" || receivedState.token != 1 {
if !received.campaign.Set || received.campaign.Value != "spring" || !receivedSelection.Config.Set || receivedSelection.Config.Value != "" || !receivedSelection.Overlay.Set || receivedSelection.Overlay.Value != "base.yml" || receivedState.token != 1 {
t.Fatalf("provider values = input %#v selection %#v state %#v", received, receivedSelection, receivedState)
}
}
func TestCatalogLifecycleCapturesSelectorsAndRefreshesInvocationValues(t *testing.T) {
type input struct {
campaign commandkit.Supplied[string]
shadow commandkit.Supplied[string]
enabled commandkit.Supplied[bool]
limit commandkit.Supplied[int]
labels commandkit.Supplied[[]string]
}
type state struct{ run int }
conversions, newInputs, newStates, checks, providers := 0, 0, 0, 0, 0
reject := false
flag := commandkit.ValueFlag(commandkit.FlagMeta{Name: "campaign"}, (*string)(nil), func(value string) (string, error) {
conversions++
return value, nil
}, func(target *input, value commandkit.Supplied[string]) { target.campaign = value })
lookalike := commandkit.ValueFlag(commandkit.FlagMeta{Name: "campaign"}, (*string)(nil),
func(value string) (string, error) { return value, nil },
func(target *input, value commandkit.Supplied[string]) { target.shadow = value })
enabled := commandkit.BoolFlag(commandkit.FlagMeta{Name: "enabled"}, true, func(target *input, value commandkit.Supplied[bool]) { target.enabled = value })
limit := commandkit.ValueFlag(commandkit.FlagMeta{Name: "limit"}, (*int)(nil), func(value string) (int, error) {
if value != "0" {
return 0, errors.New("test accepts only zero")
}
return 0, nil
}, func(target *input, value commandkit.Supplied[int]) { target.limit = value })
labels := commandkit.RepeatFlag(commandkit.FlagMeta{Name: "label"}, []string(nil), func(value string) (string, error) { return value, nil },
func(target *input, value commandkit.Supplied[[]string]) { target.labels = value })
selectors := []commandkit.Flag[input]{flag, enabled, limit, labels}
suite := Suite[input, *state]{
NewInput: func() input { newInputs++; return input{} },
NewState: func() *state {
newStates++
if checks != newStates {
t.Errorf("state initialized after %d checks; state count %d", checks, newStates)
}
return &state{run: newStates}
},
Selectors: selectors,
CheckInput: func(selection ckconfig.Selection, got input) error {
checks++
if reject {
return errors.New("catalog input rejected")
}
if checks == 1 {
if !selection.Config.Set || selection.Config.Value != "" || !selection.Overlay.Set || selection.Overlay.Value != "" || !got.campaign.Set || got.campaign.Value != "" || got.shadow.Set || !got.enabled.Set || got.enabled.Value || !got.limit.Set || got.limit.Value != 0 || !got.labels.Set || len(got.labels.Value) != 2 || got.labels.Value[0] != "late" || got.labels.Value[1] != "early" {
return errors.New("supplied catalog values were not preserved")
}
} else if selection.Config.Set || selection.Overlay.Set || got.campaign.Set || got.shadow.Set || got.enabled.Set || got.limit.Set || got.labels.Set {
return errors.New("catalog values leaked between invocations")
}
return nil
},
ListPrompts: func(_ context.Context, selection ckconfig.Selection, got input, gotState *state) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
providers++
if gotState == nil || gotState.run != providers {
t.Errorf("provider state = %#v at invocation %d", gotState, providers)
}
if providers == 1 && (!selection.Config.Set || selection.Config.Value != "" || !selection.Overlay.Set || selection.Overlay.Value != "" || !got.campaign.Set || !got.enabled.Set || !got.limit.Set || !got.labels.Set) {
t.Errorf("first provider values = selection %#v input %#v", selection, got)
}
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{}}, nil
},
}
declaration := Command(suite)
if conversions+newInputs+newStates+checks+providers != 0 {
t.Fatal("command construction invoked suite callbacks")
}
selectors[0] = lookalike
app := newPromptTestApp(t, declaration)
if conversions+newInputs+newStates+checks+providers != 0 {
t.Fatal("installation invoked suite callbacks")
}
var output bytes.Buffer
for _, args := range [][]string{{"help", "promptkit", "list", "prompts"}, {"version"}} {
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess {
t.Fatalf("non-invoking command %v = %#v", args, outcome)
}
}
output.Reset()
outcome := app.Execute(context.Background(), []string{"promptkit", "list", "prompts", "--campaign", "spring", "--help"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || conversions != 1 || newInputs+newStates+checks+providers != 0 {
t.Fatalf("help with supplied flag = %#v conversions=%d callbacks=%d/%d/%d/%d output=%q", outcome, conversions, newInputs, newStates, providers, checks, output.String())
}
output.Reset()
outcome = app.Execute(context.Background(), []string{"promptkit", "list", "prompts", "--campaign", "", "--enabled=false", "--limit", "0", "--label", "late", "--label=early", "--config", "", "--overlay", ""}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || conversions != 2 || newInputs != 1 || newStates != 1 || checks != 1 || providers != 1 {
t.Fatalf("supplied catalog invocation = %#v counts convert/input/state/check/provider=%d/%d/%d/%d/%d output=%q", outcome, conversions, newInputs, newStates, checks, providers, output.String())
}
output.Reset()
outcome = app.Execute(context.Background(), []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || newInputs != 2 || newStates != 2 || checks != 2 || providers != 2 {
t.Fatalf("fresh catalog invocation = %#v counts input/state/check/provider=%d/%d/%d/%d", outcome, newInputs, newStates, checks, providers)
}
reject = true
outcome = app.Execute(context.Background(), []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeUsage || newInputs != 3 || newStates != 2 || checks != 3 || providers != 2 {
t.Fatalf("rejected input reached state/provider: %#v counts input/state/check/provider=%d/%d/%d/%d", outcome, newInputs, newStates, checks, providers)
}
}
func TestCatalogNilFactoriesSupplyZeroValues(t *testing.T) {
app := newPromptTestApp(t, Command(Suite[*struct{}, *struct{}]{
ListPrompts: func(_ context.Context, selection ckconfig.Selection, input, state *struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
if input != nil || state != nil || selection.Config.Set || selection.Overlay.Set {
t.Errorf("nil-factory values = selection %#v input %#v state %#v", selection, input, state)
}
return commandkit.Acquired[[]promptkit.PromptIdentity]{}, nil
},
}))
outcome := app.Execute(context.Background(), []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess {
t.Fatalf("nil-factory catalog outcome = %#v", outcome)
}
}
func TestCatalogCommandsRenderNativeValuesAndEmptySequences(t *testing.T) {
prompts := []promptkit.PromptIdentity{
{ID: "later", Version: "1.10"},
{ID: "001", Version: " 03 "},
{ID: "a: true", Version: "yes"},
}
profiles := []string{"later", "001", "a: true", "later"}
suite := Suite[struct{}, struct{}]{
ListPrompts: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: prompts}, nil
},
ListProfiles: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]string], error) {
return commandkit.Acquired[[]string]{Value: profiles}, nil
},
}
app := newPromptTestApp(t, Command(suite))
var output bytes.Buffer
outcome := app.Execute(context.Background(), []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess {
t.Fatalf("default prompt YAML outcome = %#v", outcome)
}
var promptEntries []map[string]any
if err := yaml.Unmarshal(output.Bytes(), &promptEntries); err != nil {
t.Fatalf("decode prompt command output %q: %v", output.String(), err)
}
if len(promptEntries) != len(prompts) {
t.Fatalf("prompt output is not the native sequence: %#v", promptEntries)
}
for index, entry := range promptEntries {
id, idOK := entry["id"].(string)
version, versionOK := entry["version"].(string)
if len(entry) != 2 || !idOK || !versionOK || id != prompts[index].ID || version != prompts[index].Version {
t.Errorf("prompt entry %d = %#v, want string id/version %q/%q", index, entry, prompts[index].ID, prompts[index].Version)
}
}
output.Reset()
outcome = app.Execute(context.Background(), []string{"promptkit", "list", "profiles", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess {
t.Fatalf("explicit profile YAML outcome = %#v", outcome)
}
var profileEntries []any
if err := yaml.Unmarshal(output.Bytes(), &profileEntries); err != nil {
t.Fatalf("decode profile command output %q: %v", output.String(), err)
}
if len(profileEntries) != len(profiles) {
t.Fatalf("profile output is not the native sequence: %#v", profileEntries)
}
for index, entry := range profileEntries {
value, ok := entry.(string)
if !ok || value != profiles[index] {
t.Errorf("profile entry %d = %#v, want string %q", index, entry, profiles[index])
}
}
promptEmptyCalls, profileEmptyCalls := 0, 0
emptyApp := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{
ListPrompts: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
promptEmptyCalls++
if promptEmptyCalls%2 == 0 {
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{}}, nil
}
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: nil}, nil
},
ListProfiles: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]string], error) {
profileEmptyCalls++
if profileEmptyCalls%2 == 0 {
return commandkit.Acquired[[]string]{Value: []string{}}, nil
}
return commandkit.Acquired[[]string]{Value: nil}, nil
},
}))
for _, resource := range []string{"prompts", "profiles"} {
for _, inventory := range []string{"nil", "allocated"} {
output.Reset()
outcome := emptyApp.Execute(context.Background(), []string{"promptkit", "list", resource}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || strings.TrimSpace(output.String()) != "[]" {
t.Errorf("%s %s catalog = %#v, %q", inventory, resource, outcome, output.String())
}
}
}
}
func TestCatalogRejectsOperandsAndUnavailableSelectors(t *testing.T) {
calls := 0
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{ListPrompts: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
calls++
return commandkit.Acquired[[]promptkit.PromptIdentity]{}, nil
}}))
for _, args := range [][]string{
{"promptkit", "list", "prompts", "unexpected"},
{"promptkit", "list", "prompts", "--version", "2"},
{"promptkit", "list", "prompts", "--scope", "app"},
} {
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: io.Discard, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeUsage || calls != 0 {
t.Errorf("unsupported catalog arguments %v = %#v after %d provider calls", args, outcome, calls)
}
}
}
func TestCatalogFailuresRetainDiagnosticsAndDeliveryState(t *testing.T) {
diagnostic := commandkit.Diagnostic{Severity: commandkit.SeverityWarning, Write: func(writer io.Writer) error {
_, err := io.WriteString(writer, "catalog warning\n")
return err
}}
hasCatalogWarning := func(diagnostics []commandkit.Diagnostic) bool {
for _, got := range diagnostics {
if got.Severity != diagnostic.Severity || got.Write == nil {
continue
}
var output bytes.Buffer
if got.Write(&output) == nil && output.String() == "catalog warning\n" {
return true
}
}
return false
}
providerErr := errors.New("catalog provider failed")
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{ListPrompts: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
return commandkit.Acquired[[]promptkit.PromptIdentity]{Diagnostics: []commandkit.Diagnostic{diagnostic}}, providerErr
}}))
outcome := app.Execute(context.Background(), []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, providerErr) || !hasCatalogWarning(outcome.Diagnostics) {
t.Fatalf("provider failure outcome = %#v", outcome)
}
ctx, cancel := context.WithCancel(context.Background())
canceled := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{ListPrompts: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
cancel()
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: "1"}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil
}}))
var output bytes.Buffer
outcome = canceled.Execute(ctx, []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, context.Canceled) || !hasCatalogWarning(outcome.Diagnostics) || output.Len() != 0 {
t.Fatalf("post-provider cancellation outcome = %#v output=%q", outcome, output.String())
}
writeErr := errors.New("catalog report writer failed")
for _, test := range []struct {
name string
writer io.Writer
want error
}{
{name: "writer error", writer: partialReportWriter{err: writeErr}, want: writeErr},
{name: "short write", writer: partialReportWriter{}, want: io.ErrShortWrite},
} {
t.Run(test.name, func(t *testing.T) {
writerApp := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{ListPrompts: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: "1"}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil
}}))
outcome := writerApp.Execute(context.Background(), []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: test.writer, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.DeliveryError, test.want) || !errors.Is(outcome.Cause, test.want) || !outcome.ReportIncomplete || !hasCatalogWarning(outcome.Diagnostics) {
t.Fatalf("catalog delivery outcome = %#v", outcome)
}
})
}
}
func TestCommandUsesInstalledNamespaceCollisionChecks(t *testing.T) {
command := Command(Suite[struct{}, struct{}]{InspectPrompt: func(context.Context, PromptRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) {
return commandkit.Acquired[*promptkit.PromptInspection]{}, nil