developed files
This commit is contained in:
1
tests/integration/__init__.py
Normal file
1
tests/integration/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Integration tests package
|
||||
455
tests/integration/test_ai_api_integration.py
Normal file
455
tests/integration/test_ai_api_integration.py
Normal 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"]
|
||||
Reference in New Issue
Block a user