from __future__ import annotations import json import socket import threading import time from playwright.sync_api import sync_playwright import uvicorn from app import app from v1.auth.dependencies import get_auth_service from v1.auth.schemas import ( AuthUser, SessionCreateRequest, SessionRefreshRequest, SessionResponse, VerificationCreateRequest, VerificationCreateResponse, VerificationResendRequest, VerificationVerifyRequest, ) from v1.auth.service import AuthService class FakeE2EAuthService(AuthService): def __init__(self) -> None: self._user = AuthUser(id="user-1", email="user@example.com") async def create_verification( self, request: VerificationCreateRequest ) -> VerificationCreateResponse: return VerificationCreateResponse(email=request.email) async def verify_verification( self, request: VerificationVerifyRequest ) -> SessionResponse: return SessionResponse( access_token="access-1", refresh_token="refresh-1", expires_in=3600, token_type="bearer", user=self._user, ) async def resend_verification(self, request: VerificationResendRequest) -> None: return None async def create_session(self, request: SessionCreateRequest) -> SessionResponse: return SessionResponse( access_token="access-2", refresh_token="refresh-2", expires_in=3600, token_type="bearer", user=self._user, ) async def refresh_session(self, request: SessionRefreshRequest) -> SessionResponse: return SessionResponse( access_token="access-3", refresh_token="refresh-3", expires_in=3600, token_type="bearer", user=self._user, ) async def delete_session(self, refresh_token: str | None) -> None: return None def _find_free_port() -> int: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.bind(("127.0.0.1", 0)) return sock.getsockname()[1] def _wait_for_port(host: str, port: int, timeout: float = 5.0) -> None: deadline = time.time() + timeout while time.time() < deadline: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: if sock.connect_ex((host, port)) == 0: return time.sleep(0.05) raise RuntimeError("Server did not start in time") def _start_server(host: str, port: int): config = uvicorn.Config(app, host=host, port=port, log_level="info") server = uvicorn.Server(config) thread = threading.Thread(target=server.run, daemon=True) thread.start() _wait_for_port(host, port) return server, thread def test_auth_flow_e2e() -> None: app.dependency_overrides[get_auth_service] = lambda: FakeE2EAuthService() host = "127.0.0.1" port = _find_free_port() server, thread = _start_server(host, port) try: with sync_playwright() as playwright: request_context = playwright.request.new_context( base_url=f"http://{host}:{port}" ) try: verification = request_context.post( "/api/v1/auth/verifications", data=json.dumps( { "username": "demo", "email": "user@example.com", "password": "secret123", } ), headers={"Content-Type": "application/json"}, ) assert verification.status == 202 verify = request_context.post( "/api/v1/auth/verifications/verify", data=json.dumps( { "email": "user@example.com", "token": "123456", } ), headers={"Content-Type": "application/json"}, ) assert verify.status == 200 assert verify.json()["access_token"] == "access-1" login = request_context.post( "/api/v1/auth/sessions", data=json.dumps( {"email": "user@example.com", "password": "secret123"} ), headers={"Content-Type": "application/json"}, ) assert login.status == 200 assert login.json()["access_token"] == "access-2" refresh = request_context.post( "/api/v1/auth/sessions/refresh", data=json.dumps({"refresh_token": "refresh-2"}), headers={"Content-Type": "application/json"}, ) assert refresh.status == 200 assert refresh.json()["access_token"] == "access-3" logout = request_context.delete( "/api/v1/auth/sessions", data=json.dumps({"refresh_token": "refresh-3"}), headers={"Content-Type": "application/json"}, ) assert logout.status == 204 finally: request_context.dispose() finally: app.dependency_overrides = {} server.should_exit = True thread.join(timeout=5)