# retoor import random from devplacepy.services.nodes.cache import ResultCache, cache_key from devplacepy.services.nodes.spec import SPEC LIMITS = SPEC.limits class Clock: def __init__(self): self.now = 100.0 def __call__(self): return self.now def make(**kwargs): clock = Clock() return ResultCache(clock=clock, **kwargs), clock def test_hit_and_miss_counters(): cache, _ = make() assert cache.get("GET", "u") is None assert cache.put("GET", "u", 200, {"a": "b"}, b"xyz", False) hit = cache.get("GET", "u") assert hit.body == b"xyz" and hit.headers == {"a": "b"} assert (cache.hits, cache.misses) == (1, 1) def test_method_is_part_of_the_key(): assert cache_key("GET", "u") != cache_key("HEAD", "u") assert cache_key("get", "u") == cache_key("GET", "u") def test_entries_expire_after_the_ttl(): cache, clock = make(ttl_seconds=10) cache.put("GET", "u", 200, {}, b"x", False) clock.now += 9.9 assert cache.get("GET", "u") is not None clock.now += 0.2 assert cache.get("GET", "u") is None assert len(cache) == 0 and cache.size_bytes == 0 def test_ttl_is_clamped_to_the_spec_ceiling_and_zero_disables(): assert ResultCache(ttl_seconds=10**9).ttl_seconds == LIMITS.cache_ttl_seconds off, _ = make(ttl_seconds=0) assert off.put("GET", "u", 200, {}, b"x", False) is False def test_errors_and_truncated_bodies_are_never_cached(): cache, _ = make() assert not cache.put("GET", "u", 500, {}, b"x", False) assert not cache.put("GET", "u", 503, {}, b"x", False) assert not cache.put("GET", "u", 200, {}, b"x", True) assert cache.put("GET", "u", 404, {}, b"x", False) def test_an_oversized_entry_is_refused(): cache, _ = make(entry_max_bytes=10, max_bytes=100) assert not cache.put("GET", "u", 200, {}, b"x" * 11, False) assert cache.put("GET", "u", 200, {}, b"x" * 10, False) def test_the_entry_cap_cannot_exceed_the_spec_ceiling(): assert ResultCache(entry_max_bytes=10**12).entry_max_bytes == LIMITS.cache_entry_max_bytes def test_least_recently_used_goes_first(): cache, _ = make(max_bytes=30, entry_max_bytes=30) for name in "abc": cache.put("GET", name, 200, {}, b"x" * 10, False) cache.get("GET", "a") cache.put("GET", "d", 200, {}, b"x" * 10, False) assert cache.get("GET", "b") is None assert cache.get("GET", "a") is not None assert cache.get("GET", "c") is not None assert cache.get("GET", "d") is not None def test_replacing_an_entry_does_not_leak_bytes(): cache, _ = make() cache.put("GET", "u", 200, {}, b"x" * 100, False) cache.put("GET", "u", 200, {}, b"y" * 40, False) assert cache.size_bytes == 40 and len(cache) == 1 def test_size_never_exceeds_the_cap_under_a_random_workload(): cache, clock = make(max_bytes=1000, entry_max_bytes=400, ttl_seconds=50) rng = random.Random(7) for step in range(5000): clock.now += rng.random() key = f"k{rng.randint(0, 60)}" if rng.random() < 0.7: cache.put("GET", key, rng.choice([200, 404, 500]), {}, b"x" * rng.randint(0, 500), False) else: cache.get("GET", key) assert 0 <= cache.size_bytes <= 1000 assert cache.size_bytes == sum(len(e.body) for e in cache._entries.values()) def test_clear_empties_everything(): cache, _ = make() cache.put("GET", "u", 200, {}, b"x", False) cache.clear() assert len(cache) == 0 and cache.size_bytes == 0