"""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)