import pytest import uuid def unique_email(prefix: str = "test") -> str: """Generate a unique email for tests sharing the same database.""" return f"{prefix}-{uuid.uuid4().hex[:8]}@example.com" async def create_user_and_get_token(client, email: str = None, password: str = "testpass123") -> str: """Helper to create a user and return their auth token.""" if email is None: email = unique_email() response = await client.post( "/api/auth/register", json={"email": email, "password": password}, ) return response.json()["access_token"] def auth_header(token: str) -> dict: """Helper to create auth headers from token.""" return {"Authorization": f"Bearer {token}"} # Registration tests @pytest.mark.asyncio async def test_register_success(client): email = unique_email("register") response = await client.post( "/api/auth/register", json={"email": email, "password": "password123"}, ) assert response.status_code == 200 data = response.json() assert "access_token" in data assert data["token_type"] == "bearer" assert data["user"]["email"] == email assert "id" in data["user"] @pytest.mark.asyncio async def test_register_duplicate_email(client): email = unique_email("duplicate") await client.post( "/api/auth/register", json={"email": email, "password": "password123"}, ) response = await client.post( "/api/auth/register", json={"email": email, "password": "differentpass"}, ) assert response.status_code == 400 assert response.json()["detail"] == "Email already registered" @pytest.mark.asyncio async def test_register_invalid_email(client): response = await client.post( "/api/auth/register", json={"email": "notanemail", "password": "password123"}, ) assert response.status_code == 422 @pytest.mark.asyncio async def test_register_missing_password(client): response = await client.post( "/api/auth/register", json={"email": unique_email()}, ) assert response.status_code == 422 @pytest.mark.asyncio async def test_register_missing_email(client): response = await client.post( "/api/auth/register", json={"password": "password123"}, ) assert response.status_code == 422 @pytest.mark.asyncio async def test_register_empty_body(client): response = await client.post("/api/auth/register", json={}) assert response.status_code == 422 # Login tests @pytest.mark.asyncio async def test_login_success(client): email = unique_email("login") await client.post( "/api/auth/register", json={"email": email, "password": "password123"}, ) response = await client.post( "/api/auth/login", json={"email": email, "password": "password123"}, ) assert response.status_code == 200 data = response.json() assert "access_token" in data assert data["token_type"] == "bearer" assert data["user"]["email"] == email @pytest.mark.asyncio async def test_login_wrong_password(client): email = unique_email("wrongpass") await client.post( "/api/auth/register", json={"email": email, "password": "correctpassword"}, ) response = await client.post( "/api/auth/login", json={"email": email, "password": "wrongpassword"}, ) assert response.status_code == 401 assert response.json()["detail"] == "Incorrect email or password" @pytest.mark.asyncio async def test_login_nonexistent_user(client): response = await client.post( "/api/auth/login", json={"email": unique_email("nonexistent"), "password": "password123"}, ) assert response.status_code == 401 assert response.json()["detail"] == "Incorrect email or password" @pytest.mark.asyncio async def test_login_invalid_email_format(client): response = await client.post( "/api/auth/login", json={"email": "invalidemail", "password": "password123"}, ) assert response.status_code == 422 @pytest.mark.asyncio async def test_login_missing_fields(client): response = await client.post("/api/auth/login", json={}) assert response.status_code == 422 # Get current user tests @pytest.mark.asyncio async def test_get_me_success(client): email = unique_email("me") token = await create_user_and_get_token(client, email) response = await client.get("/api/auth/me", headers=auth_header(token)) assert response.status_code == 200 data = response.json() assert data["email"] == email assert "id" in data @pytest.mark.asyncio async def test_get_me_no_token(client): response = await client.get("/api/auth/me") # HTTPBearer returns 401/403 when credentials are missing assert response.status_code in [401, 403] @pytest.mark.asyncio async def test_get_me_invalid_token(client): response = await client.get( "/api/auth/me", headers={"Authorization": "Bearer invalidtoken123"}, ) assert response.status_code == 401 assert response.json()["detail"] == "Invalid authentication credentials" @pytest.mark.asyncio async def test_get_me_malformed_auth_header(client): response = await client.get( "/api/auth/me", headers={"Authorization": "NotBearer token123"}, ) # Invalid scheme returns 401/403 assert response.status_code in [401, 403] @pytest.mark.asyncio async def test_get_me_expired_token(client): response = await client.get( "/api/auth/me", headers={"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsImV4cCI6MH0.invalid"}, ) assert response.status_code == 401 # Token validation tests @pytest.mark.asyncio async def test_token_from_register_works_for_me(client): email = unique_email("tokentest") register_response = await client.post( "/api/auth/register", json={"email": email, "password": "password123"}, ) token = register_response.json()["access_token"] me_response = await client.get("/api/auth/me", headers=auth_header(token)) assert me_response.status_code == 200 assert me_response.json()["email"] == email @pytest.mark.asyncio async def test_token_from_login_works_for_me(client): email = unique_email("logintoken") await client.post( "/api/auth/register", json={"email": email, "password": "password123"}, ) login_response = await client.post( "/api/auth/login", json={"email": email, "password": "password123"}, ) token = login_response.json()["access_token"] me_response = await client.get("/api/auth/me", headers=auth_header(token)) assert me_response.status_code == 200 assert me_response.json()["email"] == email # Multiple users tests @pytest.mark.asyncio async def test_multiple_users_isolated(client): email1 = unique_email("user1") email2 = unique_email("user2") resp1 = await client.post( "/api/auth/register", json={"email": email1, "password": "password1"}, ) resp2 = await client.post( "/api/auth/register", json={"email": email2, "password": "password2"}, ) token1 = resp1.json()["access_token"] token2 = resp2.json()["access_token"] me1 = await client.get("/api/auth/me", headers=auth_header(token1)) me2 = await client.get("/api/auth/me", headers=auth_header(token2)) assert me1.json()["email"] == email1 assert me2.json()["email"] == email2 assert me1.json()["id"] != me2.json()["id"] # Password tests @pytest.mark.asyncio async def test_password_is_hashed(client): email = unique_email("hashtest") await client.post( "/api/auth/register", json={"email": email, "password": "mySecurePassword123"}, ) response = await client.post( "/api/auth/login", json={"email": email, "password": "mySecurePassword123"}, ) assert response.status_code == 200 @pytest.mark.asyncio async def test_case_sensitive_password(client): email = unique_email("casetest") await client.post( "/api/auth/register", json={"email": email, "password": "Password123"}, ) response = await client.post( "/api/auth/login", json={"email": email, "password": "password123"}, ) assert response.status_code == 401