async def test_login_refresh_logout_cycle(client, user): r = await client.post("/api/v1/auth/login", json=user) assert r.status_code == 200, r.text pair = r.json() assert pair["token_type"] == "bearer" me = await client.get( "/api/v1/auth/me", headers={"Authorization": f"Bearer {pair['access_token']}"} ) assert me.status_code == 200 assert me.json()["email"] == user["email"] # rotation: the refresh token is single-use r2 = await client.post("/api/v1/auth/refresh", json={"refresh_token": pair["refresh_token"]}) assert r2.status_code == 200 r3 = await client.post("/api/v1/auth/refresh", json={"refresh_token": pair["refresh_token"]}) assert r3.status_code == 401 assert r3.headers["content-type"].startswith("application/problem+json") new_refresh = r2.json()["refresh_token"] assert ( await client.post("/api/v1/auth/logout", json={"refresh_token": new_refresh}) ).status_code == 204 assert ( await client.post("/api/v1/auth/refresh", json={"refresh_token": new_refresh}) ).status_code == 401 async def test_wrong_password_and_rate_limit(client, user): bad = {"email": user["email"], "password": "nope"} for _ in range(5): r = await client.post("/api/v1/auth/login", json=bad) assert r.status_code == 401 r = await client.post("/api/v1/auth/login", json=bad) assert r.status_code == 429 assert "retry-after" in r.headers async def test_protected_routes_need_token(client): r = await client.get("/api/v1/auth/me") assert r.status_code == 401 r = await client.get("/api/v1/auth/me", headers={"Authorization": "Bearer garbage"}) assert r.status_code == 401