""" Integration tests for AI API endpoints """ import pytest from fastapi.testclient import TestClient from unittest.mock import patch, AsyncMock from src.backend.main import app class TestAIDocumentEndpoints: """Test AI document generation API endpoints.""" def test_generate_cover_letter_success(self, test_client, test_user_token): """Test successful cover letter generation.""" headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "job_description": "We are looking for a Senior Python Developer with FastAPI experience and PostgreSQL knowledge. The ideal candidate will have 5+ years of experience.", "company_name": "TechCorp Industries", "role_title": "Senior Python Developer" } response = test_client.post( "/api/ai/generate-cover-letter", json=request_data, headers=headers ) assert response.status_code == 200 data = response.json() assert "content" in data assert "model_used" in data assert "generation_prompt" in data # Verify content includes relevant information assert "TechCorp Industries" in data["content"] assert "Senior Python Developer" in data["content"] assert len(data["content"]) > 100 # Should be substantial # Should use template fallback without API keys assert data["model_used"] == "template" def test_generate_cover_letter_with_resume(self, test_client, test_user_token): """Test cover letter generation with user resume included.""" headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "job_description": "Python developer role requiring Django experience", "company_name": "Resume Corp", "role_title": "Python Developer", "user_resume": "John Doe\nSoftware Engineer\n\nExperience:\n- 5 years Python development\n- Django and Flask frameworks\n- PostgreSQL databases" } response = test_client.post( "/api/ai/generate-cover-letter", json=request_data, headers=headers ) assert response.status_code == 200 data = response.json() assert "Resume Corp" in data["content"] # Prompt should reference the resume assert "Resume/Background" in data["generation_prompt"] def test_generate_cover_letter_unauthorized(self, test_client): """Test cover letter generation without authentication.""" request_data = { "job_description": "Test job", "company_name": "Test Corp", "role_title": "Test Role" } response = test_client.post("/api/ai/generate-cover-letter", json=request_data) assert response.status_code == 403 # HTTPBearer returns 403 def test_generate_cover_letter_invalid_token(self, test_client): """Test cover letter generation with invalid token.""" headers = {"Authorization": "Bearer invalid.token.here"} request_data = { "job_description": "Test job", "company_name": "Test Corp", "role_title": "Test Role" } response = test_client.post( "/api/ai/generate-cover-letter", json=request_data, headers=headers ) assert response.status_code == 401 def test_generate_cover_letter_missing_fields(self, test_client, test_user_token): """Test cover letter generation with missing required fields.""" headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "job_description": "Test job", # Missing company_name and role_title } response = test_client.post( "/api/ai/generate-cover-letter", json=request_data, headers=headers ) assert response.status_code == 422 # Validation error def test_optimize_resume_success(self, test_client, test_user_token): """Test successful resume optimization.""" headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "current_resume": """ John Smith Software Engineer Experience: - 3 years Python development - Built REST APIs using Flask - Database management with MySQL - Team collaboration and code reviews """, "job_description": "Senior Python Developer role requiring FastAPI, PostgreSQL, and AI/ML experience. Must have 5+ years of experience.", "role_title": "Senior Python Developer" } response = test_client.post( "/api/ai/optimize-resume", json=request_data, headers=headers ) assert response.status_code == 200 data = response.json() assert "content" in data assert "model_used" in data assert "generation_prompt" in data # Should include original resume content assert "John Smith" in data["content"] assert "Senior Python Developer" in data["content"] assert data["model_used"] == "template" def test_optimize_resume_unauthorized(self, test_client): """Test resume optimization without authentication.""" request_data = { "current_resume": "Test resume", "job_description": "Test job", "role_title": "Test role" } response = test_client.post("/api/ai/optimize-resume", json=request_data) assert response.status_code == 403 def test_test_ai_connection_success(self, test_client, test_user_token): """Test AI connection test endpoint.""" headers = {"Authorization": f"Bearer {test_user_token}"} response = test_client.post("/api/ai/test-ai-connection", headers=headers) assert response.status_code == 200 data = response.json() assert "claude_available" in data assert "openai_available" in data assert "user" in data assert "test_generation" in data # Without API keys, should show unavailable but test should succeed assert data["claude_available"] == False assert data["openai_available"] == False assert data["test_generation"] == "success" assert data["model_used"] == "template" assert "content_preview" in data def test_test_ai_connection_unauthorized(self, test_client): """Test AI connection test without authentication.""" response = test_client.post("/api/ai/test-ai-connection") assert response.status_code == 403 class TestAIAPIErrorHandling: """Test error handling in AI API endpoints.""" @patch('src.backend.services.ai_service.ai_service.generate_cover_letter') def test_cover_letter_generation_service_error(self, mock_generate, test_client, test_user_token): """Test cover letter generation when AI service fails.""" # Mock the service to raise an exception mock_generate.side_effect = Exception("AI service unavailable") headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "job_description": "Test job", "company_name": "Error Corp", "role_title": "Test Role" } response = test_client.post( "/api/ai/generate-cover-letter", json=request_data, headers=headers ) assert response.status_code == 500 data = response.json() assert "Failed to generate cover letter" in data["detail"] @patch('src.backend.services.ai_service.ai_service.generate_resume_optimization') def test_resume_optimization_service_error(self, mock_optimize, test_client, test_user_token): """Test resume optimization when AI service fails.""" mock_optimize.side_effect = Exception("Service error") headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "current_resume": "Test resume", "job_description": "Test job", "role_title": "Test role" } response = test_client.post( "/api/ai/optimize-resume", json=request_data, headers=headers ) assert response.status_code == 500 data = response.json() assert "Failed to optimize resume" in data["detail"] def test_cover_letter_with_large_payload(self, test_client, test_user_token): """Test cover letter generation with very large job description.""" headers = {"Authorization": f"Bearer {test_user_token}"} # Create a very large job description large_description = "A" * 50000 # 50KB of text request_data = { "job_description": large_description, "company_name": "Large Corp", "role_title": "Big Role" } response = test_client.post( "/api/ai/generate-cover-letter", json=request_data, headers=headers ) # Should handle large payloads gracefully assert response.status_code in [200, 413, 422] # Success or payload too large def test_resume_optimization_empty_resume(self, test_client, test_user_token): """Test resume optimization with empty resume.""" headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "current_resume": "", "job_description": "Test job description", "role_title": "Test Role" } response = test_client.post( "/api/ai/optimize-resume", json=request_data, headers=headers ) # Should handle empty resume assert response.status_code == 200 data = response.json() assert "content" in data class TestAIAPIValidation: """Test input validation for AI API endpoints.""" def test_cover_letter_invalid_email_in_description(self, test_client, test_user_token): """Test cover letter generation with invalid characters.""" headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "job_description": "Job with special chars: ", "company_name": "Security Corp", "role_title": "Security Engineer" } response = test_client.post( "/api/ai/generate-cover-letter", json=request_data, headers=headers ) # Should sanitize or handle special characters assert response.status_code == 200 data = response.json() # The script tag should not be executed (this is handled by the template) assert "Security Corp" in data["content"] def test_resume_optimization_unicode_content(self, test_client, test_user_token): """Test resume optimization with unicode characters.""" headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "current_resume": "José González\nSoftware Engineer\n• 5 años de experiencia", "job_description": "Seeking bilingual developer", "role_title": "Desarrollador Senior" } response = test_client.post( "/api/ai/optimize-resume", json=request_data, headers=headers ) assert response.status_code == 200 data = response.json() assert "José González" in data["content"] def test_cover_letter_null_values(self, test_client, test_user_token): """Test cover letter generation with null values in optional fields.""" headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "job_description": "Test job description", "company_name": "Null Corp", "role_title": "Null Role", "job_url": None, "user_resume": None } response = test_client.post( "/api/ai/generate-cover-letter", json=request_data, headers=headers ) assert response.status_code == 200 data = response.json() assert "Null Corp" in data["content"] class TestAIAPIPerformance: """Test performance aspects of AI API endpoints.""" def test_concurrent_cover_letter_requests(self, test_client, test_user_token): """Test multiple concurrent cover letter requests.""" import threading import time headers = {"Authorization": f"Bearer {test_user_token}"} def make_request(index): request_data = { "job_description": f"Job description {index}", "company_name": f"Company {index}", "role_title": f"Role {index}" } return test_client.post( "/api/ai/generate-cover-letter", json=request_data, headers=headers ) # Make 5 concurrent requests start_time = time.time() threads = [] results = [] for i in range(5): thread = threading.Thread(target=lambda i=i: results.append(make_request(i))) threads.append(thread) thread.start() for thread in threads: thread.join() end_time = time.time() # All requests should succeed assert len(results) == 5 for response in results: assert response.status_code == 200 # Should complete in reasonable time (less than 10 seconds for template generation) assert end_time - start_time < 10 def test_response_time_cover_letter(self, test_client, test_user_token): """Test response time for cover letter generation.""" import time headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "job_description": "Standard Python developer position", "company_name": "Performance Corp", "role_title": "Python Developer" } start_time = time.time() response = test_client.post( "/api/ai/generate-cover-letter", json=request_data, headers=headers ) end_time = time.time() assert response.status_code == 200 # Template generation should be fast (less than 1 second) response_time = end_time - start_time assert response_time < 1.0 @pytest.mark.asyncio class TestAIAPIAsync: """Test AI API endpoints with async client.""" async def test_async_cover_letter_generation(self, async_client, test_user_token): """Test cover letter generation with async client.""" headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "job_description": "Async job description", "company_name": "Async Corp", "role_title": "Async Developer" } response = await async_client.post( "/api/ai/generate-cover-letter", json=request_data, headers=headers ) assert response.status_code == 200 data = response.json() assert "Async Corp" in data["content"] async def test_async_resume_optimization(self, async_client, test_user_token): """Test resume optimization with async client.""" headers = {"Authorization": f"Bearer {test_user_token}"} request_data = { "current_resume": "Async resume content", "job_description": "Async job requirements", "role_title": "Async Role" } response = await async_client.post( "/api/ai/optimize-resume", json=request_data, headers=headers ) assert response.status_code == 200 data = response.json() assert "Async resume content" in data["content"]