Files
sanguo_vnpy_v2/tests/trader/test_qmt_identity.py
T
claude_dev 434d7cb357
CI/CD / test (push) Failing after 7s
CI/CD / nas-deploy (push) Has been cancelled
CI/CD / nas-verify (push) Has been cancelled
feat(config): QMT 部署身份单一权威源 config/qmt_identity.json——P2-18 账号硬编码根治(⚖️-2 用户拍板:确认永为模拟号+令根治硬编码)
根因三层(全案=audit/20261005_qmt_identity_env_plan.md): schtask 无 per-task env+机器级 env 未设→wrapper 被迫字面量;「部署身份」无单一权威源(env 名分裂三个);模拟号零安全压力致 32 文件 88 处 copy-paste 扩散。

- qmt_gate_common.load_identity 正典装载器(:14-59): env SANGUO_QMT_ACCOUNT/BIGQMT_ACCOUNT_ID(兼容别名)>json>RuntimeError fail-loud;SANGUO_IDENTITY_JSON 显式指路不存在=立即报错;查找=repo 相对>C:\sanguo_vnpy_v2\config\(生产副本回退位)
- 灾备腿同源: relogin/qmt_bridge_probe/setclip 改 from qmt_gate_common import ACCOUNT
- 桥腿镜像同链: xt_gateway._qmt_account/qmt_gateway_client._identity_default_account/is_price_source_vps/bridge_switch_ops(3 处 env 注入+1 处内嵌探针串)/check_xtquant——env 带默认的硬编码默认值全数退役
- xt_eod_wrapper.ps1 改 Get-Content identity json(保持 ASCII);码内 docstring 字面量→占位符(engine/runner/runner_live/live.yaml 注释/README)
- .gitignore 白名单 !config/qmt_identity.json(全局 *.json 曾吞之,git check-ignore 实证)
- tests/trader/test_qmt_identity.py 五钉: 装载链语义(env 双名覆盖/显式指路 fail)+单源一致性(identity≡live.yaml≡watch_accounts≡gate_common≡各腿镜像,休市表 CI 同步测先例)+码面字面量 grep-pin(生产码面零 66639661,只 config 三件套可含)
- 监控 spec: §3.2 账号来源 bullet/§9.A-18 坑条/§12 行数+行号勘正(插入块致 bridge_ping/calendar_probe/relogin_running 引用行号推移);runbook: 非代码资产两行(副本回退路径+生产 wrapper 待改清单=10-08 窗口后 §五③)

生产 wrapper(C:\sanguo_bigqmt 库外)字面量收尾=10-08 复市验证窗口后非代码资产流程;VPS 班车同窗口(护 10-08 07:50 relogin 首班旧码纯净)。trader+api 620 绿。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-05 17:32:42 +08:00

130 lines
4.7 KiB
Python

# -*- coding: utf-8 -*-
"""QMT 部署身份单一权威源契约(P2-18 根治, 2026-10-05)——config/qmt_identity.json。
三组钉(audit/20261005_qmt_identity_env_plan.md §四):
① 装载链语义: env SANGUO_QMT_ACCOUNT / BIGQMT_ACCOUNT_ID(兼容别名)覆盖 > json;
SANGUO_IDENTITY_JSON 显式指路不存在=RuntimeError(显式覆盖不静默回退)。
② 单源一致性(anti-drift, 休市表 CI 同步测先例): identity.default_account ≡
live.yaml.account ≡ ∈ data_platform.yaml watch_accounts ≡ gate_common.ACCOUNT
≡ 各腿镜像解析值(env 清空)。
③ 字面量 grep-pin: "66639661" 只允许出现在 config 三件套; 生产码面
(scripts/、sanguo_*)零字面量——任何新增扩散直接被本单测抓住。
"""
import json
import sys
from pathlib import Path
import pytest
import yaml
_REPO = Path(__file__).resolve().parents[2]
_CFG = _REPO / "config"
_GATE_DIR = _REPO / "scripts" / "qmt_relogin"
if str(_GATE_DIR) not in sys.path:
sys.path.insert(0, str(_GATE_DIR))
import qmt_gate_common as gate # noqa: E402
_ENV_NAMES = ("SANGUO_QMT_ACCOUNT", "BIGQMT_ACCOUNT_ID", "ACCOUNT_ID")
def _ident() -> dict:
return json.loads(
(_CFG / "qmt_identity.json").read_text(encoding="utf-8"))
# ---------------- ① 装载链语义 ----------------
def test_load_identity_env_overrides(monkeypatch):
for n in _ENV_NAMES + ("SANGUO_IDENTITY_JSON",):
monkeypatch.delenv(n, raising=False)
base = gate.load_identity()
assert base["default_account"] == _ident()["default_account"]
monkeypatch.setenv("SANGUO_QMT_ACCOUNT", "123")
assert gate.load_identity()["default_account"] == "123"
monkeypatch.delenv("SANGUO_QMT_ACCOUNT")
monkeypatch.setenv("BIGQMT_ACCOUNT_ID", "456") # 兼容别名
assert gate.load_identity()["default_account"] == "456"
def test_load_identity_explicit_path_missing_fails(monkeypatch):
for n in _ENV_NAMES:
monkeypatch.delenv(n, raising=False)
monkeypatch.setenv("SANGUO_IDENTITY_JSON", "/nonexistent/x.json")
with pytest.raises(RuntimeError, match="SANGUO_IDENTITY_JSON"):
gate.load_identity()
def test_module_account_bound_to_identity():
assert gate.ACCOUNT == _ident()["default_account"]
# ---------------- ② 单源一致性 ----------------
def test_single_source_agreement(monkeypatch):
for n in _ENV_NAMES:
monkeypatch.delenv(n, raising=False)
monkeypatch.setenv("SANGUO_QMT_PATH", "/tmp") # 防扫 C:\
ident = _ident()
live = yaml.safe_load((_CFG / "live.yaml").read_text(encoding="utf-8"))
assert live["account"] == ident["default_account"]
dp = yaml.safe_load(
(_CFG / "data_platform.yaml").read_text(encoding="utf-8"))
assert ident["default_account"] in dp["live_trading"]["watch_accounts"]
assert gate.ACCOUNT == ident["default_account"]
from sanguo_qmt_bridge import xt_gateway
assert xt_gateway.ACCOUNT_ID == ident["default_account"]
from sanguo_trader import qmt_gateway_client as qgc
setting = qgc._QmtExec._setting()
assert setting["交易账号"] == ident["default_account"]
sys.path.insert(0, str(_REPO / "scripts"))
sys.path.insert(0, str(_REPO / "scripts" / "pipeline"))
try:
import bridge_switch_ops as bso # noqa: E402
assert bso._qmt_account() == ident["default_account"]
import is_price_source_vps as ips # noqa: E402
assert ips._identity_default_account() == ident["default_account"]
finally:
sys.path.remove(str(_REPO / "scripts"))
sys.path.remove(str(_REPO / "scripts" / "pipeline"))
# ---------------- ③ 字面量 grep-pin ----------------
_CODE_ROOTS = ("scripts", "sanguo_live", "sanguo_trader", "sanguo_qmt_bridge",
"sanguo_portfolio", "sanguo_data", "sanguo_factor")
_ALLOWED_EXACT = {
"config/qmt_identity.json", "config/live.yaml", "config/data_platform.yaml",
}
def test_account_literal_confined_to_config():
"""生产码面零账号字面量: 只 config 三件套可含(P2-18 前码面曾有 17 处)。"""
offenders = []
for root in _CODE_ROOTS:
base = _REPO / root
if not base.is_dir():
continue
for p in base.rglob("*"):
if (not p.is_file() or p.suffix not in
{".py", ".ps1", ".xml", ".md", ".yaml", ".json"}):
continue
rel = p.relative_to(_REPO).as_posix()
if rel in _ALLOWED_EXACT:
continue
try:
text = p.read_text(encoding="utf-8", errors="ignore")
except OSError:
continue
if "66639661" in text:
offenders.append(rel)
assert offenders == [], "账号字面量泄漏进码面: %s" % offenders