Files
Video-Tree-TRM5/tests/unit/test_redis_cache.py
T
iomgaa 9dae7dda98 feat(adapters): 实现 RedisResponseCache
content-addressed sha256 缓存键,Redis 不可用时静默降级。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-07-06 22:39:42 -04:00

137 lines
4.2 KiB
Python
Raw 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.
"""RedisResponseCache 单元测试。"""
from __future__ import annotations
import pytest
from adapters.redis_cache import RedisResponseCache
from core.types import LLMResponse
@pytest.fixture()
def fake_redis():
"""创建 fakeredis 异步实例。"""
try:
import fakeredis.aioredis
except ImportError:
pytest.skip("fakeredis not installed")
return fakeredis.aioredis.FakeRedis(decode_responses=True)
@pytest.fixture()
def sample_response() -> LLMResponse:
"""构造一个标准测试用 LLMResponse。"""
return LLMResponse(
content="你好世界",
thinking="思考过程",
model="gpt-4o",
provider="openai",
prompt_tokens=10,
completion_tokens=5,
latency_ms=120,
ttft_ms=45.0,
max_inter_token_ms=12.3,
cache_hit=False,
call_id="test-call-001",
)
MESSAGES = [{"role": "user", "content": "你好"}]
class _BrokenRedis:
"""模拟 Redis 连接异常的假实现。"""
async def get(self, _key: str) -> None:
raise ConnectionError("redis down")
async def set(self, _key: str, _value: str, **_kw: object) -> None:
raise ConnectionError("redis down")
# ── 测试用例 ──────────────────────────────────────────────
@pytest.mark.asyncio
async def test_cache_miss_returns_none(fake_redis: object) -> None:
"""缓存未命中时返回 None。"""
cache = RedisResponseCache(redis=fake_redis, ttl_s=60)
result = await cache.get("gpt-4o", MESSAGES)
assert result is None
@pytest.mark.asyncio
async def test_cache_roundtrip(
fake_redis: object, sample_response: LLMResponse
) -> None:
"""set 后 get 应返回相同内容。"""
cache = RedisResponseCache(redis=fake_redis, ttl_s=300)
await cache.set("gpt-4o", MESSAGES, sample_response)
result = await cache.get("gpt-4o", MESSAGES)
assert result is not None
assert result.content == sample_response.content
assert result.thinking == sample_response.thinking
assert result.model == sample_response.model
assert result.provider == sample_response.provider
assert result.prompt_tokens == sample_response.prompt_tokens
assert result.completion_tokens == sample_response.completion_tokens
assert result.latency_ms == sample_response.latency_ms
assert result.ttft_ms == sample_response.ttft_ms
assert result.max_inter_token_ms == sample_response.max_inter_token_ms
assert result.cache_hit == sample_response.cache_hit
assert result.call_id == sample_response.call_id
@pytest.mark.asyncio
async def test_different_messages_different_keys(
fake_redis: object, sample_response: LLMResponse
) -> None:
"""不同 messages 应产生不同缓存键。"""
cache = RedisResponseCache(redis=fake_redis, ttl_s=300)
messages_a = [{"role": "user", "content": "问题A"}]
messages_b = [{"role": "user", "content": "问题B"}]
await cache.set("gpt-4o", messages_a, sample_response)
assert await cache.get("gpt-4o", messages_a) is not None
assert await cache.get("gpt-4o", messages_b) is None
@pytest.mark.asyncio
async def test_different_models_different_keys(
fake_redis: object, sample_response: LLMResponse
) -> None:
"""不同 model 应产生不同缓存键。"""
cache = RedisResponseCache(redis=fake_redis, ttl_s=300)
await cache.set("gpt-4o", MESSAGES, sample_response)
assert await cache.get("gpt-4o", MESSAGES) is not None
assert await cache.get("claude-3-opus", MESSAGES) is None
@pytest.mark.asyncio
async def test_graceful_degradation_on_error() -> None:
"""Redis 不可用时静默降级:get→Noneset→不报错。"""
cache = RedisResponseCache(redis=_BrokenRedis(), ttl_s=60)
response = LLMResponse(
content="x",
thinking="",
model="m",
provider="p",
prompt_tokens=0,
completion_tokens=0,
latency_ms=0,
ttft_ms=None,
max_inter_token_ms=None,
cache_hit=False,
call_id="broken-test",
)
# get 应返回 None 而非抛异常
assert await cache.get("m", MESSAGES) is None
# set 应静默吞掉异常
await cache.set("m", MESSAGES, response)