fix(pipeline): A 股盘后拉取开关生效 (#216)

问题: 在设置里取消勾选「A 股」拉取(pipeline_pull_a_share=false)后完全不
生效, 每次同步仍拉取全市场 A 股日K。

根因: 两处联合导致开关不可用:
1. get_pipeline_pull_a_share() 硬编码 return True, 从不读取偏好, 使
   jobs/daily_pipeline.py 里 `if not pull_a_share` 的跳过分支永不可达。
2. set_pipeline_pull_types() 的白名单 _PIPELINE_PULL_KEYS 遗漏
   pipeline_pull_a_share, 即使 API 模型接受该字段, 值也会被静默丢弃、无法落盘。
   (ETF / 指数两个开关均正常读写, 仅 A 股异常。)

修复:
- get_pipeline_pull_a_share() 改为 load().get("pipeline_pull_a_share", True),
  与 ETF / 指数保持一致, 默认仍为 True (向后兼容, 旧配置无此键时行为不变)。
- 白名单补入 pipeline_pull_a_share, 使 PUT /api/settings/preferences/
  pipeline-pull-types 能正确落盘。

验证: backend/tests/test_pipeline_pull_types.py 覆盖默认值、关闭后 getter 生效、
setter 落盘与三开关独立性; 修复前 3 例失败, 修复后全过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kevin9327
2026-08-30 18:38:24 +09:00
co-authored by Claude Opus 4.8
parent afbf432eae
commit 64ab9a0818
2 changed files with 63 additions and 3 deletions
+3 -3
View File
@@ -284,8 +284,8 @@ def get_financial_provider() -> str:
# ===== 盘后管道拉取内容开关 (A股 / ETF / 指数 独立控制) =====
def get_pipeline_pull_a_share() -> bool:
"""A 股日K固定拉取"""
return True
"""是否拉取 A 股日K。默认 True"""
return load().get("pipeline_pull_a_share", True)
def get_pipeline_pull_etf() -> bool:
@@ -429,7 +429,7 @@ def set_mainline_filter_config(cfg: dict) -> dict:
return get_mainline_filter_config()
_PIPELINE_PULL_KEYS = ("pipeline_pull_etf", "pipeline_pull_index")
_PIPELINE_PULL_KEYS = ("pipeline_pull_a_share", "pipeline_pull_etf", "pipeline_pull_index")
def get_pipeline_pull_types() -> dict:
+60
View File
@@ -0,0 +1,60 @@
"""盘后管道「A股 / ETF / 指数」拉取开关回归测试。
覆盖 issue #216: `get_pipeline_pull_a_share()` 硬编码 True 且 A股键不在
`set_pipeline_pull_types()` 白名单内, 导致取消勾选 A 股完全不生效。
纯逻辑, 不触网, 不依赖真实数据源。
"""
from __future__ import annotations
import pytest
from app.services import preferences
@pytest.fixture(autouse=True)
def _isolated(tmp_path, monkeypatch):
path = tmp_path / "preferences.json"
monkeypatch.setattr(preferences, "_path", lambda: path)
preferences._invalidate_cache()
yield path
preferences._invalidate_cache()
def test_a_share_defaults_to_true_when_unset():
"""未设置时三个开关的默认值: A股/指数 默认开, ETF 默认关。"""
assert preferences.get_pipeline_pull_a_share() is True
assert preferences.get_pipeline_pull_index() is True
assert preferences.get_pipeline_pull_etf() is False
def test_a_share_toggle_off_is_honored():
"""写入 False 后 getter 必须返回 False(修复前硬编码 True)。"""
preferences.save({"pipeline_pull_a_share": False})
assert preferences.get_pipeline_pull_a_share() is False
def test_set_pipeline_pull_types_persists_a_share():
"""setter 必须接受并落盘 A 股开关(修复前白名单遗漏该键, 被静默丢弃)。"""
result = preferences.set_pipeline_pull_types({"pipeline_pull_a_share": False})
assert result["pipeline_pull_a_share"] is False
# 落盘后重新读取仍为 False
preferences._invalidate_cache()
assert preferences.get_pipeline_pull_a_share() is False
assert preferences.get_pipeline_pull_types()["pipeline_pull_a_share"] is False
def test_set_pipeline_pull_types_independent_switches():
"""三个开关互相独立: 关 A 股不影响 ETF / 指数。"""
result = preferences.set_pipeline_pull_types(
{
"pipeline_pull_a_share": False,
"pipeline_pull_etf": True,
"pipeline_pull_index": False,
}
)
assert result == {
"pipeline_pull_a_share": False,
"pipeline_pull_etf": True,
"pipeline_pull_index": False,
}