from datetime import UTC, datetime from decimal import Decimal import pytest from app.core.fund_contracts import FundQuoteQuery from app.infrastructure.fund_quote_cache import FundQuoteCache from app.infrastructure.memory_cache import CacheReadResult from app.service.fund_quote_service import FundQuoteService class MemoryClient: def __init__(self) -> None: self.values: dict[str, str] = {} self.ttls: dict[str, int] = {} async def get(self, key: str) -> CacheReadResult: return CacheReadResult(self.values.get(key)) async def set(self, key: str, value: str, ttl_seconds: int) -> bool: self.values[key] = value self.ttls[key] = ttl_seconds return True class Adapter: calls = 0 async def fetch_names(self, codes: list[str]) -> dict[str, str]: self.calls += 1 return {code: "测试基金" for code in codes} async def fetch_quotes(self, codes: list[str]) -> dict[str, dict[str, object]]: self.calls += 1 return {code: {"nav": Decimal("1.2")} for code in codes} async def fetch_history(self, code: str, target_date: object) -> dict[str, object]: self.calls += 1 return {"nav": Decimal("1.1"), "nav_date": target_date, "degraded": False} @pytest.mark.asyncio async def test_service_reads_and_writes_cache() -> None: client = MemoryClient() adapter = Adapter() service = FundQuoteService(adapter, FundQuoteCache(client)) query = FundQuoteQuery(fund_codes=("159511",)) first = await service.query(query, now=datetime(2026, 9, 9, 8, tzinfo=UTC)) second = await service.query(query, now=datetime(2026, 9, 9, 8, tzinfo=UTC)) assert first[0].quote_source == "eastmoney" assert second[0].quote_source == "cache" assert adapter.calls == 2 assert next(iter(client.ttls.values())) == 900