1266 lines
64 KiB
Go
1266 lines
64 KiB
Go
package ckprompt
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
commandkit "gitea.maximumdirect.net/eric/CommandKit"
|
|
"gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig"
|
|
promptkit "gitea.maximumdirect.net/eric/promptkit"
|
|
"go.yaml.in/yaml/v4"
|
|
)
|
|
|
|
func newPromptTestApp(t *testing.T, declarations ...commandkit.Declaration) *commandkit.App {
|
|
t.Helper()
|
|
app, err := commandkit.New(commandkit.Identity{Name: "example"}, declarations...)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return app
|
|
}
|
|
|
|
func testStringPointer(value string) *string { return &value }
|
|
|
|
func TestCommandBuildsIndependentOptionalTrees(t *testing.T) {
|
|
promptOnly := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{InspectPrompt: func(context.Context, PromptRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) {
|
|
return commandkit.Acquired[*promptkit.PromptInspection]{}, errors.New("unused")
|
|
}}))
|
|
var output bytes.Buffer
|
|
outcome := promptOnly.Execute(context.Background(), []string{"help", "promptkit", "show"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "\n prompt ") || strings.Contains(output.String(), "\n profile ") || strings.Contains(output.String(), "requirements") || strings.Contains(output.String(), "config") {
|
|
t.Fatalf("prompt-only help = (%#v, %q)", outcome, output.String())
|
|
}
|
|
|
|
profileOnly := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{InspectProfile: func(context.Context, ProfileRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
|
|
return commandkit.Acquired[*promptkit.ProfileInspection]{}, errors.New("unused")
|
|
}}))
|
|
output.Reset()
|
|
outcome = profileOnly.Execute(context.Background(), []string{"help", "promptkit", "show"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "\n profile ") || strings.Contains(output.String(), "\n prompt ") {
|
|
t.Fatalf("profile-only help = (%#v, %q)", outcome, output.String())
|
|
}
|
|
|
|
both := Command(Suite[struct{}, struct{}]{
|
|
InspectPrompt: func(context.Context, PromptRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) {
|
|
return commandkit.Acquired[*promptkit.PromptInspection]{}, nil
|
|
},
|
|
InspectProfile: func(context.Context, ProfileRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
|
|
return commandkit.Acquired[*promptkit.ProfileInspection]{}, nil
|
|
},
|
|
InspectRequirements: func(context.Context, RequirementsRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.RequirementInspection], error) {
|
|
return commandkit.Acquired[*promptkit.RequirementInspection]{}, nil
|
|
},
|
|
ListPrompts: func(context.Context, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
|
|
return commandkit.Acquired[[]promptkit.PromptIdentity]{}, nil
|
|
},
|
|
ListProfiles: func(context.Context, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]string], error) {
|
|
return commandkit.Acquired[[]string]{}, nil
|
|
},
|
|
})
|
|
all := newPromptTestApp(t, both)
|
|
output.Reset()
|
|
outcome = all.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") || !strings.Contains(output.String(), "requirements") {
|
|
t.Fatalf("all-capability tree = (%#v, %q)", outcome, output.String())
|
|
}
|
|
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{Selectors: []commandkit.Flag[struct{}]{{}}}))
|
|
output.Reset()
|
|
outcome = app.Execute(context.Background(), []string{"help"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || strings.Contains(output.String(), "promptkit") {
|
|
t.Fatalf("empty suite installed a namespace: (%#v, %q)", outcome, output.String())
|
|
}
|
|
|
|
invalid := FromEngine[int, int](nil)
|
|
invalid.ListPrompts, invalid.ListProfiles, invalid.ListBackends = nil, nil, nil
|
|
invalid.ListNamespaces, invalid.ListCollections = nil, nil
|
|
invalid.InspectPrompt, invalid.InspectProfile = nil, nil
|
|
invalid.InspectRequirements = nil
|
|
if err := newPromptTestApp(t).Register(Command(invalid)); err == nil || !strings.Contains(err.Error(), "PromptKit engine loader is nil") {
|
|
t.Fatalf("empty invalid adapter lost its construction issue: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T) {
|
|
type input struct{ campaign commandkit.Supplied[string] }
|
|
type state struct{ call int }
|
|
|
|
campaignFlag := commandkit.ValueFlag(commandkit.FlagMeta{Name: "campaign", Usage: "Application campaign."}, (*string)(nil), parseDefinitionString,
|
|
func(target *input, value commandkit.Supplied[string]) { target.campaign = value })
|
|
newInputCalls, checkInputCalls, newStateCalls, providerCalls := 0, 0, 0, 0
|
|
var received RequirementsRequest
|
|
var receivedInput input
|
|
var receivedState state
|
|
suite := Suite[input, state]{
|
|
NewInput: func() input {
|
|
newInputCalls++
|
|
return input{}
|
|
},
|
|
NewState: func() state {
|
|
newStateCalls++
|
|
return state{call: newStateCalls}
|
|
},
|
|
Selectors: []commandkit.Flag[input]{campaignFlag},
|
|
CheckInput: func(selection ckconfig.Selection, value input) error {
|
|
checkInputCalls++
|
|
if checkInputCalls == 1 {
|
|
if !selection.Config.Set || selection.Config.Value != "" || !selection.Overlay.Set || selection.Overlay.Value != "" {
|
|
t.Errorf("checked selection = %#v", selection)
|
|
}
|
|
if !value.campaign.Set || value.campaign.Value != "spring" {
|
|
t.Errorf("checked campaign = %#v", value.campaign)
|
|
}
|
|
} else if selection.Config.Set || selection.Overlay.Set || value.campaign.Set {
|
|
t.Errorf("second checked input retained prior invocation values: selection=%#v input=%#v", selection, value)
|
|
}
|
|
return nil
|
|
},
|
|
InspectRequirements: func(_ context.Context, request RequirementsRequest, value input, gotState state) (commandkit.Acquired[*promptkit.RequirementInspection], error) {
|
|
providerCalls++
|
|
received, receivedInput, receivedState = request, value, gotState
|
|
return commandkit.Acquired[*promptkit.RequirementInspection]{Value: &promptkit.RequirementInspection{
|
|
PromptID: request.Inspection.PromptID, ProfileID: request.Inspection.ProfileID, Complete: true,
|
|
}}, nil
|
|
},
|
|
}
|
|
app := newPromptTestApp(t, Command(suite))
|
|
streams := commandkit.Streams{Out: io.Discard, Err: io.Discard}
|
|
var help bytes.Buffer
|
|
streams.Out = &help
|
|
helpOutcome := app.Execute(context.Background(), []string{"help", "promptkit", "requirements", "show"}, streams)
|
|
if helpOutcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(help.String(), "id") {
|
|
t.Fatalf("requirements-only help = (%#v, %q)", helpOutcome, help.String())
|
|
}
|
|
var familyHelp bytes.Buffer
|
|
familyOutcome := app.Execute(context.Background(), []string{"help", "promptkit"}, commandkit.Streams{Out: &familyHelp, Err: io.Discard})
|
|
if familyOutcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(familyHelp.String(), "requirements") || strings.Contains(familyHelp.String(), "\n show ") || strings.Contains(familyHelp.String(), "\n list ") {
|
|
t.Fatalf("requirements-only family help = (%#v, %q)", familyOutcome, familyHelp.String())
|
|
}
|
|
for _, flag := range []string{"--config", "--overlay", "--profile", "--session-id", "--var", "--format", "--campaign"} {
|
|
if !strings.Contains(help.String(), flag) {
|
|
t.Errorf("requirements help omits %s: %q", flag, help.String())
|
|
}
|
|
}
|
|
if strings.Contains(help.String(), "--scope") || newInputCalls != 0 || checkInputCalls != 0 || newStateCalls != 0 || providerCalls != 0 {
|
|
t.Fatalf("help selected a scope or invoked application code: output=%q factories/checks/providers=%d/%d/%d/%d", help.String(), newInputCalls, checkInputCalls, newStateCalls, providerCalls)
|
|
}
|
|
|
|
var output bytes.Buffer
|
|
args := []string{
|
|
"promptkit", "requirements", "show", "briefing", "--campaign", "spring",
|
|
"--config", "before.yml", "--config=", "--overlay", "base.yml", "--overlay=",
|
|
"--profile", "first", "--profile", "editorial",
|
|
"--session-id", "old-session", "--session-id", "session-42", "--format", "text", "--format", "yaml",
|
|
"--var", "note=hello, world", "--var", "empty=", "--var", "token=a=b", "--var", "=value",
|
|
"--var", "mode=brief", "--var", "mode=full",
|
|
}
|
|
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || outcome.Status != 0 {
|
|
t.Fatalf("requirements invocation = %#v, output=%q", outcome, output.String())
|
|
}
|
|
if newInputCalls != 1 || checkInputCalls != 1 || newStateCalls != 1 || providerCalls != 1 {
|
|
t.Fatalf("first invocation calls = input/check/state/provider %d/%d/%d/%d", newInputCalls, checkInputCalls, newStateCalls, providerCalls)
|
|
}
|
|
if !received.Selection.Config.Set || received.Selection.Config.Value != "" || !received.Selection.Overlay.Set || received.Selection.Overlay.Value != "" {
|
|
t.Fatalf("configuration selection = %#v", received.Selection)
|
|
}
|
|
wantVariables := map[string]string{"note": "hello, world", "empty": "", "token": "a=b", "": "value", "mode": "full"}
|
|
if received.Inspection.PromptID != "briefing" || received.Inspection.ProfileID != "editorial" || received.Inspection.SessionID != "session-42" || !reflect.DeepEqual(received.Inspection.Vars, wantVariables) {
|
|
t.Fatalf("native request = %#v, want vars %#v", received.Inspection, wantVariables)
|
|
}
|
|
if !receivedInput.campaign.Set || receivedInput.campaign.Value != "spring" || receivedState.call != 1 {
|
|
t.Fatalf("provider input/state = %#v/%#v", receivedInput, receivedState)
|
|
}
|
|
var report map[string]any
|
|
if err := yaml.Unmarshal(output.Bytes(), &report); err != nil || report["prompt_id"] != "briefing" || report["complete"] != true {
|
|
t.Fatalf("default requirements YAML = %#v (decode error %v), output=%q", report, err, output.String())
|
|
}
|
|
|
|
output.Reset()
|
|
outcome = app.Execute(context.Background(), []string{"promptkit", "requirements", "show", "briefing"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || newInputCalls != 2 || checkInputCalls != 2 || newStateCalls != 2 || providerCalls != 2 {
|
|
t.Fatalf("second invocation = %#v, calls=%d/%d/%d/%d", outcome, newInputCalls, checkInputCalls, newStateCalls, providerCalls)
|
|
}
|
|
if len(received.Inspection.Vars) != 0 || received.Inspection.ProfileID != "" || received.Inspection.SessionID != "" {
|
|
t.Fatalf("second invocation retained native values: %#v", received.Inspection)
|
|
}
|
|
var defaultReport map[string]any
|
|
if err := yaml.Unmarshal(output.Bytes(), &defaultReport); err != nil || defaultReport["prompt_id"] != "briefing" {
|
|
t.Fatalf("omitted format did not produce the selected prompt's YAML report: %#v (decode error %v), output=%q", defaultReport, err, output.String())
|
|
}
|
|
}
|
|
|
|
func TestRequirementsCommandUsageFailuresPrecedeApplicationPreparation(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
args []string
|
|
}{
|
|
{name: "missing operand", args: []string{"promptkit", "requirements", "show"}},
|
|
{name: "extra operand", args: []string{"promptkit", "requirements", "show", "briefing", "extra"}},
|
|
{name: "unsupported format", args: []string{"promptkit", "requirements", "show", "briefing", "--format", "text"}},
|
|
{name: "malformed variable followed by valid assignment", args: []string{"promptkit", "requirements", "show", "briefing", "--var", "broken", "--var", "mode=full"}},
|
|
{name: "no built-in scope", args: []string{"promptkit", "requirements", "show", "briefing", "--scope", "all"}},
|
|
{name: "malformed variable on help", args: []string{"help", "promptkit", "requirements", "show", "--var", "broken"}},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
newInputCalls, checkInputCalls, newStateCalls, providerCalls := 0, 0, 0, 0
|
|
suite := Suite[struct{}, struct{}]{
|
|
NewInput: func() struct{} { newInputCalls++; return struct{}{} },
|
|
CheckInput: func(ckconfig.Selection, struct{}) error {
|
|
checkInputCalls++
|
|
return nil
|
|
},
|
|
NewState: func() struct{} { newStateCalls++; return struct{}{} },
|
|
InspectRequirements: func(context.Context, RequirementsRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.RequirementInspection], error) {
|
|
providerCalls++
|
|
return commandkit.Acquired[*promptkit.RequirementInspection]{Value: &promptkit.RequirementInspection{}}, nil
|
|
},
|
|
}
|
|
var output bytes.Buffer
|
|
outcome := newPromptTestApp(t, Command(suite)).Execute(context.Background(), test.args, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeUsage || outcome.Status != 2 || newInputCalls != 0 || checkInputCalls != 0 || newStateCalls != 0 || providerCalls != 0 {
|
|
t.Fatalf("usage failure = %#v, factories/checks/providers=%d/%d/%d/%d output=%q", outcome, newInputCalls, checkInputCalls, newStateCalls, providerCalls, output.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCatalogCommandTreesAreIndependentAndOmitShow(t *testing.T) {
|
|
prompts := Command(Suite[struct{}, struct{}]{
|
|
ListPrompts: func(context.Context, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
|
|
return commandkit.Acquired[[]promptkit.PromptIdentity]{}, nil
|
|
},
|
|
})
|
|
profiles := Command(Suite[struct{}, struct{}]{
|
|
ListProfiles: func(context.Context, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]string], error) {
|
|
return commandkit.Acquired[[]string]{}, nil
|
|
},
|
|
})
|
|
for _, test := range []struct {
|
|
name string
|
|
cmd commandkit.Command
|
|
want string
|
|
omit string
|
|
}{
|
|
{name: "prompts", cmd: prompts, want: "prompts", omit: "profiles"},
|
|
{name: "profiles", cmd: profiles, want: "profiles", omit: "prompts"},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
app := newPromptTestApp(t, test.cmd)
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"help", "promptkit", "list"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "list") || !strings.Contains(output.String(), test.want) || strings.Contains(output.String(), test.omit) || strings.Contains(output.String(), "show") {
|
|
t.Fatalf("catalog-only help = (%#v, %q)", outcome, output.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
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, CatalogRequest, 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 TestAdditionalCatalogsInstallIndependentlyAndCaptureCallbacks(t *testing.T) {
|
|
for _, name := range []string{"backends", "namespaces", "collections"} {
|
|
t.Run(name, func(t *testing.T) {
|
|
calls := 0
|
|
var namespaceRequest NamespaceRequest
|
|
suite := Suite[struct{}, struct{}]{}
|
|
args := []string{"promptkit", "list", name}
|
|
switch name {
|
|
case "backends":
|
|
suite.ListBackends = func(context.Context, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]string], error) {
|
|
calls++
|
|
return commandkit.Acquired[[]string]{Value: []string{"local"}}, nil
|
|
}
|
|
case "namespaces":
|
|
suite.ListNamespaces = func(_ context.Context, request NamespaceRequest, _, _ struct{}) (commandkit.Acquired[[]string], error) {
|
|
calls++
|
|
namespaceRequest = request
|
|
return commandkit.Acquired[[]string]{Value: []string{"team"}}, nil
|
|
}
|
|
case "collections":
|
|
suite.ListCollections = func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.CollectionInfo], error) {
|
|
calls++
|
|
return commandkit.Acquired[[]promptkit.CollectionInfo]{Value: []promptkit.CollectionInfo{}}, nil
|
|
}
|
|
}
|
|
app := newPromptTestApp(t, Command(suite))
|
|
switch name {
|
|
case "backends":
|
|
suite.ListBackends = nil
|
|
case "namespaces":
|
|
suite.ListNamespaces = nil
|
|
case "collections":
|
|
suite.ListCollections = nil
|
|
}
|
|
|
|
var help bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"help", "promptkit", "list"}, commandkit.Streams{Out: &help, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(help.String(), "\n "+name+" ") || calls != 0 {
|
|
t.Fatalf("independent list help = (%#v, %q), calls=%d", outcome, help.String(), calls)
|
|
}
|
|
var unavailableArgs []string
|
|
switch name {
|
|
case "namespaces":
|
|
unavailableArgs = append(append([]string(nil), args...), "--namespace", "team")
|
|
case "collections":
|
|
unavailableArgs = append(append([]string(nil), args...), "--kind", "prompt")
|
|
}
|
|
if len(unavailableArgs) != 0 {
|
|
if got := app.Execute(context.Background(), unavailableArgs, commandkit.Streams{Out: io.Discard, Err: io.Discard}); got.Kind != commandkit.OutcomeUsage || calls != 0 {
|
|
t.Fatalf("unavailable %s filter = %#v, calls=%d", name, got, calls)
|
|
}
|
|
}
|
|
if got := app.Execute(context.Background(), args, commandkit.Streams{Out: io.Discard, Err: io.Discard}); got.Kind != commandkit.OutcomeSuccess || calls != 1 {
|
|
t.Fatalf("captured %s callback = (%#v, calls=%d)", name, got, calls)
|
|
}
|
|
if name == "namespaces" && (namespaceRequest.Selection.Config.Set || len(namespaceRequest.Kinds) != 0) {
|
|
t.Fatalf("omitted namespace selection = %#v", namespaceRequest)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDiscoveryCommandsForwardFiltersAndRenderCollections(t *testing.T) {
|
|
var promptRequest, profileRequest, backendRequest CatalogRequest
|
|
var namespaceRequest NamespaceRequest
|
|
var collectionSelection ckconfig.Selection
|
|
providers := 0
|
|
suite := Suite[struct{}, struct{}]{
|
|
ListPrompts: func(_ context.Context, request CatalogRequest, _, _ struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
|
|
providers++
|
|
promptRequest = request
|
|
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "team/briefing", Version: testStringPointer("")}}}, nil
|
|
},
|
|
ListProfiles: func(_ context.Context, request CatalogRequest, _, _ struct{}) (commandkit.Acquired[[]string], error) {
|
|
providers++
|
|
profileRequest = request
|
|
return commandkit.Acquired[[]string]{Value: []string{"default"}}, nil
|
|
},
|
|
ListBackends: func(_ context.Context, request CatalogRequest, _, _ struct{}) (commandkit.Acquired[[]string], error) {
|
|
providers++
|
|
backendRequest = request
|
|
return commandkit.Acquired[[]string]{Value: []string{"local"}}, nil
|
|
},
|
|
ListNamespaces: func(_ context.Context, request NamespaceRequest, _, _ struct{}) (commandkit.Acquired[[]string], error) {
|
|
providers++
|
|
namespaceRequest = request
|
|
return commandkit.Acquired[[]string]{Value: []string{"team", "root"}}, nil
|
|
},
|
|
ListCollections: func(_ context.Context, selection ckconfig.Selection, _, _ struct{}) (commandkit.Acquired[[]promptkit.CollectionInfo], error) {
|
|
providers++
|
|
collectionSelection = selection
|
|
return commandkit.Acquired[[]promptkit.CollectionInfo]{Value: []promptkit.CollectionInfo{
|
|
{Namespace: "team", ReleaseVersion: testStringPointer("v1")},
|
|
{Namespace: "empty-version", ReleaseVersion: testStringPointer("")},
|
|
{Namespace: "unversioned"},
|
|
}}, nil
|
|
},
|
|
}
|
|
app := newPromptTestApp(t, Command(suite))
|
|
run := func(args ...string) (commandkit.Outcome, string) {
|
|
t.Helper()
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
return outcome, output.String()
|
|
}
|
|
|
|
outcome, output := run("promptkit", "list", "prompts", "--namespace", "first", "--namespace", "team", "--all-namespaces=false", "--config", "selected.yml")
|
|
if outcome.Kind != commandkit.OutcomeSuccess || promptRequest.Filter != (promptkit.NamespaceFilter{Mode: promptkit.NamespaceNamed, Name: "team"}) || !promptRequest.Selection.Config.Set || promptRequest.Selection.Config.Value != "selected.yml" {
|
|
t.Fatalf("prompt list outcome/request = (%#v, %#v), output=%q", outcome, promptRequest, output)
|
|
}
|
|
var promptRows []map[string]any
|
|
if err := yaml.Unmarshal([]byte(output), &promptRows); err != nil || len(promptRows) != 1 || promptRows[0]["id"] != "team/briefing" || promptRows[0]["version"] != "" {
|
|
t.Fatalf("prompt catalog YAML = %#v, error=%v, output=%q", promptRows, err, output)
|
|
}
|
|
|
|
outcome, _ = run("promptkit", "list", "profiles", "--namespace", "")
|
|
if outcome.Kind != commandkit.OutcomeSuccess || profileRequest.Filter.Mode != promptkit.NamespaceUnqualified || profileRequest.Filter.Name != "" {
|
|
t.Fatalf("profile list outcome/filter = (%#v, %#v)", outcome, profileRequest.Filter)
|
|
}
|
|
outcome, _ = run("promptkit", "list", "backends", "--all-namespaces=false")
|
|
if outcome.Kind != commandkit.OutcomeSuccess || backendRequest.Filter.Mode != promptkit.NamespaceDefault {
|
|
t.Fatalf("explicit false backend filter = (%#v, %#v)", outcome, backendRequest.Filter)
|
|
}
|
|
outcome, _ = run("promptkit", "list", "backends", "--all-namespaces")
|
|
if outcome.Kind != commandkit.OutcomeSuccess || backendRequest.Filter.Mode != promptkit.NamespaceAll {
|
|
t.Fatalf("backend list outcome/filter = (%#v, %#v)", outcome, backendRequest.Filter)
|
|
}
|
|
outcome, output = run("promptkit", "list", "namespaces", "--kind", "prompt", "--kind", "backend")
|
|
if outcome.Kind != commandkit.OutcomeSuccess || !reflect.DeepEqual(namespaceRequest.Kinds, []promptkit.ResourceKind{promptkit.ResourcePrompt, promptkit.ResourceBackend}) {
|
|
t.Fatalf("namespace list outcome/kinds = (%#v, %#v)", outcome, namespaceRequest.Kinds)
|
|
}
|
|
var namespaces []string
|
|
if err := yaml.Unmarshal([]byte(output), &namespaces); err != nil || !reflect.DeepEqual(namespaces, []string{"team", "root"}) {
|
|
t.Fatalf("namespace YAML = %#v, error=%v, output=%q", namespaces, err, output)
|
|
}
|
|
outcome, output = run("promptkit", "list", "collections", "--config", "collections.yml")
|
|
if outcome.Kind != commandkit.OutcomeSuccess || !collectionSelection.Config.Set || collectionSelection.Config.Value != "collections.yml" {
|
|
t.Fatalf("collection list outcome/selection = (%#v, %#v)", outcome, collectionSelection)
|
|
}
|
|
var collections []map[string]any
|
|
if err := yaml.Unmarshal([]byte(output), &collections); err != nil || len(collections) != 3 || collections[0]["namespace"] != "team" || collections[0]["version"] != "v1" || collections[1]["version"] != "" || collections[2]["version"] != nil {
|
|
t.Fatalf("collection YAML = %#v, error=%v, output=%q", collections, err, output)
|
|
}
|
|
|
|
beforeConflict := providers
|
|
outcome, _ = run("promptkit", "list", "backends", "--namespace", "team", "--all-namespaces")
|
|
if outcome.Kind != commandkit.OutcomeUsage || providers != beforeConflict {
|
|
t.Fatalf("conflicting namespace filters = %#v, provider calls %d -> %d", outcome, beforeConflict, providers)
|
|
}
|
|
if providers != 6 {
|
|
t.Fatalf("catalog providers called = %d, want six", providers)
|
|
}
|
|
}
|
|
|
|
func TestCatalogListUsesPreparedInputAndPreflightsFormat(t *testing.T) {
|
|
type input struct{ campaign commandkit.Supplied[string] }
|
|
type state struct{ token int }
|
|
flag := commandkit.ValueFlag(commandkit.FlagMeta{Name: "campaign"}, (*string)(nil),
|
|
func(value string) (string, error) { return value, nil },
|
|
func(target *input, value commandkit.Supplied[string]) { target.campaign = value })
|
|
newInputCalls, newStateCalls, providerCalls := 0, 0, 0
|
|
var received input
|
|
var receivedSelection ckconfig.Selection
|
|
var receivedState state
|
|
suite := Suite[input, state]{
|
|
NewInput: func() input { newInputCalls++; return input{} },
|
|
NewState: func() state { newStateCalls++; return state{token: newStateCalls} },
|
|
Selectors: []commandkit.Flag[input]{flag},
|
|
CheckInput: func(selection ckconfig.Selection, value input) error {
|
|
if !value.campaign.Set || value.campaign.Value != "spring" {
|
|
t.Errorf("checked campaign = %#v", value.campaign)
|
|
}
|
|
if !selection.Config.Set || selection.Config.Value != "" || !selection.Overlay.Set || selection.Overlay.Value != "base.yml" {
|
|
t.Errorf("checked selection = %#v", selection)
|
|
}
|
|
return nil
|
|
},
|
|
ListPrompts: func(_ context.Context, request CatalogRequest, value input, gotState state) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
|
|
providerCalls++
|
|
received, receivedSelection, receivedState = value, request.Selection, gotState
|
|
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("1")}}}, nil
|
|
},
|
|
}
|
|
app := newPromptTestApp(t, Command(suite))
|
|
var output bytes.Buffer
|
|
invalid := app.Execute(context.Background(), []string{"promptkit", "list", "prompts", "--format", "text"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if invalid.Kind != commandkit.OutcomeUsage || newInputCalls != 0 || newStateCalls != 0 || providerCalls != 0 || output.Len() != 0 {
|
|
t.Fatalf("invalid format preflight = %#v, factories/providers=%d/%d/%d output=%q", invalid, newInputCalls, newStateCalls, providerCalls, output.String())
|
|
}
|
|
|
|
outcome := app.Execute(context.Background(), []string{"promptkit", "list", "prompts", "--config", "", "--overlay", "base.yml", "--campaign", "spring"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
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 != "" || !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, request CatalogRequest, 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 && (!request.Selection.Config.Set || request.Selection.Config.Value != "" || !request.Selection.Overlay.Set || request.Selection.Overlay.Value != "" || !got.campaign.Set || !got.enabled.Set || !got.limit.Set || !got.labels.Set) {
|
|
t.Errorf("first provider values = selection %#v input %#v", request.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, request CatalogRequest, input, state *struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
|
|
if input != nil || state != nil || request.Selection.Config.Set || request.Selection.Overlay.Set {
|
|
t.Errorf("nil-factory values = selection %#v input %#v state %#v", request.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: testStringPointer("1.10")},
|
|
{ID: "001", Version: testStringPointer(" 03 ")},
|
|
{ID: "a: true", Version: testStringPointer("yes")},
|
|
{ID: "without-version"},
|
|
}
|
|
profiles := []string{"later", "001", "a: true", "later"}
|
|
suite := Suite[struct{}, struct{}]{
|
|
ListPrompts: func(context.Context, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
|
|
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: prompts}, nil
|
|
},
|
|
ListProfiles: func(context.Context, CatalogRequest, 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)
|
|
if !idOK || id != prompts[index].ID {
|
|
t.Errorf("prompt entry %d = %#v, want ID %q", index, entry, prompts[index].ID)
|
|
}
|
|
version, versionExists := entry["version"]
|
|
if prompts[index].Version == nil {
|
|
if versionExists || len(entry) != 1 {
|
|
t.Errorf("prompt entry %d emitted absent version: %#v", index, entry)
|
|
}
|
|
} else if !versionExists || version != *prompts[index].Version || len(entry) != 2 {
|
|
t.Errorf("prompt entry %d version = %#v, want %q", index, version, *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, CatalogRequest, 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, CatalogRequest, 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, CatalogRequest, 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, CatalogRequest, 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, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
|
|
cancel()
|
|
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("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, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
|
|
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("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
|
|
}})
|
|
other := commandkit.Group("promptkit", "Another command tree.", commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "child", Summary: "A child."}))
|
|
first, err := commandkit.New(commandkit.Identity{Name: "example"}, other)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := first.Register(command); err == nil {
|
|
t.Fatal("PromptKit tree installed over an existing namespace")
|
|
}
|
|
second, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := second.Register(other); err == nil {
|
|
t.Fatal("later command tree installed over the PromptKit namespace")
|
|
}
|
|
}
|
|
|
|
func TestCommandValidatesOnlyGeneratedCapturedBindings(t *testing.T) {
|
|
unused := Command(Suite[struct{}, struct{}]{Selectors: []commandkit.Flag[struct{}]{{}}})
|
|
if err := newPromptTestApp(t).Register(unused); err != nil {
|
|
t.Fatalf("unused malformed selector affected empty suite: %v", err)
|
|
}
|
|
generated := Command(Suite[struct{}, struct{}]{Selectors: []commandkit.Flag[struct{}]{{}}, InspectProfile: func(context.Context, ProfileRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
|
|
return commandkit.Acquired[*promptkit.ProfileInspection]{}, nil
|
|
}})
|
|
if err := newPromptTestApp(t).Register(generated); err == nil {
|
|
t.Fatal("generated command accepted an invalid captured selector")
|
|
}
|
|
}
|
|
|
|
func TestDefinitionInputAndStateLifetimesFollowChecks(t *testing.T) {
|
|
type inspectInput struct{ Campaign commandkit.Supplied[string] }
|
|
type runState struct{}
|
|
conversions := 0
|
|
flag := commandkit.ValueFlag[*inspectInput, string](commandkit.FlagMeta{Name: "campaign", Required: true}, (*string)(nil), func(value string) (string, error) {
|
|
conversions++
|
|
return value, nil
|
|
}, func(input **inspectInput, value commandkit.Supplied[string]) { (*input).Campaign = value })
|
|
newInputs, newStates, checks, callbacks := 0, 0, 0, 0
|
|
rejectInput := false
|
|
var checked *inspectInput
|
|
var wantContext context.Context
|
|
suite := Suite[*inspectInput, *runState]{
|
|
NewInput: func() *inspectInput { newInputs++; return &inspectInput{} },
|
|
NewState: func() *runState {
|
|
newStates++
|
|
if checks != 1 {
|
|
t.Errorf("state initialized after %d input checks", checks)
|
|
}
|
|
return &runState{}
|
|
},
|
|
Selectors: []commandkit.Flag[*inspectInput]{flag},
|
|
CheckInput: func(selection ckconfig.Selection, input *inspectInput) error {
|
|
checks++
|
|
checked = input
|
|
if rejectInput {
|
|
return errors.New("rejected definition input")
|
|
}
|
|
if !selection.Config.Set || selection.Config.Value != "base.yml" || !selection.Overlay.Set || selection.Overlay.Value != "local.yml" || !input.Campaign.Set || input.Campaign.Value != "spring" {
|
|
return errors.New("definition input was incomplete")
|
|
}
|
|
return nil
|
|
},
|
|
InspectPrompt: func(ctx context.Context, request PromptRequest, input *inspectInput, state *runState) (commandkit.Acquired[*promptkit.PromptInspection], error) {
|
|
callbacks++
|
|
if ctx != wantContext || input != checked || request.ID != "briefing" || state == nil {
|
|
t.Errorf("prompt callback arguments = ctx=%v request=%#v input=%p state=%p", ctx, request, input, state)
|
|
}
|
|
return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{PromptID: request.ID}}, nil
|
|
},
|
|
}
|
|
app := newPromptTestApp(t, Command(suite))
|
|
wantContext = context.WithValue(context.Background(), struct{}{}, "context")
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(wantContext, []string{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--config", "base.yml", "--overlay", "local.yml"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || newInputs != 1 || newStates != 1 || checks != 1 || callbacks != 1 || conversions != 1 {
|
|
t.Fatalf("execution outcome=%#v counts conversion/input/state/check/callback=%d/%d/%d/%d/%d report=%q", outcome, conversions, newInputs, newStates, checks, callbacks, output.String())
|
|
}
|
|
rejectInput = true
|
|
output.Reset()
|
|
outcome = app.Execute(wantContext, []string{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--config", "base.yml", "--overlay", "local.yml"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeUsage || newInputs != 2 || newStates != 1 || checks != 2 || callbacks != 1 || conversions != 2 {
|
|
t.Fatalf("rejected input reached state/callback: outcome=%#v counts=%d/%d/%d/%d/%d", outcome, conversions, newInputs, newStates, checks, callbacks)
|
|
}
|
|
}
|
|
|
|
func TestDefinitionReportsRetainNativeFieldsAndSafeCredentialReferences(t *testing.T) {
|
|
t.Setenv("PROMPTKIT_ADAPTER_TEST_KEY", "credential-value-must-not-appear")
|
|
engine := adapterEngine(t)
|
|
loadCalls := 0
|
|
suite := FromEngine(func(context.Context, ckconfig.Selection, struct{}, struct{}) (*promptkit.Engine, error) {
|
|
loadCalls++
|
|
return engine, nil
|
|
})
|
|
app := newPromptTestApp(t, Command(suite))
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "briefing", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || loadCalls != 1 {
|
|
t.Fatalf("prompt report outcome=%#v load calls=%d report=%s", outcome, loadCalls, output.String())
|
|
}
|
|
var prompt map[string]any
|
|
if err := yaml.Unmarshal(output.Bytes(), &prompt); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inputs := prompt["inputs"].([]any)
|
|
if len(inputs) != 2 || inputs[0].(map[string]any)["name"] != "topic" || inputs[1].(map[string]any)["name"] != "audience" || prompt["prompt_hash"] == "" {
|
|
t.Fatalf("prompt fields/order = %#v", prompt)
|
|
}
|
|
contract := prompt["output_contract"].(map[string]any)
|
|
if contract["repair_attempts"] != 0 {
|
|
t.Fatalf("zero-valued contract field was lost: %#v", contract)
|
|
}
|
|
|
|
output.Reset()
|
|
outcome = app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "briefing"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || loadCalls != 2 || !strings.Contains(output.String(), "Prompt version: \"1\"") || !strings.Contains(output.String(), `- name: "topic"`) {
|
|
t.Fatalf("default text prompt report = (%#v, calls=%d, %s)", outcome, loadCalls, output.String())
|
|
}
|
|
|
|
output.Reset()
|
|
outcome = app.Execute(context.Background(), []string{"promptkit", "show", "profile", "default", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || loadCalls != 3 {
|
|
t.Fatalf("profile report outcome=%#v calls=%d report=%s", outcome, loadCalls, output.String())
|
|
}
|
|
var profile map[string]any
|
|
if err := yaml.Unmarshal(output.Bytes(), &profile); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
params := profile["effective_model_params"].(map[string]any)
|
|
if params["api_key_env"] != "PROMPTKIT_ADAPTER_TEST_KEY" || params["temperature"] != 0 || params["max_tokens"] != 0 || strings.Contains(output.String(), "credential-value-must-not-appear") {
|
|
t.Fatalf("credential or zero model fields = %#v / %s", params, output.String())
|
|
}
|
|
extra := params["extra_params"].(map[string]any)
|
|
nested := extra["nested"].(map[string]any)
|
|
sequence := extra["sequence"].([]any)
|
|
if extra["token"] != "caller-owned-value" || nested["null_value"] != nil || nested["visible"] != true || len(sequence) != 3 || sequence[1] != nil || sequence[2] != 3 {
|
|
t.Fatalf("nested ExtraParams changed: %#v", extra)
|
|
}
|
|
|
|
output.Reset()
|
|
outcome = app.Execute(context.Background(), []string{"promptkit", "show", "profile", "default"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || loadCalls != 4 || !strings.Contains(output.String(), "API key required: false") || !strings.Contains(output.String(), `api_key_env: "PROMPTKIT_ADAPTER_TEST_KEY"`) {
|
|
t.Fatalf("default text profile report = (%#v, calls=%d, %s)", outcome, loadCalls, output.String())
|
|
}
|
|
|
|
output.Reset()
|
|
outcome = app.Execute(context.Background(), []string{"promptkit", "show", "profile", "requires-key", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "api_key_required: true") {
|
|
t.Fatalf("credential requirement report = (%#v, %s)", outcome, output.String())
|
|
}
|
|
|
|
output.Reset()
|
|
outcome = app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "empty", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "inputs: []") {
|
|
t.Fatalf("empty prompt input list = (%#v, %s)", outcome, output.String())
|
|
}
|
|
}
|
|
|
|
func TestOptionalPromptVersionsPreserveAbsenceAndEmptyValues(t *testing.T) {
|
|
empty := ""
|
|
label := "release-1"
|
|
for _, test := range []struct {
|
|
name string
|
|
version *string
|
|
present bool
|
|
want string
|
|
}{
|
|
{name: "absent"},
|
|
{name: "present empty", version: &empty, present: true, want: ""},
|
|
{name: "nonempty", version: &label, present: true, want: "release-1"},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
prompt := promptReportFromInspection(&promptkit.PromptInspection{PromptID: "briefing", PromptVersion: test.version})
|
|
promptBytes, err := encodePromptYAMLBytes(prompt)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var promptReport map[string]any
|
|
if err := yaml.Unmarshal(promptBytes, &promptReport); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertOptionalVersion(t, promptReport, test.present, test.want)
|
|
text := formatPromptText(prompt)
|
|
if test.present && !strings.Contains(text, "Prompt version: "+strconv.Quote(test.want)+"\n") {
|
|
t.Errorf("text omitted present version %q: %q", test.want, text)
|
|
}
|
|
if !test.present && strings.Contains(text, "Prompt version:") {
|
|
t.Errorf("text emitted absent version: %q", text)
|
|
}
|
|
|
|
requirementsBytes, err := encodePromptYAMLBytes(requirementsReportFromInspection(&promptkit.RequirementInspection{PromptID: "briefing", PromptVersion: test.version}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var requirementsReport map[string]any
|
|
if err := yaml.Unmarshal(requirementsBytes, &requirementsReport); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertOptionalVersion(t, requirementsReport, test.present, test.want)
|
|
|
|
catalogBytes, err := encodePromptCatalog([]promptkit.PromptIdentity{{ID: "briefing", Version: test.version}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var catalog []map[string]any
|
|
if err := yaml.Unmarshal(catalogBytes, &catalog); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(catalog) != 1 || catalog[0]["id"] != "briefing" {
|
|
t.Fatalf("catalog = %#v", catalog)
|
|
}
|
|
assertOptionalVersion(t, catalog[0], test.present, test.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
func assertOptionalVersion(t *testing.T, report map[string]any, present bool, want string) {
|
|
t.Helper()
|
|
version, found := report["version"]
|
|
if found != present {
|
|
t.Fatalf("version presence = %t in %#v, want %t", found, report, present)
|
|
}
|
|
if present && version != want {
|
|
t.Errorf("version = %#v, want %q", version, want)
|
|
}
|
|
}
|
|
|
|
func TestNativeLookupErrorsAndBlankIDsReachCallbacks(t *testing.T) {
|
|
nativeErr := errors.New("native lookup failure")
|
|
calls := 0
|
|
diagnostic := commandkit.MessageDiagnostic(commandkit.SeverityWarning, "lookup context")
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{InspectPrompt: func(_ context.Context, request PromptRequest, _ struct{}, _ struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) {
|
|
calls++
|
|
if request.ID != "" {
|
|
t.Errorf("blank ID was rewritten to %q", request.ID)
|
|
}
|
|
return commandkit.Acquired[*promptkit.PromptInspection]{Diagnostics: []commandkit.Diagnostic{diagnostic}}, nativeErr
|
|
}}))
|
|
var output, diagnosticOutput bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"promptkit", "show", "prompt"}, commandkit.Streams{Out: &output, Err: &diagnosticOutput})
|
|
if outcome.Kind != commandkit.OutcomeUsage || calls != 0 || output.Len() != 0 {
|
|
t.Fatalf("missing ID was not a usage failure: (%#v, calls=%d, out=%q)", outcome, calls, output.String())
|
|
}
|
|
output.Reset()
|
|
diagnosticOutput.Reset()
|
|
outcome = app.Execute(context.Background(), []string{"promptkit", "show", "prompt", ""}, commandkit.Streams{Out: &output, Err: &diagnosticOutput})
|
|
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, nativeErr) || calls != 1 || output.Len() != 0 || !strings.Contains(diagnosticOutput.String(), "lookup context") {
|
|
t.Fatalf("native lookup failure = (%#v, calls=%d, out=%q, err=%q)", outcome, calls, output.String(), diagnosticOutput.String())
|
|
}
|
|
}
|
|
|
|
func TestNamedPromptInspectionRejectsRemovedVersionFlag(t *testing.T) {
|
|
calls := 0
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{InspectPrompt: func(_ context.Context, request PromptRequest, _, _ struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) {
|
|
calls++
|
|
return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{PromptID: request.ID}}, nil
|
|
}}))
|
|
outcome := app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "briefing", "--version", "1"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeUsage || calls != 0 {
|
|
t.Fatalf("removed version flag outcome = %#v, provider calls=%d", outcome, calls)
|
|
}
|
|
}
|
|
|
|
func TestCancelledInspectionDoesNotRender(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
called := 0
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{InspectProfile: func(context.Context, ProfileRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
|
|
called++
|
|
cancel()
|
|
return commandkit.Acquired[*promptkit.ProfileInspection]{Value: &promptkit.ProfileInspection{ProfileID: "default"}}, nil
|
|
}}))
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(ctx, []string{"promptkit", "show", "profile", "default"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if !errors.Is(outcome.Cause, context.Canceled) || outcome.Kind != commandkit.OutcomeOperational || called != 1 || output.Len() != 0 {
|
|
t.Fatalf("cancelled inspection = (%#v, called=%d, output=%q)", outcome, called, output.String())
|
|
}
|
|
}
|
|
|
|
func TestDefinitionFormatPreflightAndReportDeliveryFailure(t *testing.T) {
|
|
calls := 0
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{InspectProfile: func(context.Context, ProfileRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
|
|
calls++
|
|
return commandkit.Acquired[*promptkit.ProfileInspection]{Value: &promptkit.ProfileInspection{ProfileID: "default"}}, nil
|
|
}}))
|
|
var output bytes.Buffer
|
|
outcome := app.Execute(context.Background(), []string{"promptkit", "show", "profile", "default", "--format", "json"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeUsage || calls != 0 || output.Len() != 0 {
|
|
t.Fatalf("bad format was not rejected before callback: outcome=%#v calls=%d output=%q", outcome, calls, output.String())
|
|
}
|
|
|
|
writeErr := errors.New("report writer failed")
|
|
outcome = app.Execute(context.Background(), []string{"promptkit", "show", "profile", "default"}, commandkit.Streams{Out: partialReportWriter{err: writeErr}, Err: io.Discard})
|
|
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Cause, writeErr) || outcome.DeliveryError != writeErr || !outcome.ReportIncomplete || calls != 1 {
|
|
t.Fatalf("report delivery outcome = %#v calls=%d", outcome, calls)
|
|
}
|
|
}
|
|
|
|
type partialReportWriter struct{ err error }
|
|
|
|
func (writer partialReportWriter) Write(content []byte) (int, error) {
|
|
if len(content) == 0 {
|
|
return 0, writer.err
|
|
}
|
|
return 1, writer.err
|
|
}
|
|
|
|
var _ io.Writer = partialReportWriter{}
|
|
|
|
func TestProfileTextUsesCredentialNamesAndExplicitEmptyExtraParams(t *testing.T) {
|
|
report := profileReportFromInspection(&promptkit.ProfileInspection{ProfileID: "default", EffectiveModelParams: promptkit.ExecutionTarget{APIKeyEnv: "APP_KEY", ExtraParams: nil}})
|
|
text, err := formatProfileText(report)
|
|
if err != nil || !strings.Contains(text, "api_key_env: \"APP_KEY\"") || !strings.Contains(text, "extra_params:\n {}\n") || !strings.Contains(text, "API key required: false") {
|
|
t.Fatalf("profile text = (%q, %v)", text, err)
|
|
}
|
|
if strings.Contains(text, "APP_KEY_VALUE") {
|
|
t.Fatalf("report contains a credential value: %q", text)
|
|
}
|
|
}
|
|
|
|
func TestPromptCatalogYAMLPreservesSchemaStringsAndOrder(t *testing.T) {
|
|
want := []promptkit.PromptIdentity{
|
|
{ID: " briefing ", Version: testStringPointer(" 03 ")},
|
|
{ID: "001", Version: testStringPointer("1.10")},
|
|
{ID: " briefing ", Version: testStringPointer(" 03 ")},
|
|
}
|
|
output, err := encodePromptCatalog(want)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var decoded []map[string]any
|
|
if err := yaml.Unmarshal(output, &decoded); err != nil {
|
|
t.Fatalf("decode prompt catalog %q: %v", output, err)
|
|
}
|
|
if len(decoded) != len(want) {
|
|
t.Fatalf("catalog entry count = %d, want %d: %q", len(decoded), len(want), output)
|
|
}
|
|
for index, entry := range decoded {
|
|
if len(entry) != 2 {
|
|
t.Errorf("entry %d fields = %#v, want only id and version", index, entry)
|
|
}
|
|
id, idOK := entry["id"].(string)
|
|
version, versionOK := entry["version"].(string)
|
|
if !idOK || !versionOK || id != want[index].ID || want[index].Version == nil || version != *want[index].Version {
|
|
t.Errorf("entry %d = %#v, want id/version %q/%#v", index, entry, want[index].ID, want[index].Version)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCatalogYAMLEncodersRenderNilAndEmptyInventoriesAsSequences(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
data func() ([]byte, error)
|
|
}{
|
|
{name: "nil prompts", data: func() ([]byte, error) { return encodePromptCatalog(nil) }},
|
|
{name: "empty prompts", data: func() ([]byte, error) { return encodePromptCatalog(make([]promptkit.PromptIdentity, 0)) }},
|
|
{name: "nil profiles", data: func() ([]byte, error) { return encodeProfileCatalog(nil) }},
|
|
{name: "empty profiles", data: func() ([]byte, error) { return encodeProfileCatalog(make([]string, 0)) }},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
output, err := test.data()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.TrimSpace(string(output)) != "[]" {
|
|
t.Fatalf("empty catalog YAML = %q, want empty sequence", output)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProfileCatalogYAMLPreservesStringValuesAndOrder(t *testing.T) {
|
|
want := []string{" default ", "001", "same", "same"}
|
|
output, err := encodeProfileCatalog(want)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var decoded []any
|
|
if err := yaml.Unmarshal(output, &decoded); err != nil {
|
|
t.Fatalf("decode profile catalog %q: %v", output, err)
|
|
}
|
|
if len(decoded) != len(want) {
|
|
t.Fatalf("catalog entry count = %d, want %d: %q", len(decoded), len(want), output)
|
|
}
|
|
for index, item := range decoded {
|
|
value, ok := item.(string)
|
|
if !ok || value != want[index] {
|
|
t.Errorf("profile %d = %#v, want string %q", index, item, want[index])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRequirementsReportRendersEmptyInventoriesAndFalseValues(t *testing.T) {
|
|
output, err := encodePromptYAMLBytes(requirementsReportFromInspection(&promptkit.RequirementInspection{}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var report map[string]any
|
|
if err := yaml.Unmarshal(output, &report); err != nil {
|
|
t.Fatalf("decode requirements report %q: %v", output, err)
|
|
}
|
|
for _, key := range []string{"prompt_id", "profile_id", "complete", "inputs", "variables", "issues"} {
|
|
if _, ok := report[key]; !ok {
|
|
t.Errorf("requirements report is missing %q: %q", key, output)
|
|
}
|
|
}
|
|
if _, ok := report["version"]; ok {
|
|
t.Errorf("absent prompt version was emitted: %q", output)
|
|
}
|
|
if report["complete"] != false {
|
|
t.Errorf("complete = %#v, want false", report["complete"])
|
|
}
|
|
for _, key := range []string{"inputs", "variables", "issues"} {
|
|
items, ok := report[key].([]any)
|
|
if !ok || len(items) != 0 {
|
|
t.Errorf("%s = %#v, want an empty sequence", key, report[key])
|
|
}
|
|
}
|
|
for _, key := range []string{"prompt_id", "profile_id"} {
|
|
if report[key] != "" {
|
|
t.Errorf("%s = %#v, want an explicitly present empty string", key, report[key])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRequirementsReportPreservesNativeFieldsAndMetadata(t *testing.T) {
|
|
empty := ""
|
|
contentType := "true"
|
|
inspection := &promptkit.RequirementInspection{
|
|
PromptID: "true",
|
|
PromptVersion: testStringPointer("001"),
|
|
ProfileID: "null",
|
|
Complete: true,
|
|
Inputs: []promptkit.InputRequirement{
|
|
{Name: "true", Kind: promptkit.RequirementKind("123"), Declared: false, Description: &empty},
|
|
{Name: "001", Kind: promptkit.RequirementKind("future_kind"), Declared: true, ContentType: &contentType, Explanation: "false"},
|
|
},
|
|
Variables: []promptkit.VariableRequirement{
|
|
{Name: "false", Kind: promptkit.RequirementKind("on"), Supplied: false},
|
|
{Name: "001", Kind: promptkit.RequirementKind("future_kind"), Supplied: true, Explanation: "true"},
|
|
},
|
|
Issues: []promptkit.RequirementIssue{{
|
|
Code: promptkit.RequirementIssueCode("123"),
|
|
InputName: "",
|
|
VariableName: "false",
|
|
Description: "",
|
|
}},
|
|
}
|
|
|
|
for _, complete := range []bool{true, false} {
|
|
t.Run(map[bool]string{true: "complete", false: "partial"}[complete], func(t *testing.T) {
|
|
inspection.Complete = complete
|
|
output, err := encodePromptYAMLBytes(requirementsReportFromInspection(inspection))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var report map[string]any
|
|
if err := yaml.Unmarshal(output, &report); err != nil {
|
|
t.Fatalf("decode requirements report %q: %v", output, err)
|
|
}
|
|
if report["complete"] != complete || report["prompt_id"] != "true" || report["version"] != "001" || report["profile_id"] != "null" {
|
|
t.Fatalf("selected context or completeness = %#v", report)
|
|
}
|
|
|
|
inputs, ok := report["inputs"].([]any)
|
|
if !ok || len(inputs) != 2 {
|
|
t.Fatalf("inputs = %#v", report["inputs"])
|
|
}
|
|
firstInput, ok := inputs[0].(map[string]any)
|
|
if !ok || len(firstInput) != 6 || firstInput["name"] != "true" || firstInput["kind"] != "123" || firstInput["declared"] != false || firstInput["content_type"] != nil || firstInput["description"] != "" || firstInput["explanation"] != "" {
|
|
t.Fatalf("first input = %#v", inputs[0])
|
|
}
|
|
secondInput, ok := inputs[1].(map[string]any)
|
|
if !ok || len(secondInput) != 6 || secondInput["name"] != "001" || secondInput["kind"] != "future_kind" || secondInput["declared"] != true || secondInput["content_type"] != "true" || secondInput["description"] != nil || secondInput["explanation"] != "false" {
|
|
t.Fatalf("second input = %#v", inputs[1])
|
|
}
|
|
|
|
variables, ok := report["variables"].([]any)
|
|
if !ok || len(variables) != 2 {
|
|
t.Fatalf("variables = %#v", report["variables"])
|
|
}
|
|
firstVariable, ok := variables[0].(map[string]any)
|
|
if !ok || len(firstVariable) != 4 || firstVariable["name"] != "false" || firstVariable["kind"] != "on" || firstVariable["supplied"] != false || firstVariable["explanation"] != "" {
|
|
t.Fatalf("first variable = %#v", variables[0])
|
|
}
|
|
secondVariable, ok := variables[1].(map[string]any)
|
|
if !ok || len(secondVariable) != 4 || secondVariable["name"] != "001" || secondVariable["kind"] != "future_kind" || secondVariable["supplied"] != true || secondVariable["explanation"] != "true" {
|
|
t.Fatalf("second variable = %#v", variables[1])
|
|
}
|
|
|
|
issues, ok := report["issues"].([]any)
|
|
if !ok || len(issues) != 1 {
|
|
t.Fatalf("issues = %#v", report["issues"])
|
|
}
|
|
issue, ok := issues[0].(map[string]any)
|
|
if !ok || len(issue) != 4 || issue["code"] != "123" || issue["input_name"] != "" || issue["variable_name"] != "false" || issue["description"] != "" {
|
|
t.Fatalf("issue = %#v", issues[0])
|
|
}
|
|
})
|
|
}
|
|
}
|