e3b688354f
merge_increment/verify_increment 增量staging→验证→合并工具; raw_redownload/run_daily_update/import_vnpy_daily 强化; 补 data_platform 与 index_downloader 测试.
156 lines
5.5 KiB
Python
156 lines
5.5 KiB
Python
"""断路器单元测试(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")
|