2026-11-07 04:19:58 +00:00
|
|
|
package ckprompt
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"io"
|
|
|
|
|
"reflect"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"go.maximumdirect.net/ckconfig"
|
|
|
|
|
commandkit "go.maximumdirect.net/commandkit"
|
|
|
|
|
promptkit "go.maximumdirect.net/promptkit"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestPreparationCommandForwardsOrderedInputsAndNativeReport(t *testing.T) {
|
|
|
|
|
type key struct{}
|
|
|
|
|
ctx := context.WithValue(context.Background(), key{}, "context")
|
|
|
|
|
var received PreparationRequest
|
|
|
|
|
calls := 0
|
|
|
|
|
native := &promptkit.PreparedRun{PromptID: "native", Messages: []promptkit.RenderedMessage{{Content: "rendered"}}}
|
|
|
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{PrepareRequest: func(got context.Context, req PreparationRequest, _, _ struct{}) (commandkit.Acquired[*promptkit.PreparedRun], error) {
|
|
|
|
|
if got.Value(key{}) != "context" {
|
|
|
|
|
t.Fatal("invocation context lost")
|
|
|
|
|
}
|
|
|
|
|
calls++
|
|
|
|
|
received = req
|
|
|
|
|
return commandkit.Acquired[*promptkit.PreparedRun]{Value: native}, nil
|
|
|
|
|
}}))
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
result := app.Execute(ctx, []string{"promptkit", "show", "prepared", "briefing", "--config=chosen", "--llm-profile=editorial", "--llm-input-file=x=first.txt", "--llm-input-text=x=a=b", "--llm-input-text=y=", "--llm-input-file=z=path", "--llm-input-text=z=last", "--llm-input-file=z=final", "--format=json"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
|
|
|
if result.Kind != commandkit.OutcomeSuccess || calls != 1 || received.Request.PromptID != "briefing" || received.Request.ProfileID != "editorial" || received.Selection.Config.Value != "chosen" || !received.Selection.Config.Set {
|
|
|
|
|
t.Fatalf("outcome/request = %#v %#v", result, received)
|
|
|
|
|
}
|
|
|
|
|
want := map[string]promptkit.ArtifactRef{"x": promptkit.Inline("a=b"), "y": promptkit.Inline(""), "z": promptkit.File("final")}
|
|
|
|
|
if !reflect.DeepEqual(received.Request.Inputs, want) {
|
|
|
|
|
t.Fatalf("inputs = %#v", received.Request.Inputs)
|
|
|
|
|
}
|
|
|
|
|
var report, expected any
|
|
|
|
|
encoded, err := json.Marshal(native)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err := json.Unmarshal(encoded, &expected); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err := json.Unmarshal(output.Bytes(), &report); err != nil || !reflect.DeepEqual(report, expected) {
|
|
|
|
|
t.Fatalf("report = %s (%v)", output.String(), err)
|
|
|
|
|
}
|
|
|
|
|
output.Reset()
|
|
|
|
|
result = app.Execute(ctx, []string{"promptkit", "show", "prepared", "fresh", "--format=json"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
|
|
|
|
if result.Kind != commandkit.OutcomeSuccess || received.Request.Inputs != nil {
|
|
|
|
|
t.Fatalf("fresh request = %#v %#v", result, received)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPreparationErrorsAndHelpDoNotInvokeUnrelatedCallbacks(t *testing.T) {
|
|
|
|
|
cause := errors.New("preparation failure")
|
|
|
|
|
calls := 0
|
|
|
|
|
suite := Suite[struct{}, struct{}]{PrepareRequest: func(context.Context, PreparationRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PreparedRun], error) {
|
|
|
|
|
calls++
|
|
|
|
|
return commandkit.Acquired[*promptkit.PreparedRun]{}, cause
|
|
|
|
|
}}
|
|
|
|
|
app := newPromptTestApp(t, Command(suite))
|
|
|
|
|
var help bytes.Buffer
|
|
|
|
|
result := app.Execute(context.Background(), []string{"promptkit", "show", "prepared", "--help"}, commandkit.Streams{Out: &help, Err: io.Discard})
|
|
|
|
|
if result.Kind != commandkit.OutcomeSuccess || calls != 0 || !bytes.Contains(help.Bytes(), []byte("resource I/O")) || !bytes.Contains(help.Bytes(), []byte("rendered application content")) {
|
|
|
|
|
t.Fatalf("help = %#v %q", result, help.String())
|
|
|
|
|
}
|
|
|
|
|
result = app.Execute(context.Background(), []string{"promptkit", "show", "prepared", "missing"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
|
|
|
|
if result.Kind != commandkit.OutcomeOperational || !errors.Is(result.Cause, cause) || calls != 1 {
|
|
|
|
|
t.Fatalf("failure = %#v", result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNativePreparationBuilderAndErrorIdentity(t *testing.T) {
|
|
|
|
|
client := &validationFailOnGenerate{}
|
|
|
|
|
engine := validationTestEngine(t, adapterPrompt, promptkit.WithLLMClient(client))
|
|
|
|
|
built := promptkit.RunRequest{PromptID: "briefing", ProfileID: "default", Inputs: map[string]promptkit.ArtifactRef{"topic": promptkit.Inline("builder value")}}
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
want, err := engine.Prepare(ctx, built)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
calls := 0
|
|
|
|
|
suite := WithRequestPreparation(Suite[struct{}, struct{}]{}, func(context.Context, ckconfig.Selection, struct{}, struct{}) (*promptkit.Engine, error) {
|
|
|
|
|
return engine, nil
|
|
|
|
|
}, func(_ context.Context, seed PreparationRequest, _, _ struct{}) (promptkit.RunRequest, error) {
|
|
|
|
|
calls++
|
|
|
|
|
if seed.Request.PromptID != "seed" {
|
|
|
|
|
t.Fatalf("seed = %#v", seed)
|
|
|
|
|
}
|
|
|
|
|
return built, nil
|
|
|
|
|
})
|
|
|
|
|
got, err := suite.PrepareRequest(ctx, PreparationRequest{Request: promptkit.RunRequest{PromptID: "seed"}}, struct{}{}, struct{}{})
|
|
|
|
|
if err != nil || calls != 1 || got.Value == nil || got.Value.PromptID != want.PromptID || !reflect.DeepEqual(got.Value.Messages, want.Messages) || client.calls != 0 {
|
|
|
|
|
t.Fatalf("prepared = %#v, err = %v, calls = %d, model calls = %d", got, err, calls, client.calls)
|
|
|
|
|
}
|
|
|
|
|
withoutBuilder := WithRequestPreparation(Suite[struct{}, struct{}]{}, func(context.Context, ckconfig.Selection, struct{}, struct{}) (*promptkit.Engine, error) {
|
|
|
|
|
return engine, nil
|
|
|
|
|
}, nil)
|
|
|
|
|
_, err = withoutBuilder.PrepareRequest(ctx, PreparationRequest{Request: promptkit.RunRequest{PromptID: "missing"}}, struct{}{}, struct{}{})
|
|
|
|
|
if !errors.Is(err, promptkit.ErrPromptNotFound) {
|
|
|
|
|
t.Fatalf("native error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-11-07 15:41:18 +00:00
|
|
|
|
|
|
|
|
func TestPreparationCommandRejectsMissingResultAndRetainsEvidence(t *testing.T) {
|
|
|
|
|
cause := errors.New("provider failure")
|
|
|
|
|
for _, tc := range []struct {
|
|
|
|
|
name string
|
|
|
|
|
providerErr error
|
|
|
|
|
cancel bool
|
|
|
|
|
}{
|
|
|
|
|
{name: "missing result"}, {name: "provider error", providerErr: cause}, {name: "cancellation", cancel: true},
|
|
|
|
|
} {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
defer cancel()
|
|
|
|
|
app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{PrepareRequest: func(context.Context, PreparationRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PreparedRun], error) {
|
|
|
|
|
if tc.cancel {
|
|
|
|
|
cancel()
|
|
|
|
|
}
|
|
|
|
|
acquired := commandkit.Acquired[*promptkit.PreparedRun]{
|
|
|
|
|
Diagnostics: []commandkit.Diagnostic{{Severity: commandkit.SeverityWarning, Write: func(w io.Writer) error { _, err := io.WriteString(w, "provider diagnostic\n"); return err }}},
|
|
|
|
|
Explanation: commandkit.Explanation{Message: "provider explanation"},
|
|
|
|
|
}
|
|
|
|
|
if tc.providerErr != nil {
|
|
|
|
|
acquired.Value = &promptkit.PreparedRun{PromptID: "briefing"}
|
|
|
|
|
}
|
|
|
|
|
return acquired, tc.providerErr
|
|
|
|
|
}}))
|
|
|
|
|
var output, diagnostics bytes.Buffer
|
|
|
|
|
result := app.Execute(ctx, []string{"promptkit", "show", "prepared", "briefing", "--format=json", "--verbose"}, commandkit.Streams{Out: &output, Err: &diagnostics})
|
|
|
|
|
if result.Kind != commandkit.OutcomeOperational || output.Len() != 0 || !bytes.Contains(diagnostics.Bytes(), []byte("provider diagnostic")) || !bytes.Contains(diagnostics.Bytes(), []byte("provider explanation")) {
|
|
|
|
|
t.Fatalf("result=%#v output=%q diagnostics=%q", result, output.String(), diagnostics.String())
|
|
|
|
|
}
|
|
|
|
|
if tc.providerErr != nil && !errors.Is(result.Cause, tc.providerErr) {
|
|
|
|
|
t.Fatalf("provider cause = %v", result.Cause)
|
|
|
|
|
}
|
|
|
|
|
if tc.cancel && !errors.Is(result.Cause, context.Canceled) {
|
|
|
|
|
t.Fatalf("cancellation cause = %v", result.Cause)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|