package promptkit_test import ( "context" "errors" "sync/atomic" "testing" "gitea.maximumdirect.net/eric/promptkit" ) func TestValidateRequestMatchesPrepareContract(t *testing.T) { var modelCalls atomic.Int64 profiles := map[string]any{ "offline": map[string]any{"endpoint": "http://example.test/v1", "model": "offline-model"}, } newEngine := func(t *testing.T, definition map[string]any) *promptkit.Engine { t.Helper() return requirementsEngine(t, oneVersionPrompt("synthetic", "v1", definition), profiles, promptkit.WithLLMClient(countingLLMClient{calls: &modelCalls})) } testParity := func(t *testing.T, engine *promptkit.Engine, request promptkit.RunRequest, want error) { t.Helper() prepared, prepareErr := engine.Prepare(context.Background(), request) validateErr := engine.ValidateRequest(context.Background(), request) if want == nil { if prepareErr != nil || prepared == nil { t.Fatalf("Prepare() = (%+v, %v), want a prepared result", prepared, prepareErr) } if validateErr != nil { t.Fatalf("ValidateRequest() error = %v, want nil", validateErr) } return } if prepared != nil || !errors.Is(prepareErr, want) { t.Fatalf("Prepare() = (%+v, %v), want error matching %v", prepared, prepareErr, want) } if !errors.Is(validateErr, want) { t.Fatalf("ValidateRequest() error = %v, want error matching %v", validateErr, want) } } t.Run("successful request", func(t *testing.T) { engine := newEngine(t, contractPrompt("offline", "hello", nil, "", noValidationOutput)) testParity(t, engine, promptkit.RunRequest{PromptID: "synthetic"}, nil) }) t.Run("nil engine", func(t *testing.T) { var engine *promptkit.Engine request := promptkit.RunRequest{PromptID: "synthetic"} prepared, prepareErr := engine.Prepare(context.Background(), request) validateErr := engine.ValidateRequest(context.Background(), request) if prepared != nil || !errors.Is(prepareErr, promptkit.ErrInvalidConfig) { t.Fatalf("Prepare() = (%+v, %v), want ErrInvalidConfig", prepared, prepareErr) } if !errors.Is(validateErr, promptkit.ErrInvalidConfig) { t.Fatalf("ValidateRequest() error = %v, want ErrInvalidConfig", validateErr) } }) t.Run("invalid appended message", func(t *testing.T) { engine := newEngine(t, contractPrompt("offline", "hello", nil, "", noValidationOutput)) request := promptkit.RunRequest{ PromptID: "synthetic", AppendedMessages: []promptkit.RenderedMessage{{ Role: "unsupported-role", Content: "not sent to a model", }}, } testParity(t, engine, request, promptkit.ErrInvalidRequest) }) t.Run("missing required input", func(t *testing.T) { definition := contractPrompt("offline", `{{input "required"}}`, []any{ map[string]any{"name": "required", "required": true}, }, "", noValidationOutput) engine := newEngine(t, definition) testParity(t, engine, promptkit.RunRequest{PromptID: "synthetic"}, promptkit.ErrPromptRender) }) if got := modelCalls.Load(); got != 0 { t.Fatalf("preparation operations made %d model calls, want none", got) } }