"""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→None,set→不报错。""" 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)