Shortlist / backend /tests /test_api.py
Eren-Sama
Initial commit — full-stack AI portfolio architect
53e1531
"""
Shortlist — API Endpoint Tests
Tests for the FastAPI application endpoints.
Uses TestClient for synchronous testing.
"""
import pytest
from fastapi.testclient import TestClient
from app.main import app
@pytest.fixture
def client():
"""Create a test client for the FastAPI app."""
return TestClient(app)
class TestHealthCheck:
"""Tests for the /health endpoint."""
def test_health_returns_200(self, client):
response = client.get("/health")
assert response.status_code == 200
def test_health_returns_status(self, client):
response = client.get("/health")
data = response.json()
assert data["status"] == "healthy"
assert "version" in data
assert "environment" in data
def test_health_has_security_headers(self, client):
response = client.get("/health")
assert response.headers.get("X-Content-Type-Options") == "nosniff"
assert response.headers.get("X-Frame-Options") == "DENY"
assert response.headers.get("X-XSS-Protection") == "1; mode=block"
class TestCORS:
"""Tests for CORS configuration."""
def test_cors_allows_configured_origin(self, client):
response = client.options(
"/health",
headers={
"Origin": "http://localhost:3000",
"Access-Control-Request-Method": "GET",
},
)
assert response.headers.get("access-control-allow-origin") == "http://localhost:3000"
def test_cors_blocks_unknown_origin(self, client):
response = client.options(
"/health",
headers={
"Origin": "http://evil.com",
"Access-Control-Request-Method": "GET",
},
)
# Unknown origins should not get CORS headers
assert response.headers.get("access-control-allow-origin") != "http://evil.com"
class TestAuthRequiredEndpoints:
"""Tests that protected endpoints require authentication."""
def test_jd_analyze_requires_auth(self, client):
response = client.post("/api/v1/jd/analyze", json={
"jd_text": "x" * 100,
"role": "Backend Engineer",
"company_type": "startup",
})
assert response.status_code == 401
def test_capstone_generate_requires_auth(self, client):
response = client.post("/api/v1/capstone/generate", json={
"analysis_id": "test-id",
})
assert response.status_code == 401
def test_repo_analyze_requires_auth(self, client):
response = client.post("/api/v1/repo/analyze", json={
"github_url": "https://github.com/test/repo",
})
assert response.status_code == 401
def test_scaffold_generate_requires_auth(self, client):
response = client.post("/api/v1/scaffold/generate", json={
"project_title": "Test Project",
"project_description": "A test project description here",
})
assert response.status_code == 401
def test_portfolio_optimize_requires_auth(self, client):
response = client.post("/api/v1/portfolio/optimize", json={
"project_title": "Test",
"project_description": "A test project description here",
})
assert response.status_code == 401
class TestInputValidation:
"""Tests for request validation (Pydantic enforcement)."""
def test_jd_rejects_short_text(self, client):
"""JD text under 50 chars should be rejected (auth blocks first with fake token)."""
response = client.post(
"/api/v1/jd/analyze",
json={
"jd_text": "too short",
"role": "Engineer",
"company_type": "startup",
},
headers={"Authorization": "Bearer fake-token"},
)
# Auth runs before validation, so we get 401 with fake token
assert response.status_code in (401, 422)
def test_jd_rejects_invalid_company_type(self, client):
"""Invalid company type should be rejected (auth blocks first with fake token)."""
response = client.post(
"/api/v1/jd/analyze",
json={
"jd_text": "x" * 100,
"role": "Engineer",
"company_type": "invalid_type",
},
headers={"Authorization": "Bearer fake-token"},
)
assert response.status_code in (401, 422)
def test_repo_rejects_invalid_github_url(self, client):
"""Non-GitHub URLs should be rejected (auth blocks first with fake token)."""
response = client.post(
"/api/v1/repo/analyze",
json={"github_url": "https://gitlab.com/user/repo"},
headers={"Authorization": "Bearer fake-token"},
)
assert response.status_code in (401, 422)