2026-02-05 15:13:06 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import socket
|
|
|
|
|
import threading
|
|
|
|
|
import time
|
|
|
|
|
from uuid import UUID
|
|
|
|
|
|
|
|
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
|
import uvicorn
|
|
|
|
|
|
|
|
|
|
from app import app
|
|
|
|
|
from core.auth.models import CurrentUser
|
2026-02-26 14:43:52 +08:00
|
|
|
from v1.users.dependencies import get_current_user, get_user_service
|
|
|
|
|
from v1.users.schemas import UserResponse, UserUpdateRequest
|
2026-02-05 15:13:06 +08:00
|
|
|
|
|
|
|
|
|
2026-02-26 14:43:52 +08:00
|
|
|
class FakeUserService:
|
2026-02-05 15:13:06 +08:00
|
|
|
"""Fake service for E2E testing."""
|
|
|
|
|
|
2026-02-26 14:43:52 +08:00
|
|
|
def __init__(self, user: UserResponse) -> None:
|
|
|
|
|
self._user = user
|
2026-02-05 15:13:06 +08:00
|
|
|
|
2026-02-26 14:43:52 +08:00
|
|
|
async def get_me(self) -> UserResponse:
|
|
|
|
|
return self._user
|
2026-02-05 15:13:06 +08:00
|
|
|
|
2026-02-26 14:43:52 +08:00
|
|
|
async def update_me(self, update: UserUpdateRequest) -> UserResponse:
|
|
|
|
|
return UserResponse(
|
|
|
|
|
id=self._user.id,
|
2026-02-25 10:20:43 +08:00
|
|
|
username=(
|
2026-02-26 14:43:52 +08:00
|
|
|
update.username if update.username is not None else self._user.username
|
2026-02-05 15:13:06 +08:00
|
|
|
),
|
|
|
|
|
avatar_url=(
|
|
|
|
|
update.avatar_url
|
|
|
|
|
if update.avatar_url is not None
|
2026-02-26 14:43:52 +08:00
|
|
|
else self._user.avatar_url
|
2026-02-05 15:13:06 +08:00
|
|
|
),
|
2026-02-26 14:43:52 +08:00
|
|
|
bio=update.bio if update.bio is not None else self._user.bio,
|
2026-02-05 15:13:06 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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_profile_flow_e2e() -> None:
|
|
|
|
|
user_id = UUID("00000000-0000-0000-0000-000000000001")
|
2026-02-26 14:43:52 +08:00
|
|
|
user = UserResponse(
|
2026-02-05 15:13:06 +08:00
|
|
|
id=str(user_id),
|
|
|
|
|
username="demo",
|
|
|
|
|
avatar_url=None,
|
|
|
|
|
bio=None,
|
|
|
|
|
)
|
2026-02-26 14:43:52 +08:00
|
|
|
app.dependency_overrides[get_user_service] = lambda: FakeUserService(user) # type: ignore[return-value]
|
2026-02-05 15:13:06 +08:00
|
|
|
app.dependency_overrides[get_current_user] = lambda: CurrentUser(id=user_id)
|
|
|
|
|
|
|
|
|
|
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:
|
2026-02-26 14:43:52 +08:00
|
|
|
me = request_context.get("/api/v1/users/me")
|
2026-02-05 15:13:06 +08:00
|
|
|
assert me.status == 200
|
|
|
|
|
assert me.json()["username"] == "demo"
|
|
|
|
|
|
|
|
|
|
updated = request_context.patch(
|
2026-02-26 14:43:52 +08:00
|
|
|
"/api/v1/users/me",
|
2026-02-25 10:20:43 +08:00
|
|
|
data=json.dumps({"username": "updated"}),
|
2026-02-05 15:13:06 +08:00
|
|
|
headers={"Content-Type": "application/json"},
|
|
|
|
|
)
|
|
|
|
|
assert updated.status == 200
|
2026-02-25 10:20:43 +08:00
|
|
|
assert updated.json()["username"] == "updated"
|
2026-02-05 15:13:06 +08:00
|
|
|
finally:
|
|
|
|
|
request_context.dispose()
|
|
|
|
|
finally:
|
|
|
|
|
app.dependency_overrides = {}
|
|
|
|
|
server.should_exit = True
|
|
|
|
|
thread.join(timeout=5)
|