"""Unit tests for api_client.py's own plumbing (cache eviction, single-flight) -- as opposed to test_content.py, which exercises it indirectly through the rendered pages via the `client` fixture's monkeypatched api_client functions. """ import threading import time import types import api_client def setup_function(_): api_client._cache.clear() api_client._inflight.clear() def test_cache_eviction_is_lru(monkeypatch): monkeypatch.setattr(api_client, "_CACHE_MAX_ENTRIES", 3) for i in range(3): api_client._cache_put(f"k{i}", i) # Touch k0 so it's most-recently-used; k1 is now the least-recently-used # entry and should be the one evicted when the cap is exceeded. api_client._cache_get("k0") api_client._cache_put("k3", 3) assert api_client._cache_get("k1") is None assert api_client._cache_get("k0") == 0 assert api_client._cache_get("k2") == 2 assert api_client._cache_get("k3") == 3 assert len(api_client._cache) == 3 def test_cache_entries_expire_after_ttl(monkeypatch): monkeypatch.setattr(api_client, "_TTL", 0.01) api_client._cache_put("k", "v") assert api_client._cache_get("k") == "v" time.sleep(0.02) assert api_client._cache_get("k") is None def test_single_flight_dedupes_concurrent_misses(monkeypatch): """N concurrent misses on the same key must reach the backend once, not N times -- every caller gets the same result either way.""" calls = [] release = threading.Event() class _FakeResp: def raise_for_status(self): pass def json(self): return {"ok": True} def fake_get(path, headers=None): calls.append(path) release.wait(timeout=2) return _FakeResp() monkeypatch.setattr(api_client, "_client", types.SimpleNamespace(get=fake_get)) results = [] results_lock = threading.Lock() def worker(): r = api_client._get("/thing") with results_lock: results.append(r) threads = [threading.Thread(target=worker) for _ in range(5)] for t in threads: t.start() time.sleep(0.1) # let every thread reach (and block behind) the first fetch release.set() for t in threads: t.join(timeout=2) assert len(calls) == 1 # exactly one backend call for 5 concurrent misses assert results == [{"ok": True}] * 5 assert api_client._cache_get("/thing") == {"ok": True} def test_single_flight_clears_inflight_entry_after_completion(monkeypatch): class _FakeResp: def raise_for_status(self): pass def json(self): return {"n": 1} monkeypatch.setattr(api_client, "_client", types.SimpleNamespace(get=lambda path, headers=None: _FakeResp())) api_client._get("/thing") assert "/thing" not in api_client._inflight