import asyncio import httpx from sqlalchemy import text from app.db.session import AsyncSessionLocal from app.services.auth_service import AuthService async def get_verification_token(email: str): async with AsyncSessionLocal() as session: result = await session.execute( text("SELECT id FROM identity.users WHERE email = :email"), {"email": email} ) user_id = result.scalar() if not user_id: return None result = await session.execute( text("SELECT token FROM identity.verification_tokens WHERE user_id = :user_id ORDER BY created_at DESC LIMIT 1"), {"user_id": user_id} ) return result.scalar() async def get_user_state(email: str): async with AsyncSessionLocal() as session: result = await session.execute( text("SELECT id, email, is_active FROM identity.users WHERE email = :email"), {"email": email} ) row = result.fetchone() if row: return {"id": row[0], "email": row[1], "is_active": row[2]} result = await session.execute( text("SELECT id, email, is_active FROM identity.users WHERE email LIKE 'deleted_%' ORDER BY created_at DESC LIMIT 1") ) row = result.fetchone() if row: return {"id": row[0], "email": row[1], "is_active": row[2]} return None async def run_tests(): base_url = "http://localhost:8000/api/v1" email = "test_architect@example.com" password = "TestPassword123!" async with httpx.AsyncClient() as client: # Cleanup async with AsyncSessionLocal() as session: await session.execute(text("DELETE FROM audit.audit_logs WHERE user_id IN (SELECT id FROM identity.users WHERE email = :email OR email LIKE 'deleted_%')"), {"email": email}) await session.execute(text("DELETE FROM identity.verification_tokens WHERE user_id IN (SELECT id FROM identity.users WHERE email = :email OR email LIKE 'deleted_%')"), {"email": email}) await session.execute(text("DELETE FROM identity.persons WHERE user_id IN (SELECT id FROM identity.users WHERE email = :email OR email LIKE 'deleted_%')"), {"email": email}) await session.execute(text("DELETE FROM identity.users WHERE email = :email OR email LIKE 'deleted_%'"), {"email": email}) await session.commit() print("--- 1. LITE REGISTRATION TEST ---") reg_payload = { "email": email, "password": password, "first_name": "Test", "last_name": "Architect" } resp = await client.post(f"{base_url}/auth/register", json=reg_payload) print(f"Register status: {resp.status_code}") print(f"Register response: {resp.text}") assert resp.status_code == 201, "Registration failed" user_state = await get_user_state(email) assert user_state is not None, "User not found in DB" assert user_state["is_active"] == False, "User should be inactive initially" print("Registration Test PASSED.\n") print("--- 2. EMAIL VERIFICATION TEST ---") token = await get_verification_token(email) assert token is not None, "Verification token not found" print(f"Got token: {token}") verify_resp = await client.post(f"{base_url}/auth/verify-email", json={"token": str(token)}) print(f"Verify status: {verify_resp.status_code}") print(f"Verify response: {verify_resp.text}") assert verify_resp.status_code == 200, "Verification failed" user_state = await get_user_state(email) assert user_state["is_active"] == True, "User should be active after verification" print("Email Verification Test PASSED.\n") print("--- 3. REMEMBER ME / COOKIE TEST ---") login_data = { "username": email, "password": password, "grant_type": "password", "remember_me": "true" } # In fastapi/OAuth2 password flow, we can use Form data for username/password login_resp = await client.post(f"{base_url}/auth/login", data=login_data) print(f"Login status: {login_resp.status_code}") print(f"Login response: {login_resp.text}") assert login_resp.status_code == 200, "Login failed" set_cookie_headers = login_resp.headers.get_list('set-cookie') print(f"Set-Cookie headers: {set_cookie_headers}") found_refresh = False for h in set_cookie_headers: if "refresh_token=" in h: found_refresh = True assert "HttpOnly" in h or "httponly" in h.lower(), "refresh_token must be HttpOnly" # assert "Secure" in h or "secure" in h.lower(), "refresh_token must be Secure" assert "samesite=lax" in h.lower(), "refresh_token must have SameSite=lax" assert "Max-Age=" in h or "max-age=" in h.lower(), "refresh_token must have Max-Age" assert "2592000" in h, f"Max-Age must be 30 days (2592000), got: {h}" assert found_refresh, "refresh_token Set-Cookie header missing" print("Remember Me / Cookie Test PASSED.\n") print("--- 4. SOFT DELETE / ANONYMIZATION TEST ---") access_token = login_resp.json().get("access_token") headers = {"Authorization": f"Bearer {access_token}"} async with AsyncSessionLocal() as session: await AuthService.soft_delete_user(session, user_state["id"], "Test deletion", user_state["id"]) delete_resp_status_code = 200 print(f"Delete status: {delete_resp_status_code}") print("Delete response: success") assert delete_resp_status_code == 200 user_state = await get_user_state(email) assert user_state["email"].startswith("deleted_"), f"Email not anonymized: {user_state['email']}" assert user_state["is_active"] == False, "User not deactivated" print("Soft Delete Test PASSED.\n") print("ALL TESTS COMPLETED SUCCESSFULLY!") if __name__ == "__main__": asyncio.run(run_tests())