164 lines
5.4 KiB
Python
164 lines
5.4 KiB
Python
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)
|