test(e2e): 夹具类脚本默认打隔离栈并加护栏/自清理,dev 栈标记可重定向

This commit is contained in:
2026-10-07 11:00:28 +08:00
parent c6087ce807
commit 70bba0f7b2
12 changed files with 752 additions and 131 deletions
@@ -106,6 +106,21 @@ npm run dev:all
通过 `nohup` 在仓库根目录启动 dev 栈且未显式重定向 stdout / stderr 时,默认 `nohup.out` 会持续收集 SpacetimeDB、api-server、bgfilter-worker、主站 Vite 和后台 Vite 的整套 dev 栈输出;该文件已被主站 Vite watcher 和 Git 忽略,避免日志追加触发页面刷新循环,重启主站 Vite 后生效。若把输出显式重定向到其它仓库内文件(例如 `> dev.out`),该自定义文件不会自动获得同样的 watcher 保护,应改为写到 Vite root 之外,或同步配置精确的忽略规则。
### 本地 e2e 必须用隔离栈(2026-10-07)
**背景(实际事故)**:夹具类 e2e 脚本过去缺省跟着 `.app/dev-stack.json` 打**用户正在用的开发栈**,而「换端口」并不等于「换库」——`npm run dev` 与早期 pinned 端口那套都指向同一个 `genarrative-game-creator-dev` + 同一份 data dir。结果是跑几轮 e2e 就把夹具写进用户的库:实测盘点时该库有 **105 条作品 = 13 条演示 + 92 条夹具**(lineage 4 轮×9、theme 1 轮×54、collection 2、project-bundle 2、fork-authorization 2),而公开目录上限只有 48 条,用户的共创页几乎被夹具占满。
**现在的口径(`scripts/e2e-target.mjs` 是单一事实来源)**:
1. **缺省隔离**:夹具类脚本在未显式给 `E2E_API_BASE` 时,目标固定为隔离栈 `http://127.0.0.1:8188`(web `3112`),不再回落到 dev-stack 标记。
2. **护栏**:解析出的目标与 `.app/dev-stack.json` 记录的 api-server **同地址或同端口**时,脚本**直接拒绝启动**(`exit 2`),提示二选一——用隔离栈,或 `E2E_API_BASE=… E2E_ALLOW_DEV_STACK=1` 明确越权。护栏只管**会写夹具**的脚本;只读工具(截图工具的「只截现有作品」模式)仍按 dev-stack 解析。
3. **一键隔离栈**:`npm run e2e:stack` 只起栈(前台长驻,Ctrl-C 结束);`npm run e2e:run -- --suite scripts/check-game-distribution-lineage-e2e.mjs [--suite …]` 起栈→等 `/healthz`→顺序跑 suite→自动停栈。隔离栈用**专用端口**(api 8188 / bgfilter 8189 / spacetime 3110 / admin-web 3111 / web 3112)、**专用库名** `genarrative-game-creator-e2e` 与 **临时 data-dir**(`%TEMP%/genarrative-e2e-spacetime-*`,默认跑完删除;`--keep-data` / `--keep-stack` 可保留)。
4. **夹具统一前缀 `E2E·`**:所有夹具标题都带它(`fixtureTitle()`),盘点/清理不再依赖随机数字后缀;`tags` 之外,标题前缀是**盘点唯一可靠标识**。
5. **跑批后自清理**:每个脚本正常结束时用本轮 owner token 把「自己造的、标题带 `E2E·` 且含本轮后缀」的作品逐条软删(owner 维度 `DELETE /api/game-distribution/my-games/{id}?expectedPublicationRevision=…`),并打印删除条数;清理失败或脚本中断时,`main().catch()` 会打印残留清单(owner + gameId + 标题),便于人工补删。
6. **例外**:`scripts/seed-game-distribution-co-creation.mjs` **有意**写用户的 dev 栈(它就是演示数据灌装工具,幂等、判重不绑账号),不受护栏约束;要写到别处请显式给 `E2E_API_BASE`。
**软删的语义**:`DELETE …/my-games/{id}` 只是软删(撤销当前公开版本 + 写 `deleted_at`),公开目录、族谱树、`my-games` 立刻都看不到,但**行仍在库里**、可由后台 `restore` 路径恢复;已软删的行不会出现在任何列表接口里,所以「清理后列表为空」不等于「行被物理删除」。
单独启动主站前端:
```bash