Migrate prompt fixtures to direct definitions
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Dependency upgrades, discovery, and smoke testing implementation plan
|
||||
|
||||
Status: Stages 1–4 complete; Stage 5 is next.
|
||||
Status: Stages 1–5 complete; Stage 6 is next.
|
||||
|
||||
## Authority and baseline
|
||||
|
||||
@@ -343,6 +343,16 @@ presentation, and error identity, not upstream detection algorithms.
|
||||
**Complete when:** the previous delivered feature set works through v0.16.0
|
||||
without compatibility scaffolding or implicit catalogs.
|
||||
|
||||
**Completed:** 2026-10-17. PromptKit fixtures now use direct prompt definitions,
|
||||
explicit profiles/backends, and distinct IDs for multiple prompts. ckprompt
|
||||
tests use the ID-only inspection and validation requests and verify that the
|
||||
removed version flags are unavailable. Semantic YAML tests cover absent,
|
||||
present-empty, and nonempty versions in prompt inspection, requirements, and
|
||||
catalog reports; PromptKit-specific requirement classification assertions were
|
||||
removed from the affected adapter/consumer tests. The ckprompt suite passed,
|
||||
and composed named inspection, requirements show, and request validation all
|
||||
ran successfully against the migrated fixture.
|
||||
|
||||
## Stage 6: Extend catalog contracts and engine adapters
|
||||
|
||||
**Primary files:** ckprompt public request declarations, `Suite`, `FromEngine`,
|
||||
|
||||
@@ -19,31 +19,32 @@ import (
|
||||
|
||||
const adapterPrompt = `prompts:
|
||||
briefing:
|
||||
"1":
|
||||
default_profile: default
|
||||
inputs:
|
||||
- name: topic
|
||||
required: true
|
||||
content_type: text/plain
|
||||
description: "Topic"
|
||||
- name: audience
|
||||
required: false
|
||||
content_type: text/plain
|
||||
description: ""
|
||||
messages:
|
||||
- role: user
|
||||
content: "Hello"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
version: "1"
|
||||
default_profile: default
|
||||
inputs:
|
||||
- name: topic
|
||||
required: true
|
||||
content_type: text/plain
|
||||
description: "Topic"
|
||||
- name: audience
|
||||
required: false
|
||||
content_type: text/plain
|
||||
description: ""
|
||||
messages:
|
||||
- role: user
|
||||
content: "Hello"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
empty:
|
||||
"1":
|
||||
messages:
|
||||
- role: user
|
||||
content: "Empty inputs"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
version: "1"
|
||||
default_profile: default
|
||||
messages:
|
||||
- role: user
|
||||
content: "Empty inputs"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
backends:
|
||||
local:
|
||||
endpoint: "https://example.invalid/v1"
|
||||
@@ -68,15 +69,15 @@ profiles:
|
||||
|
||||
const adapterRequirementsPrompt = `prompts:
|
||||
briefing:
|
||||
"1":
|
||||
default_profile: default
|
||||
session_id: '{{input "session-only"}} {{.session_variable}}'
|
||||
messages:
|
||||
- role: user
|
||||
content: '{{if eq .audience "internal"}}{{input "internal-notes"}}{{else}}{{input "public-notes"}}{{end}} {{.audience}}'
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
version: "1"
|
||||
default_profile: default
|
||||
session_id: '{{input "session-only"}} {{.session_variable}}'
|
||||
messages:
|
||||
- role: user
|
||||
content: '{{if eq .audience "internal"}}{{input "internal-notes"}}{{else}}{{input "public-notes"}}{{end}} {{.audience}}'
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
backends:
|
||||
local:
|
||||
endpoint: "https://example.invalid/v1"
|
||||
@@ -319,22 +320,18 @@ func TestFromEnginePassesRequestsAndRetainsNilLoaderIssue(t *testing.T) {
|
||||
return engine, nil
|
||||
})
|
||||
selection := ckconfig.Selection{Overlay: commandkit.Supplied[string]{Value: "overlay.yml", Set: true}}
|
||||
version := commandkit.Supplied[string]{Value: "1", Set: true}
|
||||
prompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing", Version: version}, wantInput, wantState)
|
||||
if err != nil || prompt.Value == nil || prompt.Value.PromptID != "briefing" || prompt.Value.PromptVersion != "1" || loaderCalls != 1 {
|
||||
prompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing"}, wantInput, wantState)
|
||||
if err != nil || prompt.Value == nil || prompt.Value.PromptID != "briefing" || prompt.Value.PromptVersion == nil || *prompt.Value.PromptVersion != "1" || loaderCalls != 1 {
|
||||
t.Fatalf("prompt inspection = (%#v, %v), calls=%d", prompt, err, loaderCalls)
|
||||
}
|
||||
defaultPrompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing"}, wantInput, wantState)
|
||||
if err != nil || defaultPrompt.Value == nil || defaultPrompt.Value.PromptVersion != "1" || loaderCalls != 2 {
|
||||
t.Fatalf("empty version default = (%#v, %v), calls=%d", defaultPrompt, err, loaderCalls)
|
||||
secondPrompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing"}, wantInput, wantState)
|
||||
if err != nil || secondPrompt.Value == nil || secondPrompt.Value.PromptID != "briefing" || loaderCalls != 2 {
|
||||
t.Fatalf("ID-only prompt inspection = (%#v, %v), calls=%d", secondPrompt, err, loaderCalls)
|
||||
}
|
||||
profile, err := suite.InspectProfile(wantContext, ProfileRequest{Selection: selection, ID: "default"}, wantInput, wantState)
|
||||
if err != nil || profile.Value == nil || profile.Value.ProfileID != "default" || loaderCalls != 3 {
|
||||
t.Fatalf("profile inspection = (%#v, %v), calls=%d", profile, err, loaderCalls)
|
||||
}
|
||||
if version.Set != true || version.Value != "1" {
|
||||
t.Fatalf("custom callback request lost version presence: %#v", version)
|
||||
}
|
||||
|
||||
empty := FromEngine[int, int](nil)
|
||||
empty.ListPrompts = nil
|
||||
@@ -363,11 +360,10 @@ func TestFromEngineInspectsRequirementsWithNativeRequest(t *testing.T) {
|
||||
request := RequirementsRequest{
|
||||
Selection: selection,
|
||||
Inspection: promptkit.RequirementInspectionRequest{
|
||||
PromptID: "briefing",
|
||||
PromptVersion: "1",
|
||||
ProfileID: "default",
|
||||
Vars: map[string]string{"audience": "internal"},
|
||||
SessionID: "session-42",
|
||||
PromptID: "briefing",
|
||||
ProfileID: "default",
|
||||
Vars: map[string]string{"audience": "internal"},
|
||||
SessionID: "session-42",
|
||||
},
|
||||
}
|
||||
want, err := engine.InspectRequirements(wantContext, request.Inspection)
|
||||
@@ -387,26 +383,9 @@ func TestFromEngineInspectsRequirementsWithNativeRequest(t *testing.T) {
|
||||
if err != nil || acquired.Value == nil || !reflect.DeepEqual(acquired.Value, want) || loaderCalls != 1 {
|
||||
t.Fatalf("requirements inspection = (%#v, %v), calls=%d; want native result %#v", acquired, err, loaderCalls, want)
|
||||
}
|
||||
if acquired.Value.PromptID != "briefing" || acquired.Value.PromptVersion != "1" || acquired.Value.ProfileID != "default" {
|
||||
if acquired.Value.PromptID != "briefing" || acquired.Value.PromptVersion == nil || *acquired.Value.PromptVersion != "1" || acquired.Value.ProfileID != "default" {
|
||||
t.Fatalf("requirements selection = %#v", acquired.Value)
|
||||
}
|
||||
inputs := make(map[string]promptkit.InputRequirement, len(acquired.Value.Inputs))
|
||||
for _, input := range acquired.Value.Inputs {
|
||||
inputs[input.Name] = input
|
||||
}
|
||||
if inputs["internal-notes"].Kind != promptkit.RequirementRequired || inputs["public-notes"].Kind != promptkit.RequirementOptional {
|
||||
t.Fatalf("caller variable did not reach native discovery: %#v", acquired.Value.Inputs)
|
||||
}
|
||||
if _, found := inputs["session-only"]; found {
|
||||
t.Fatalf("session template was not bypassed by the supplied session ID: %#v", acquired.Value.Inputs)
|
||||
}
|
||||
variables := make(map[string]promptkit.VariableRequirement, len(acquired.Value.Variables))
|
||||
for _, variable := range acquired.Value.Variables {
|
||||
variables[variable.Name] = variable
|
||||
}
|
||||
if !variables["audience"].Supplied {
|
||||
t.Fatalf("caller variable presence was not forwarded: %#v", acquired.Value.Variables)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromEngineRequirementsRetainLoadDiagnosticsAndSelectionErrors(t *testing.T) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -24,6 +25,8 @@ func newPromptTestApp(t *testing.T, commands ...commandkit.Command) *commandkit.
|
||||
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")
|
||||
@@ -121,8 +124,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T)
|
||||
providerCalls++
|
||||
received, receivedInput, receivedState = request, value, gotState
|
||||
return commandkit.Acquired[*promptkit.RequirementInspection]{Value: &promptkit.RequirementInspection{
|
||||
PromptID: request.Inspection.PromptID, PromptVersion: request.Inspection.PromptVersion,
|
||||
ProfileID: request.Inspection.ProfileID, Complete: true,
|
||||
PromptID: request.Inspection.PromptID, ProfileID: request.Inspection.ProfileID, Complete: true,
|
||||
}}, nil
|
||||
},
|
||||
}
|
||||
@@ -139,7 +141,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T)
|
||||
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", "--version", "--profile", "--session-id", "--var", "--format", "--campaign"} {
|
||||
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())
|
||||
}
|
||||
@@ -152,7 +154,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T)
|
||||
args := []string{
|
||||
"promptkit", "requirements", "show", "briefing", "--campaign", "spring",
|
||||
"--config", "before.yml", "--config=", "--overlay", "base.yml", "--overlay=",
|
||||
"--version", "1", "--version", "2", "--profile", "first", "--profile", "editorial",
|
||||
"--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",
|
||||
@@ -168,7 +170,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T)
|
||||
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.PromptVersion != "2" || received.Inspection.ProfileID != "editorial" || received.Inspection.SessionID != "session-42" || !reflect.DeepEqual(received.Inspection.Vars, wantVariables) {
|
||||
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 {
|
||||
@@ -184,7 +186,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T)
|
||||
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.PromptVersion != "" || received.Inspection.ProfileID != "" || received.Inspection.SessionID != "" {
|
||||
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
|
||||
@@ -299,7 +301,7 @@ func TestCatalogListUsesPreparedInputAndPreflightsFormat(t *testing.T) {
|
||||
ListPrompts: func(_ context.Context, selection ckconfig.Selection, value input, gotState state) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
|
||||
providerCalls++
|
||||
received, receivedSelection, receivedState = value, selection, gotState
|
||||
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: "1"}}}, nil
|
||||
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("1")}}}, nil
|
||||
},
|
||||
}
|
||||
app := newPromptTestApp(t, Command(suite))
|
||||
@@ -437,9 +439,10 @@ func TestCatalogNilFactoriesSupplyZeroValues(t *testing.T) {
|
||||
|
||||
func TestCatalogCommandsRenderNativeValuesAndEmptySequences(t *testing.T) {
|
||||
prompts := []promptkit.PromptIdentity{
|
||||
{ID: "later", Version: "1.10"},
|
||||
{ID: "001", Version: " 03 "},
|
||||
{ID: "a: true", Version: "yes"},
|
||||
{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{}]{
|
||||
@@ -465,9 +468,16 @@ func TestCatalogCommandsRenderNativeValuesAndEmptySequences(t *testing.T) {
|
||||
}
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,7 +575,7 @@ func TestCatalogFailuresRetainDiagnosticsAndDeliveryState(t *testing.T) {
|
||||
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
|
||||
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})
|
||||
@@ -584,7 +594,7 @@ func TestCatalogFailuresRetainDiagnosticsAndDeliveryState(t *testing.T) {
|
||||
} {
|
||||
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
|
||||
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) {
|
||||
@@ -663,22 +673,22 @@ func TestDefinitionInputAndStateLifetimesFollowChecks(t *testing.T) {
|
||||
},
|
||||
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" || !request.Version.Set || request.Version.Value != "2" || state == nil {
|
||||
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, PromptVersion: request.Version.Value}}, nil
|
||||
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", "--version", "2"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
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", "--version", "2"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
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)
|
||||
}
|
||||
@@ -694,7 +704,7 @@ func TestDefinitionReportsRetainNativeFieldsAndSafeCredentialReferences(t *testi
|
||||
})
|
||||
app := newPromptTestApp(t, Command(suite))
|
||||
var output bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "briefing", "--version", "1", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
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())
|
||||
}
|
||||
@@ -756,6 +766,75 @@ func TestDefinitionReportsRetainNativeFieldsAndSafeCredentialReferences(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -780,6 +859,18 @@ func TestNativeLookupErrorsAndBlankIDsReachCallbacks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -838,9 +929,9 @@ func TestProfileTextUsesCredentialNamesAndExplicitEmptyExtraParams(t *testing.T)
|
||||
|
||||
func TestPromptCatalogYAMLPreservesSchemaStringsAndOrder(t *testing.T) {
|
||||
want := []promptkit.PromptIdentity{
|
||||
{ID: " briefing ", Version: " 03 "},
|
||||
{ID: "001", Version: "1.10"},
|
||||
{ID: " briefing ", Version: " 03 "},
|
||||
{ID: " briefing ", Version: testStringPointer(" 03 ")},
|
||||
{ID: "001", Version: testStringPointer("1.10")},
|
||||
{ID: " briefing ", Version: testStringPointer(" 03 ")},
|
||||
}
|
||||
output, err := encodePromptCatalog(want)
|
||||
if err != nil {
|
||||
@@ -859,8 +950,8 @@ func TestPromptCatalogYAMLPreservesSchemaStringsAndOrder(t *testing.T) {
|
||||
}
|
||||
id, idOK := entry["id"].(string)
|
||||
version, versionOK := entry["version"].(string)
|
||||
if !idOK || !versionOK || id != want[index].ID || version != want[index].Version {
|
||||
t.Errorf("entry %d = %#v, want id/version %q/%q as strings", index, entry, want[index].ID, want[index].Version)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -917,11 +1008,14 @@ func TestRequirementsReportRendersEmptyInventoriesAndFalseValues(t *testing.T) {
|
||||
if err := yaml.Unmarshal(output, &report); err != nil {
|
||||
t.Fatalf("decode requirements report %q: %v", output, err)
|
||||
}
|
||||
for _, key := range []string{"prompt_id", "prompt_version", "profile_id", "complete", "inputs", "variables", "issues"} {
|
||||
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"])
|
||||
}
|
||||
@@ -931,7 +1025,7 @@ func TestRequirementsReportRendersEmptyInventoriesAndFalseValues(t *testing.T) {
|
||||
t.Errorf("%s = %#v, want an empty sequence", key, report[key])
|
||||
}
|
||||
}
|
||||
for _, key := range []string{"prompt_id", "prompt_version", "profile_id"} {
|
||||
for _, key := range []string{"prompt_id", "profile_id"} {
|
||||
if report[key] != "" {
|
||||
t.Errorf("%s = %#v, want an explicitly present empty string", key, report[key])
|
||||
}
|
||||
@@ -943,7 +1037,7 @@ func TestRequirementsReportPreservesNativeFieldsAndMetadata(t *testing.T) {
|
||||
contentType := "true"
|
||||
inspection := &promptkit.RequirementInspection{
|
||||
PromptID: "true",
|
||||
PromptVersion: "001",
|
||||
PromptVersion: testStringPointer("001"),
|
||||
ProfileID: "null",
|
||||
Complete: true,
|
||||
Inputs: []promptkit.InputRequirement{
|
||||
@@ -973,7 +1067,7 @@ func TestRequirementsReportPreservesNativeFieldsAndMetadata(t *testing.T) {
|
||||
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["prompt_version"] != "001" || report["profile_id"] != "null" {
|
||||
if report["complete"] != complete || report["prompt_id"] != "true" || report["version"] != "001" || report["profile_id"] != "null" {
|
||||
t.Fatalf("selected context or completeness = %#v", report)
|
||||
}
|
||||
|
||||
|
||||
@@ -225,13 +225,14 @@ func comparisonEngine(t *testing.T, greeting string) *promptkit.Engine {
|
||||
t.Helper()
|
||||
configuration := `prompts:
|
||||
briefing:
|
||||
"1":
|
||||
messages:
|
||||
- role: user
|
||||
content: "Hello"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
version: "1"
|
||||
default_profile: default
|
||||
messages:
|
||||
- role: user
|
||||
content: "Hello"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
backends:
|
||||
local:
|
||||
endpoint: "https://example.invalid/v1"
|
||||
|
||||
@@ -70,13 +70,14 @@ func TestPublicConfigurationAndDefinitionFamiliesRemainIndependent(t *testing.T)
|
||||
inputs, err := promptkit.FilesystemInputs("composition fixture", fstest.MapFS{
|
||||
"config/prompts.yml": &fstest.MapFile{Data: []byte(`prompts:
|
||||
briefing:
|
||||
"1":
|
||||
messages:
|
||||
- role: user
|
||||
content: "Hello"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
version: "1"
|
||||
default_profile: default
|
||||
messages:
|
||||
- role: user
|
||||
content: "Hello"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
backends:
|
||||
local:
|
||||
endpoint: "https://example.invalid/v1"
|
||||
|
||||
@@ -118,12 +118,11 @@ func loadPromptEngine(ctx context.Context, selection ckconfig.Selection, input *
|
||||
},
|
||||
}},
|
||||
"prompts": map[string]any{chosen.PromptID: map[string]any{
|
||||
chosen.PromptVersion: map[string]any{
|
||||
"default_profile": chosen.ProfileID,
|
||||
"inputs": []any{map[string]any{"name": "topic", "required": true, "content_type": "text/plain", "description": "Topic to brief"}},
|
||||
"messages": []any{map[string]any{"role": "user", "content": "Write a concise briefing about {{.topic}}. {{if .mode}}{{input \"notes\"}}{{end}}"}},
|
||||
"output": map[string]any{"format": "text", "validation_mode": "none"},
|
||||
},
|
||||
"version": chosen.PromptVersion,
|
||||
"default_profile": chosen.ProfileID,
|
||||
"inputs": []any{map[string]any{"name": "topic", "required": true, "content_type": "text/plain", "description": "Topic to brief"}},
|
||||
"messages": []any{map[string]any{"role": "user", "content": "Write a concise briefing about {{.topic}}. {{if .mode}}{{input \"notes\"}}{{end}}"}},
|
||||
"output": map[string]any{"format": "text", "validation_mode": "none"},
|
||||
}},
|
||||
})
|
||||
return promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{Source: engineConfig}}})
|
||||
|
||||
@@ -127,21 +127,11 @@ func TestComposedRequirementsDiscoveryUsesSelectedCampaign(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
args []string
|
||||
wantMode bool
|
||||
wantModeIssue bool
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
name: "conditional dependency is unresolved without mode",
|
||||
args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring"},
|
||||
wantModeIssue: true,
|
||||
},
|
||||
{
|
||||
name: "supplied mode discovers conditional notes",
|
||||
args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring", "--var", "mode=full", "--format", "yaml"},
|
||||
wantMode: true,
|
||||
},
|
||||
{name: "without variables", args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring"}},
|
||||
{name: "with caller variable", args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring", "--var", "mode=full", "--format", "yaml"}},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
@@ -150,65 +140,17 @@ func TestComposedRequirementsDiscoveryUsesSelectedCampaign(t *testing.T) {
|
||||
t.Fatalf("requirements outcome = %#v, report %q", outcome, output.String())
|
||||
}
|
||||
|
||||
var report struct {
|
||||
PromptID string `yaml:"prompt_id"`
|
||||
PromptVersion string `yaml:"prompt_version"`
|
||||
ProfileID string `yaml:"profile_id"`
|
||||
Complete bool `yaml:"complete"`
|
||||
Inputs []struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
Declared bool `yaml:"declared"`
|
||||
} `yaml:"inputs"`
|
||||
Variables []struct {
|
||||
Name string `yaml:"name"`
|
||||
Supplied bool `yaml:"supplied"`
|
||||
} `yaml:"variables"`
|
||||
Issues []struct {
|
||||
Code string `yaml:"code"`
|
||||
InputName string `yaml:"input_name"`
|
||||
VariableName string `yaml:"variable_name"`
|
||||
} `yaml:"issues"`
|
||||
}
|
||||
var report map[string]any
|
||||
if err := yaml.Unmarshal(output.Bytes(), &report); err != nil {
|
||||
t.Fatalf("decode requirements report %q: %v", output.String(), err)
|
||||
}
|
||||
if report.PromptID != "briefing" || report.PromptVersion != "1" || report.ProfileID != "spring" {
|
||||
t.Fatalf("requirements identity = %#v", report)
|
||||
}
|
||||
|
||||
foundTopic, foundNotes, foundMode := false, false, false
|
||||
for _, input := range report.Inputs {
|
||||
switch input.Name {
|
||||
case "topic":
|
||||
foundTopic = input.Declared && input.Kind == "required"
|
||||
case "notes":
|
||||
foundNotes = true
|
||||
wantKind := "conditional"
|
||||
if test.wantMode {
|
||||
wantKind = "required"
|
||||
}
|
||||
if input.Declared || input.Kind != wantKind {
|
||||
t.Errorf("notes input classification = %#v; want undeclared %s", input, wantKind)
|
||||
}
|
||||
for _, key := range []string{"prompt_id", "version", "profile_id", "complete", "inputs", "variables", "issues"} {
|
||||
if _, found := report[key]; !found {
|
||||
t.Errorf("requirements report is missing %q: %#v", key, report)
|
||||
}
|
||||
}
|
||||
for _, variable := range report.Variables {
|
||||
if variable.Name == "mode" {
|
||||
foundMode = variable.Supplied == test.wantMode
|
||||
}
|
||||
}
|
||||
if !foundTopic || !foundNotes || !foundMode {
|
||||
t.Fatalf("requirements inventory topic=%t notes=%t mode=%t; report %#v", foundTopic, foundNotes, foundMode, report)
|
||||
}
|
||||
if test.wantModeIssue && (len(report.Issues) != 1 || report.Issues[0].Code != "missing_variable" || report.Issues[0].VariableName != "mode") {
|
||||
t.Fatalf("missing mode issue = %#v", report.Issues)
|
||||
}
|
||||
if !test.wantModeIssue && len(report.Issues) != 0 {
|
||||
t.Fatalf("requirements issues with mode supplied=%t: %#v", test.wantMode, report.Issues)
|
||||
}
|
||||
if report.Complete != test.wantMode {
|
||||
t.Fatalf("requirements completeness = %t with mode supplied=%t; report %#v", report.Complete, test.wantMode, report)
|
||||
if report["prompt_id"] != "briefing" || report["version"] != "1" || report["profile_id"] != "spring" {
|
||||
t.Fatalf("requirements report metadata = %#v", report)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -268,19 +210,18 @@ func TestBuildValidationRequestDefaultsOnlyAbsentVariables(t *testing.T) {
|
||||
inputs := map[string]promptkit.ArtifactRef{"topic": promptkit.File("fixtures/topic.txt")}
|
||||
explicit := map[string]string{"topic": "", "mode": "", "audience": ""}
|
||||
seed := ckprompt.ValidationRequest{Request: promptkit.RunRequest{
|
||||
PromptID: "briefing",
|
||||
PromptVersion: "2",
|
||||
ProfileID: "custom-profile",
|
||||
SessionID: "session-9",
|
||||
Vars: explicit,
|
||||
Inputs: inputs,
|
||||
PromptID: "briefing",
|
||||
ProfileID: "custom-profile",
|
||||
SessionID: "session-9",
|
||||
Vars: explicit,
|
||||
Inputs: inputs,
|
||||
}}
|
||||
|
||||
got, err := buildValidationRequest(context.Background(), seed, input, cache)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.PromptID != seed.Request.PromptID || got.PromptVersion != seed.Request.PromptVersion || got.ProfileID != seed.Request.ProfileID || got.SessionID != seed.Request.SessionID {
|
||||
if got.PromptID != seed.Request.PromptID || got.ProfileID != seed.Request.ProfileID || got.SessionID != seed.Request.SessionID {
|
||||
t.Fatalf("request identity/context changed: %#v", got)
|
||||
}
|
||||
if got.Vars["topic"] != "" || got.Vars["mode"] != "" || got.Vars["audience"] != "" {
|
||||
|
||||
@@ -17,41 +17,40 @@ import (
|
||||
|
||||
const requirementsCommandPrompt = `prompts:
|
||||
briefing:
|
||||
"2":
|
||||
default_profile: editorial
|
||||
session_id: '{{input "session-only"}} {{.session_variable}}'
|
||||
messages:
|
||||
- role: user
|
||||
content: '{{if .mode}}{{input "notes"}}{{end}}'
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
version: "2"
|
||||
default_profile: editorial
|
||||
session_id: '{{input "session-only"}} {{.session_variable}}'
|
||||
messages:
|
||||
- role: user
|
||||
content: '{{if .mode}}{{input "notes"}}{{end}}'
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
required-variable:
|
||||
"1":
|
||||
default_profile: editorial
|
||||
messages:
|
||||
- role: user
|
||||
content: '{{.required}}'
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
ambiguous:
|
||||
"1":
|
||||
default_profile: editorial
|
||||
messages:
|
||||
- role: user
|
||||
content: "First version"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
"2":
|
||||
default_profile: editorial
|
||||
messages:
|
||||
- role: user
|
||||
content: "Second version"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
version: "1"
|
||||
default_profile: editorial
|
||||
messages:
|
||||
- role: user
|
||||
content: '{{.required}}'
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
ambiguous-first:
|
||||
default_profile: editorial
|
||||
messages:
|
||||
- role: user
|
||||
content: "First definition"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
ambiguous-second:
|
||||
default_profile: editorial
|
||||
messages:
|
||||
- role: user
|
||||
content: "Second definition"
|
||||
output:
|
||||
format: text
|
||||
validation_mode: none
|
||||
backends:
|
||||
local:
|
||||
endpoint: "https://example.invalid/v1"
|
||||
@@ -101,7 +100,7 @@ func executeRequirementsAgainstNative(t *testing.T, engine *promptkit.Engine, re
|
||||
return native, outcome, actual, diagnostic.String()
|
||||
}
|
||||
|
||||
func TestRequirementsCommandMatchesNativePromptKitDiscovery(t *testing.T) {
|
||||
func TestRequirementsCommandForwardsNativeRequestAndProjectsInspection(t *testing.T) {
|
||||
engine := requirementsCommandEngine(t)
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
@@ -109,82 +108,50 @@ func TestRequirementsCommandMatchesNativePromptKitDiscovery(t *testing.T) {
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
name: "partial conditional discovery",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", PromptVersion: "2", ProfileID: "editorial"},
|
||||
args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "editorial"},
|
||||
},
|
||||
{
|
||||
name: "supplied branch variable",
|
||||
request: promptkit.RequirementInspectionRequest{
|
||||
PromptID: "briefing", PromptVersion: "2", ProfileID: "editorial", Vars: map[string]string{"mode": "full"},
|
||||
},
|
||||
args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "editorial", "--var", "mode=full", "--format", "yaml"},
|
||||
},
|
||||
{
|
||||
name: "complete with an unsupplied required variable",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "required-variable", PromptVersion: "1", ProfileID: "editorial"},
|
||||
args: []string{"promptkit", "requirements", "show", "required-variable", "--version", "1", "--profile", "editorial"},
|
||||
},
|
||||
{
|
||||
name: "native version and default profile selection",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "briefing"},
|
||||
args: []string{"promptkit", "requirements", "show", "briefing"},
|
||||
},
|
||||
{
|
||||
name: "session template discovery",
|
||||
name: "conditional discovery",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial"},
|
||||
args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial"},
|
||||
},
|
||||
{
|
||||
name: "session ID bypasses the session template",
|
||||
name: "supplied branch variable",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial", Vars: map[string]string{"mode": "full"}},
|
||||
args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial", "--var", "mode=full", "--format", "yaml"},
|
||||
},
|
||||
{
|
||||
name: "prompt with a required variable",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "required-variable", ProfileID: "editorial"},
|
||||
args: []string{"promptkit", "requirements", "show", "required-variable", "--profile", "editorial"},
|
||||
},
|
||||
{
|
||||
name: "default profile selection",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "briefing"},
|
||||
args: []string{"promptkit", "requirements", "show", "briefing"},
|
||||
},
|
||||
{
|
||||
name: "first distinct prompt ID",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "ambiguous-first", ProfileID: "editorial"},
|
||||
args: []string{"promptkit", "requirements", "show", "ambiguous-first", "--profile", "editorial"},
|
||||
},
|
||||
{
|
||||
name: "second distinct prompt ID",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "ambiguous-second", ProfileID: "editorial"},
|
||||
args: []string{"promptkit", "requirements", "show", "ambiguous-second", "--profile", "editorial"},
|
||||
},
|
||||
{
|
||||
name: "session template context",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial"},
|
||||
args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial"},
|
||||
},
|
||||
{
|
||||
name: "session ID context",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial", SessionID: "session-42"},
|
||||
args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial", "--session-id", "session-42"},
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
native, outcome, _, _ := executeRequirementsAgainstNative(t, engine, test.request, test.args)
|
||||
_, outcome, _, _ := executeRequirementsAgainstNative(t, engine, test.request, test.args)
|
||||
if outcome.Status != 0 {
|
||||
t.Fatalf("partial or complete inspection returned status %d", outcome.Status)
|
||||
}
|
||||
switch test.name {
|
||||
case "partial conditional discovery":
|
||||
if native.Complete || len(native.Issues) == 0 || len(native.Inputs) == 0 {
|
||||
t.Fatalf("partial discovery lost useful evidence: %#v", native)
|
||||
}
|
||||
case "supplied branch variable":
|
||||
if !native.Complete || len(native.Issues) != 0 {
|
||||
t.Fatalf("resolved discovery = %#v", native)
|
||||
}
|
||||
case "complete with an unsupplied required variable":
|
||||
if !native.Complete || len(native.Variables) != 1 || native.Variables[0].Supplied {
|
||||
t.Fatalf("complete discovery with unsupplied variable = %#v", native)
|
||||
}
|
||||
case "native version and default profile selection":
|
||||
if native.PromptVersion != "2" || native.ProfileID != "editorial" {
|
||||
t.Fatalf("native default selection = %#v", native)
|
||||
}
|
||||
case "session template discovery":
|
||||
foundInput, foundVariable := false, false
|
||||
for _, input := range native.Inputs {
|
||||
foundInput = foundInput || input.Name == "session-only"
|
||||
}
|
||||
for _, variable := range native.Variables {
|
||||
foundVariable = foundVariable || variable.Name == "session_variable"
|
||||
}
|
||||
if !foundInput || !foundVariable {
|
||||
t.Fatalf("session template dependencies missing: %#v", native)
|
||||
}
|
||||
case "session ID bypasses the session template":
|
||||
for _, input := range native.Inputs {
|
||||
if input.Name == "session-only" {
|
||||
t.Fatalf("session template input remained with session ID: %#v", native.Inputs)
|
||||
}
|
||||
}
|
||||
for _, variable := range native.Variables {
|
||||
if variable.Name == "session_variable" {
|
||||
t.Fatalf("session template variable remained with session ID: %#v", native.Variables)
|
||||
}
|
||||
}
|
||||
t.Fatalf("inspection returned status %d", outcome.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -193,40 +160,38 @@ func TestRequirementsCommandMatchesNativePromptKitDiscovery(t *testing.T) {
|
||||
func TestRequirementsCommandPreservesNativeSelectionErrors(t *testing.T) {
|
||||
engine := requirementsCommandEngine(t)
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
request promptkit.RequirementInspectionRequest
|
||||
args []string
|
||||
cause error
|
||||
name string
|
||||
args []string
|
||||
cause error
|
||||
}{
|
||||
{
|
||||
name: "missing prompt",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "missing", PromptVersion: "2", ProfileID: "editorial"},
|
||||
args: []string{"promptkit", "requirements", "show", "missing", "--version", "2", "--profile", "editorial"},
|
||||
cause: promptkit.ErrPromptNotFound,
|
||||
name: "missing prompt",
|
||||
args: []string{"promptkit", "requirements", "show", "missing", "--profile", "editorial"},
|
||||
cause: promptkit.ErrPromptNotFound,
|
||||
},
|
||||
{
|
||||
name: "missing profile",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "briefing", PromptVersion: "2", ProfileID: "missing"},
|
||||
args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "missing"},
|
||||
cause: promptkit.ErrProfileNotFound,
|
||||
name: "missing profile",
|
||||
args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "missing"},
|
||||
cause: promptkit.ErrProfileNotFound,
|
||||
},
|
||||
{
|
||||
name: "ambiguous prompt version",
|
||||
request: promptkit.RequirementInspectionRequest{PromptID: "ambiguous", ProfileID: "editorial"},
|
||||
args: []string{"promptkit", "requirements", "show", "ambiguous", "--profile", "editorial"},
|
||||
cause: promptkit.ErrPromptLoad,
|
||||
name: "removed prompt version flag",
|
||||
args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "editorial"},
|
||||
cause: nil,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, nativeErr := engine.InspectRequirements(context.Background(), test.request)
|
||||
if !errors.Is(nativeErr, test.cause) {
|
||||
t.Fatalf("native selection error = %v, want %v", nativeErr, test.cause)
|
||||
}
|
||||
app := newPromptTestApp(t, Command(FromEngine[int, int](func(context.Context, ckconfig.Selection, int, int) (*promptkit.Engine, error) {
|
||||
return engine, nil
|
||||
})))
|
||||
var output bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), test.args, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
if test.cause == nil {
|
||||
if outcome.Kind != commandkit.OutcomeUsage || output.Len() != 0 {
|
||||
t.Fatalf("removed flag outcome = %#v output=%q", outcome, output.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
if outcome.Kind != commandkit.OutcomeOperational || outcome.Status != 1 || !errors.Is(outcome.Cause, test.cause) || output.Len() != 0 {
|
||||
t.Fatalf("native selection outcome = %#v output=%q", outcome, output.String())
|
||||
}
|
||||
|
||||
@@ -64,13 +64,13 @@ func TestValidationCommandComposesWithRequirementsShow(t *testing.T) {
|
||||
outcome = app.Execute(context.Background(), []string{
|
||||
"promptkit", "requirements", "validate", "briefing",
|
||||
"--config", "config.yml", "--overlay", "overlay.yml", "--campaign", "spring",
|
||||
"--version", "v2", "--profile", "editorial", "--session-id", "session-1",
|
||||
"--profile", "editorial", "--session-id", "session-1",
|
||||
"--var", "topic=roadmap", "--input", "notes=fixtures/notes=final.txt", "--format", "yaml",
|
||||
}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeSuccess || outcome.Status != 0 || validateCalls != 1 {
|
||||
t.Fatalf("validation outcome = (%#v, %q), calls=%d", outcome, output.String(), validateCalls)
|
||||
}
|
||||
if received.Selection != wantSelection || received.Request.PromptID != "briefing" || received.Request.PromptVersion != "v2" || received.Request.ProfileID != "editorial" || received.Request.SessionID != "session-1" {
|
||||
if received.Selection != wantSelection || received.Request.PromptID != "briefing" || received.Request.ProfileID != "editorial" || received.Request.SessionID != "session-1" {
|
||||
t.Errorf("validation request = %#v", received)
|
||||
}
|
||||
if received.Request.Vars["topic"] != "roadmap" || len(received.Request.Vars) != 1 {
|
||||
@@ -197,7 +197,7 @@ func TestValidationCommandPreservesPresenceAndRepeatedAssignmentsPerInvocation(t
|
||||
|
||||
firstArgs := []string{
|
||||
"promptkit", "requirements", "validate", "briefing", "--config", "", "--overlay", "",
|
||||
"--version", "", "--profile", "", "--session-id", "", "--campaign", "spring",
|
||||
"--profile", "", "--session-id", "", "--campaign", "spring",
|
||||
"--var", "=", "--var", "phrase= two words, three ", "--var", "duplicate=first", "--var", "duplicate=last=value",
|
||||
"--input", "=", "--input", "file= path, with commas=and=equals ", "--input", "duplicate=first.txt", "--input", "duplicate=last=path.txt",
|
||||
}
|
||||
@@ -209,7 +209,7 @@ func TestValidationCommandPreservesPresenceAndRepeatedAssignmentsPerInvocation(t
|
||||
if first.Selection.Config.Value != "" || !first.Selection.Config.Set || first.Selection.Overlay.Value != "" || !first.Selection.Overlay.Set {
|
||||
t.Errorf("explicit empty selection = %#v", first.Selection)
|
||||
}
|
||||
if first.Request.PromptVersion != "" || first.Request.ProfileID != "" || first.Request.SessionID != "" {
|
||||
if first.Request.PromptID != "briefing" || first.Request.ProfileID != "" || first.Request.SessionID != "" {
|
||||
t.Errorf("empty scalar values = %#v", first.Request)
|
||||
}
|
||||
wantVars := map[string]string{"": "", "phrase": " two words, three ", "duplicate": "last=value"}
|
||||
@@ -233,7 +233,7 @@ func TestValidationCommandPreservesPresenceAndRepeatedAssignmentsPerInvocation(t
|
||||
t.Fatalf("second invocation = %#v, requests=%d", outcome, len(requests))
|
||||
}
|
||||
second := requests[1]
|
||||
if second.Selection.Config.Set || second.Selection.Overlay.Set || second.Request.PromptVersion != "" || second.Request.ProfileID != "" || second.Request.SessionID != "" || len(second.Request.Vars) != 0 || len(second.Request.Inputs) != 0 {
|
||||
if second.Selection.Config.Set || second.Selection.Overlay.Set || second.Request.PromptID != "briefing" || second.Request.ProfileID != "" || second.Request.SessionID != "" || len(second.Request.Vars) != 0 || len(second.Request.Inputs) != 0 {
|
||||
t.Errorf("first invocation values leaked into second request: %#v", second)
|
||||
}
|
||||
if selectedInputs[1].campaign.Set || selectedInputs[1].campaign.Value != "" {
|
||||
@@ -256,6 +256,7 @@ func TestValidationCommandUsageAndInputFailuresStopProviderWork(t *testing.T) {
|
||||
{name: "extra operand", args: []string{"promptkit", "requirements", "validate", "briefing", "extra", "--campaign", "spring"}},
|
||||
{name: "unsupported format", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--format", "text"}},
|
||||
{name: "unknown flag", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--unknown", "value"}},
|
||||
{name: "removed prompt version flag", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--version", "2"}},
|
||||
{name: "malformed variable before valid assignment", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--var", "broken", "--var", "mode=full"}},
|
||||
{name: "malformed input before valid assignment", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--input", "broken", "--input", "notes=file.txt"}},
|
||||
{name: "missing required selector", args: []string{"promptkit", "requirements", "validate", "briefing"}},
|
||||
|
||||
Reference in New Issue
Block a user