diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-21 02:23:43 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-21 02:23:43 +0800 |
| commit | f55ed58d2c75b53774ef7256fddb973ae3e73323 (patch) | |
| tree | 63269f233f4305d5b68aa252e9548244a362adcf /qa/README.md | |
| parent | be1afd248e4cc9b03a1e21090602a945af6988b2 (diff) | |
| download | fund-lab-f55ed58d2c75b53774ef7256fddb973ae3e73323.tar.gz | |
feat(web): 基金创建/读取界面与仓库内浏览器 QA
[变更性质]
- 本提交完成 3c-1 切片:Web 端基金建档(创建/重读)表单接入既有幂等 API,并提供仓库内可复现的端到端 QA 入口与生命周期回归。
[新增功能]
- 前端新增 03 / CREATE 面板:名称与初始现金(decimal 字符串)输入、创建/重新读取按钮、服务端返回的基金档案行(份额诚实显示 0、来源标注真实建档/合成标记);移动端单列布局。
- 创建请求携带 32 位十六进制 Idempotency-Key;网络失败重试复用同一 key,输入变更或成功后换新 key;token 变更清空档案并按序列号拒绝过期响应。
[实现方案]
- 金额全程 decimal 字符串,不经 JS float;update 层 fundSeq 守卫 FundCreate/FundRead 完成消息;BoundaryTests 新增 FundBoundaryTests 9 项(幂等键长度/复用、token 失效、stale 读取拒绝等)。
- qa/run.sh 一次性拉起非默认端口 Postgres/API/Vite(桩行情,_synthetic 标记),清理仅限本次创建的容器(ID+名称归属校验);临时目录 mktemp 隔离;qa/lifecycle-test.sh 验证拒绝路径不破坏既有资源。
[影响范围]
- Web 测试增至 17 项,全套 56 项通过;浏览器 QA 30/30 通过;README 更新阶段描述。本提交不包含 docs/overnight-progress.md 的现有修改。
Diffstat (limited to 'qa/README.md')
| -rw-r--r-- | qa/README.md | 60 |
1 files changed, 60 insertions, 0 deletions
diff --git a/qa/README.md b/qa/README.md new file mode 100644 index 0000000..96aea72 --- /dev/null +++ b/qa/README.md @@ -0,0 +1,60 @@ +# fund-lab QA(3c-1) + +仓库内可复现的端到端浏览器 QA 入口。一条命令启动一次性 Postgres、API(桩行情)与 Vite,然后用无头 Chromium 驱动页面断言 3c-1 行为。 + +## 运行 + +```bash +bash qa/run.sh +``` + +前提:`docker`(含本地 `postgres:16-alpine` 镜像)、`dotnet`、仓库内 `.tools/node-v22.23.2`(`node`/`npm`)。首次运行会在 `qa/driver/` 安装 `playwright-core`(node_modules 已被 .gitignore 覆盖)。 + +`run.sh` 的行为约定: + +- 端口均为非默认:API `5095`、Vite `5175`、Postgres `55434`;容器名 `fund-lab-qa-pg-3c1`。 +- 启动前预检端口/容器占用,被占用即中止,绝不复用或杀掉既有进程(拒绝路径有 `qa/lifecycle-test.sh` 回归保障)。 +- 每次运行使用 `mktemp -d /tmp/opencode/fund-lab-qa-*` 专属临时目录,日志、截图、env 互不覆盖,失败现场完整保留。 +- Postgres 密码随机生成,仅写入本次临时目录的 `pg-env`(0600),不回显、不进仓库。 +- API 环境变量:`FUND_LAB_AUTH_TOKEN=qa-token`、`FUND_LAB_AKSHARE_PYTHON=<repo>/qa/stub/fund-lab-python`。 +- 退出时清理:杀掉自身启动的 API/Vite PID;仅删除本次 `docker run` 创建的容器(按容器 ID 与名称双重归属校验,拒绝路径绝不触碰既有容器);删除临时 env 文件。日志与截图保留在临时目录中供排查。 + +## 生命周期回归(qa/lifecycle-test.sh) + +```bash +bash qa/lifecycle-test.sh +``` + +验证 `run.sh` 的拒绝路径不破坏既有资源:预置同名容器或占用端口时 `run.sh` 必须拒绝退出,且既有容器与端口占用进程原样保留。脚本自身也只管理它创建的资源。 + +## 行情桩(stub) + +`qa/stub/fund-lab-python` 是一个假解释器:`MarketDataService` 以 `FUND_LAB_AKSHARE_PYTHON` 调用它并传 `akshare_collector.py --operation search|nav ...`,它按 collector JSON 契约向 stdout 输出 envelope: + +- `schema_version="fund-lab.akshare.v1"`、`operation`、`source="stub-synthetic"`、`source_revision`、`collected_at` +- 每个载荷都带 `_synthetic:true`,来源标记为 stub(合成数据不做真实披露) + +## 浏览器场景(qa/driver/browser-test.js) + +- A 初始渲染:创建面板、名称/现金输入框、按钮文案。 +- B 校验:空名称、非法金额(`abc`、`-5.00`)被拒绝且不发出任何 POST。 +- C 创建成功:双击仅 1 次 POST;`Idempotency-Key` 为 32 位十六进制;请求体金额为 decimal 字符串(`initialCash="12345.67"`、`initialUnitNav="1.00000000"`,不经 JS float);`isSynthetic=false`;档案展示服务端返回值,份额诚实显示 0。 +- D 重新读取:恰好 1 次 GET,金额持久化复核。 +- E token 失效:换 token 立即清空档案;拦截并延迟的旧 GET 响应落地后被忽略(stale 防护)。 +- F 幂等重试:首 POST 被掐断显示错误横幅,重试复用同一 `Idempotency-Key` 成功。 +- G 全程无浏览器 console/page 错误。 + +## 真实行情模式 + +QA 默认走合成桩。需要真实 AKShare 时,不运行 `run.sh`,而是自行启动 Postgres 并把 API 的 `FUND_LAB_AKSHARE_PYTHON` 指向装有 akshare 的解释器,例如本机已验证可用: + +```bash +PYTHONPATH=/tmp/opencode/fund-lab-akshare-site /usr/bin/python3 -c "import akshare" # 1.18.96 +``` + +真实模式输出的 envelope 不含 `_synthetic`,来源为真实数据源;前端“数据来源”行相应显示“真实建档”。 + +## 不变量 + +- 金额永远以 decimal 字符串提交/展示;浏览器端不经过任何 float 运算(见 `qa/driver/browser-test.js` 场景 C3 对请求体的断言)。 +- 网络失败后的重试复用同一 `Idempotency-Key`;输入变更或创建成功后才换新 key。 |
