diff options
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。 |
