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.
This commit is contained in:
pyr0ball 2026-06-14 14:00:31 -07:00
parent 88b6943527
commit e85fb9bba3
2 changed files with 24 additions and 5 deletions

18
tests/conftest.py Normal file
View file

@ -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

View file

@ -5,11 +5,12 @@ from fastapi.testclient import TestClient
@pytest.fixture @pytest.fixture
def client(): def client(monkeypatch):
import sys import importlib
sys.path.insert(0, "/Library/Development/CircuitForge/peregrine/.worktrees/feature-vue-spa") monkeypatch.delenv("DEMO_MODE", raising=False)
from dev_api import app import dev_api
return TestClient(app) importlib.reload(dev_api)
return TestClient(dev_api.app)
# ── /api/jobs/{id}/research ───────────────────────────────────────────────── # ── /api/jobs/{id}/research ─────────────────────────────────────────────────