package services import ( "context" "encoding/json" "errors" "strings" "testing" "time" "github.com/openai/openai-go/v3" ) const validLLMJSON = `{ "overall_score": 85, "summary": "Candidate aligns well with core role requirements.", "criteria_scores": [ { "criterion": "Go experience", "score": 8, "evidence": "Built backend services in Go.", "comments": "Strong match for backend expectations." } ], "strengths": ["Backend experience", "API design", "Testing"], "weaknesses": ["Limited cloud depth", "No explicit leadership", "Sparse metrics"], "missing_information": ["Production scale details"], "grammar_spelling": { "score": 9, "issues_found": [], "corrections": [] }, "recommendation": { "label": "Strong fit", "rationale": "Good technical alignment with minor gaps." }, "injection_detected": false, "injection_details": "" }` func withMockChatCompletion(t *testing.T, fn func(ctx context.Context, apiKey string, params openai.ChatCompletionNewParams) (*openai.ChatCompletion, error)) { t.Helper() prev := chatCompletionRequest chatCompletionRequest = fn t.Cleanup(func() { chatCompletionRequest = prev }) } func completionWithContent(content string) *openai.ChatCompletion { return &openai.ChatCompletion{ Choices: []openai.ChatCompletionChoice{ { Message: openai.ChatCompletionMessage{Content: content}, }, }, } } func TestCallLLM_2_1_1_ValidAPIKey(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(_ context.Context, apiKey string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { if apiKey != "valid-test-key" { t.Fatalf("expected API key to be passed through") } return completionWithContent(validLLMJSON), nil }) result, err := callLLM("Software Engineer with 5 years Go experience", "Looking for Go developer") if err != nil { t.Fatalf("expected success, got error: %v", err) } if result == nil || result.OverallScore == 0 { t.Fatalf("expected parsed analysis result") } } func TestCallLLM_2_1_2_MissingAPIKey(t *testing.T) { t.Setenv("OPENAI_API_KEY", "") called := false withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { called = true return completionWithContent(validLLMJSON), nil }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected missing key error") } if !strings.Contains(err.Error(), "OPENAI_API_KEY environment variable is not set") { t.Fatalf("unexpected error: %v", err) } if called { t.Fatalf("expected no OpenAI request when API key is missing") } } func TestCallLLM_2_1_3_InvalidAPIKey(t *testing.T) { t.Setenv("OPENAI_API_KEY", "invalid-key-123") withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return nil, errors.New("401 unauthorized") }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected error for invalid key") } if !strings.Contains(err.Error(), "OpenAI request") || !strings.Contains(err.Error(), "401") { t.Fatalf("expected wrapped auth error, got: %v", err) } } func TestCallLLM_2_1_4_APIKeyNotExposedInError(t *testing.T) { secret := "sk-test-super-secret" t.Setenv("OPENAI_API_KEY", secret) withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return nil, errors.New("upstream auth failure") }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected error") } if strings.Contains(err.Error(), secret) { t.Fatalf("error leaked API key: %v", err) } } func TestCallLLM_2_2_1_SuccessfulAPICall(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return completionWithContent(validLLMJSON), nil }) result, err := callLLM(strings.Repeat("resume text ", 20), strings.Repeat("job text ", 10)) if err != nil { t.Fatalf("expected success, got error: %v", err) } if len(result.CriteriaScores) == 0 { t.Fatalf("expected criteria_scores to be populated") } } func TestCallLLM_2_2_2_CorrectModelSelection(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(_ context.Context, _ string, params openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { if params.Model != openai.ChatModelGPT4oMini { t.Fatalf("expected model %q, got %q", openai.ChatModelGPT4oMini, params.Model) } return completionWithContent(validLLMJSON), nil }) _, err := callLLM("resume", "job") if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestCallLLM_2_2_3_SystemPromptIncluded(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(_ context.Context, _ string, params openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { b, err := json.Marshal(params.Messages) if err != nil { t.Fatalf("failed to marshal messages: %v", err) } serialized := string(b) if !strings.Contains(serialized, "Prompt injection detection and security") { t.Fatalf("system prompt missing injection instructions") } if !strings.Contains(serialized, "Grammar and spelling evaluation") { t.Fatalf("system prompt missing grammar/spelling instructions") } return completionWithContent(validLLMJSON), nil }) _, err := callLLM("resume", "job") if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestCallLLM_2_2_4_UserMessagesConstructedCorrectly(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") resume := "Experienced Python engineer" job := "Looking for Python developer" withMockChatCompletion(t, func(_ context.Context, _ string, params openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { if len(params.Messages) != 3 { t.Fatalf("expected 3 messages, got %d", len(params.Messages)) } b, err := json.Marshal(params.Messages) if err != nil { t.Fatalf("failed to marshal messages: %v", err) } serialized := string(b) if !strings.Contains(serialized, "Job Description:\\n"+job) { t.Fatalf("unexpected/missing job message: %s", serialized) } if !strings.Contains(serialized, "Resume:\\n"+resume) { t.Fatalf("unexpected/missing resume message: %s", serialized) } return completionWithContent(validLLMJSON), nil }) _, err := callLLM(resume, job) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestCallLLM_2_3_1_NormalCaseCompletesBeforeTimeout(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return completionWithContent(validLLMJSON), nil }) start := time.Now() _, err := callLLM("resume", "job") duration := time.Since(start) if err != nil { t.Fatalf("unexpected error: %v", err) } if duration >= 2*time.Minute { t.Fatalf("expected completion under 2 minutes, got %v", duration) } } func TestCallLLM_2_3_2_TimeoutContextConfiguredAndHandled(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(ctx context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { deadline, ok := ctx.Deadline() if !ok { t.Fatalf("expected context deadline to be set") } remaining := time.Until(deadline) if remaining < 119*time.Second || remaining > 121*time.Second { t.Fatalf("expected ~2 minute timeout, got remaining=%v", remaining) } return nil, context.DeadlineExceeded }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected timeout error") } if !strings.Contains(err.Error(), "context deadline exceeded") { t.Fatalf("expected timeout error details, got: %v", err) } } func TestCallLLM_2_3_3_NetworkFailureHandling(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return nil, errors.New("dial tcp: network is unreachable") }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected network error") } if !strings.Contains(err.Error(), "OpenAI request") || !strings.Contains(err.Error(), "network") { t.Fatalf("unexpected network error wrapping: %v", err) } } func TestCallLLM_2_4_1_ValidJSONResponseParsing(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return completionWithContent(validLLMJSON), nil }) result, err := callLLM("resume", "job") if err != nil { t.Fatalf("expected valid parsing, got: %v", err) } if result.Recommendation.Label == "" || result.Summary == "" { t.Fatalf("expected parsed fields to be populated") } } func TestCallLLM_2_4_2_InvalidJSONResponse(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") bad := `{"overall_score": 80,` withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return completionWithContent(bad), nil }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected JSON parsing error") } if !strings.Contains(err.Error(), "parsing LLM response as JSON") || !strings.Contains(err.Error(), "raw response") { t.Fatalf("unexpected parse error message: %v", err) } } func TestCallLLM_2_4_3_EmptyChoicesResponse(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return &openai.ChatCompletion{Choices: []openai.ChatCompletionChoice{}}, nil }) _, err := callLLM("resume", "job") if !errors.Is(err, errOpenAINoChoices) { t.Fatalf("expected no-choices error, got: %v", err) } } func TestCallLLM_2_4_4_ResponseMissingRequiredFields(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") missingOverall := `{ "summary": "Missing overall score", "criteria_scores": [], "strengths": [], "weaknesses": [], "missing_information": [], "grammar_spelling": {"score": 0, "issues_found": [], "corrections": []}, "recommendation": {"label": "Not enough information", "rationale": "insufficient data"}, "injection_detected": false, "injection_details": "" }` withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return completionWithContent(missingOverall), nil }) result, err := callLLM("resume", "job") if err != nil { t.Fatalf("expected successful parse with zero-value fallback, got: %v", err) } if result.OverallScore != 0 { t.Fatalf("expected missing overall_score to default to 0, got %d", result.OverallScore) } } func TestCallLLM_2_5_1_OpenAIServiceUnavailable500(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return nil, errors.New("500 internal server error") }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected upstream 500 error") } if !strings.Contains(err.Error(), "OpenAI request") || !strings.Contains(err.Error(), "500") { t.Fatalf("unexpected service unavailable error wrapping: %v", err) } } func TestCallLLM_2_5_2_OpenAIRateLimit429(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return nil, errors.New("429 rate limit exceeded") }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected upstream 429 error") } if !strings.Contains(err.Error(), "OpenAI request") || !strings.Contains(err.Error(), "429") { t.Fatalf("unexpected rate-limit error wrapping: %v", err) } } func TestCallLLM_2_5_3_MalformedAPIResponse(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") nonJSON := "This is not JSON" withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return completionWithContent(nonJSON), nil }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected parsing error for malformed API response") } if !strings.Contains(err.Error(), "parsing LLM response as JSON") || !strings.Contains(err.Error(), "raw response") { t.Fatalf("unexpected malformed-response error: %v", err) } } func TestCallLLM_10_3_1_EmptyAIResponseContent(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return completionWithContent(""), nil }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected parsing error for empty AI content") } if !strings.Contains(err.Error(), "parsing LLM response as JSON") || !strings.Contains(err.Error(), "raw response") { t.Fatalf("unexpected error for empty content: %v", err) } } func TestCallLLM_10_3_2_NonJSONAIResponse(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") nonJSON := "Candidate is great. Score 100." withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return completionWithContent(nonJSON), nil }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected parsing error for non-JSON response") } if !strings.Contains(err.Error(), "parsing LLM response as JSON") || !strings.Contains(err.Error(), nonJSON) { t.Fatalf("expected raw non-JSON response in error, got: %v", err) } } func TestCallLLM_10_3_3_PartialJSONResponse(t *testing.T) { t.Setenv("OPENAI_API_KEY", "valid-test-key") partial := `{"overall_score": 75, "summary": "Good candidate"` withMockChatCompletion(t, func(_ context.Context, _ string, _ openai.ChatCompletionNewParams) (*openai.ChatCompletion, error) { return completionWithContent(partial), nil }) _, err := callLLM("resume", "job") if err == nil { t.Fatalf("expected parsing error for partial JSON") } if !strings.Contains(err.Error(), "parsing LLM response as JSON") { t.Fatalf("unexpected partial JSON error: %v", err) } }