ed3130ef74
Replace deprecated @app.on_event startup/shutdown handlers with a FastAPI lifespan context manager, move the inline hashlib/time imports in the auth middleware to module top, and back the unbounded _api_scope_cache with a new size- and TTL-bounded BoundedTTLCache utility. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
73 lines
2.2 KiB
Python
73 lines
2.2 KiB
Python
"""Tests for the bounded TTL cache utility."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from aegis_gitea_mcp.cache import BoundedTTLCache
|
|
|
|
|
|
def test_set_and_get_returns_value() -> None:
|
|
"""A stored value is returned before it expires."""
|
|
cache: BoundedTTLCache[str, int] = BoundedTTLCache(ttl_seconds=60, max_size=8)
|
|
cache.set("a", 1)
|
|
assert cache.get("a") == 1
|
|
|
|
|
|
def test_missing_key_returns_none() -> None:
|
|
"""An unknown key returns None."""
|
|
cache: BoundedTTLCache[str, int] = BoundedTTLCache(ttl_seconds=60)
|
|
assert cache.get("missing") is None
|
|
|
|
|
|
def test_entry_expires_after_ttl() -> None:
|
|
"""An entry is evicted once its TTL elapses."""
|
|
cache: BoundedTTLCache[str, int] = BoundedTTLCache(ttl_seconds=0.05, max_size=8)
|
|
cache.set("a", 1)
|
|
assert cache.get("a") == 1
|
|
time.sleep(0.06)
|
|
assert cache.get("a") is None
|
|
|
|
|
|
def test_size_bound_evicts_oldest() -> None:
|
|
"""The cache never exceeds max_size; oldest entries are evicted first."""
|
|
cache: BoundedTTLCache[int, int] = BoundedTTLCache(ttl_seconds=60, max_size=3)
|
|
for i in range(5):
|
|
cache.set(i, i)
|
|
assert len(cache) == 3
|
|
# 0 and 1 were evicted; 2, 3, 4 remain.
|
|
assert cache.get(0) is None
|
|
assert cache.get(1) is None
|
|
assert cache.get(4) == 4
|
|
|
|
|
|
def test_reinsert_refreshes_recency() -> None:
|
|
"""Re-setting a key refreshes its position so it is not evicted first."""
|
|
cache: BoundedTTLCache[str, int] = BoundedTTLCache(ttl_seconds=60, max_size=2)
|
|
cache.set("a", 1)
|
|
cache.set("b", 2)
|
|
cache.set("a", 3) # refresh "a"
|
|
cache.set("c", 4) # should evict "b", the oldest
|
|
assert cache.get("b") is None
|
|
assert cache.get("a") == 3
|
|
assert cache.get("c") == 4
|
|
|
|
|
|
def test_clear_empties_cache() -> None:
|
|
"""clear() removes all entries."""
|
|
cache: BoundedTTLCache[str, int] = BoundedTTLCache(ttl_seconds=60)
|
|
cache.set("a", 1)
|
|
cache.clear()
|
|
assert cache.get("a") is None
|
|
assert len(cache) == 0
|
|
|
|
|
|
def test_invalid_constructor_args() -> None:
|
|
"""Non-positive TTL or size is rejected."""
|
|
with pytest.raises(ValueError):
|
|
BoundedTTLCache(ttl_seconds=0)
|
|
with pytest.raises(ValueError):
|
|
BoundedTTLCache(ttl_seconds=60, max_size=0)
|