- Introduced `convert_meta_api` endpoint to fetch the latest NAV date for conversion processes, restricted to users with the "risk_officer" role. - Updated `created_at` field in `AgentMessage` to use UTC timezone for consistency in timestamp handling. - Added `get_max_product_nav_date` method in `CoreReadOnlyRepository` to support the new API functionality. - Enhanced Milvus template loading in `MilvusTemplateVectorStore` to ensure collections are loaded when they exist. This update improves the API's capability to handle conversion metadata and ensures accurate timestamp management across the application.
224 lines
12 KiB
Markdown
224 lines
12 KiB
Markdown
# 顾问前端 + 风控新功能(基金转换 T+1)+ 风控前端 端到端测试(第三轮)
|
||
|
||
> **状态:已执行完毕(2026-09-12)**。本文件是实施计划的入库副本,勾选状态为**实际结果**。
|
||
> 设计说明见 `docs/superpowers/specs/2026-09-12-agent-frontend-e2e-design.md`;
|
||
> 产出报告见 `docs/memory/tests/2026-09-12-agent-frontend-e2e/TEST-LOG-2026-09-12-FE-001.md`。
|
||
>
|
||
> 本 plan 只做**测试**与**报告**,**不改任何业务代码** —— 实际执行亦如此(`app/` 与 `web/src/` 零改动)。
|
||
|
||
## Context
|
||
|
||
前两轮都是 API-only,且都被后续合并改写了基线:
|
||
|
||
| 轮次 | 包 | 覆盖 | 现状 |
|
||
|---|---|---|---|
|
||
| 第一轮 | `docs/memory/tests/2026-09-12-advisor-agent-e2e/` | 顾问 API 全量 | **已失真**:结论写于 `03fce88` 之前 → 本轮**已改写为 v1.1** |
|
||
| 第二轮 | `docs/memory/tests/2026-09-11-risk-agent-e2e/` | 风控 API(进程内 TestClient) | 基线有效,但未覆盖基金转换 |
|
||
| 本轮 | `docs/memory/tests/2026-09-12-agent-frontend-e2e/` | 顾问前端 + 风控新功能 + 风控前端 | **已完成** |
|
||
|
||
驱动本轮的三件事:
|
||
|
||
1. **顾问前端已就绪** —— 四页此前从未在真实浏览器里点过。
|
||
2. **风控新功能已合入** —— `03fce88` 把「基金转换 T+1」整条线并入 `merger`(110 文件、仅后端);`c09b987` 补上 `RiskConvertPage` 前端与迁移。该功能**从未做过端到端**。
|
||
3. **基线被合并改写** —— 当前工作区在 `merger` @ `c09b987`。
|
||
|
||
### 开工前必须知道的三条基线订正(已实测)
|
||
|
||
- **D1 已修** —— `app/repository/core_ro.py:839,851` ✅ 实测转绿(见 Task 4)
|
||
- **D2 未修** —— `app/model/entities.py:116` 的 `created_at` 仍无 Python 侧 default ✅ 实测仍 500
|
||
- **D3 定性存疑** —— 设计时的假设「高层 `MilvusClient` 自动 load,所以不是未 load 的问题」**被服务端日志证伪**,详见 Task 4 结果
|
||
|
||
## Goal
|
||
|
||
对 A(顾问前端)· B(风控转换 + RBAC)· C(风控前端)三块做真实端到端(真浏览器 / 真 HTTP / 真 MySQL / 真 DeepSeek)验证,产出可复现的通过矩阵与缺陷清单,**先报告、确认后再修**。
|
||
|
||
## Architecture
|
||
|
||
```
|
||
Part A/C 浏览器跑道 Part B API 跑道
|
||
──────────────────────── ────────────────────────
|
||
Chromium(channel:chrome) httpx.Client
|
||
↓ http://localhost:5173 ↓ http://127.0.0.1:8000
|
||
Vite dev server (proxy /api) uvicorn app.main:app
|
||
↓ ↓
|
||
└──────── 同一套 MySQL + Redis + DeepSeek ────────┘
|
||
```
|
||
|
||
浏览器跑道**必须**经 Vite(`app/` 无 CORS 中间件)。
|
||
|
||
## Tech Stack
|
||
|
||
Playwright 1.63.0(Node,仓库外)+ 系统 Chrome · Python 3.13 + httpx 0.28 · MySQL 8 双库 + Redis 6380 + Milvus Lite + Ollama bge-m3
|
||
|
||
## Global Constraints(全部遵守 ✅)
|
||
|
||
1. ✅ **不改任何业务代码** —— `app/`、`web/src/` 零改动
|
||
2. ✅ **不修 D1–D4**,只报告
|
||
3. ✅ **不改 `web/package.json`** —— 浏览器脚本留在仓库外
|
||
4. ✅ **不修 `sandbox_risk_test.py:325-326`** —— 只作为「测试资产腐化」上报
|
||
5. ✅ **不动 `run_demo_smoke.ps1`** —— 同上
|
||
6. ✅ 未执行任何 DROP 重建(保留取证数据)
|
||
7. ✅ 凭据未逐字写入交付文档
|
||
8. ✅ KYC / 话术两页如实标 FAIL 并附页面取证
|
||
|
||
## File map(实际)
|
||
|
||
| 文件 | 动作 | 结果 |
|
||
|---|---|---|
|
||
| `C:/Users/Windows/e2e-jinrong/harness.mjs` | 改 | ✅ `ROUTES` 补页 + `clearAuth` / `waitRows` / `cjk`;`login()` 加 `reload:false` |
|
||
| `C:/Users/Windows/e2e-jinrong/09-advisor-pages.mjs` | 建 | ✅ 17 用例 · 15 PASS / 2 FAIL |
|
||
| `C:/Users/Windows/e2e-jinrong/10-risk-pages.mjs` | 建 | ✅ 21 用例 · 19 PASS / 2 FAIL |
|
||
| `C:/Users/Windows/e2e-jinrong/11-cross-role.mjs` | 建 | ✅ 9 用例 · 4 PASS / 5 FAIL |
|
||
| `scripts/dev/risk_e2e_smoke.py` | 建 | ✅ 64 用例 · **63 PASS / 0 FAIL** / 1 SKIP · 退出码 0 |
|
||
| `scripts/dev/advisor_e2e_smoke.py` | 复用不改 | ✅ 重跑取当前基线 · 63 用例 · 60 PASS / 3 FAIL |
|
||
| `docs/memory/tests/2026-09-12-advisor-agent-e2e/TEST-LOG-...-ADV-001.md` | 改 | ✅ 升 v1.1 · 关闭 D1 |
|
||
| `docs/memory/tests/2026-09-12-agent-frontend-e2e/` | 建 | ✅ README + FE-001 + `_raw/` |
|
||
| `docs/superpowers/specs/2026-09-12-agent-frontend-e2e-design.md` | 建 | ✅ |
|
||
| `docs/superpowers/plans/2026-09-12-agent-frontend-e2e.md` | 建 | ✅ 本文件 |
|
||
| `docs/memory/MEMORY.md` · `TODO.md` | 改 | ✅ 已同步 |
|
||
|
||
---
|
||
|
||
## Task 0 · 环境前置自检 ✅
|
||
|
||
- [x] 确认树:`merger` @ `c09b987`
|
||
- [x] 只读探测演示数据未过期(未 DROP)
|
||
- [x] 记录基线:NAV 最新 **`2026-09-11`** · `core_product_nav` 覆盖 14 产品
|
||
- [x] **抓到 merge 前的旧 uvicorn 进程**(PID 27348,启动于 16:55:24,早于代码落地 17:52–17:59)→ 重启,否则全部转换用例假失败
|
||
|
||
**结果**:前置通过。**新增前置项**:`/openapi.json` 必须含转换路径 —— 已写入报告 §3。
|
||
|
||
## Task 1 · 起服务 ✅
|
||
|
||
- [x] `python -m uvicorn app.main:app --host 127.0.0.1 --port 8000`(无 `--reload`)
|
||
- [x] `/health` 返回 `{"status":"ok"}`
|
||
- [x] `cd web && npm run dev` → `http://localhost:5173`
|
||
- [x] Vite 代理 `/api` 生效
|
||
|
||
## Task 2 · Part B:风控 API 真实 HTTP E2E ✅
|
||
|
||
- [x] 新建 `scripts/dev/risk_e2e_smoke.py`,照搬 `advisor_e2e_smoke.py` 骨架(`Ctx` / `ok`/`bad` / `--base-url` / `--only` / `--report` / 退出码 0·1·2)
|
||
- [x] R1 鉴权负例 · R2 台账 RBAC · R3 处置状态机 · R4 适当性 · R5 AML · R6 转换线
|
||
- [x] 顺序依赖:先 `simulate/trade` + `aml/scan` 造 alert,再做 R2/R3
|
||
|
||
**结果**:**63 PASS / 0 FAIL / 1 SKIP**,退出码 0。
|
||
|
||
**过程中的三条假 FAIL(全是脚本自身的 bug,已定位并修)**
|
||
|
||
| 假 FAIL | 真因 | 修法 |
|
||
|---|---|---|
|
||
| 转换受理 `可用份额不足:申请 30000,可用 0(已扣除在途转换占用)` | 反复跑测把 `CUST-1001` 持有的 30000 份 `PROD-110022` 占满 | 换仍有 80000 份余量的 `CUST-9527` |
|
||
| `accept_date=2026/09/15` 期 400 | FastAPI 查询校验 pre-handler 就拦下,实为 **422** | 期望改 422 `REQUEST_VALIDATION_FAILED` |
|
||
| 撤单 409 `CANCEL_NOT_ALLOWED` | **我自己的顺序错**:先跑了 `confirm_batch`,单子已进 `nav_pending`(不可撤) | 撤单挪到 confirm 之前,并另开一笔专用转换 |
|
||
|
||
> 三条都不是产品缺陷 —— 已用独立探针脚本逐条证实。
|
||
|
||
## Task 3 · Part A/C:浏览器 E2E ✅
|
||
|
||
- [x] `harness.mjs` 补路由 + 抽 `clearAuth` / `waitRows` / `cjk`
|
||
- [x] 登录回归:**去掉 `reload` 兜底仍能登录** → A6.1 PASS(BUG-1 回归确认)
|
||
- [x] **Part A 顾问**:首页 / 合规 / 话术模板 / KYC / 市场异动 → 15 PASS / 2 FAIL(D2 · D3)
|
||
- [x] **Part C 风控**:首页 / 台账 / 适当性 / AML / 模拟交易 / 转换页 → 19 PASS / 2 FAIL(F1 · F2)
|
||
- [x] **跨角色**:4 PASS / 5 FAIL(F3)
|
||
- [x] **明确排除**:两个 SSE 页
|
||
|
||
**结果**:47 用例 · **38 PASS / 9 FAIL**。
|
||
|
||
**AntD 6 的坑(本轮踩到,已固化)**
|
||
|
||
| 坑 | 实际 |
|
||
|---|---|
|
||
| `.ant-tabs-tabpane-active` 匹配 0 元素 | 活跃面板是 **`.ant-tabs-content-active`** |
|
||
| `.ant-select-selection-item` 取不到值 | 值节点是 **`.ant-select-content`** |
|
||
| `.ant-table-row` 匹配 0 元素 | 用 `.ant-table-tbody tr` |
|
||
| CJK 之间被插入空格 | 加 `cjk()` 辅助函数生成 `\s*` 间隔正则 |
|
||
| 非活跃 Tab 面板**仍挂载**且隐藏 | 所有交互必须 scope 到 `pane()`,否则选到隐藏元素而超时 |
|
||
|
||
## Task 4 · 改写第一轮顾问报告 ✅
|
||
|
||
- [x] 重跑 `advisor_e2e_smoke.py`
|
||
- [x] §3 被测版本 → `merger @ c09b987`
|
||
- [x] 关闭 D1(附 `core_ro.py:839,851` 证据 + G5 10/10 实测)
|
||
- [x] §4 用例数按实跑口径(63 用例)
|
||
- [x] D3 按本轮实测**重新定性** —— 见下
|
||
- [x] §11 结论改写 · §12 追加 v1.1 行
|
||
|
||
**D1 结论**:✅ **已关闭**。v1.0 为 3 PASS / 2 FAIL / 5 SKIP,本轮 G5 **10/10 PASS**,`scanned=14 created=2 used_fallback=False`。
|
||
|
||
**D3 结论 —— 我的设计期假设被证伪**:
|
||
|
||
- plan 假设「高层 `MilvusClient` 在 search 时自动 load,所以不是未 load 的问题」。**这个假设是错的。**
|
||
- uvicorn 日志里 `MilvusException (code=101, message=Collection 'kb_script_templates' is in state 'released'; call load() before search/get/query)` **出现 10 次**,且无其它 Milvus 错误类型。
|
||
- **第二层观察**:集合目录 5.0K / 2 文件,而已灌集合为 50K / 5 文件 ⇒ **疑似同时是空集**。仅补 `load()` 未必恢复召回。
|
||
- 如实记入报告,不掩饰假设被推翻。
|
||
|
||
> `pymilvus` 探针两次失败:① `Illegal uri`(legacy `connections` 在 import 期读 `MILVUS_URI`);② `Open local milvus failed`(运行中的 uvicorn 持有 Milvus Lite 文件锁)。最终改用**服务端自身日志**作为判别依据 —— 这比进程外探针更直接,且不需要停止服务。
|
||
|
||
## Task 5 · 产出本轮报告包 ✅
|
||
|
||
- [x] 建包:`README.md` + `TEST-LOG-2026-09-12-FE-001.md`(12 节 `_TEMPLATE.md` 结构)
|
||
- [x] 缺陷四类归属:**实现缺陷**(F1/F2/F4)· **纵深防御缺口**(F3)· **承接未修**(D2/D3)· **测试资产腐化**(T1)
|
||
- [x] 每个缺陷给:现象 / 复现 / 期望-实际 / 根因 `file:line` / 修复方向(只写方向)
|
||
- [x] 建 superpowers spec + plan 入库副本
|
||
- [x] 同步 `MEMORY.md` / `TODO.md`
|
||
|
||
## Task 6 · 收口与复现性 ✅
|
||
|
||
- [x] 报告附**复现三连**
|
||
- [x] 记录浏览器脚本在仓库外的事实与代价,挂到 `TODO.md` 的「E2E 最小集进仓库」P0
|
||
- [x] 列出未测/不可判定项及原因
|
||
|
||
---
|
||
|
||
## 结果总览
|
||
|
||
| 跑道 | 用例 | PASS | FAIL | SKIP |
|
||
|---|---|---|---|---|
|
||
| 顾问 API | 63 | 60 | 3 | 0 |
|
||
| 风控 API | 64 | **63** | **0** | 1 |
|
||
| 顾问前端 | 17 | 15 | 2 | 0 |
|
||
| 风控前端 | 21 | 19 | 2 | 0 |
|
||
| 跨角色 | 9 | 4 | 5 | 0 |
|
||
| **合计** | **174** | **161** | **12** | **1** |
|
||
|
||
**收敛 6 缺陷**:D2(承接)· D3(承接,定性更正)· **F1** 转换页预置跨主体必失败 · **F2** `alert_id`/`alert_ids` 字段名不匹配 · **F3** 无路由级角色守卫 · **F4** 确认日默认越界
|
||
**+ 1 项测试资产腐化 T1**:`sandbox_risk_test.py:325-326` · `run_demo_smoke.ps1`
|
||
|
||
**交叉印证成立**:浏览器侧失败与 API 侧同名用例**同因同果**,无额外分歧。
|
||
|
||
**建议修复顺序**:F1 → F2(演示可见、改动极小)→ D2 → F4 → F3 → D3
|
||
|
||
---
|
||
|
||
## 验证方式(总)· 复现三连 ✅
|
||
|
||
```bash
|
||
# 1) 后端(WinError 10013 时退 18000,并同步改下面所有 URL)
|
||
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000
|
||
|
||
# 2) 前端 —— 必须用 localhost:5173(app/ 无 CORS,浏览器只能走 Vite 代理)
|
||
cd web && npm run dev
|
||
|
||
# 3) 三支脚本
|
||
python scripts/dev/risk_e2e_smoke.py # 退出码 0 = 全过 / 1 = 有 FAIL / 2 = 前置失败
|
||
python scripts/dev/advisor_e2e_smoke.py
|
||
cd C:/Users/Windows/e2e-jinrong && node 09-advisor-pages.mjs && node 10-risk-pages.mjs && node 11-cross-role.mjs
|
||
```
|
||
|
||
**本机特有状态依赖**(换机复现须知):
|
||
|
||
- 浏览器三脚本**不在仓库内** —— 需手工拷贝 `C:/Users/Windows/e2e-jinrong/`
|
||
- `CUST-9527` 的 `PROD-110022` 可用份额会被转换用例消耗,耗尽后需重灌种子
|
||
- Milvus `kb_script_templates` 本轮**刻意未灌**(写操作,且会掩盖 D3 的待修状态)
|
||
- NAV 需覆盖到 `2026-09-11`(决定确认批处理的可用日期区间)
|
||
|
||
## 不在本 plan 范围
|
||
|
||
- 不修 D1–D4 及任何业务代码
|
||
- 不改 `web/package.json`
|
||
- 不修 `sandbox_risk_test.py` 失效断言、不修 `run_demo_smoke.ps1` —— 只上报
|
||
- 不测两个 SSE 对话页、不测 `agent_tools.py` / `agent_graph.py`
|
||
- 不收敛既有 `test_sprint*` 红项
|
||
- 不执行 `prepare_all.ps1` 式双库 DROP 重建
|
||
- 不做性能/压力测试;不做响应式与可访问性专项
|