101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
|
|
"""Auth HTTP 契约:注册 / 登录 / 刷新 / 受保护 /me(依赖注入 mock)。"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from unittest.mock import AsyncMock, MagicMock
|
|||
|
|
|
|||
|
|
import pytest
|
|||
|
|
from fastapi import FastAPI
|
|||
|
|
from httpx import ASGITransport, AsyncClient
|
|||
|
|
|
|||
|
|
from app.core.dependencies import get_current_user
|
|||
|
|
from app.features.auth.deps import get_auth_service
|
|||
|
|
from app.features.auth.router import router as auth_router
|
|||
|
|
from app.features.auth.service import AuthService
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.fixture
|
|||
|
|
def auth_app(make_test_user) -> FastAPI:
|
|||
|
|
app = FastAPI()
|
|||
|
|
app.include_router(auth_router)
|
|||
|
|
|
|||
|
|
mock_service = MagicMock(spec=AuthService)
|
|||
|
|
mock_service.register = AsyncMock(
|
|||
|
|
return_value={"access_token": "access-reg", "refresh_token": "refresh-reg"}
|
|||
|
|
)
|
|||
|
|
mock_service.login = AsyncMock(
|
|||
|
|
return_value={"access_token": "access-login", "refresh_token": "refresh-login"}
|
|||
|
|
)
|
|||
|
|
mock_service.refresh_tokens = AsyncMock(
|
|||
|
|
return_value={"access_token": "access-new", "refresh_token": "refresh-new"}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
app.dependency_overrides[get_auth_service] = lambda: mock_service
|
|||
|
|
app.dependency_overrides[get_current_user] = lambda: make_test_user()
|
|||
|
|
|
|||
|
|
app.state._mock_auth_service = mock_service
|
|||
|
|
return app
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.asyncio
|
|||
|
|
async def test_register_login_refresh_me(auth_app: FastAPI, unique_phone: str) -> None:
|
|||
|
|
transport = ASGITransport(app=auth_app)
|
|||
|
|
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|||
|
|
reg = await ac.post(
|
|||
|
|
"/api/auth/register",
|
|||
|
|
json={
|
|||
|
|
"phone": unique_phone,
|
|||
|
|
"password": "secret12",
|
|||
|
|
"nickname": "T",
|
|||
|
|
"agreed_to_terms": True,
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
assert reg.status_code == 201
|
|||
|
|
body = reg.json()
|
|||
|
|
assert body["access_token"] == "access-reg"
|
|||
|
|
|
|||
|
|
login = await ac.post(
|
|||
|
|
"/api/auth/login",
|
|||
|
|
json={
|
|||
|
|
"phone": unique_phone,
|
|||
|
|
"password": "secret12",
|
|||
|
|
"agreed_to_terms": True,
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
assert login.status_code == 200
|
|||
|
|
assert login.json()["access_token"] == "access-login"
|
|||
|
|
|
|||
|
|
ref = await ac.post(
|
|||
|
|
"/api/auth/refresh",
|
|||
|
|
json={"refresh_token": "refresh-login"},
|
|||
|
|
)
|
|||
|
|
assert ref.status_code == 200
|
|||
|
|
assert ref.json()["access_token"] == "access-new"
|
|||
|
|
|
|||
|
|
me = await ac.get(
|
|||
|
|
"/api/auth/me",
|
|||
|
|
headers={"Authorization": "Bearer any"},
|
|||
|
|
)
|
|||
|
|
assert me.status_code == 200
|
|||
|
|
assert me.json()["nickname"] == "测试用户"
|
|||
|
|
|
|||
|
|
svc: MagicMock = auth_app.state._mock_auth_service
|
|||
|
|
svc.register.assert_awaited_once()
|
|||
|
|
svc.login.assert_awaited_once()
|
|||
|
|
svc.refresh_tokens.assert_awaited_once_with(refresh_token="refresh-login")
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.asyncio
|
|||
|
|
async def test_me_without_auth_returns_401() -> None:
|
|||
|
|
app = FastAPI()
|
|||
|
|
app.include_router(auth_router)
|
|||
|
|
|
|||
|
|
mock_service = MagicMock(spec=AuthService)
|
|||
|
|
app.dependency_overrides[get_auth_service] = lambda: mock_service
|
|||
|
|
# 不覆盖 get_current_user — 使用真实 OAuth2,无 header 时应 401
|
|||
|
|
|
|||
|
|
transport = ASGITransport(app=app)
|
|||
|
|
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|||
|
|
r = await ac.get("/api/auth/me")
|
|||
|
|
assert r.status_code == 401
|