mirror of
http://88.130.71.182:3000/BlitTech/contexta_be.git
synced 2026-06-12 23:23:21 +00:00
88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
"""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
|