"""Tests for auth endpoints: register, login, refresh, logout.""" import pytest from httpx import AsyncClient from tests.conftest import create_user_and_login @pytest.mark.asyncio async def test_register_success(client: AsyncClient): resp = await client.post( "/api/v1/auth/register", json={"email": "alice@example.com", "password": "s3cr3t", "full_name": "Alice"}, ) assert resp.status_code == 201 data = resp.json() assert data["email"] == "alice@example.com" assert data["full_name"] == "Alice" assert "id" in data assert "hashed_password" not in data @pytest.mark.asyncio async def test_register_duplicate_email(client: AsyncClient): payload = {"email": "dup@example.com", "password": "pass", "full_name": "Dup"} await client.post("/api/v1/auth/register", json=payload) resp = await client.post("/api/v1/auth/register", json=payload) assert resp.status_code == 409 @pytest.mark.asyncio async def test_register_creates_default_categories(client: AsyncClient): _, token = await create_user_and_login(client) resp = await client.get( "/api/v1/categories", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 cats = resp.json() assert len(cats) >= 6 names = {c["name"] for c in cats} assert "Alimentation" in names assert "Salaire" in names @pytest.mark.asyncio async def test_login_success(client: AsyncClient): await client.post( "/api/v1/auth/register", json={"email": "bob@example.com", "password": "mypass", "full_name": "Bob"}, ) resp = await client.post( "/api/v1/auth/login", data={"username": "bob@example.com", "password": "mypass"}, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) assert resp.status_code == 200 data = resp.json() assert "access_token" in data assert "refresh_token" in data assert data["token_type"] == "bearer" @pytest.mark.asyncio async def test_login_wrong_password(client: AsyncClient): await client.post( "/api/v1/auth/register", json={"email": "carol@example.com", "password": "correct", "full_name": "Carol"}, ) resp = await client.post( "/api/v1/auth/login", data={"username": "carol@example.com", "password": "wrong"}, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) assert resp.status_code == 401 @pytest.mark.asyncio async def test_login_unknown_email(client: AsyncClient): resp = await client.post( "/api/v1/auth/login", data={"username": "nobody@example.com", "password": "x"}, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) assert resp.status_code == 401 @pytest.mark.asyncio async def test_access_protected_route_with_valid_token(client: AsyncClient): _, token = await create_user_and_login(client) resp = await client.get( "/api/v1/categories", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 @pytest.mark.asyncio async def test_access_protected_route_without_token(client: AsyncClient): resp = await client.get("/api/v1/categories") assert resp.status_code == 403 # HTTPBearer returns 403 when no credentials @pytest.mark.asyncio async def test_access_protected_route_invalid_token(client: AsyncClient): resp = await client.get( "/api/v1/categories", headers={"Authorization": "Bearer invalidtoken"}, ) assert resp.status_code == 401 @pytest.mark.asyncio async def test_refresh_token(client: AsyncClient): await client.post( "/api/v1/auth/register", json={"email": "dave@example.com", "password": "pass", "full_name": "Dave"}, ) login_resp = await client.post( "/api/v1/auth/login", data={"username": "dave@example.com", "password": "pass"}, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) refresh_token = login_resp.json()["refresh_token"] resp = await client.post( "/api/v1/auth/refresh", json={"refresh_token": refresh_token}, ) assert resp.status_code == 200 assert "access_token" in resp.json() @pytest.mark.asyncio async def test_refresh_token_cannot_be_reused(client: AsyncClient): """Refresh token is revoked after use (rotation).""" await client.post( "/api/v1/auth/register", json={"email": "eve@example.com", "password": "pass", "full_name": "Eve"}, ) login_resp = await client.post( "/api/v1/auth/login", data={"username": "eve@example.com", "password": "pass"}, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) refresh_token = login_resp.json()["refresh_token"] # First use r1 = await client.post("/api/v1/auth/refresh", json={"refresh_token": refresh_token}) assert r1.status_code == 200 # Second use of the same token should fail r2 = await client.post("/api/v1/auth/refresh", json={"refresh_token": refresh_token}) assert r2.status_code == 401 @pytest.mark.asyncio async def test_logout(client: AsyncClient): _, token = await create_user_and_login(client, email="frank@example.com") login_resp = await client.post( "/api/v1/auth/login", data={"username": "frank@example.com", "password": "password123"}, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) refresh_token = login_resp.json()["refresh_token"] resp = await client.post( "/api/v1/auth/logout", json={"refresh_token": refresh_token}, headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 204 # Token should be revoked now r = await client.post("/api/v1/auth/refresh", json={"refresh_token": refresh_token}) assert r.status_code == 401