package aireview import ( "encoding/json" "net/http" "testing" ) func TestBuildStructuredRequestBodyResponsesStyle(t *testing.T) { t.Parallel() svc := NewService("https://example.test/v1/responses", "test-key", "test-model") body, err := svc.buildStructuredRequestBody("system prompt", "user prompt", "test_schema", reviewSchema()) if err != nil { t.Fatalf("buildStructuredRequestBody returned error: %v", err) } if got := body["model"]; got != "test-model" { t.Fatalf("model = %v, want test-model", got) } if _, ok := body["input"]; !ok { t.Fatalf("responses body missing input field: %#v", body) } if _, ok := body["text"]; !ok { t.Fatalf("responses body missing text field: %#v", body) } if _, ok := body["messages"]; ok { t.Fatalf("responses body should not include messages: %#v", body) } if _, ok := body["response_format"]; ok { t.Fatalf("responses body should not include response_format: %#v", body) } } func TestBuildStructuredRequestBodyVLLMChatStyle(t *testing.T) { t.Parallel() svc := NewService("http://100.92.130.19:8000/v1/chat/completions", "test-key", "qwen3.6-27b") body, err := svc.buildStructuredRequestBody("system prompt", "user prompt", "assignment_review", reviewSchema()) if err != nil { t.Fatalf("buildStructuredRequestBody returned error: %v", err) } if got := body["model"]; got != "qwen3.6-27b" { t.Fatalf("model = %v, want qwen3.6-27b", got) } if _, ok := body["messages"]; !ok { t.Fatalf("chat body missing messages: %#v", body) } responseFormat, ok := body["response_format"].(map[string]any) if !ok { t.Fatalf("response_format missing or wrong type: %#v", body["response_format"]) } if got := responseFormat["type"]; got != "json_schema" { t.Fatalf("response_format.type = %v, want json_schema", got) } kwargs, ok := body["chat_template_kwargs"].(map[string]any) if !ok { t.Fatalf("expected chat_template_kwargs for vLLM chat style: %#v", body) } if got := kwargs["enable_thinking"]; got != false { t.Fatalf("enable_thinking = %v, want false", got) } if _, ok := body["input"]; ok { t.Fatalf("chat body should not include responses input field: %#v", body) } } func TestBuildStructuredRequestBodyAzureChatStyleDoesNotDisableThinking(t *testing.T) { t.Parallel() svc := NewService("https://example.openai.azure.com/openai/deployments/review/chat/completions?api-version=2025-01-01-preview", "test-key", "gpt-4.1-mini") body, err := svc.buildStructuredRequestBody("system prompt", "user prompt", "assignment_review", reviewSchema()) if err != nil { t.Fatalf("buildStructuredRequestBody returned error: %v", err) } if _, ok := body["chat_template_kwargs"]; ok { t.Fatalf("azure chat body should not include chat_template_kwargs: %#v", body) } } func TestApplyAuthHeader(t *testing.T) { t.Parallel() t.Run("azure uses api-key", func(t *testing.T) { t.Parallel() svc := NewService("https://example.openai.azure.com/openai/deployments/review/chat/completions?api-version=2025-01-01-preview", "azure-key", "gpt-4.1-mini") req, err := http.NewRequest(http.MethodPost, svc.endpoint, nil) if err != nil { t.Fatalf("http.NewRequest returned error: %v", err) } svc.applyAuthHeader(req) if got := req.Header.Get("api-key"); got != "azure-key" { t.Fatalf("api-key header = %q, want azure-key", got) } if got := req.Header.Get("Authorization"); got != "" { t.Fatalf("Authorization header = %q, want empty", got) } }) t.Run("non-azure uses bearer auth", func(t *testing.T) { t.Parallel() svc := NewService("http://100.92.130.19:8000/v1/chat/completions", "vllm-key", "qwen3.6-27b") req, err := http.NewRequest(http.MethodPost, svc.endpoint, nil) if err != nil { t.Fatalf("http.NewRequest returned error: %v", err) } svc.applyAuthHeader(req) if got := req.Header.Get("Authorization"); got != "Bearer vllm-key" { t.Fatalf("Authorization header = %q, want %q", got, "Bearer vllm-key") } if got := req.Header.Get("api-key"); got != "" { t.Fatalf("api-key header = %q, want empty", got) } }) } func TestExtractChatCompletionText(t *testing.T) { t.Parallel() t.Run("string content", func(t *testing.T) { t.Parallel() respBytes := []byte(`{"choices":[{"message":{"content":"{\"status\":\"ok\"}"}}]}`) got, err := extractChatCompletionText(respBytes) if err != nil { t.Fatalf("extractChatCompletionText returned error: %v", err) } if got != `{"status":"ok"}` { t.Fatalf("content = %q, want %q", got, `{"status":"ok"}`) } }) t.Run("array content", func(t *testing.T) { t.Parallel() payload := map[string]any{ "choices": []any{ map[string]any{ "message": map[string]any{ "content": []any{ map[string]any{"type": "text", "text": "{\"status\":"}, map[string]any{"type": "text", "text": "\"ok\"}"}, }, }, }, }, } respBytes, err := json.Marshal(payload) if err != nil { t.Fatalf("json.Marshal returned error: %v", err) } got, err := extractChatCompletionText(respBytes) if err != nil { t.Fatalf("extractChatCompletionText returned error: %v", err) } if got != "{\"status\":\n\"ok\"}" { t.Fatalf("content = %q, want joined text output", got) } }) }