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。
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# -*- 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)
|
||||
Reference in New Issue
Block a user