From e85fb9bba3404a7451b178d46b24854bb5226c48 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Sun, 14 Jun 2026 14:00:31 -0700 Subject: [PATCH] test: fix rate limiter cross-test contamination Each importlib.reload(dev_api) re-applies @limiter.limit() decorators to the shared slowapi Limiter singleton, accumulating stale registrations in _route_limits. One real HTTP request then triggered N limit-checks (N = reload count), exhausting per-hour budgets prematurely. Fix: conftest.py autouse fixture resets both _storage and _route_limits before each test, giving a clean slate regardless of prior reloads. Also updates test_dev_api_prep.py client fixture to use monkeypatch to clear DEMO_MODE + importlib.reload to get a fresh IS_DEMO module state (prevents 403 bleed from test_demo_guard.py tests running first). All 842 tests passing. --- tests/conftest.py | 18 ++++++++++++++++++ tests/test_dev_api_prep.py | 11 ++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..46bc87f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,18 @@ +"""Shared pytest fixtures for the Peregrine test suite.""" +import pytest + + +@pytest.fixture(autouse=True) +def reset_rate_limiter(): + """Reset slowapi state before each test. + + Each importlib.reload(dev_api) re-applies @limiter.limit() decorators, + accumulating stale registrations in _route_limits on the shared limiter + singleton. One real request then triggers N limit-checks (N = reload count), + exhausting per-hour budgets prematurely. Clearing both _storage and + _route_limits before each test gives each test a clean slate. + """ + from scripts.rate_limit import limiter + limiter._storage.reset() + limiter._route_limits.clear() + yield diff --git a/tests/test_dev_api_prep.py b/tests/test_dev_api_prep.py index 4a5f964..0584f3f 100644 --- a/tests/test_dev_api_prep.py +++ b/tests/test_dev_api_prep.py @@ -5,11 +5,12 @@ from fastapi.testclient import TestClient @pytest.fixture -def client(): - import sys - sys.path.insert(0, "/Library/Development/CircuitForge/peregrine/.worktrees/feature-vue-spa") - from dev_api import app - return TestClient(app) +def client(monkeypatch): + import importlib + monkeypatch.delenv("DEMO_MODE", raising=False) + import dev_api + importlib.reload(dev_api) + return TestClient(dev_api.app) # ── /api/jobs/{id}/research ─────────────────────────────────────────────────