test(e2e): 新增填报流程端到端脚本与测试计划

既有 37 个脚本是按模块切片的(门禁/草稿/提交/审核各一个),有三处固定盲区:
跨步骤的状态轨迹没人断言、切片之间那条缝(驳回后改了字段再交)、以及脚本
docstring 自述与实际断言不符(e2e-m3-draft.py:11 自述的那条断言全文没有)。

docs/11 给出 A~J 十组覆盖矩阵与判据口径(含用 grep 核对实际断言、不读自述的
规程),scripts/e2e-m3-flow.py 只补这三处、其余交给既有脚本,并钉住 F-125 /
F-128 的现状(XFAIL)。verify-all 的脚本数口径随之变成 38。

配套 scripts/dev-reveal-roster-idcard.py:L3-B 走查要在门禁界面手输身份证,
而明文只在建夹具那一刻存在过(库里是 AES 密文 + 脱敏 + HMAC 索引),
此脚本只读解密打印,不入库;用完连同夹具任务一起 --clean。
This commit is contained in:
2026-10-14 15:40:18 +08:00
parent 623e6393d5
commit 4e68d0e5da
4 changed files with 1921 additions and 3 deletions
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""临时脚本(只读):解出本轮走查夹具(e2e-m3-flow.py 建的 taskMain)名册行身份证明文。
用途:L3-B 浏览器走查要在门禁界面里**手动输入姓名 + 身份证号**,
而明文只在建夹具的那一刻存在过(库里是 AES 密文 / 脱敏 / HMAC 索引)。
dev 的 AES 密钥在 application-dev.yml 里(提交进仓库的开发专用密钥),格式见 AesGcmCipher:
Base64( IV(12) || ciphertext || tag(16) )。
**只读,不写库。** 走查完请连同夹具任务一起清掉(`python scripts/e2e-m3-flow.py --clean <taskId>`)。
"""
from __future__ import annotations
import base64
import re
import subprocess
import sys
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
TASK_ID = int(sys.argv[1]) if len(sys.argv) > 1 else 2311
DEV_YML = r"D:\IDEA_Project\xiaotutongxue\backend\xt-server\src\main\resources\application-dev.yml"
TEXT = open(DEV_YML, encoding="utf-8").read()
AES_KEY = base64.b64decode(re.search(r"aes-key:\s*(\S+)", TEXT).group(1))
CIPHER = AESGCM(AES_KEY)
def psql(sql: str) -> list[list[str]]:
p = subprocess.run(
["docker", "exec", "xt-pg", "psql", "-U", "fill", "-d", "filldb",
"-A", "-F", "\t", "-P", "pager=off", "-t", "-c", sql],
capture_output=True,
)
if p.returncode != 0:
raise SystemExit(p.stderr.decode("utf-8", "replace"))
# ⚠️ 不能对整段输出 .strip() —— psql 的 -A 模式在**末尾字段为空**时会留下结尾制表符,
# 整段 strip 会把它吃掉,于是"未绑定"的那一行少一列、解包直接报
# `ValueError: not enough values to unpack`(而这个报错看起来像解密失败)。
out = p.stdout.decode("utf-8", "replace")
return [ln.split("\t") for ln in out.splitlines() if ln.strip()]
def decrypt(cipher_b64: str) -> str:
raw = base64.b64decode(cipher_b64.strip())
return CIPHER.decrypt(raw[:12], raw[12:], None).decode("utf-8")
rows = psql(f"""
SELECT id, real_name, COALESCE(id_card_no,''), COALESCE(id_card_mask,''),
COALESCE(bind_user_id::text,'')
FROM collect_roster
WHERE deleted = 0 AND task_id = {TASK_ID}
ORDER BY id
""")
print(f"任务 {TASK_ID} 的名册(走查用;只打印,不入库)")
print("名册行 | 姓名 | 身份证(明文) | 脱敏 | 绑定用户")
for r in rows:
rid, name, enc = r[0], r[1], r[2]
mask = r[3] if len(r) > 3 else ""
bind = r[4] if len(r) > 4 else ""
try:
plain = decrypt(enc)
except Exception as e: # noqa: BLE001
print(f"{rid} | {name} | [解不开:{type(e).__name__}] | {mask} | {bind or '未绑定'}")
continue
print(f"{rid} | {name} | {plain} | {mask} | {bind or '未绑定'}")