developed files

This commit is contained in:
2025-08-02 20:51:59 -04:00
parent c9f25ea149
commit 2d6c3bff56
24 changed files with 2660 additions and 220 deletions

View File

@@ -0,0 +1,455 @@
"""
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: <script>alert('xss')</script>",
"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"]