honcho/tests/test_cache_key_namespace.py

63 lines
2.0 KiB
Python

"""The namespace hash tag must survive both ways a cache key gets built.
cashews format-substitutes `prefix=`, direct construction does not, so the two
need different spellings of the same tag. If they ever diverge, a write and its
invalidation land on different keys and nothing raises -- the cache just serves
stale rows. These tests are what fails instead.
"""
import pytest
from redis.crc import key_slot
from src.cache.client import (
cache,
cache_key_namespace,
cache_prefix_namespace,
get_cache_namespace,
)
from src.crud.session import SESSION_CACHE_KEY_TEMPLATE, session_cache_key
def test_both_spellings_render_the_same_tag():
ns = get_cache_namespace()
assert cache_key_namespace() == "{" + ns + "}"
# Doubled braces collapse to single ones when cashews formats the prefix.
assert cache_prefix_namespace().format() == cache_key_namespace()
@pytest.mark.asyncio
async def test_decorator_key_matches_helper_key():
"""The key the decorator writes is the key the helper computes."""
@cache(
key=SESSION_CACHE_KEY_TEMPLATE,
prefix=cache_prefix_namespace(),
ttl="60s",
)
async def get_session(workspace_name: str, session_name: str) -> str:
# The names matter: cashews fills the key template from them.
return f"{workspace_name}/{session_name}"
await get_session(workspace_name="w1", session_name="s1")
written = [k async for k in cache.scan("*")]
assert session_cache_key("w1", "s1") in written
def test_one_namespace_hashes_to_one_slot():
"""Every key an instance writes shares a Redis Cluster slot."""
keys = [
session_cache_key("w1", "s1"),
session_cache_key("w2", "s2"),
f"{cache_key_namespace()}:lock:v2:anything",
]
assert len({key_slot(k.encode()) for k in keys}) == 1
def test_namespaces_hash_independently():
"""Tagging must not collapse the whole fleet onto one shard."""
slots = {
key_slot(("{" + n + "}:v2:workspace:w").encode()) for n in ("a1", "b2", "c3")
}
assert len(slots) > 1