Files
tick-stock-panel/backend/tests/test_override_signature_cache.py
T
shy3130 697c27bb02 feat(v0.2): 市场阶段与主线识别 + 因子挖掘全链路 + 数据层完善
- 市场环境: 新增情绪周期6阶段(冰点/启动/主升/高潮/退潮/修复, 连板梯队驱动,
  EMA平滑+2日确认+弱档否决, 平均段长9.7天)与概念/行业主线排名(涨停梯队聚合,
  可配置宽基/风格标签过滤); 市场环境页重构, regime 透明加列, 与5档state并存
- 挖掘: 因子与策略挖掘全链路(API/worker/进程锁/候选库/前端工作台/文档),
  周度调度默认关闭且永不自动发布
- 回测: 财务快照因子(点时口径), 批量回测预计算共享下期收益,
  信号路径矩阵列依赖展开修复(consecutive_limit_ups 缺列报错)
- 数据/性能: enriched 生成与预热治理, 重任务限流, 行情/K线缓存复用, 时区修复
- 测试: 后端全量 914 通过; GUI 黑盒验证截图存证 gui-test-screenshots/
2026-08-16 23:39:07 +08:00

98 lines
3.4 KiB
Python

"""策略 override mtime 签名缓存测试 — 读盘去重, 写入/删除后立即可见, 返回深拷贝。"""
from __future__ import annotations
import json
import os
from pathlib import Path
import pytest
from app.strategy import config as strat_config
@pytest.fixture(autouse=True)
def _clean_cache():
strat_config._override_cache.clear()
strat_config._override_cache_sig.clear()
yield
strat_config._override_cache.clear()
strat_config._override_cache_sig.clear()
def _patched_loads(monkeypatch, counter: dict):
real_loads = json.loads
def _counting(text):
counter["loads"] += 1
return real_loads(text)
monkeypatch.setattr(strat_config.json, "loads", _counting)
def test_second_load_hits_cache_without_disk_parse(tmp_path, monkeypatch):
strat_config.save_override(tmp_path, "s1", {"params": {"p": 1}})
counter = {"loads": 0}
_patched_loads(monkeypatch, counter)
assert strat_config.load_override(tmp_path, "s1")["params"] == {"p": 1}
assert strat_config.load_override(tmp_path, "s1")["params"] == {"p": 1}
assert counter["loads"] == 0 or counter["loads"] == 1, "save 后首次 load 允许一次 parse"
before = counter["loads"]
strat_config.load_override(tmp_path, "s1")
assert counter["loads"] == before, "签名未变时不得重复读盘+parse"
def test_external_file_change_visible(tmp_path):
strat_config.save_override(tmp_path, "s1", {"params": {"p": 1}})
assert strat_config.load_override(tmp_path, "s1")["params"]["p"] == 1
p: Path = tmp_path / "user_data" / "strategy_overrides" / "s1.json"
p.write_text(json.dumps({"params": {"p": 2}}), encoding="utf-8")
st = p.stat()
os.utime(p, ns=(st.st_atime_ns, st.st_mtime_ns + 1_000_000))
assert strat_config.load_override(tmp_path, "s1")["params"]["p"] == 2
def test_save_override_invalidates_cache(tmp_path):
strat_config.save_override(tmp_path, "s1", {"params": {"p": 1}})
assert strat_config.load_override(tmp_path, "s1")["params"]["p"] == 1
strat_config.save_override(tmp_path, "s1", {"params": {"p": 9}})
assert strat_config.load_override(tmp_path, "s1")["params"]["p"] == 9
def test_delete_override_invalidates_cache(tmp_path):
strat_config.save_override(tmp_path, "s1", {"params": {"p": 1}})
assert strat_config.load_override(tmp_path, "s1") != {}
strat_config.delete_override(tmp_path, "s1")
assert strat_config.load_override(tmp_path, "s1") == {}
assert strat_config.load_override(tmp_path, "s1") == {}
def test_load_returns_deep_copy_not_cached_object(tmp_path):
strat_config.save_override(tmp_path, "s1", {"params": {"p": 1}, "basic_filter": {"a": 1}})
first = strat_config.load_override(tmp_path, "s1")
first["params"]["p"] = 999
first["extra"] = True
again = strat_config.load_override(tmp_path, "s1")
assert again["params"]["p"] == 1
assert "extra" not in again
def test_basic_filter_cleaning_preserved(tmp_path):
strat_config.save_override(
tmp_path, "s1", {"basic_filter": {"keep": 1, "drop": None}},
)
data = strat_config.load_override(tmp_path, "s1")
assert data["basic_filter"] == {"keep": 1}
strat_config.save_override(tmp_path, "s2", {"basic_filter": {"drop": None}})
assert "basic_filter" not in strat_config.load_override(tmp_path, "s2")
def test_load_missing_override_returns_empty(tmp_path):
assert strat_config.load_override(tmp_path, "never_saved") == {}