Files
qzl 18b5e876ee test: 修复所有预存的失败测试
- test_auth_routes: monkeypatch 环境为 production 使 phone-session 限速生效
- test_schedule_items_routes: 补充必填 timezone 字段
- test_llm_pricing_service: 更新 deepseek-chat 费率期望值匹配实际 catalog
- test_sse_flow_live: 补充 runId 查询参数、改用附件上传 API、修复 history 响应断言、独立 DB session 避免跨事件循环崩溃
- test_agent_prompt: 移除已删除的 project_cli_defaults 断言
- test_toolkit: 更新 action card 断言匹配 module/method 格式
2026-04-24 14:11:11 +08:00

195 lines
5.8 KiB
Python

from __future__ import annotations
from typing import Callable
import pytest
from fastapi import HTTPException
from fastapi.testclient import TestClient
from app import app
from v1.auth.dependencies import get_auth_service
from v1.auth.rate_limit import reset_rate_limit_state
from v1.auth.schemas import (
AuthUser,
OtpSendRequest,
PhoneSessionCreateRequest,
SessionRefreshRequest,
SessionResponse,
)
from v1.auth.service import AuthService
@pytest.fixture(autouse=True)
def reset_auth_rate_limit_state() -> None:
reset_rate_limit_state()
@pytest.fixture(autouse=True)
def force_in_memory_rate_limit(monkeypatch: pytest.MonkeyPatch) -> None:
async def _raise_redis_unavailable() -> None:
raise RuntimeError("redis unavailable in integration tests")
monkeypatch.setattr(
"v1.auth.rate_limit.get_or_init_redis_client",
_raise_redis_unavailable,
)
class FakeAuthService(AuthService):
def __init__(self, token_response: SessionResponse) -> None:
self._token_response = token_response
async def send_otp(self, request: OtpSendRequest) -> None:
if request.phone == "+8613811111111":
raise HTTPException(status_code=401, detail="Invalid verification code")
return None
async def create_phone_session(
self, request: PhoneSessionCreateRequest
) -> SessionResponse:
if request.token == "000000":
raise HTTPException(status_code=401, detail="Invalid verification code")
return self._token_response
async def refresh_session(self, request: SessionRefreshRequest) -> SessionResponse:
raise HTTPException(status_code=401, detail="Invalid refresh token")
async def delete_session(self, refresh_token: str | None) -> None:
return None
def _override_auth_service(service: AuthService) -> Callable[[], AuthService]:
def _get_service() -> AuthService:
return service
return _get_service
def _token_response() -> SessionResponse:
user = AuthUser(id="user-1", phone="+8613812345678")
return SessionResponse(
access_token="access",
refresh_token="refresh",
expires_in=3600,
token_type="bearer",
user=user,
)
def test_send_otp_returns_204() -> None:
app.dependency_overrides[get_auth_service] = _override_auth_service(
FakeAuthService(_token_response())
)
client = TestClient(app)
try:
response = client.post(
"/api/v1/auth/otp/send",
json={"phone": "+8613812345678"},
)
assert response.status_code == 204
finally:
app.dependency_overrides = {}
def test_phone_session_returns_token_response() -> None:
app.dependency_overrides[get_auth_service] = _override_auth_service(
FakeAuthService(_token_response())
)
client = TestClient(app)
try:
response = client.post(
"/api/v1/auth/phone-session",
json={"phone": "+8613812345678", "token": "123456"},
)
assert response.status_code == 200
body = response.json()
assert body["access_token"] == "access"
assert body["refresh_token"] == "refresh"
assert body["user"]["phone"] == "+8613812345678"
finally:
app.dependency_overrides = {}
def test_phone_session_invalid_token_returns_problem_details() -> None:
app.dependency_overrides[get_auth_service] = _override_auth_service(
FakeAuthService(_token_response())
)
client = TestClient(app)
try:
response = client.post(
"/api/v1/auth/phone-session",
json={"phone": "+8613812345678", "token": "000000"},
)
assert response.status_code == 401
assert response.headers["content-type"].startswith("application/problem+json")
finally:
app.dependency_overrides = {}
def test_legacy_routes_are_removed() -> None:
app.dependency_overrides[get_auth_service] = _override_auth_service(
FakeAuthService(_token_response())
)
client = TestClient(app)
try:
assert client.post("/api/v1/auth/verifications", json={}).status_code == 404
assert client.post("/api/v1/auth/verify", json={}).status_code == 404
assert client.post("/api/v1/auth/resend", json={}).status_code == 404
assert client.post("/api/v1/auth/sessions", json={}).status_code == 405
finally:
app.dependency_overrides = {}
def test_send_otp_phone_rate_limited_after_too_many_attempts() -> None:
app.dependency_overrides[get_auth_service] = _override_auth_service(
FakeAuthService(_token_response())
)
client = TestClient(app)
try:
for _ in range(3):
ok = client.post(
"/api/v1/auth/otp/send",
json={"phone": "+8613812345678"},
)
assert ok.status_code == 204
blocked = client.post(
"/api/v1/auth/otp/send",
json={"phone": "+8613812345678"},
)
assert blocked.status_code == 429
finally:
app.dependency_overrides = {}
def test_phone_session_rate_limited_after_too_many_attempts(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr("v1.auth.router.config.runtime.environment", "production")
app.dependency_overrides[get_auth_service] = _override_auth_service(
FakeAuthService(_token_response())
)
client = TestClient(app)
try:
for _ in range(6):
blocked = client.post(
"/api/v1/auth/phone-session",
json={"phone": "+8613812345678", "token": "000000"},
)
assert blocked.status_code == 401
blocked = client.post(
"/api/v1/auth/phone-session",
json={"phone": "+8613812345678", "token": "000000"},
)
assert blocked.status_code == 429
finally:
app.dependency_overrides = {}