Files
2026-10-04 17:32:38 +08:00

241 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 测试规范
项目只维护两层测试,不增加第三套 `scripts/*smoke*`、`*e2e*` 或一次性测试框架。
## 1. 纯单元测试
- 仅放在 `packages/*/src`,验证确定性、可复用的引擎和领域逻辑。
- 使用 Vitest;先运行受影响的测试,再根据影响范围运行 `pnpm run test`。
- 不引入数据库、Redis、HTTP、认证或第三方服务模拟;不在 `apps/api/src`、`apps/web/src` 或 `scripts` 添加单元测试。
### 测试保留与清理
- 保留能说明业务错误的断言:非法输入、经济数量与费用边界、确定性、不修改输入、状态转换、实际战斗效果及仍在使用的兼容逻辑。
- 迁移完成后移除只证明新旧实现完全一致的整包哈希。混合文件只删这类断言,保留配置联动、引用完整性和拒绝非法内容的测试。
- 不把源码关键词扫描、固定目录树、静态文案和美术文件存在性作为领域单元测试。架构约束通过代码审查核对,文案和素材在相关页面验收。
- 不按文件名或运行耗时删除测试。`phase` 可能仍验证真实战斗规则,`legacy`/`migration` 可能仍保护正式兑换和存量数据;先查实际调用方。
2026-10-04 清理记录:盘点 256 个测试文件,清理前 2402 个用例通过。此次处理如下,运行时实现不变:
| 对象(路径相对 `packages/`) | 处理及依据 |
| --- | --- |
| `game-rules/src/sect-organization/testing/architecture/architectureGuard.test.ts` | 删除目录形状、源码关键词和已退役接口名扫描;原文件中的宗门结构与关键词限制不再由单元测试自动检查,后续改动仍须遵守架构文档并进行代码审查 |
| `game-rules/src/equipment/generation-baseline.test.ts` | 删除旧生成流的整批哈希;保留装备生成、随机阈值、打造、器形和阵纹专项测试 |
| `game-rules/src/beasts/generation-baseline.test.ts` | 删除生成/成长/投影整批哈希;三个物种的资质、成长范围用例并入 `beasts.test.ts` |
| `game-rules/src/combat/projection/character-build.test.ts` 及 `fixtures/character-build.json` | 删除整个人物投影哈希和大型快照;经脉装备测试使用的唯一输入移到该测试内,保留属性贡献、资源策略及装备联动专项测试 |
| `game-content/src/combat/training/pack.test.ts`、`game-rules/src/sects/progression/pack.test.ts`、`game-rules/src/sects/skill-learning.test.ts`、`game-rules/src/rewards/dungeon-pack.test.ts` | 仅删除迁移等价哈希,保留 Schema 同步、配置生效和错误输入测试 |
| `game-content/src/items/equipment-blueprints.test.ts` | 删除图纸静态数量、名称和禁止字符断言 |
| `game-rules/src/sect-organization/production/onboardingPresentation.test.ts`、`affairsPresentation.test.ts` | 删除固定 NPC 姓名、地图说明、剧情段数和素材文件扫描;访客入口用例并入后者,保留任务和设施交互绑定 |
| `game-rules/src/world/mapAtlas.test.ts` | 仅删除固定“大晋 20 地点”断言;保留区域归属、全地点覆盖和锚点检查 |
| `game-rules/src/tower/strategy-value.test.ts` | 删除 420 场阶段校准比较:仅要求至少一个策略的胜率、伤害或回合数不同,不能判定策略是否正确;保留实际护卫/治疗/重击机制、平衡及七境界通关测试 |
旧法宝与旧功法兑换的规则和契约测试继续保留:`apps/api/src/legacy-items` 和对应前端页面仍调用它们。`combat-core/src/phase-3-primitives.test.ts`、幻境 `phase-two.test.ts`/`phase-three.test.ts` 也继续保留,它们覆盖现行资源扣除、战斗与通关行为。
本轮清理后 `pnpm run test` 通过 249 个文件/2360 个用例,净移除 42 个用例;测试及其快照净减少 1998 行。`pnpm run lint`、`pnpm run typecheck`、`git diff --check` 均通过。测试命令已包含六个库的构建;因本次仅修改测试及文档,未另跑应用完整构建和浏览器验收。
## 2. Codex 浏览器/Playwright 模拟
由 Codex 在当次开发任务中操作真实本地页面,不将操作固化为一次性仓库脚本。
1. 从 `env/local.example.env` 准备 `env/local.env`(已有文件保留),执行 `pnpm run services up -d --wait` 和 `pnpm run db:migrate`。普通验证使用 `pnpm run dev`;长流程需关闭watch时,先执行`pnpm run build:server`,再在两个终端执行`APP_ENV=local NODE_ENV=development node --env-file=env/local.env apps/api/dist/main.js`和`pnpm run dev:web`。确认页面为 `127.0.0.1:5174`、服务指向专用本地数据;不得使用 `prd` 进行测试写入。
2. 列出本次改动需要验证的用户行为、预期结果和准备条件。通过页面注册/登录,邮件验证使用 Mailpit;优先复用已有测试角色。缺少 LLM 或角色构筑等条件时如实说明,不通过假成功继续。
3. 使用可用的 Codex 浏览器能力或 Playwright,按可访问名称和实际页面定位控件。操作后等待具体界面/网络状态,不依赖固定长时间 sleep。多人测试使用独立浏览器上下文或配置文件,每个角色有独立会话。
4. 同时观察界面、控制台和网络:检查请求次数、错误、权限边界、提交结果及恢复过程。涉及布局时检查桌面和 360px;涉及性能时使用网络面板/Profiler,不维护一次性基准脚本。
5. 多人战按本次改动选择场景:收令、重复点击、超时、断线重连、逐行动播放、终局和回放权限。故障恢复可在本地暂停/重启实际服务后观察,不添加模拟故障 Worker 或另一套接口测试程序。
6. 测试完成后退出或结束当次创建的活动状态,恢复暂停的服务;只清理本次测试数据,不重置整个环境。
7. 记录环境、验证步骤、实际结果和未验证项。截图、网络记录等留在当次任务产物中;不得把旧验证记录当成本轮通过证据。
Lint、TypeScript/构建、Prettier 是静态质量检查,不是额外一层集成测试。根据改动运行必要检查;不以新增脚本替代真实用户流程验证。
## 3. 本地测试账号与密码
- 本地测试账号统一密码:`LocalCombat123!`。仅用于 `env/local.env` 对应的纯本地环境,不适用于预发布或生产账号。
- 优先复用已有账号与角色。已知账号包括 `local1@daoyou.local` 至 `local8@daoyou.local`,实际是否存在、邮箱是否验证及角色状态以当前本地数据库为准,不假定环境重建后仍然保留。
- 需要选择账号时,可直接只读查询本地 PostgreSQL(`127.0.0.1:15432`,数据库 `daoyou_local`)的 `better_auth.user` 与角色表,读取邮箱、验证状态和所需角色信息;无需再次向用户索要已有测试账号或统一密码。不读取或输出密码哈希、会话令牌等无关认证数据。
- 使用真实页面和上述密码登录。邮箱未验证时,通过 Mailpit(`http://127.0.0.1:18025`)完成正常验证流程,不直接修改数据库中的验证标记或认证状态。
- 缺少账号时通过页面注册本地测试账号,并沿用统一密码;已有账号登录失败时先确认环境、账号存在性及邮箱验证状态,不擅自重置密码或修改认证配置。
- 多人测试使用独立浏览器上下文或配置文件,确认每个会话实际登录的角色,避免共享 Cookie 将多人测试变成同账号多标签页。
## 4. 本地物资发放与角色调整
`POST http://127.0.0.1:3001/api/dev/resources` 不要求登录,仅在显式 `APP_ENV=local` 且 `NODE_ENV` 非 production 时注册;缺省、预发布及生产不开启。不要将 local 配置用于外部可访问的部署。
先只读查询已有测试角色 ID,然后提交 JSON,例如:
```json
{
"cultivatorId": "替换为已有本地角色的UUID",
"grants": [
{
"type": "item",
"item": { "definitionId": "blueprint.weapon.10", "quantity": 1 }
},
{
"type": "vault-material",
"facts": { "name": "验收玄铁", "type": "ore", "rank": "凡品" },
"quantity": 1
},
{ "type": "spirit-stones", "amount": 1000 },
{ "type": "qi", "amount": 20 }
]
}
```
新库存材料使用 `type: item`、`definitionId: material.v1`、`instanceData: { name, type, rank, element?, description? }`。传承灵印使用注册的定义 ID;随机道装使用 `{ "type": "equipment", "slot": "weapon", "level": 10 }`,法兵默认生成剑,可加 `weaponType` 指定 `axe/blade/spear/staff/sword/fan/bell/brush/banner`,非武器不得指定。全部定义在 `packages/game-content/src/items`,参数边界见 `packages/contracts/src/forging.ts`。
旧丹药/消耗品验收可提交 `{ "type": "vault-consumable", "facts": { name, type, quality, description, prompt, score, spec }, "quantity": 2 }`,facts 须符合 `ConsumableFactsSchema`,quantity 为 1–3960;沿用历史消耗品发放入口写入洞府宝库,再通过正式取出操作转换。新版消耗品直接发放使用 `type: item`、`definitionId: consumable.v1` 及同一完整 facts 作为 instanceData。该能力仍只在纯本地开放。
邮件附件验收可提交 `{ "type": "mail", "format": "historical" | "new_reward", "attachments": [...] }`。附件遵循共享 MailAttachmentsSchema,每次1–20个。format默认为new_reward,经过正式新奖励生产入口;historical原样保存合法附件协议,可构造新旧混合邮件。邮件固定使用本地验收标题与正文,返回邮件ID。该能力只创建样本,领取仍需真实角色通过正式邮件接口完成,不直接标记领取或绕过好友赠送成本。
发放遵循正式背包容量、材料校验、资源上限和战斗占用规则,整批事务成功或整体回滚。不支持修改任意字段、账号创建或整库清空。测试前记录物资与资源基准,完成后只清理本次发放和生成的测试物品;保留其他测试已产生的角色进度及历史记录。
灵兽可通过同一发放接口提交 `{ "type": "beast", "speciesId": "combat.wild.species.rock-boar" }`(speciesId 必须来自灵兽定义)。生成标准 10 级个体,遵循兽栏容量;不会自动携带或设为首发,随后使用正式阵容接口配置。
融合验收可增加 `level: 30`,范围10—180且不得超过主人等级。通过正式经验成长函数生成相应等级宝宝,保留初始50自由点;省略仍为10级。此参数仅存在于纯本地物资发放接口。
验收多技能展示时,可额外传入 `skills`,例如 `["beast.strength", "beast.advanced-defense"]`。允许 1–8 个不重复的当前有效技能 ID,技能格容量同步为技能数量,并通过完整灵兽数据校验;省略时沿用初始技能生成规则。
`PATCH http://127.0.0.1:3001/api/dev/cultivators/:id` 使用相同的纯本地环境限制,无需登录。只读查询已有角色 UUID 后,可按需提交以下白名单字段:
```json
{
"realm": "金丹",
"realmStage": "初期",
"attributes": { "vitality": 1000, "strength": 1000 },
"unallocatedAttributePoints": 0,
"spiritStones": 10000,
"reputation": 0,
"resources": { "hp": 10000, "mp": 10000 }
}
```
所有数值为新绝对值,省略字段保持原值。基础属性支持 vitality、strength、spirit、endurance、speed、willpower(1–10000);待分配属性点 0–100000、灵石 0–100000000、声望 0–1000000。resources 必须同时给出 hp、mp(0–10000000),按调整后 v6 构筑的资源上限裁剪。不自动分配属性、补满资源或迁移宗门深度。
空对象、未知字段、非法境界或越界值返回 400;不存在或非活跃角色、战斗占用返回 409。角色调整采用角色锁和数据库事务,提交后更新资源版本;脚本发起的变更后应刷新页面获取新数据。禁止修改用户归属、认证、角色状态等任意数据库字段。测试结束恢复事先记录的境界、属性和准备性资源改动,保留实际玩法奖励与战绩。
### 本地灵根与先天命格
同一 `PATCH /api/dev/cultivators/:id` 支持以下两个数组。字段省略时保留原值,传 `[]` 清空该类记录,非空数组整体替换;修改沿用角色锁、战斗占用检查、事务与资源版本提交。仅用于纯本地环境,无需数据库迁移。
```json
{
"spiritualRoots": [
{ "element": "木", "baseStrength": 76, "marrowWashBonus": 6 },
{ "element": "水", "baseStrength": 64 }
],
"preHeavenFates": [
{
"name": "青木长生",
"quality": "玄品",
"description": "生来气脉绵长,调息时如草木逢春。",
"effectIds": ["natural-recovery"]
},
{
"name": "悟道忘尘",
"quality": "地品",
"effectIds": ["retreat-insight-gain", "system-spirit-stone-surcharge"]
}
]
}
```
- 灵根最多 8 条,元素须有效且不重复;`baseStrength` 为 0–100 的整数,`marrowWashBonus` 默认 0,两者之和不超过 120。品级由现有仓储规则根据元素及灵根数量推导;不接受客户端传入品级或当前强度。
- 命格最多 3 条;名称 1–100 字、品质须为现有品质,说明可省略(最多 2000 字)。每条指定 1–2 个不重复的效果 ID。
- 效果 ID 来自 `apps/api/src/lib/services/FateFragmentRegistry.ts`,例如 `retreat-exp-gain`、`retreat-insight-gain`、`natural-recovery`、`toxicity-mitigation`、`system-spirit-stone-surcharge`。数值、标签、说明及 rollMeta 由既有构造器按品质和固定 0.5 分位生成;测试接口允许自由组合现有效果,不模拟创角抽签概率,也不接受自定义效果数值。
- 非法结构、越界、重复元素或重复效果返回 400;未知效果 ID 返回 409,整笔事务回滚。响应在 `data.spiritualRoots` / `data.preHeavenFates` 返回本次调整后的运行时结构,便于核对先天与后天展示。
- 后天增益仅作为本地测试数据注入,不推进洗髓等级或执行破限;先天设定页显示 `baseStrength`,肉身修炼页显示后天增益与当前总强度。命格使用真实效果,会影响相应玩法。
- 临时验收前记录原值并恢复;用户明确要求补齐并保留的本地测试角色配置可以保留。外部调用后刷新页面读取新版本。
同一接口支持调整角色**当前已加入宗门**的数据。
高阶宗门技能验收也可通过`sectCombat`临时准备并恢复当前已正式启用的宗门构筑:
```json
{
"realm": "化神",
"realmStage": "中期",
"sectCombat": {
"methods": { "jiujie.method.canon": 60, "jiujie.method.thunder": 60 },
"meridianDepth": 3
}
}
```
`methods`接受当前宗门已有的1~6个心法ID及1~180的整数等级;省略的心法保持原值。`meridianDepth`为0~7的整数。调整采用同一角色锁、宗门成员锁和事务,校验调整后境界的心法上限、主支心法关系、经脉等级门槛及两套流派方案;未知心法或不合法构筑返回409,整笔联合调整回滚。它不初始化流派、不自动选择或清除节点、不执行研习扣费;不能将准备数据记作正式养成验收。正式选择节点、保存与技能施放仍使用页面。
准备前记录境界、心法、经脉深度、节点和资源。结束训练后,通过正式页面恢复本次节点修改,再在一次PATCH中恢复原境界、心法等级和经脉深度,必要时同时恢复准备性资源;深度降低后仍有越层节点时会拒绝恢复,不静默删除。修订号正常递增,保留实际战斗记录,不回滚其他角色数据。组织身份与贡献使用下面的`sect`字段。
```json
{
"sect": {
"discipleRank": "inner",
"contribution": 3000,
"lifetimeContribution": 3000
}
}
```
`sect` 至少填写一个字段;弟子身份为 `registered`、`outer`、`inner`、`true`,两类贡献为 0–100000000 的整数绝对值,省略保持原值。调整后的累计贡献不得小于可用贡献,否则返回 409;角色没有 active 宗门成员记录也返回 409。可与境界/属性调整放在一次请求中,整笔事务成功或回滚,并更新宗门成员、任务和角色资源版本。此能力只准备身份与贡献,不执行正式晋升、不生成晋升资格、不修改任务完成记录、职务或宗门归属。准备前记录基准,验收后恢复身份、贡献等准备字段,保留真实试炼战绩与资格记录。
突破试炼验收可使用同一 PATCH 接口补齐准备条件:
```json
{
"realm": "元婴",
"realmStage": "圆满",
"cultivation": { "experience": 100000, "insight": 90 },
"breakthroughPreparation": {
"clearMind": true,
"protectMeridians": true,
"completedDungeonObjectiveIds": ["clear-archive"]
}
}
```
`cultivation` 的 experience 为 0–1000000000000 整数修为绝对值,insight 为 0–200 整数感悟;修为上限仍按调整后的境界实时计算,不写入 exp_cap。两个准备对象均不能为空,省略字段不变。清心/护脉 true 添加带 devTools 标记的系统状态,false 只移除本接口添加的对应状态,保留正常玩法已有状态。
调整 realm、realmStage 或 breakthroughPreparation 时会同步当前破境任务,确保准备目标有对应任务。通过外部 dev 请求准备后应刷新浏览器再开始验收,避免页面沿用先前角色缓存。10F 实际运行记录及准备数据去向见 [阶段验收记录](./combat-v6-phase-10f-breakthrough-plan.md#73-解锁后的真实运行验收2026-09-08-至-09)。
`completedDungeonObjectiveIds` 只接受当前大境界破境任务中 kind 为 complete_dungeon 的目标 ID,用于跳过验收准备所需的长秘境流程,不创建秘境通关记录或奖励。未知目标及 win_task_challenge 战斗目标返回 409,整笔联合调整回滚;不能用本接口直接制造试炼胜利。真实试炼胜负、灵兽消耗、任务推进和最终突破必须通过正式页面完成。准备前记录境界、六属性、资源、修为、感悟及任务目标基准;验收记录须区分 dev 准备目标和真实战斗完成目标,结束后恢复临时角色字段并移除 dev 状态,不把准备数据记作真实验收证据。
归元灵露洗炼验收:通过上述`type: item`发放`beast.refinement.origin-dew`或`beast.refinement.superior-origin-dew`,在灵兽详情「学习兽诀」旁选择「洗炼」。规则及示例见[洗炼文档](combat-v6-beast-refinement.md)。首版不配置掉落或商店。
## 本地清空随身物品
`DELETE http://127.0.0.1:3001/api/dev/cultivators/:id/inventory/bag` 仅在显式 `APP_ENV=local` 且 `NODE_ENV` 非 production 时可用,不要求登录。只清空指定活跃角色 bag 位置的随身物品,并解除这些物品的残留穿戴引用;保留独立 equipped 位置的已穿戴道装、储藏室、旧宝库、灵石、灵兽及已参悟功法。需要清空已穿戴道装时,先通过正式卸下操作移回背包。
接口复用角色 Redis 锁、SQL 事务、战斗占用检查和资源事件提交/发布。返回 `data: { removed, unequipped }`;空背包重复调用返回两个 0。非法 UUID 返回 400,角色不存在或不活跃、战斗占用等业务冲突返回 409。
需要重新准备物资时,清空后使用已有 `POST /api/dev/resources` 发放,不直接写数据库。清空与发放是两个独立事务;发放失败时背包保持空态,应修正参数后重新发放。验收前保存要保留的样本事实,结束后检查物品数量和储藏室没有变化。
## 本地重置每日占卜
`DELETE http://127.0.0.1:3001/api/dev/cultivators/:id/divination` 仅在显式 `APP_ENV=local` 且 `NODE_ENV` 非 production 时可用,不要求登录。`:id` 为已有活跃角色 UUID。
```bash
curl -X DELETE http://127.0.0.1:3001/api/dev/cultivators/角色UUID/divination
```
成功返回 `{ "success": true, "data": { "removed": 1 } }`。清除该角色唯一的占卜记录(含骰子、卦象、解读和发奖标记),包括尚未完成的旧签;没有记录时幂等返回 `removed: 0`。已领取的符箓保留,天地灵气与其他角色记录不变;再次完成占卜会再次正常发奖。刷新占卜页面或切回窗口后即可重新选择方向、掷骰。
重置与正式掷骰、解签共用角色占卜 Redis 锁,并在事务内锁定角色行;正在解签或领取时返回锁冲突,应等待完成后重试,不强制中断 SSE。非法 UUID 返回 400,不存在或非活跃角色返回 404。不要对预发布或生产执行此接口。
## 架构边界回归验收
涉及 workspace、Nest Provider、内部库公开入口、请求或布局加载边界时,复用本地账号与正式界面。以下为可重复步骤,实际结果需在每次任务重新记录,不把本文当作通过证据。
| 范围 | 步骤 | 预期 |
| --- | --- | --- |
| 未登录入口 | 打开登录页,再直接访问受保护游戏路由 | 登录页可显示;受保护路由转登录,API 无 Cookie 请求被拒绝 |
| 游戏布局 | 登录后从洞府进入角色、储物袋、宗门、灵田和战绩;刷新当前路由 | 正确布局/标题/HUD/底部导航,资源正常加载,无错误页 |
| 懒加载详情 | 从 HUD 打开炼体详情与洗髓详情,再关闭 | 正确内容、滚动及关闭行为,无静态/动态导入冲突 |
| 移动端 | 在 360px 宽度检查背包和详情,横向查看 HUD 状态 | 正文无水平溢出;底部导航和传闻条不遮挡主要操作;验收后恢复视口 |
| 请求入口 | 浏览器开发调试中使用 apiFetch 的字符串、URL、Request 形式读取相同 API | 认证一致,均取得合法响应;不输出 Cookie 或真实密钥 |
| Request 请求体 | 用 JSON Request 向本地背包正式入口提交不存在的 action;再验证取消信号 | 返回输入校验 400且无资产变动;取消返回 AbortError |
| API 地址/BYOK | 临时使用本地 API 显式地址和非真实 BYOK 测试值;查看网络请求 | API 地址正确、请求头合并、Cookie 正常;非 API 请求不带 BYOK;finally 恢复原设置 |
| 实时恢复 | 打开资源/聊天页面,重连或刷新后检查当前内容 | 无重复消息/资源,版本和快照恢复;发送测试聊天仅限明确授权的本地测试场景 |
| 运行依赖 | 读取 /api/health-check;以缺少 Redis 的生产配置尝试启动本地构建 | 正常设施全 up;缺少生产 Redis 明确拒绝启动,不接触生产服务 |
| 交付 | frozen install、lint、typecheck、纯逻辑包测试、API/Web build;必要时构建本地 Docker 镜像 | 包边界有效,两个应用独立产物可构建;不发布镜像、不执行额外迁移 |
发布采用停机维护、SPA/API 配套更新,清单见 [架构审查与规范第 11 节](monorepo-architecture.md#停机维护发布清单)。旧 SPA/新 API 的混合版本兼容不作为验收要求。跨应用、活动状态或消息协议变化时,记录配套产物身份、留存状态处理及回滚条件;停机不会自动清除 Redis 活动状态或 NATS 待处理消息。