"""Tests for the in-memory response cache.""" import time import pytest from app.services import cache @pytest.fixture(autouse=True) def clear_cache(): """Wipe cache state before each test.""" cache._store.clear() cache._index.clear() yield cache._store.clear() cache._index.clear() class TestCacheGetSet: def test_miss_on_empty_cache(self): assert cache.get("col-1", "hello") is None def test_set_then_get_returns_value(self): payload = {"response": "Hi", "sources": []} cache.set("col-1", "hello", payload) assert cache.get("col-1", "hello") == payload def test_different_collections_are_independent(self): cache.set("col-a", "query", {"response": "A"}) cache.set("col-b", "query", {"response": "B"}) assert cache.get("col-a", "query")["response"] == "A" assert cache.get("col-b", "query")["response"] == "B" def test_query_normalisation_ignores_case_and_whitespace(self): cache.set("col-1", " Hello World ", {"response": "hi"}) assert cache.get("col-1", "hello world") is not None assert cache.get("col-1", "HELLO WORLD") is not None def test_overwrite_updates_value(self): cache.set("col-1", "q", {"response": "old"}) cache.set("col-1", "q", {"response": "new"}) assert cache.get("col-1", "q")["response"] == "new" class TestCacheExpiry: def test_expired_entry_returns_none(self, monkeypatch): cache.set("col-1", "q", {"response": "old"}) # Manually expire the entry k = list(cache._store.keys())[0] cache._store[k] = (cache._store[k][0], time.monotonic() - 1) assert cache.get("col-1", "q") is None def test_expired_entry_is_evicted_from_store(self, monkeypatch): cache.set("col-1", "q", {"response": "x"}) k = list(cache._store.keys())[0] cache._store[k] = (cache._store[k][0], time.monotonic() - 1) cache.get("col-1", "q") assert k not in cache._store class TestCacheInvalidation: def test_invalidate_removes_all_entries_for_collection(self): cache.set("col-1", "query a", {"response": "a"}) cache.set("col-1", "query b", {"response": "b"}) cache.set("col-2", "query a", {"response": "c"}) removed = cache.invalidate("col-1") assert removed == 2 assert cache.get("col-1", "query a") is None assert cache.get("col-1", "query b") is None # Other collection unaffected assert cache.get("col-2", "query a") is not None def test_invalidate_unknown_collection_returns_zero(self): assert cache.invalidate("nonexistent") == 0 def test_index_cleaned_up_after_invalidation(self): cache.set("col-1", "q", {"response": "x"}) cache.invalidate("col-1") assert "col-1" not in cache._index class TestCacheEviction: def test_does_not_exceed_max_entries(self, monkeypatch): monkeypatch.setattr(cache, "_MAX_ENTRIES", 5) for i in range(10): cache.set("col-1", f"query {i}", {"response": str(i)}) assert len(cache._store) <= 5