"""Integration tests for registration/login (section 17, additive) -- the first authenticated-user flow in Aurora Core. Exercised at the HTTP layer (register -> login -> /me with the returned bearer token) rather than just the service layer, since that's the actual contract a frontend integrates against.""" from __future__ import annotations import pytest from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession @pytest.mark.asyncio async def test_register_returns_a_usable_access_token(client: AsyncClient, db_session: AsyncSession): response = await client.post( "/api/v1/auth/register", json={"email": "neo@zion.net", "password": "wake-up-neo", "display_name": "Neo"}, ) assert response.status_code == 201 token = response.json()["access_token"] assert response.json()["token_type"] == "bearer" me = await client.get("/api/v1/auth/me", headers={"Authorization": f"Bearer {token}"}) assert me.status_code == 200 body = me.json() assert body["email"] == "neo@zion.net" assert body["display_name"] == "Neo" assert body["id"].startswith("aur_user_") @pytest.mark.asyncio async def test_register_normalizes_email_case(client: AsyncClient, db_session: AsyncSession): response = await client.post( "/api/v1/auth/register", json={"email": " Trinity@Zion.NET ", "password": "follow-the-white-rabbit"}, ) assert response.status_code == 201 token = response.json()["access_token"] me = await client.get("/api/v1/auth/me", headers={"Authorization": f"Bearer {token}"}) assert me.json()["email"] == "trinity@zion.net" @pytest.mark.asyncio async def test_register_rejects_duplicate_email(client: AsyncClient, db_session: AsyncSession): await client.post( "/api/v1/auth/register", json={"email": "morpheus@zion.net", "password": "red-pill-please"} ) response = await client.post( "/api/v1/auth/register", json={"email": "morpheus@zion.net", "password": "a-different-password"}, ) assert response.status_code == 409 assert response.json()["error"]["code"] == "email_already_registered" @pytest.mark.asyncio async def test_register_rejects_duplicate_email_case_insensitively( client: AsyncClient, db_session: AsyncSession ): await client.post( "/api/v1/auth/register", json={"email": "cypher@zion.net", "password": "i-want-steak"} ) response = await client.post( "/api/v1/auth/register", json={"email": "CYPHER@ZION.NET", "password": "something-else"} ) assert response.status_code == 409 @pytest.mark.asyncio async def test_register_rejects_short_password(client: AsyncClient, db_session: AsyncSession): response = await client.post( "/api/v1/auth/register", json={"email": "short@zion.net", "password": "short"} ) assert response.status_code == 422 @pytest.mark.asyncio async def test_register_rejects_malformed_email(client: AsyncClient, db_session: AsyncSession): response = await client.post( "/api/v1/auth/register", json={"email": "not-an-email", "password": "whatever-password"} ) assert response.status_code == 422 @pytest.mark.asyncio async def test_login_succeeds_with_correct_credentials(client: AsyncClient, db_session: AsyncSession): await client.post( "/api/v1/auth/register", json={"email": "tank@zion.net", "password": "operator-standing-by"} ) response = await client.post( "/api/v1/auth/login", json={"email": "tank@zion.net", "password": "operator-standing-by"} ) assert response.status_code == 200 assert "access_token" in response.json() @pytest.mark.asyncio async def test_login_rejects_wrong_password(client: AsyncClient, db_session: AsyncSession): await client.post( "/api/v1/auth/register", json={"email": "dozer@zion.net", "password": "the-real-password"} ) response = await client.post( "/api/v1/auth/login", json={"email": "dozer@zion.net", "password": "wrong-password-here"} ) assert response.status_code == 401 assert response.json()["error"]["code"] == "invalid_credentials" @pytest.mark.asyncio async def test_login_rejects_unknown_email_with_the_same_error_as_wrong_password( client: AsyncClient, db_session: AsyncSession ): # Same status code and error code as a wrong password -- see # app/services/user_service.py:authenticate_user. Distinguishing "no # such account" from "wrong password" would let a client enumerate # registered emails. response = await client.post( "/api/v1/auth/login", json={"email": "nobody@zion.net", "password": "does-not-matter"} ) assert response.status_code == 401 assert response.json()["error"]["code"] == "invalid_credentials" @pytest.mark.asyncio async def test_me_rejects_missing_token(client: AsyncClient): response = await client.get("/api/v1/auth/me") assert response.status_code == 401 @pytest.mark.asyncio async def test_me_rejects_garbage_token(client: AsyncClient): response = await client.get( "/api/v1/auth/me", headers={"Authorization": "Bearer not-a-real-token"} ) assert response.status_code == 401 @pytest.mark.asyncio async def test_me_rejects_a_token_for_a_different_secret(client: AsyncClient): import jwt forged = jwt.encode({"sub": "00000000-0000-0000-0000-000000000000"}, "wrong-secret", algorithm="HS256") response = await client.get("/api/v1/auth/me", headers={"Authorization": f"Bearer {forged}"}) assert response.status_code == 401