Files
service-finder/tests/active/test_auth_e2e.py

135 lines
6.1 KiB
Python

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())