Files
2026-03-17 16:16:08 +00:00

178 lines
5.8 KiB
Python

"""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