"""Tests for the in-memory rate limiter.""" from __future__ import annotations import asyncio import time import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from src.web.rate_limit import _reset_buckets, rate_limit @pytest.fixture(autouse=True) def reset_buckets(): _reset_buckets() yield _reset_buckets() def _make_app(max_calls: int, window: float) -> FastAPI: app = FastAPI() @app.get("/limited", dependencies=[pytest.importorskip("fastapi").Depends(rate_limit(max_calls, window))]) async def _route(): return {"ok": True} return app def test_allows_within_window(): app = _make_app(3, 60.0) client = TestClient(app) for _ in range(3): assert client.get("/limited").status_code == 200 def test_blocks_when_exceeded(): app = _make_app(2, 60.0) client = TestClient(app) assert client.get("/limited").status_code == 200 assert client.get("/limited").status_code == 200 response = client.get("/limited") assert response.status_code == 429 assert response.headers.get("retry-after") is not None def test_resets_after_window(): app = _make_app(1, 0.1) client = TestClient(app) assert client.get("/limited").status_code == 200 assert client.get("/limited").status_code == 429 time.sleep(0.15) assert client.get("/limited").status_code == 200 def test_returns_dependency_callable(): """Direct unit test on the closure (no FastAPI dependency injection).""" dep = rate_limit(2, 60.0) assert asyncio.iscoroutinefunction(dep)