tradingSystem/scripts/test_page_enum_guard.py

151 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""页面文案守卫:枚举字段不许直接印到页面上(静态扫描,不连库、不起浏览器)。
## 为什么要有它
app/web/static/index.html 是三千行的单文件页面,**没有任何单测**。改错了不会有
东西报警。而它上面最容易出的错,是把后端送来的枚举值直接塞进模板——
页面上就出现 COMMAND_HALT、REJECT、issue_command:EXIT_STOCK 这种东西。
页面里其实早就有翻译层T 表加 tx 函数),问题是它被系统性地绕开了:
2026-09-04 实测,同一份文件里 :899 走了 tx('frozen', …)、:1533 没走,印的是
同一个字段。这不是没想到,是执行漏网。所以需要一道机器检查。
## 它检查什么
一、**枚举字段直出**:模板里形如 {{ x.frozen_reason }} 的插值,字段名落在
ENUM_FIELDS 里,而外面没有包 tx( 或别的翻译函数,就报。
二、**判据码直出**failed_checks、reason 这类会带 `CODE: ` 前缀的字段,
必须过 plainCheck 才能显示。
三、**整个对象打给人看**JSON.stringify 出现在给交易员看的区域就报。
运维面板那几处是有意的,登记在 JSON_OK 里放行。
四、**翻译表兜底不许裸奔**tx 找不到码时必须把码退到括号里,
不能让一个英文词单独立在页面上当句子。
## 怎么维护
加了新的枚举字段就往 ENUM_FIELDS 里加一行。这份名单是这道检查的全部依据,
不加就等于不检查——所以它和 T 表要一起改。
跑法python3 scripts/test_page_enum_guard.py
"""
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
PAGE = os.path.join(os.path.dirname(HERE), "app", "web", "static", "index.html")
# 后端会送到页面的枚举字段。取值全集在 T 表里,两者要一起改。
# 只列**枚举**字段后端已经写成中文句子的自由文本basis、why、note 这类)不在此列,
# 那些本来就该原样显示。
ENUM_FIELDS = [
"frozen_reason", # 冻结来路COMMAND_HALT / BRAKE / MANUAL / NONE
"cushion_state", # 安全垫SOLID / THIN / NONE
"lot_kind", # 批次BASE / FILL / ADD / DCA / T0 / RECON
"arbiter", # 谁裁的rule / judge / user
"stype", # 交易方案类型T0 / GRID / TRAIL
"autonomy", # 自主档full / propose_only / off
"ref_source", # 参考位来源bionic / self_calc / user / none
]
# 这些字段带 `CODE: ` 前缀,显示前必须剥掉。
CODED_FIELDS = ["failed_checks"]
# 把整个对象打给人看,只有落在**交易员会看的区域**才算问题。两类不算:
# 一是存本地状态sessionStorage / localStorage那不是给人看的
# 二是运维面板里那几个"原样看接口返回"的框,读者是运维不是交易员,
# 它们本来就该显示原始数据。
# 判定靠变量名:写进 opsResult / planResult 这类运维面板的框,或者进浏览器存储,
# 都放行。别的地方出现 JSON.stringify 就报。
JSON_SKIP_MARKS = (
"sessionStorage", "localStorage", # 存本地状态,不是给人看的
"opsResult", "planResult", # 运维面板:有意显示接口原文
"console.", # 控制台
# 下面几处都在运维面板里,读者是运维不是交易员,本来就该看原始数据。
# 每加一条都要写清它在哪个面板、为什么放行。
"planStatus.theme_sync", # 上游计划诊断行:主题映射,排查用
"planStatus.query", # 上游计划诊断行:本次请求参数,排查用
"chgLog", # 运维面板「快照台账」:整行原文
"report.report", # 运维面板「快照台账」:日报原文
"wsc.queue", # 运维面板「ws 通道」:出口队列计数
"s.row.payload", # 运维面板「ws 通道」:报文原文
"s.row.hard_numbers", # 只在悬浮提示里作原文备查,正文已是中文
"s.row)", # 运维面板「快照台账」:整行原文
)
# 允许包住枚举字段的翻译函数。
WRAPPERS = ("tx(", "opCn(", "plainCheck(", "propVerdictLine(")
def fail(msgs):
for m in msgs:
print(" FAIL", m)
def main():
src = open(PAGE, encoding="utf-8").read()
lines = src.split("\n")
bad = []
# 一、枚举字段直出
for i, line in enumerate(lines, 1):
for f in ENUM_FIELDS:
# 只看插值与属性绑定里出现的字段引用
for m in re.finditer(r"[\w.\[\]]*\.%s\b" % re.escape(f), line):
seg = line[max(0, m.start() - 60):m.end() + 20]
if any(w in seg for w in WRAPPERS):
continue
# v-if 这类判断用途不算显示
if re.search(r'v-(if|else-if|show)="[^"]*\.%s\b' % re.escape(f), line):
continue
# JS 里做比较也不算显示
if re.search(r"\.%s\s*(===|!==|==|!=|&&|\|\||\?)" % re.escape(f), seg):
continue
if "{{" not in line and ":title=" not in line and ":content=" not in line:
continue
bad.append(f"{i} 行:枚举字段 {f} 直接印到页面上,没走翻译。\n"
f" {line.strip()[:120]}")
# 二、判据码直出
for i, line in enumerate(lines, 1):
for f in CODED_FIELDS:
if f"{f}" in line and "{{" in line and "plainCheck" not in line:
bad.append(f"{i} 行:{f} 带大写判据码前缀,显示前必须过 plainCheck。\n"
f" {line.strip()[:120]}")
# 三、整个对象打给人看
for i, line in enumerate(lines, 1):
if "JSON.stringify" in line:
if not any(k in line for k in JSON_SKIP_MARKS):
bad.append(f"{i} 行:把整个对象打给交易员看。要么改成中文说明,"
f"要么说明它落在运维面板并登记进 JSON_SKIP_MARKS。\n"
f" {line.strip()[:120]}")
# 四、兜底不许裸奔
m = re.search(r"const tx = \(g, c\) => \{(.*?)\n \};", src, re.S)
if not m:
bad.append("找不到 tx 函数,这道检查的前提没了")
elif "return String(c);" in m.group(1):
bad.append("tx 的兜底是原样返回,会让一个英文词单独立在页面上当句子。"
"改成把码退到括号里。")
# 五、翻译表与本文件的字段名单要对得上
for f in ENUM_FIELDS:
group = {"frozen_reason": "frozen", "cushion_state": "cushion",
"lot_kind": "lot", "arbiter": "arbiter", "stype": "stype",
"autonomy": "autonomy", "ref_source": "refSource"}.get(f)
if group and f" {group}: {{" not in src and f" {group}:{{" not in src:
bad.append(f"字段 {f} 在名单里,但 T 表里没有对应的 {group}")
if bad:
print(f"页面文案守卫:发现 {len(bad)}")
fail(bad)
sys.exit(1)
print(f"ALL OK — 页面文案守卫:{len(ENUM_FIELDS)} 个枚举字段、"
f"{len(CODED_FIELDS)} 个带判据码的字段、整对象直出、翻译兜底 全部通过")
if __name__ == "__main__":
main()