- Introduced `_recent_days_series_hint` to handle queries related to "最近N天" for time series data aggregation. - Added `_cn_num_to_int` function to convert Chinese numerals to integers for better query parsing. - Updated `_nl_sql_hints` to incorporate the new hint generation logic, ensuring accurate SQL output for recent days queries. - Enhanced documentation to reflect these changes and improve clarity on the new functionalities.
9.4 KiB
9.4 KiB
测试日志:风控线(风控 Agent)端到端边界补测与恒真断言修复对照
1. 文档元数据
| 项 | 值 |
|---|---|
| 测试记录编号 | TEST-LOG-2026-09-13-RK-001 |
| 缺陷/变更标题 | 风控线端到端补测(RK7 后端边界与语义 · RK8 恒真断言修复后对照)+ Part B/C |
| 文档版本 | v1.0 |
| 创建日期 | 2026-09-13 |
| 关联分支 | merger |
| 关联拍板/TODO | 本轮七项口径;另有 RK 发现-1「未知资源语义分裂」待拍板 |
| 风险等级 | LOW(无 P0/P1;两条发现均为 P3 契约/口径类) |
| 缺陷类型 | 契约不一致("找不到"语义分裂)· 时区口径 · 测试资产腐化(F-01,已修) |
| 发现阶段 | 端到端覆盖缺口补测轮 · Part A/B/C |
2. 组织与责任
| 项 | 值 |
|---|---|
| 所属模块 | 风控线(风控 Agent):app/api/risk.py · app/service/risk_* · app/tool/core_ro_tool.py |
| 模块负责人 | — |
| 发现人 | 本轮补测 |
| 修改人 | 无(本轮只报告不修业务代码);测试资产 F-01 已修(单独记账) |
| 测试执行人 | 本轮补测 |
| 评审人 | — |
3. 缺陷描述
RK 发现-1 —— suitability/check 的「找不到」语义在同一路由内按角色分裂
| 项 | 值 |
|---|---|
| 缺陷编号 | RK-1 |
| 严重等级 | P3(契约不一致;fail-closed,无安全后果) |
| 复现步骤 | python scripts/dev/risk_gap_e2e_smoke.py --only RK7(用例 7-07 / 7-07c / 7-07d / 7-07e / 7-08b) |
| 期望 | 未知客户的响应形态在角色间一致 |
| 实际 | risk_officer → 200(blocked=true mismatch_type='not_found' SUIT_NOT_FOUND)· advisor → 403 AUTH_403_NOT_ASSIGNED · customer → 403 AUTH_403_NOT_OWNER;未知产品则 200+blocked。404 从未出现 |
| 根因 | 归属/数据域校验先于资源存在性校验 ⇒ "不存在"被"无权"遮蔽;app/api/risk.py:134 的 LookupError → 404 分支因此不可达(本轮未能构造) |
| 修复方案 | 本轮不修。待拍板:统一为 404(信息更准)还是维持现状(不泄漏客户存在性,安全上更优)。当前实现 fail-closed,不静默放行,三个角色均被拒 |
RK 发现-2 —— risk_alert.created_at 的时区口径未统一
| 项 | 值 |
|---|---|
| 缺陷编号 | RK-2 |
| 严重等级 | P3(口径/系统性坑) |
| 复现步骤 | python scripts/dev/risk_gap_e2e_smoke.py --only RK7(用例 7-10) |
| 期望 | 时间字段带明确时区语义 |
| 实际 | '2026-09-13T21:05:39.109000' —— 本地时区、无偏移后缀的 ISO 串 |
| 根因 | 落库写入未带时区,读出不标注 |
| 修复方案 | 本轮不修。影响面:audit_log.created_at >= DATE_SUB(NOW(), INTERVAL 15 MINUTE) 这类「近 N 分钟」查询,距上轮 <15 分钟复跑会假 FAIL(本项目 R4 已多次踩到)。处置:同类窗口查询一律距上轮 ≥15 分钟,或改按本轮 runstamp 过滤(更优) |
测试资产腐化 F-01 —— 03-risk.mjs:23 恒真断言(本轮已修,单独记账)
| 项 | 值 |
|---|---|
| 缺陷编号 | F-01 |
| 严重等级 | P2(测试基建:该断言永远 PASS,不构成任何证据) |
| 复现步骤 | 修复前:任意运行 03-risk.mjs,第 23 行恒 PASS |
| 期望 | 对实测值做真实断言 |
| 实际 | 原写法 ok(..., true, ...) —— 期望值写死为 true |
| 根因 | 资产腐化:脚本从未被校准(本轮首跑才暴露同类问题) |
| 修复方案 | 已修:改为对实测值的真实断言(风控首页 KPI 为真实正整数(非 0/非占位),实测 heroTotal:37)。不写死为某个具体数字 —— 因为预警数随演示动作变化,写死会在下次跑批时假 FAIL |
4. 变更清单
| 类型 | 路径 | 说明 |
|---|---|---|
| 新建 | scripts/dev/risk_gap_e2e_smoke.py |
RK7–RK8 缺口补测脚本(真实 HTTP) |
| 修复(测试资产) | scripts/e2e/03-risk.mjs:23 |
F-01 恒真断言 → 真实断言 |
| 收复 | scripts/e2e/10-risk-pages.mjs · scripts/e2e/03-risk.mjs |
自仓库外收回,改指向仓库内 scripts/e2e/harness.mjs(F-05) |
| 未改 | scripts/dev/risk_e2e_smoke.py |
回归基线,一个字未动 |
| 未改 | app/** · web/src/** |
本轮边界:不改业务代码 |
5. 测试执行记录
| 序号 | 类型 | 用例/命令 | 执行人 | 结果 | 证据 |
|---|---|---|---|---|---|
| 1 | Part A | python scripts/dev/risk_gap_e2e_smoke.py --report …/rk-gap.md |
本轮 | 23 PASS / 0 FAIL / 0 SKIP / 8 INFO(退出码 0) | _raw/rk-gap.md · _raw/rk-gap.stdout.txt |
| 2 | Part B | node 10-risk-pages.mjs |
本轮 | 21 PASS / 0 FAIL | _raw/results-10-risk.json |
| 3 | Part B | node 03-risk.mjs(F-01 修复后) |
本轮 | 14 PASS / 0 FAIL / 2 INFO | _raw/…(见横切包 03-risk.stdout.txt) |
| 4 | Part C | python scripts/dev/risk_e2e_smoke.py --report …/_partc-risk.md |
本轮 | 63 PASS / 0 FAIL / 1 SKIP(退出码 0,基线未退步) | _raw/_partc-risk.md · _raw/_partc-risk.stdout.txt |
分组明细(Part A):
| 组 | 覆盖对象 | PASS | FAIL | SKIP | INFO |
|---|---|---|---|---|---|
| RK7 | 后端补漏(分页边界 · 接口↔库对账 · 处置前后差值 · 语义分裂 · G-08 三要素 · 时区口径) | 22 | 0 | 0 | 4 |
| RK8 | 浏览器复跑对照(恒真断言修复后逐条比对基线) | 1 | 0 | 0 | 4 |
| 合计 | 23 | 0 | 0 | 8 |
RK7 逐条关键实测:
| 用例 | 断言 | 实测 |
|---|---|---|
| 7-01 / 7-01b | page=0 / page=-3 → 422 |
HTTP 422 REQUEST_VALIDATION_FAILED ×2 |
| 7-02 / 7-02a / 7-02b / 7-02c | 超末页 page=999999 → 200、items=[]、total 与首页一致、回显 page 一致 |
total=53(两页一致) |
| 7-03 / 7-03a | 分页不重复,且两页各自非空 | 重复 [] · page1=5 page2=5 |
| 7-04 / 7-04a / 7-04b | status=pending_review 过滤生效 + stats 自洽 |
n=36 states=['pending_review'] · stats=36 total=36 |
| 7-05 | 接口 total ↔ 库计数 | API 36 / DB 36 ✅ |
| 7-06 / 7-06a | 处置 → 计数恰好 −1(前后差值) | 36 → 35 |
| 7-07 / 7-07a | 未知客户(officer)→ 200 且 fail-closed | blocked=True mismatch_type='not_found' code='SUIT_NOT_FOUND' |
| 7-07c | 通道值与账号类型不符 → 矩阵层先拦 | HTTP 403 AUTH_403_AGENT_MISMATCH |
| 7-07d | 未知客户(advisor 归属分支)→ 403 非 404 | HTTP 403 AUTH_403_NOT_ASSIGNED |
| 7-07e | 未知客户(customer self 域)→ 403 非 404 | HTTP 403 AUTH_403_NOT_OWNER |
| 7-08 / 7-08a | 已知客户 + 未知产品 → 200 + blocked | blocked=True mismatch_type='not_found' |
| 7-09 | 阻断响应补齐 G-08 三要素 | advice='请联系持证投资顾问' · notice='本次请求已记录' |
RK8 对照结果(F-01 修复后):
| 用例 | 断言 | 实测 |
|---|---|---|
| 8-00 | 对照规模 | 基线 21 条 / 本轮 21 条 · 交集 21 条 |
| 8-01 | 无 PASS→FAIL 退化 | ✅ 比较 21 条交集,退化集 ∅ |
| 8-02 / 8-03 / 8-04 | 修复的旧 FAIL / 本轮新增 / 基线独有 | 均无 |
这条对照是本包的核心价值: 恒真断言被替换为真实断言后,原 21 条 PASS 全部仍然 PASS ⇒ 反证原基线不是被恒真断言撑起来的。缺少这一步,"修好了"就无法与"改断言把 FAIL 藏起来"区分。
本轮我方订正(1 处,非产品缺陷): 7-07 首跑我按 risk.py:134 的代码读法期望未知客户 → 404,实测 200。三角色逐一探测后确认是三分的 fail-closed 语义而非 404,遂改写为实测真值并把 404 分支登记为未覆盖路径。是我的期望错,不是产品错。
6. 数据准备
| 数据集 | 是否重灌 | 操作 |
|---|---|---|
jinrong_agent.risk_alert |
否(且按纪律不还原) | RK7-06 每跑一次推进 1 条 pending_review → confirmed_normal;本轮共 4 次含处置跑批,pending_review 41 → 35。追加型审计表,还原反而破坏审计链 |
jinrong_core 5 写表 |
— | Part C 的 risk_e2e_smoke.py 会写入;已在 Phase 6 依 snapshot.sql 还原并逐表 CHECKSUM 校验通过(见横切包 XC-001 §6) |
| 演示态恢复 | 如需 | scripts/demo/seed_risk_agent.ps1 |
7. 结论
| 项 | 结论 |
|---|---|
| 需求覆盖 | 风控线后端 4 条路由此前已全覆盖;本包补齐分页边界 · 对账一致性 · 语义分裂取证 · 恒真断言修复对照四块 |
| 回归 | ✅ 未退步:Part C 基线 risk_e2e_smoke.py 63 PASS / 0 FAIL(退出码 0);Part B 10-risk-pages.mjs 21/21 PASS 且 无 PASS→FAIL 退化 |
| 剩余风险 | ① risk.py:134 的 404 分支仍不可达(未覆盖路径);② pending_review 计数不可还原(41→35,已登记);③ created_at 时区口径未统一 |
| 建议人工再验 | ① 打开 /app/risk/home 目视确认 KPI 与台账在 pending_review=35 下自洽;② 若决定统一"找不到"语义,须先拍板(404 信息更准 vs 现状不泄漏客户存在性) |
8. 签核 / 修订历史
| 版本 | 日期 | 修订人 | 说明 |
|---|---|---|---|
| v1.0 | 2026-09-13 | 本轮补测 | 首次建立:RK7–RK8 + Part B/C 证据归档;F-01 修复对照 |