| """ |
| 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", |
| }, |
| ) |
| |
| 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"}, |
| ) |
| |
| 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) |
|
|