package format import ( "encoding/json" "errors" "strings" "testing" "gitea.maximumdirect.net/eric/scriptorium/internal/domain" ) func TestTextFormatterIncludesPreparedRunDetails(t *testing.T) { prepared := samplePreparedRun() out, err := FormatPreparedRun(prepared, PreparedRunFormatText) if err != nil { t.Fatalf("expected no error, got %v", err) } s := string(out) for _, want := range []string{ "prompt: prompt.id", "prompt_version: v1", "selected_profile_id: local-fast", "endpoint: http://llm/v1", "model: gpt-test", "temperature: 0.4", "max_tokens: 256", "top_p: 0.8", "timeout_seconds: 45", "service_tier: priority", "reasoning_effort: medium", "api_key_env: SCRIPTORIUM_API_KEY", "prompt_hash: prompt-hash", "rendered_prompt_hash: rendered-hash", "glossary: hash-glossary", "transcript: hash-transcript", "messages:", " system:", " user:", "System guidance.", "Summarize the transcript.", "Include key entities.", "Second user message.", } { if !strings.Contains(s, want) { t.Fatalf("expected text output to include %q, got:\n%s", want, s) } } } func TestTextFormatterDoesNotIncludeResolvedAPIKeyValue(t *testing.T) { const secret = "super-secret-api-key" t.Setenv("SCRIPTORIUM_API_KEY", secret) out, err := FormatPreparedRun(samplePreparedRun(), PreparedRunFormatText) if err != nil { t.Fatalf("expected no error, got %v", err) } if strings.Contains(string(out), secret) { t.Fatalf("text output should not include resolved api key value: %s", out) } } func TestJSONFormatterEmitsValidJSONAndIncludesPreparedRunFields(t *testing.T) { prepared := samplePreparedRun() out, err := FormatPreparedRun(prepared, PreparedRunFormatJSON) if err != nil { t.Fatalf("expected no error, got %v", err) } var decoded map[string]any if err := json.Unmarshal(out, &decoded); err != nil { t.Fatalf("expected valid json output, got %v", err) } if decoded["prompt_id"] != "prompt.id" { t.Fatalf("expected prompt_id in json output, got %#v", decoded["prompt_id"]) } if decoded["prompt_version"] != "v1" { t.Fatalf("expected prompt_version in json output, got %#v", decoded["prompt_version"]) } if decoded["selected_profile_id"] != "local-fast" { t.Fatalf("expected selected_profile_id in json output, got %#v", decoded["selected_profile_id"]) } if decoded["rendered_prompt_hash"] != "rendered-hash" { t.Fatalf("expected rendered_prompt_hash in json output, got %#v", decoded["rendered_prompt_hash"]) } if _, ok := decoded["effective_model_params"]; !ok { t.Fatalf("expected effective_model_params in json output, got %#v", decoded) } if _, ok := decoded["input_hashes"]; !ok { t.Fatalf("expected input_hashes in json output, got %#v", decoded) } if _, ok := decoded["messages"]; !ok { t.Fatalf("expected messages in json output, got %#v", decoded) } } func TestJSONFormatterDoesNotIncludeResolvedAPIKeyValue(t *testing.T) { const secret = "super-secret-api-key" t.Setenv("SCRIPTORIUM_API_KEY", secret) out, err := FormatPreparedRun(samplePreparedRun(), PreparedRunFormatJSON) if err != nil { t.Fatalf("expected no error, got %v", err) } if strings.Contains(string(out), secret) { t.Fatalf("json output should not include resolved api key value: %s", out) } } func TestParsePreparedRunOutputFormatRecognizesSupportedNames(t *testing.T) { tests := []struct { name string input string want PreparedRunOutputFormat }{ {name: "default empty", input: "", want: DefaultPreparedRunOutputFormat}, {name: "text", input: "text", want: PreparedRunFormatText}, {name: "json", input: "json", want: PreparedRunFormatJSON}, {name: "trim and case", input: " JSON ", want: PreparedRunFormatJSON}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got, err := ParsePreparedRunOutputFormat(tc.input) if err != nil { t.Fatalf("expected no error, got %v", err) } if got != tc.want { t.Fatalf("expected %q, got %q", tc.want, got) } }) } } func TestParsePreparedRunOutputFormatUnknownFails(t *testing.T) { _, err := ParsePreparedRunOutputFormat("yaml") if err == nil { t.Fatal("expected error for unknown format") } if !errors.Is(err, ErrUnknownPreparedRunFormat) { t.Fatalf("expected ErrUnknownPreparedRunFormat, got %v", err) } } func TestFormatPreparedRunByNameUnknownFailsClearly(t *testing.T) { _, err := FormatPreparedRunByName(samplePreparedRun(), "yaml") if err == nil { t.Fatal("expected unknown format error") } if !errors.Is(err, ErrUnknownPreparedRunFormat) { t.Fatalf("expected ErrUnknownPreparedRunFormat, got %v", err) } } func samplePreparedRun() *domain.PreparedRun { return &domain.PreparedRun{ PromptID: "prompt.id", PromptVersion: "v1", PromptHash: "prompt-hash", SelectedProfileID: "local-fast", EffectiveModelParams: domain.ExecutionTarget{ Endpoint: "http://llm/v1", Model: "gpt-test", Temperature: 0.4, MaxTokens: 256, TopP: 0.8, TimeoutSeconds: 45, ServiceTier: "priority", ReasoningEffort: "medium", APIKeyEnv: "SCRIPTORIUM_API_KEY", }, InputHashes: map[string]string{ "transcript": "hash-transcript", "glossary": "hash-glossary", }, RenderedPromptHash: "rendered-hash", Messages: []domain.RenderedMessage{ {Role: "system", Content: "System guidance."}, {Role: "user", Content: "Summarize the transcript.\nInclude key entities."}, {Role: "user", Content: "Second user message."}, }, } }