Test typed sources through configuration composition
This commit is contained in:
@@ -494,6 +494,10 @@ the helper itself neither accesses files nor resolves inheritance.
|
||||
|
||||
## Stage 10: Verify Typed Sources Through Real Composition
|
||||
|
||||
**Completed:** Real engine composition tests cover inherited backend and prompt
|
||||
records, layered completion, snapshots, explicit values, relative inherited file
|
||||
content using ConfigKit's filesystem context, and deferred final validation.
|
||||
|
||||
**Scope:** Focused source-helper contract tests, not a second ConfigKit suite.
|
||||
|
||||
1. Extend existing backend-source tests with a real inherited child, preserving
|
||||
|
||||
94
prompt_sources_contract_test.go
Normal file
94
prompt_sources_contract_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package promptkit_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
"go.maximumdirect.net/configkit"
|
||||
"go.maximumdirect.net/promptkit"
|
||||
)
|
||||
|
||||
func TestTypedSourcesComposeAndCaptureValues(t *testing.T) {
|
||||
endpoint, childEndpoint, base := "https://base.test/v1", "https://child.test/v1", "base"
|
||||
limit, queue := 2, 0
|
||||
backendSource := promptkit.BackendSource("backends", []promptkit.BackendConfig{{ID: "base", Endpoint: &endpoint, ConcurrencyLimit: &limit}, {ID: "child", Extends: &base, Endpoint: &childEndpoint, QueueCapacity: &queue}})
|
||||
role, content, secondContent, empty, cacheType := "user", "hello {{.name}}", "second", "", "ephemeral"
|
||||
format, validation, description, profileID := "text", "none", "description", "local"
|
||||
required, repairs := false, 0
|
||||
prompts := []promptkit.PromptConfig{
|
||||
{ID: "base", DefaultProfile: &profileID, Description: &description, Inputs: []promptkit.PromptInputConfig{{Name: "input", Required: &required, Description: &empty}}, Messages: []promptkit.PromptMessageConfig{{Role: &role, Content: &content, CacheControl: &promptkit.PromptCacheControlConfig{Type: &cacheType, TTL: &empty}}, {Role: &role, Content: &secondContent}}, Output: &promptkit.PromptOutputConfig{Format: &format, ValidationMode: &validation, RepairAttempts: &repairs}},
|
||||
{ID: "child", Extends: &base},
|
||||
{ID: "layered"},
|
||||
{ID: "empty-inputs", Inputs: []promptkit.PromptInputConfig{}, Messages: []promptkit.PromptMessageConfig{{Role: &role, Content: &content}}, Output: &promptkit.PromptOutputConfig{Format: &format, ValidationMode: &validation}},
|
||||
{ID: "omitted-inputs", Messages: []promptkit.PromptMessageConfig{{Role: &role, Content: &content}}, Output: &promptkit.PromptOutputConfig{Format: &format, ValidationMode: &validation}},
|
||||
}
|
||||
promptSource := promptkit.PromptSource("typed prompts", prompts)
|
||||
endpoint, childEndpoint, role, content, description = "changed", "changed", "changed", "changed", "changed"
|
||||
prompts[0].Inputs[0].Name = "changed"
|
||||
prompts[0].Messages[1].Content = &empty
|
||||
prompts[1].ID = "changed"
|
||||
profileEndpoint, model := "https://profile.test/v1", "model"
|
||||
main := configkit.Input{Source: configkit.Mapping("completion", map[string]any{"prompts": []any{map[string]any{"id": "layered", "messages": []any{map[string]any{"role": "user", "content": "layer"}}, "output": map[string]any{"format": "text", "validation_mode": "none"}}}})}
|
||||
profileSource := promptkit.ProfileSource("profiles", []promptkit.ProfileConfig{{ID: "local", Endpoint: &profileEndpoint, Model: &model}})
|
||||
underlays := []configkit.Underlay{{Inputs: []configkit.Input{{Source: backendSource}, {Source: promptSource}, {Source: profileSource}}}}
|
||||
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Underlays: underlays, Main: &main}}, promptkit.WithBundledOpenRouter(false))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
backend, err := engine.InspectBackend(ctx, "child")
|
||||
if err != nil || backend.BackendID != "child" || backend.Endpoint != "https://child.test/v1" || backend.ConcurrencyLimit == nil || *backend.ConcurrencyLimit != 2 || backend.QueueCapacity == nil || *backend.QueueCapacity != 0 {
|
||||
t.Fatalf("backend = %+v, %v", backend, err)
|
||||
}
|
||||
prompt, err := engine.InspectPrompt(ctx, "child")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if prompt.DefaultProfileID == nil || *prompt.DefaultProfileID != "local" || prompt.Description == nil || *prompt.Description != "description" || prompt.Inputs == nil || len(*prompt.Inputs) != 1 || (*prompt.Inputs)[0].Name != "input" || (*prompt.Inputs)[0].Required == nil || *(*prompt.Inputs)[0].Required || (*prompt.Inputs)[0].Description == nil || *(*prompt.Inputs)[0].Description != "" || len(prompt.Messages) != 2 || prompt.Messages[0].Content != "hello {{.name}}" || prompt.Messages[1].Content != "second" || prompt.Messages[0].CacheControl.TTL == nil || *prompt.Messages[0].CacheControl.TTL != "" || prompt.OutputContract.RepairAttempts == nil || *prompt.OutputContract.RepairAttempts != 0 {
|
||||
t.Fatalf("prompt = %+v", prompt)
|
||||
}
|
||||
prepared, err := engine.Prepare(ctx, promptkit.RunRequest{PromptID: "child", Vars: map[string]string{"name": "reader"}})
|
||||
if err != nil || prepared.Messages[0].Content != "hello reader" {
|
||||
t.Fatalf("preparation = %+v, %v", prepared, err)
|
||||
}
|
||||
for _, id := range []string{"layered", "empty-inputs", "omitted-inputs"} {
|
||||
result, err := engine.InspectPrompt(ctx, id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if id == "empty-inputs" && (result.Inputs == nil || len(*result.Inputs) != 0) {
|
||||
t.Fatalf("empty inputs = %+v", result.Inputs)
|
||||
}
|
||||
if id == "omitted-inputs" && result.Inputs != nil {
|
||||
t.Fatalf("omitted inputs = %+v", result.Inputs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypedPromptSourceUsesExplicitFilesystemContext(t *testing.T) {
|
||||
role, path, format, validation, parent := "user", "body.md", "text", "none", "base"
|
||||
source := promptkit.PromptSource("file prompts", []promptkit.PromptConfig{{ID: "base", Messages: []promptkit.PromptMessageConfig{{Role: &role, ContentFile: &path}}, Output: &promptkit.PromptOutputConfig{Format: &format, ValidationMode: &validation}}, {ID: "child", Extends: &parent}})
|
||||
files := fstest.MapFS{"templates/body.md": {Data: []byte("captured {{.value}}")}}
|
||||
filesystem := configkit.NewFilesystem("fixture", files)
|
||||
input := configkit.Input{Source: source, Filesystem: &configkit.FilesystemContext{FS: &filesystem, Root: ".", BaseDir: "templates"}}
|
||||
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &input}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
files["templates/body.md"].Data = []byte("changed")
|
||||
prompt, err := engine.InspectPrompt(context.Background(), "child")
|
||||
if err != nil || prompt.Messages[0].Content != "captured {{.value}}" {
|
||||
t.Fatalf("file prompt = %+v, %v", prompt, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypedPromptSourceDefersValidation(t *testing.T) {
|
||||
role, body, path, format, validation := "user", "body", "also-file", "text", "none"
|
||||
source := promptkit.PromptSource("invalid", []promptkit.PromptConfig{{ID: "bad", Messages: []promptkit.PromptMessageConfig{{Role: &role, Content: &body, ContentFile: &path}}, Output: &promptkit.PromptOutputConfig{Format: &format, ValidationMode: &validation}}})
|
||||
_, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{Source: source}}})
|
||||
if !errors.Is(err, promptkit.ErrInvalidConfig) {
|
||||
t.Fatalf("invalid final source: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user