"""Integration tests for the auth endpoints (register + login). These tests hit the real FastAPI app with an in-memory SQLite database, so they exercise the full stack: Pydantic validation → DB → JWT → response. """ import pytest from fastapi.testclient import TestClient from app.main import app from app.database import get_db, override_get_db from app.routers.auth import pwd_context # --------------------------------------------------------------------------- # Test fixtures # --------------------------------------------------------------------------- @pytest.fixture(autouse=True) def _override_db(): """Use an in-memory SQLite DB for every test.""" from app.database import engine, Base, async_session_factory Base.metadata.create_all(bind=engine) from app.database import SessionLocal async def _get_db(): async_session = async_session_factory() try: yield async_session await async_session.commit() except Exception: await async_session.rollback() raise finally: await async_session.close() app.dependency_overrides[get_db] = _get_db yield app.dependency_overrides.clear() # --------------------------------------------------------------------------- # Register endpoint # --------------------------------------------------------------------------- class TestRegister: """Tests for POST /api/auth/register.""" def test_register_success(self): """A valid registration returns 201 with a token.""" client = TestClient(app) resp = client.post( "/api/auth/register", json={ "username": "newuser", "email": "new@example.com", "password": "secret123", "date_of_birth": "2000-01-01", }, ) assert resp.status_code == 201 data = resp.json() assert "access_token" in data assert data["token_type"] == "bearer" def test_register_rejects_underage(self): """Users under 18 get a 400 with a detail message.""" client = TestClient(app) resp = client.post( "/api/auth/register", json={ "username": "kid", "email": "kid@example.com", "password": "secret123", "date_of_birth": "2010-06-15", # ~15 years old }, ) assert resp.status_code == 400 assert "18 or older" in resp.json()["detail"] def test_register_rejects_duplicate_username(self): """Registering the same username twice returns 409.""" client = TestClient(app) # First registration client.post( "/api/auth/register", json={ "username": "duplicate", "email": "dup1@example.com", "password": "secret123", "date_of_birth": "2000-01-01", }, ) # Second registration with same username, different email resp = client.post( "/api/auth/register", json={ "username": "duplicate", "email": "dup2@example.com", "password": "secret123", "date_of_birth": "2000-01-01", }, ) assert resp.status_code == 409 assert "Username already registered" in resp.json()["detail"] def test_register_rejects_duplicate_email(self): """Registering the same email twice returns 409.""" client = TestClient(app) client.post( "/api/auth/register", json={ "username": "user1", "email": "same@example.com", "password": "secret123", "date_of_birth": "2000-01-01", }, ) resp = client.post( "/api/auth/register", json={ "username": "user2", "email": "same@example.com", "password": "secret123", "date_of_birth": "2000-01-01", }, ) assert resp.status_code == 409 assert "Email already registered" in resp.json()["detail"] def test_register_invalid_date_format(self): """Invalid date format returns 422 validation error.""" client = TestClient(app) resp = client.post( "/api/auth/register", json={ "username": "user", "email": "user@example.com", "password": "secret123", "date_of_birth": "01-01-2000", # wrong format }, ) assert resp.status_code == 422 def test_register_exactly_18(self): """A user who just turned 18 can register.""" client = TestClient(app) resp = client.post( "/api/auth/register", json={ "username": "just18", "email": "just18@example.com", "password": "secret123", "date_of_birth": "2007-06-15", }, ) assert resp.status_code == 201 # --------------------------------------------------------------------------- # Login endpoint # --------------------------------------------------------------------------- class TestLogin: """Tests for POST /api/auth/login.""" def _create_user(self, username: str, password: str, email: str = None): """Helper to create a user via register.""" client = TestClient(app) if email is None: email = f"{username}@example.com" resp = client.post( "/api/auth/register", json={ "username": username, "email": email, "password": password, "date_of_birth": "2000-01-01", }, ) assert resp.status_code == 201 return resp.json()["access_token"] def test_login_success(self): """Correct credentials return a token.""" self._create_user("loginuser", "correctpass") client = TestClient(app) resp = client.post( "/api/auth/login", data={"username": "loginuser", "password": "correctpass"}, ) assert resp.status_code == 200 data = resp.json() assert "access_token" in data assert data["token_type"] == "bearer" def test_login_wrong_password(self): """Wrong password returns 401.""" self._create_user("loginuser", "correctpass") client = TestClient(app) resp = client.post( "/api/auth/login", data={"username": "loginuser", "password": "wrongpass"}, ) assert resp.status_code == 401 assert "Incorrect username or password" in resp.json()["detail"] def test_login_nonexistent_user(self): """Non-existent user returns 401 (not 404).""" client = TestClient(app) resp = client.post( "/api/auth/login", data={"username": "nobody", "password": "any"}, ) assert resp.status_code == 401 assert "Incorrect username or password" in resp.json()["detail"] def test_login_accepts_form_encoded(self): """Login accepts application/x-www-form-urlencoded (OAuth2).""" self._create_user("formuser", "formpass") client = TestClient(app) resp = client.post( "/api/auth/login", data={"username": "formuser", "password": "formpass"}, ) assert resp.status_code == 200 assert "access_token" in resp.json() def test_login_token_is_valid_jwt(self): """The returned token is a valid JWT that can be decoded.""" self._create_user("jwttoken", "jwt123") client = TestClient(app) resp = client.post( "/api/auth/login", data={"username": "jwttoken", "password": "jwt123"}, ) token = resp.json()["access_token"] # Verify it's a 3-part JWT parts = token.split(".") assert len(parts) == 3