package domain import ( "encoding/json" "strings" "testing" ) func TestPreparedRunJSONDoesNotIncludeSecretValues(t *testing.T) { const envName = "PROMPTKIT_TEST_API_KEY" const secret = "super-secret-value" t.Setenv(envName, secret) prepared := PreparedRun{ PromptID: "prompt.id", PromptVersion: "v1", PromptHash: "prompt-hash", SelectedProfileID: "local-fast", EffectiveModelParams: ExecutionTarget{ Endpoint: "http://llm/v1", Model: "gpt-test", APIKeyEnv: envName, APIKey: secret, }, InputHashes: map[string]string{"transcript": "hash-1"}, RenderedPromptHash: "rendered-hash", Messages: []RenderedMessage{ {Role: "system", Content: "You are helpful."}, {Role: "user", Content: "Summarize this."}, }, } b, err := json.Marshal(prepared) if err != nil { t.Fatalf("marshal failed: %v", err) } out := string(b) if strings.Contains(out, secret) { t.Fatalf("prepared run JSON unexpectedly contains secret value: %s", out) } if !strings.Contains(out, `"api_key_env":"`+envName+`"`) { t.Fatalf("prepared run JSON should include api_key_env name: %s", out) } var top map[string]any if err := json.Unmarshal(b, &top); err != nil { t.Fatalf("unmarshal failed: %v", err) } for _, forbidden := range []string{"raw_output", "validation", "artifact"} { if _, ok := top[forbidden]; ok { t.Fatalf("prepared run JSON should not include %q", forbidden) } } } func TestPreparedRunJSONIncludesMessageCacheControlOnlyWhenPresent(t *testing.T) { prepared := PreparedRun{ PromptID: "prompt.id", SelectedProfileID: "local-fast", EffectiveModelParams: ExecutionTarget{ Endpoint: "http://llm/v1", Model: "gpt-test", }, RenderedPromptHash: "rendered-hash", Messages: []RenderedMessage{ { Role: "system", Content: "You are helpful.", CacheControl: &CacheControl{ Type: CacheControlEphemeral, TTL: "1h", }, }, {Role: "user", Content: "Summarize this."}, }, } b, err := json.Marshal(prepared) if err != nil { t.Fatalf("marshal failed: %v", err) } var decoded struct { Messages []map[string]any `json:"messages"` } if err := json.Unmarshal(b, &decoded); err != nil { t.Fatalf("unmarshal failed: %v", err) } if len(decoded.Messages) != 2 { t.Fatalf("expected 2 messages, got %d", len(decoded.Messages)) } cacheControl, ok := decoded.Messages[0]["cache_control"].(map[string]any) if !ok { t.Fatalf("expected cache_control on first message, got %#v", decoded.Messages[0]) } if cacheControl["type"] != string(CacheControlEphemeral) || cacheControl["ttl"] != "1h" { t.Fatalf("unexpected cache_control payload: %#v", cacheControl) } if _, ok := decoded.Messages[1]["cache_control"]; ok { t.Fatalf("expected second message to omit cache_control, got %#v", decoded.Messages[1]) } } func TestPreparedRunJSONIncludesSessionIDOnlyWhenPresent(t *testing.T) { prepared := PreparedRun{ PromptID: "prompt.id", SelectedProfileID: "local-fast", EffectiveModelParams: ExecutionTarget{ Endpoint: "http://llm/v1", Model: "gpt-test", }, SessionID: "session-123", RenderedPromptHash: "rendered-hash", Messages: []RenderedMessage{{Role: "user", Content: "Summarize this."}}, } b, err := json.Marshal(prepared) if err != nil { t.Fatalf("marshal failed: %v", err) } var decoded map[string]any if err := json.Unmarshal(b, &decoded); err != nil { t.Fatalf("unmarshal failed: %v", err) } if decoded["session_id"] != "session-123" { t.Fatalf("expected session_id in prepared run JSON, got %#v", decoded["session_id"]) } prepared.SessionID = "" b, err = json.Marshal(prepared) if err != nil { t.Fatalf("marshal failed: %v", err) } if strings.Contains(string(b), "session_id") { t.Fatalf("expected empty session_id to be omitted, got %s", b) } }