# 测试规范 项目只维护两层测试,不增加第三套 `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 待处理消息。