""" Tests for JWT Authentication endpoints. Tests: 1. POST /auth/register → 501 when pyjwt/passlib not installed 2. POST /auth/login → 501 when pyjwt/passlib not installed 3. POST /auth/login → 422 when missing email/password 4. POST /auth/register → 422 when missing fields 5. POST /auth/register → 422 when password too short 6. GET /auth/me → 401 when no Authorization header 7. Regression: existing endpoints unaffected by auth routes These tests validate the graceful degradation pattern: auth deps (pyjwt, passlib) are optional — endpoints return 501 with clear instructions when they're not installed. """ import pytest import httpx BASE = "http://localhost:5000/api/v1" @pytest.fixture def client(): with httpx.Client(base_url=BASE, timeout=10) as c: yield c class TestAuthRegister: """Tests for POST /auth/register.""" def test_register_returns_501_without_deps(self, client): """Should return 501 with clear message about missing auth deps.""" r = client.post("/auth/register", json={ "email": "test@example.com", "password": "testpassword123", # pragma: allowlist secret "full_name": "Test User", }) # If deps ARE installed, this will be 200. # If deps are NOT installed, this will be 501. assert r.status_code in (200, 501, 409) if r.status_code == 501: body = r.json() assert body["detail"]["code"] == "AUTH_NOT_CONFIGURED" assert "pyjwt" in body["detail"]["message"] or "passlib" in body["detail"]["message"] def test_register_requires_fields(self, client): """Should return 422 when required fields are missing.""" r = client.post("/auth/register", json={}) assert r.status_code == 422 body = r.json() assert body["detail"]["code"] == "VALIDATION_ERROR" def test_register_password_min_length(self, client): """Should return 422 when password is too short.""" r = client.post("/auth/register", json={ "email": "test@example.com", "password": "short", # pragma: allowlist secret "full_name": "Test User", }) assert r.status_code == 422 body = r.json() assert "8 characters" in body["detail"]["message"] class TestAuthLogin: """Tests for POST /auth/login.""" def test_login_returns_501_without_deps(self, client): """Should return 501 with clear message about missing auth deps.""" r = client.post("/auth/login", json={ "email": "test@example.com", "password": "testpassword123", # pragma: allowlist secret }) assert r.status_code in (200, 401, 501) if r.status_code == 501: body = r.json() assert body["detail"]["code"] == "AUTH_NOT_CONFIGURED" def test_login_requires_fields(self, client): """Should return 422 when email or password missing.""" r = client.post("/auth/login", json={}) assert r.status_code == 422 body = r.json() assert body["detail"]["code"] == "VALIDATION_ERROR" class TestAuthMe: """Tests for GET /auth/me.""" def test_me_requires_authorization(self, client): """Should return 401 when no auth header provided.""" r = client.get("/auth/me") assert r.status_code == 401 body = r.json() assert body["detail"]["code"] == "UNAUTHORIZED" def test_me_rejects_invalid_token(self, client): """Should return 401 with an invalid token.""" r = client.get("/auth/me", headers={ "Authorization": "Bearer invalid.token.here", }) assert r.status_code in (401, 501) class TestAuthRegression: """Ensure auth routes don't break existing endpoints.""" def test_existing_endpoints_still_work(self, client): r = client.get("/health") assert r.status_code == 200 r = client.get("/visits", params={"page": 1, "per_page": 1}) assert r.status_code == 200 r = client.get("/outlets") assert r.status_code == 200