Files
xiaotutongxue/scripts/check-manual.py
T
lovife 30c3714d35 test(scripts): 36 个 E2E 与 6 个机械校验工具(1548 条断言)+ 一键交付自检
新增:check-api-parity(契约 121 接口 ↔ 控制器,自带「必须恰好 121」自检)、
check-entity-columns(基类要求的列 vs 迁移建表列,F-35 那个坑踩了四次之后上的工具)、
check-manual(手册引用的错误码与提示文案对得上代码)、check-test-profile(test profile 真起一次)、
e2e-prodguard、load-test(四场景、按端点分别统计)、verify-all(一条命令跑完交付自检)、
dev-seed-audit(界面验收用的演示数据),以及 M1~M9 各轮 E2E。

压测抓到一个真缺陷并修掉(F-92):门禁 IP 限流原来计「所有请求」,于是一个班连同一个
WiFi 时 60 个不同学生同时核验只过了 20 个 → 改为只计失败 + 单独的成功上限,20/60 → 60/60。

度量工具的偏差会伪装成产品缺陷(F-60/F-77/F-97),所以每个脚本都断言「动作自己的结论字段」
(submitted / created / canExport),而不是 HTTP 200 + code 0。

验证:verify-all.py --quick 5/5;--with-e2e 见 docs/09 §8。
2026-09-30 09:02:57 +08:00

89 lines
3.2 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 -*-
"""手册与代码的一致性检查(客服话术的可信度靠它)。
## 为什么要查这个
手册(`docs/10-用户手册与客服话术.md`)的价值**完全建立在
"手册里那句话 == 用户屏幕上那句话"**上 —— 客服照着它回答,
对不上就会开始自己编,那比没有手册更糟。
而**手册是文档、文案在代码里**:改一次代码文案,手册就静默失准,
且没有任何东西会红。这与"首页在说一件不真的事"(F-82)是同一类问题。
所以这里把它变成机械检查,与 `check-api-parity.py`(契约↔路由)、
`check-entity-columns.py`(实体↔DDL)并列 —— 都是"文档与代码是否还对得上"。
## 查两件事
1. 手册里引用的每个错误码(反引号包着的 5 位数)在 `ErrorCode.java` 里**确实存在**;
2. 手册里"客服会照着念"的那些提示文案在代码里**逐字存在**。
⚠️ 它**不检查**手册有没有漏掉新错误码 —— 那要靠人。
它拦的是"手册写了代码里没有的东西"(这会让客服查到一半卡住)。
跑法:python scripts/check-manual.py
"""
import io
import re
import sys
sys.stdout.reconfigure(encoding="utf-8")
manual = io.open('docs/10-用户手册与客服话术.md', encoding='utf-8').read()
codes_src = io.open('backend/xt-common/src/main/java/cn/tianbaotong/fill/common/api/'
'ErrorCode.java', encoding='utf-8').read()
# ① 码:三个捕获组 → 自己建索引
rows = re.findall(r'([A-Z_]+)\((\d{4,5}),\s*"([^"]*)"\)', codes_src)
declared_by_code = {code: (name, msg) for name, code, msg in rows}
print("ErrorCode 里声明了 %d 个码" % len(declared_by_code))
used = sorted(set(re.findall(r'`(\d{5})`', manual)))
print("手册引用了 %d 个码" % len(used))
bad = []
for c in used:
if c in declared_by_code:
name, msg = declared_by_code[c]
print(" ✓ %s %-24s %s" % (c, name, msg[:44]))
else:
bad.append(c)
print(" ✗ %s **ErrorCode 里没有这个码**" % c)
# ② 关键文案
key_phrases = [
"没有找到你的信息,请核对后重试",
"操作过于频繁,请稍后再试",
"该信息已被登记",
"你已在名单中,请返回重新核验",
"你已提交过申请,请耐心等待",
"这份材料可能不是你的,请重新上传",
"已进入审核,需先撤回才能删材料",
"已通过终审,不可撤回",
"任务已截止、已归档或已暂停",
"驳回次数已达上限",
"存在不合格文件,禁止导出",
"你的材料被退回了",
"导出包已过期",
"这个学生还没扫码核对身份",
]
src_all = ""
import glob
for p in glob.glob('backend/**/*.java', recursive=True):
try:
src_all += io.open(p, encoding='utf-8').read()
except Exception:
pass
print("\n核对手册里引用的提示文案(%d 句):" % len(key_phrases))
missing = []
for ph in key_phrases:
if ph in src_all:
print(" ✓ %s" % ph)
else:
missing.append(ph)
print(" ✗ %s —— **代码里找不到这句**" % ph)
print("\n结果:错误的码 %d 个,找不到的文案 %d 句" % (len(bad), len(missing)))
sys.exit(1 if (bad or missing) else 0)