数据库与模型:新增多版迁移(章节证据快照、对话血缘、记忆事实/时间线 lineage 等),把「成稿 ↔ 对话/记忆」的溯源信息落到表结构里。 业务链路:会话与 WS、回忆录/故事流水线、记忆写入与 enrichment 等跟着接上线索与快照;新增章节证据快照与评测侧 EvalTraceService 等模块,方便组评审用的证据包。 内部评测:自动化 run 与手工 memoir 评审共用可追溯证据;rubric/ judge 相关脚本与文档有配套调整。 app-eval-web:Memoir/实验详情里能展开看证据摘要与 evidence_trace(含对话轮次 id);Vite 代理与 development.sh 注入的 API 端口与当前默认内部评测端口一致,避免改端口后页面连错服务。 工程杂项:GitHub Actions / 仓库说明有更新;各适配器与支付/配额/plan 等多处为小改动或跟随主改动的收尾;新增/扩充了?
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
|