"""统一数据服务测试。""" from datetime import date, datetime from pathlib import Path from typing import List, Tuple import pytest from sidecar.config import SidecarConfig from sidecar.models import KlineBar, Snapshot from sidecar.service import UnifiedDataService from sidecar.storage import DuckDBStore class FakeSource: """测试用假数据源。""" def __init__(self) -> None: """ 功能说明:初始化测试数据源计数器。 参数说明:无。 返回值说明:无返回值。 注意事项:仅用于单元测试。 """ self.kline_calls = 0 self.snapshot_calls = 0 def fetch_kline(self, symbol: str, period: str, limit: int) -> List[KlineBar]: """ 功能说明:返回固定 K 线测试数据。 参数说明:symbol 为股票代码,period 为周期,limit 为条数。 返回值说明:返回 KlineBar 列表。 注意事项:会记录调用次数用于验证缓存。 """ self.kline_calls += 1 return [ KlineBar(symbol, period, date(2026, 6, 22), 1, 2, 1, 2, 100, 200, "fake"), KlineBar(symbol, period, date(2026, 6, 23), 2, 3, 2, 3, 200, 300, "fake"), ][:limit] def fetch_snapshot(self, symbol: str) -> Snapshot: """ 功能说明:返回固定快照测试数据。 参数说明:symbol 为股票代码。 返回值说明:返回 Snapshot。 注意事项:会记录调用次数用于验证 TTL 缓存。 """ self.snapshot_calls += 1 return Snapshot( symbol=symbol, name="测试股", trade_time=datetime(2026, 6, 23, 15, 0, 0), price=10, previous_close=9, open=9.5, high=10.5, low=9.4, volume=1000, amount=10000, change=1, change_percent=11.11, turnover_rate=2, pe_ttm=8, pe_static=9, pb=1.2, market_cap=100, float_market_cap=80, limit_up=9.9, limit_down=8.1, source="fake", ) @pytest.fixture() def service(tmp_path: Path) -> Tuple[UnifiedDataService, FakeSource]: """ 功能说明:创建使用临时 DuckDB 的统一服务。 参数说明:tmp_path 为 pytest 临时目录。 返回值说明:返回服务实例和假数据源。 注意事项:用例间数据库完全隔离。 """ source = FakeSource() store = DuckDBStore(str(tmp_path / "test.duckdb")) config = SidecarConfig(db_path=str(tmp_path / "test.duckdb"), snapshot_ttl_seconds=60) return UnifiedDataService(store, config, source, [source]), source def test_2_1_kline_fetches_then_reads_cache(service: Tuple[UnifiedDataService, FakeSource]) -> None: """ 功能说明:验证 K 线首次远端获取后可从 DuckDB 缓存读取。 参数说明:service 为测试夹具。 返回值说明:无返回值。 注意事项:用例编号 2-1,第二次 limit 小于缓存数量时不再调用远端。 """ data_service, source = service first = data_service.get_kline("600000", limit=2) second = data_service.get_kline("sh600000", limit=1) assert len(first) == 2 assert len(second) == 1 assert second[0].trade_date == date(2026, 6, 23) assert source.kline_calls == 1 def test_2_2_snapshot_uses_ttl_cache(service: Tuple[UnifiedDataService, FakeSource]) -> None: """ 功能说明:验证快照在 TTL 内复用 DuckDB 缓存。 参数说明:service 为测试夹具。 返回值说明:无返回值。 注意事项:用例编号 2-2,第二次读取不得再次调用远端。 """ data_service, source = service first = data_service.get_snapshot("600000") second = data_service.get_snapshot("sh600000") assert first.price == 10 assert second.price == 10 assert source.snapshot_calls == 1