Files
commandkit/integrations/ckprompt/preparation_test.go

108 lines
5.2 KiB
Go

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)
}
}