"""Regression test: RateLimitMiddleware must share its bucket across instances. When ``importlib.reload(cyclone.api)`` runs (e.g. from ``test_cors_extra_origins_via_env``), Starlette rebuilds the middleware stack with a NEW ``RateLimitMiddleware`` instance whose ``_buckets`` dict is fresh. Tests that imported ``app`` at module load time still reference the OLD app — and the OLD RateLimitMiddleware — so its private ``_buckets`` dict keeps accumulating requests across the rest of the suite. After ~300 requests it hits the rate limit and the remaining tests get spurious 429s. Fix: hoist ``_buckets`` to a class-level dict so every ``RateLimitMiddleware`` instance (current + stale) shares the same sliding-window state. The per-instance ``_lock`` stays per-instance since it guards mutation of the shared dict. This test pins that invariant: two ``RateLimitMiddleware`` instances share the same underlying bucket dict. """ from __future__ import annotations from cyclone.security import RateLimitMiddleware class _DummyApp: """Minimal ASGI app stand-in for the middleware's inner app.""" async def __call__(self, scope, receive, send): pass def test_rate_limit_buckets_are_shared_across_instances(): """Two RateLimitMiddleware instances must share _buckets.""" m1 = RateLimitMiddleware(_DummyApp()) m2 = RateLimitMiddleware(_DummyApp()) # After the fix: m1._buckets IS m2._buckets (same class-level dict). # Before the fix: each instance had its own {} dict. assert m1._buckets is m2._buckets, ( "RateLimitMiddleware instances do NOT share their bucket dict. " "After importlib.reload(cyclone.api), the new instance's " "_buckets is independent of the old one — orphaned buckets " "accumulate across tests that hold stale `app` references and " "trip the 300 req/60s limit. Hoist _buckets to a class-level " "dict so every instance shares state." ) def test_class_level_buckets_attribute_exists(): """The class must declare a class-level _buckets dict.""" # This is the structural pre-condition for the sharing invariant. assert hasattr(RateLimitMiddleware, "_buckets"), ( "RateLimitMiddleware must declare a class-level _buckets " "attribute so every instance shares it. Without this, " "importlib.reload(cyclone.api) creates an orphaned bucket." ) assert isinstance(RateLimitMiddleware._buckets, dict)