"""SanguoMiniQmtProvider.get_security_info_batch 委托测试(2026-08-21 挂死根治)。 背景:实盘 momentum/small_cap 9:30 选股挂死——filters.filter_st/filter_new 逐只 get_security_info,每只 2 次 xtdata RPC 无超时,开盘时段 600 次 RPC 47min+ 不归。 本方法委托内部 LocalUnifiedProvider 批量(2 条本地 SQL),filters 探测到即走批量。 Mac 无 bullet_trade:类定义处 MiniQMTProvider=object 兜底,可直接实例化; _unified 用 MagicMock 替换,只测委托语义(不连本地库)。 """ from __future__ import annotations from unittest.mock import MagicMock from sanguo_portfolio import SanguoMiniQmtProvider from sanguo_portfolio import filters def _provider(unified: MagicMock) -> SanguoMiniQmtProvider: # __new__ 绕过 __init__:Mac 无 bullet_trade 时构造直接 raise # (RuntimeError);被测方法只用 self._unified,委托语义不依赖基类初始化。 p = object.__new__(SanguoMiniQmtProvider) p._unified = unified return p class TestGetSecurityInfoBatch: def test_delegates_to_unified(self): unified = MagicMock(name="unified") expected = { "600519.XSHG": {"display_name": "贵州茅台", "start_date": "2001-08-27"}, "600036.XSHG": {"display_name": "招商银行", "start_date": "2002-04-09"}, } unified.get_security_info_batch.return_value = expected p = _provider(unified) out = p.get_security_info_batch(["600519.XSHG", "600036.XSHG"]) assert out == expected unified.get_security_info_batch.assert_called_once_with( ["600519.XSHG", "600036.XSHG"], None) def test_unified_failure_returns_none_not_raise(self): """委托失败 → None(不崩策略),filters 自动回退逐只旧路径。""" unified = MagicMock(name="unified") unified.get_security_info_batch.side_effect = RuntimeError("db locked") p = _provider(unified) assert p.get_security_info_batch(["600519.XSHG"]) is None def test_non_dict_result_returns_none(self): unified = MagicMock(name="unified") unified.get_security_info_batch.return_value = ["not", "a", "dict"] p = _provider(unified) assert p.get_security_info_batch(["600519.XSHG"]) is None class TestFiltersConsumeBatch: def test_filter_st_uses_batch_and_skips_per_stock_rpc(self): """filters 探测到批量方法 → 一次预取,绝不再逐只 get_security_info (即挂死根因的 RPC 风暴路径)。""" provider = MagicMock(name="provider") provider.get_security_info_batch.return_value = { "600519.XSHG": {"display_name": "贵州茅台", "start_date": "2001-08-27"}, "600000.XSHG": {"display_name": "ST浦发", "start_date": "1999-11-10"}, } out = filters.filter_st_stock(["600519.XSHG", "600000.XSHG"], provider) assert out == ["600519.XSHG"] # ST 被滤掉 provider.get_security_info_batch.assert_called_once() provider.get_security_info.assert_not_called() # 零逐只 RPC def test_filter_new_stock_uses_batch(self): provider = MagicMock(name="provider") provider.get_security_info_batch.return_value = { "600519.XSHG": {"display_name": "贵州茅台", "start_date": "2001-08-27"}, "301999.XSHE": {"display_name": "次新股", "start_date": "2026-08-01"}, } out = filters.filter_new_stock( ["600519.XSHG", "301999.XSHE"], provider, "2026-08-21", days=375) assert out == ["600519.XSHG"] # 上市 20 天的次新被滤掉 provider.get_security_info.assert_not_called()