Files
stock/backend/tests/test_cache_local.py
2026-09-09 15:07:58 +08:00

95 lines
2.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""cache.py 本地层(不碰 RedisTTL 过期、容量淘汰、熔断冷却恢复。"""
from __future__ import annotations
import pytest
import app.cache as cache_mod
from app import cache
class _FakeClock:
"""替换 cache 命名空间里的 time不影响全局 time 模块)。"""
def __init__(self):
self.now = 1000.0
def monotonic(self) -> float:
return self.now
def time(self) -> float:
return self.now
@pytest.fixture(autouse=True)
def _reset_state():
"""每个用例干净的本地缓存状态。"""
cache._local_store.clear()
cache._local_bytes = 0
cache._disabled_until = 0.0
yield
cache._local_store.clear()
cache._local_bytes = 0
cache._disabled_until = 0.0
def test_local_set_get_roundtrip():
cache.local_set("k", '{"a":1}', ttl=60)
assert cache.local_get("k") == '{"a":1}'
def test_local_get_miss():
assert cache.local_get("nope") is None
def test_local_expiry(monkeypatch):
clock = _FakeClock()
monkeypatch.setattr(cache_mod, "time", clock)
cache.local_set("k", "v", ttl=10)
clock.now = 1005.0
assert cache.local_get("k") == "v"
clock.now = 1101.0 # 过期
assert cache.local_get("k") is None
assert "k" not in cache._local_store # 过期读取顺手清理
def test_local_ttl_capped_at_120s():
"""本地层恒 ≤120s多进程部署时最多比 Redis 多陈旧 120s 的约定)。"""
base = cache.time.monotonic()
cache.local_set("k", "v", ttl=99999)
ent = cache._local_store["k"]
assert ent[0] - base <= 120.0 + 5 # 相对当前 monotonic 的上限(留误差余量)
def test_local_entries_cap_evicts_oldest():
for i in range(cache._LOCAL_MAX_ENTRIES + 5):
cache.local_set(f"k{i}", "v", ttl=60)
assert len(cache._local_store) <= cache._LOCAL_MAX_ENTRIES
# 先插入的(最旧)被近似 LRU 淘汰
assert cache.local_get("k0") is None
assert cache.local_get(f"k{cache._LOCAL_MAX_ENTRIES + 4}") == "v"
def test_local_overwrite_releases_bytes():
cache.local_set("k", "x" * 1000, ttl=60)
before = cache._local_bytes
cache.local_set("k", "y", ttl=60)
assert cache._local_bytes < before
assert cache.local_get("k") == "y"
def test_bail_cooldown_recovers(monkeypatch):
"""熔断 60s期间 _client 为 None到期自动放行Redis 未配置时也返回 None但不熔断"""
clock = _FakeClock()
monkeypatch.setattr(cache_mod, "time", clock)
cache._bail()
assert cache._disabled_until > clock.now
clock.now += 59.0
assert clock.monotonic() < cache._disabled_until # 仍在熔断期
clock.now += 2.0 # 越过 60s 冷却
assert clock.monotonic() >= cache._disabled_until # 恢复探测资格
def test_digest_stable_and_distinct():
assert cache.digest("a", 1, None) == cache.digest("a", 1, None)
assert cache.digest("a", 1) != cache.digest("a", 2)