generated from kgod/ai-review-template
自内部仓库剥离深度优化与 RAG 知识库后的交付版本: - Builder 对话式简历生成(FSM + 意图路由 LLM 兜底增强) - 条目级轻度优化:事实覆盖门禁 + STAR/bullet 修复链,功能/简介/成果与技术栈同级保护 - 简历导入:DOCX/PDF 解析、结构归一、手机号脱敏 - PostgreSQL 运行时 + Alembic 迁移链 Co-Authored-By: Claude <noreply@anthropic.com>
91 lines
2.8 KiB
Python
91 lines
2.8 KiB
Python
"""Per-session sliding-window rate limiting for light optimization."""
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from app.fsm import FSMError
|
|
from app.optimization_models import OptimizationRunView
|
|
from app.rate_limit import SlidingWindowRateLimiter
|
|
from app.resume_routes import register_resume_routes
|
|
|
|
|
|
class FakeClock:
|
|
def __init__(self) -> None:
|
|
self.now = 1000.0
|
|
|
|
def __call__(self) -> float:
|
|
return self.now
|
|
|
|
|
|
def test_allows_up_to_limit_then_rejects() -> None:
|
|
clock = FakeClock()
|
|
limiter = SlidingWindowRateLimiter(limit=3, window_seconds=3600, clock=clock)
|
|
|
|
assert limiter.allow("s1") is True
|
|
assert limiter.allow("s1") is True
|
|
assert limiter.allow("s1") is True
|
|
assert limiter.allow("s1") is False
|
|
|
|
|
|
def test_window_slides_and_allows_again() -> None:
|
|
clock = FakeClock()
|
|
limiter = SlidingWindowRateLimiter(limit=2, window_seconds=3600, clock=clock)
|
|
|
|
assert limiter.allow("s1") is True
|
|
assert limiter.allow("s1") is True
|
|
assert limiter.allow("s1") is False
|
|
|
|
clock.now += 3601
|
|
assert limiter.allow("s1") is True
|
|
|
|
|
|
def test_sessions_are_independent() -> None:
|
|
limiter = SlidingWindowRateLimiter(
|
|
limit=1, window_seconds=3600, clock=FakeClock()
|
|
)
|
|
|
|
assert limiter.allow("s1") is True
|
|
assert limiter.allow("s2") is True
|
|
assert limiter.allow("s1") is False
|
|
|
|
|
|
def test_light_route_returns_429_without_calling_optimizer_when_limited() -> None:
|
|
view = OptimizationRunView(
|
|
id="r1",
|
|
mode="light",
|
|
status="proposal_pending",
|
|
entry_id="e1",
|
|
)
|
|
|
|
class StubAgent:
|
|
def __init__(self) -> None:
|
|
self.calls = 0
|
|
|
|
def optimize_light(
|
|
self, session_id: str, request: object
|
|
) -> OptimizationRunView:
|
|
self.calls += 1
|
|
return view
|
|
|
|
application = FastAPI()
|
|
|
|
@application.exception_handler(FSMError)
|
|
async def handle_fsm_error(_request: object, exc: FSMError) -> JSONResponse:
|
|
return JSONResponse(status_code=exc.status_code, content={"detail": exc.message})
|
|
application.state.light_opt_limiter = SlidingWindowRateLimiter(
|
|
limit=2, window_seconds=3600
|
|
)
|
|
agent = StubAgent()
|
|
register_resume_routes(application, agent, "/ai-api/resume-agent")
|
|
client = TestClient(application, raise_server_exceptions=False)
|
|
url = "/ai-api/resume-agent/sessions/s1/resume/optimize/light"
|
|
|
|
assert client.post(url, json={"entry_id": "e1"}).status_code == 200
|
|
assert client.post(url, json={"entry_id": "e1"}).status_code == 200
|
|
blocked = client.post(url, json={"entry_id": "e1"})
|
|
|
|
assert blocked.status_code == 429
|
|
assert blocked.json()["detail"] == "操作过于频繁,请稍后再试(轻度优化每小时最多 20 次)。"
|
|
assert agent.calls == 2
|