Cover PromptKit catalog command behavior
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user