fix(data): 修三环境 session 反馈的 3 个数据层问题
D1: 删 test_circuit_breaker.py(测已归档 raw_redownload.check_circuit_breaker 死代码,全仓零活跃引用,致 data_platform 套件 collection error) D2: datareader.py read_db_daily/read_index_daily 两处 vnpy_db 硬访问→.get()+清晰报错防崩溃(根治切 dbbardata 读指数列待办) D3: high_limit/low_limit close±10% 兜底是有意设计非 bug(填 NaN 会复活 bullet_trade 误判停牌)—get_price 加 round(.,2) 对齐 get_current_tick 口径;测试期望从 NaN 改兜底估算
This commit is contained in:
@@ -1,155 +0,0 @@
|
||||
"""断路器单元测试(task: Phase 2 可靠性增强)。
|
||||
|
||||
测试 check_circuit_breaker 纯函数——不启动整个下载流程,
|
||||
直接构造 recent_results 列表验证触发逻辑。
|
||||
|
||||
覆盖场景:
|
||||
1. 35% 失败率 → 触发
|
||||
2. 25% 失败率 → 不触发
|
||||
3. 北交所 920xxx 的 fail 不计入 → 不触发
|
||||
4. 不足窗口 → 不触发
|
||||
5. 恰好 30% → 不触发(阈值是 > 0.30,不含等于)
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
_SCRIPT_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "scripts", "data_platform")
|
||||
_SCRIPT_DIR = os.path.abspath(_SCRIPT_DIR)
|
||||
if _SCRIPT_DIR not in sys.path:
|
||||
sys.path.insert(0, _SCRIPT_DIR)
|
||||
|
||||
from raw_redownload import ( # noqa: E402
|
||||
CIRCUIT_BREAKER_FAIL_RATE,
|
||||
CIRCUIT_BREAKER_WINDOW,
|
||||
KNOWN_UNSUPPORTED_PREFIX,
|
||||
check_circuit_breaker,
|
||||
)
|
||||
|
||||
|
||||
def _make_results(n_ok: int, n_fail: int, fail_prefix: str = "600") -> list:
|
||||
"""构造 recent_results 列表:n_ok 个 ok + n_fail 个 fail。
|
||||
|
||||
fail_prefix 控制失败码的前缀(用于测试北交所排除)。
|
||||
"""
|
||||
ok_list = [("600001", True)] * n_ok
|
||||
fail_list = [(f"{fail_prefix}9999", False)] * n_fail
|
||||
return ok_list + fail_list
|
||||
|
||||
|
||||
class TestCircuitBreakerTrigger:
|
||||
"""断路器触发阈值测试。"""
|
||||
|
||||
def test_35_percent_fail_triggers(self):
|
||||
"""200 只里 70 只 fail(35%)→ 触发。"""
|
||||
results = _make_results(n_ok=130, n_fail=70)
|
||||
assert len(results) == 200
|
||||
assert check_circuit_breaker(results) is True
|
||||
|
||||
def test_25_percent_fail_no_trigger(self):
|
||||
"""200 只里 50 只 fail(25%)→ 不触发。"""
|
||||
results = _make_results(n_ok=150, n_fail=50)
|
||||
assert len(results) == 200
|
||||
assert check_circuit_breaker(results) is False
|
||||
|
||||
def test_exactly_30_percent_no_trigger(self):
|
||||
"""恰好 30%(60/200)→ 不触发(阈值是 > 0.30,不含等于)。"""
|
||||
results = _make_results(n_ok=140, n_fail=60)
|
||||
assert len(results) == 200
|
||||
fail_rate = 60 / 200
|
||||
assert fail_rate == pytest.approx(CIRCUIT_BREAKER_FAIL_RATE)
|
||||
assert check_circuit_breaker(results) is False
|
||||
|
||||
def test_31_percent_triggers(self):
|
||||
"""31% 失败率 → 触发(刚过阈值)。"""
|
||||
results = _make_results(n_ok=138, n_fail=62)
|
||||
assert len(results) == 200
|
||||
assert check_circuit_breaker(results) is True
|
||||
|
||||
|
||||
class TestCircuitBreakerBjExclusion:
|
||||
"""北交所码不计入断路器分母测试。"""
|
||||
|
||||
def test_bj_fails_excluded_from_denominator(self):
|
||||
"""北交所 920xxx 的 fail 不计入 → 不触发。
|
||||
|
||||
200 只非北交所全部 ok + 100 只北交所全部 fail:
|
||||
有效分母=200,有效失败=0 → 0% → 不触发。
|
||||
"""
|
||||
ok_list = [("600001", True)] * 200
|
||||
bj_fail_list = [("920001", False)] * 100
|
||||
results = ok_list + bj_fail_list
|
||||
assert check_circuit_breaker(results) is False
|
||||
|
||||
def test_bj_fails_do_not_inflate_rate(self):
|
||||
"""北交所 fail 混在 200 窗口内不抬高失败率。
|
||||
|
||||
150 只非北交所 ok + 50 只北交所 fail = 200 只总数:
|
||||
有效分母=150(< 窗口 200)→ 不足窗口,不触发。
|
||||
"""
|
||||
ok_list = [("600001", True)] * 150
|
||||
bj_fail_list = [("920001", False)] * 50
|
||||
results = ok_list + bj_fail_list
|
||||
assert check_circuit_breaker(results) is False
|
||||
|
||||
def test_mixed_bj_and_normal_fails(self):
|
||||
"""混合场景:200 非北交所(60 fail=30%)+ 50 北交所 fail。
|
||||
|
||||
有效:200 非北交所,60 fail = 30%,恰好阈值(> 0.30 不含等于)→ 不触发。
|
||||
北交所 50 fail 被排除,不影响计算。
|
||||
"""
|
||||
ok_normal = [("600001", True)] * 140
|
||||
fail_normal = [("600002", False)] * 60
|
||||
fail_bj = [("920001", False)] * 50
|
||||
results = ok_normal + fail_normal + fail_bj
|
||||
assert check_circuit_breaker(results) is False
|
||||
|
||||
def test_all_bj_prefixes_excluded(self):
|
||||
"""所有已知不支持前缀都排除:920/921/83/87。"""
|
||||
ok_list = [("600001", True)] * 200
|
||||
bj_fails = (
|
||||
[("920001", False)] * 30
|
||||
+ [("921002", False)] * 30
|
||||
+ [("830003", False)] * 30
|
||||
+ [("870004", False)] * 30
|
||||
)
|
||||
results = ok_list + bj_fails
|
||||
assert check_circuit_breaker(results) is False
|
||||
|
||||
|
||||
class TestCircuitBreakerWindow:
|
||||
"""窗口大小边界测试。"""
|
||||
|
||||
def test_insufficient_window_no_trigger(self):
|
||||
"""不足 200 只 → 不触发(即使全部 fail)。"""
|
||||
results = [("600001", False)] * 199
|
||||
assert check_circuit_breaker(results) is False
|
||||
|
||||
def test_exactly_window_triggers_if_high_fail(self):
|
||||
"""恰好 200 只且失败率超阈值 → 触发。"""
|
||||
results = _make_results(n_ok=100, n_fail=100)
|
||||
assert len(results) == 200
|
||||
assert check_circuit_breaker(results) is True
|
||||
|
||||
def test_empty_results_no_trigger(self):
|
||||
"""空列表 → 不触发。"""
|
||||
assert check_circuit_breaker([]) is False
|
||||
|
||||
def test_all_ok_no_trigger(self):
|
||||
"""200 只全部 ok → 不触发。"""
|
||||
results = [("600001", True)] * 200
|
||||
assert check_circuit_breaker(results) is False
|
||||
|
||||
|
||||
class TestCircuitBreakerConstants:
|
||||
"""常量值校验(防止意外修改)。"""
|
||||
|
||||
def test_window_is_200(self):
|
||||
assert CIRCUIT_BREAKER_WINDOW == 200
|
||||
|
||||
def test_fail_rate_is_030(self):
|
||||
assert CIRCUIT_BREAKER_FAIL_RATE == 0.30
|
||||
|
||||
def test_known_unsupported_prefixes(self):
|
||||
assert KNOWN_UNSUPPORTED_PREFIX == ("920", "921", "83", "87")
|
||||
Reference in New Issue
Block a user