# 分层测试与双端验收矩阵

本页将源码检查、自动测试、界面验证、真实设备与生产验收分开。矩阵中的“期望”是测试断言或必须满足的产品边界，不表示本轮已经运行。历史记录按其日期和目标保留；所有新增执行都应记录当前基线、设备、环境、命令、结果与剩余范围。基础命令见[测试](https://document.thestarline.cn/app/testing.md)，模块入口见[模块地图](https://document.thestarline.cn/app/module-map.md)。

## 环境与证据约定

每个场景至少记录 App 平台、构建、OS、网络、Account／聊天环境、合成账号代号和前置关系。正常、拒绝、断网与重启不能混为同一用例。一次失败若发生在环境准备阶段，应记为“前置条件未满足”，不能删掉或改成测试跳过后再宣称功能通过。

| 证据层 | 能确认 | 不能替代 |
| --- | --- | --- |
| 源码与配置 | 路由、类型、校验、状态机、默认值 | 实际运行配置与业务结果 |
| 单元／本地集成 | 可重复的输入、状态和权限断言 | 真实平台回调与公网服务 |
| 系统 UI 测试 | 指定设备的元素、焦点和操作路径 | 其他屏幕、OS、硬件和商店环境 |
| 授权真机闭环 | 指定账号、设备与网络的实际结果 | 永久登录、所有地区或所有用户 |
| 正式回执 | 上传、提审、审核与发布的各自阶段 | 未记录的后续动作 |

Account 自动测试使用专用 `identity_dev` MySQL 与随机表前缀；聊天测试使用内存库或临时 SQLite；原生设备测试使用自己的文件和设置命名空间。禁止把生产 token、真实记忆、私人聊天或现有设备数据作为通用夹具。测试前后可核对密文数量和摘要，不读取正文证明“没有修改”。

## 登录与设备会话

| 场景 | 正常与拒绝 | 断网／重启及双端期望 | 验证方法 |
| --- | --- | --- | --- |
| 发起原生授权 | 随机 verifier、S256、保存 pending 后才开浏览器；错 URL 或存储失败拒绝打开 | 连点只创建当前事务；退出后的迟到响应不重新登录 | iOS `ChatNativeAuthTests`、Android `NativeAuthTest`、服务端 native-auth 测试 |
| state 与 ticket | 正确 proof 单次换票；缺失、错误、重复、取消、过期均不替换原有效账号 | 回调不能从另一环境或设备拼接；重放不产生第二个 session | 服务端验证与两端隔离状态机断言 |
| 网页到 App | 系统浏览器显示正确 Account、用户完成授权、自动回 App | 真正替换进程后回跳；需保存完整阶段与设备时间线 | 授权真机人工闭环；iOS 系统“继续”提示单独观察 |
| 会话恢复 | `/me` 成功恢复资料；401 清理当前凭据 | 503、超时和临时离线保留凭据；恢复网络后重试 | Android `NativeAuthSessionTest`；iOS 授权与 Keychain 测试，加真机恢复 |
| 退出竞态 | 本机退出并停止轮询、通话和待处理授权 | 服务端退出失败不能显示为已撤销全部会话；旧请求不能回写新账号 | 测试迟到响应、同机账号切换及离线退出 |

iOS 会保存待换票 ticket 与 API 来源，Android pending 没有相同字段，不能把 iOS 的“换票时中断可重试”结论直接套用 Android。Android 当前没有 Debug 本地 Account 信任例外，隔离普通 API 可测，隔离浏览器原生登录仍需受控测试配置。聊天会话 24 小时，无 refresh-token 路由；不得用修改系统时钟或共享真实 token 代替可控时钟测试。

## 联系人、消息与在线状态

| 模块与场景 | 正常期望 | 拒绝／断网／重启／双端期望 | 验证入口 |
| --- | --- | --- | --- |
| 好友申请 | 唯一好友 ID 或当前临时码发申请，对方同意后可私聊 | 自己、未知、过期码、限流与屏蔽拒绝；重开后关系仍一致 | `chat.test.mjs`、`management.test.mjs`，两合成账号与第三观察账号 |
| 群成员 | 群主邀请、改名、移除；成员按权限读取 | 非成员不可读；被移除后不可发送或拿新媒体凭证；新成员历史边界正确 | `group.test.mjs`、通话成员变化测试 |
| 文本去重 | 成功写入一条并产生当前用户事件 | 丢响应后使用原 clientId 重试只保留一条；换内容／换范围复用 ID 拒绝 | `chat.test.mjs`、`MessageSendIdCoordinatorTest`、iOS 聊天测试 |
| 已读与删除 | 已读位置单向前进；本人删除只改变自己的视图 | 旧读位置不倒退；恢复聊天不恢复删除前历史；双方结果分别核对 | 服务端权限断言、双端可见列表与历史分页 |
| 事件同步 | 消费普通事件 ID 并重新读取资源 | 断线保留已处理游标；重建快照不漏消息；不把消息 ID 或 MLS sequence 当普通事件游标 | `chat.test.mjs` 与真实网络中断后列表核对 |
| 媒体 | 上传者可读，发送引用后授权接收者可读 | 未发送、外人、删除可见历史后不能越权；重开不丢引用；大文件拒绝 | `media.test.mjs`、`profile.test.mjs` 与下载字节摘要 |
| 新事 | 好友、指定人、已登录公开、仅本人各自可见 | 解除关系、屏蔽、隐藏、删除立即影响读取；视频 Range 边界正确 | `moments.test.mjs`，至少三合成账号逐项比对 |
| 城市贴纸 | 内置 ID 与三端素材一致，设备城市优先 | 权限拒绝用 IP 或手选；离开会话／换账号丢弃迟到结果；坏素材不伪造成功 | city-location、city-stickers 服务端测试和原生城市测试 |

消息行应同时观察发送者和接收者，不能只凭本机气泡出现判断服务端成功，也不能凭 HTTP 200 判断对方已读。断网测试分别覆盖发送前、服务端提交后丢响应、媒体已上传但消息未发送三个时点；记录相同 clientId 的行为，避免测试本身重复点击产生另一条合法新消息。

## 位置、通话与系统能力

| 场景 | 期望 | 需要独立验证 |
| --- | --- | --- |
| 实时位置 | 用户明确开始；PUT 只更新已开启共享；停止、离群或权限变化处理采集与服务端记录 | 两端前后台、粗略权限、最后更新时间、断网后旧坐标与手动停止 |
| 通话状态 | 成员才能发起／接听；重试不新建通话；响铃超时、忙、拒绝、挂断与群离开终态正确 | calls 与 call-media 测试；至少两设备逐角色检查 |
| 媒体连接 | 只有 active 当前参与者可获得短时房间 token | 麦克风、扬声器、蓝牙、摄像头切换、双向画面与声音、网络切换 |
| 系统来电 | 正确签名环境、topic、session 与设备注册 | APNs 真正到达、锁屏展示、后台、取消来电、多设备竞争接听 |
| 相机与文档 | 只读取用户选择的内容，拒绝后仍有可用替代入口 | 实体相机、无相机、永久拒绝、系统撤权、文件授权失效 |
| MLS 独立协议 | 密钥包、Welcome、Commit、ack、激活、密文重试与发件人校验 | iOS 独立传输与 Rust 测试；不能写成主聊天及全部附件 E2EE 通过 |

LiveKit 本机入房与 APNs 请求格式仅属于部分证据。媒体连接成功还应在双方分别断开与恢复、对方退群、挂断后迟到 token 等场景检查，不能让旧异步操作恢复已结束通话。普通聊天、新事、位置与推送元数据的数据可读性，应与[安全边界](https://document.thestarline.cn/app/security-and-privacy.md)一致。

位置退出必须单独验收：当前 iOS 退出只停止本机采集，不逐范围 DELETE 远端共享；聊天 session 注销或到期也不会自动删除 `live_locations`，其他仍有资格成员可能继续读取最后位置。Android 远端清理采用 bestEffort，失败时同样可能残留。用合成共享分别测试退出、到期、DELETE 成功与失败，记录本机是否采集、远端是否仍有记录、其他成员能否读取，不能把一项成功当作三项全部完成。

## 本机工作台、记忆与转出

| 场景 | 正常与拒绝期望 | 重启／离线／双端区别与验证 |
| --- | --- | --- |
| 任务与收藏 | 新建、编辑、筛选、去重与最近访问按本机规则保存 | 离线可读写；同设备重开保持；另一设备不自动同步；`LocalWorkspaceStoreTests`、任务 UI 与 Android 工作台流程 |
| 记忆与附件 | 合法内容加密保存；空模板不落盘；字段按上限处理 | 重开、编辑、搜索与附件字节一致；iOS/Android 文件格式分别检查，不直接互拷 |
| 损坏或丢密钥 | 显示无法读取并阻止覆盖 | 不把损坏库当空库；不新建密钥覆盖旧密文；`MemoryStoreTests` 与 Android 医疗／转出测试 |
| 医疗与宠物 | 八项医疗字段、两段 2000 字与换行保留；宠物优先规则正确 | 英文缩写与动物词边界、未知旧字段、附件、超长内容分别回归；不用真实医疗记录 |
| 系统推荐 | iOS 系统模型可用时生成待确认结构，不可用则本机回退 | Android 固定关键词路线独立；服务器识别 API 不等于本机识别，也不等于云同步 |
| 授权转出 | 当前用户取得额外许可后才生成明文 JSON；取消不生成完整输出 | 错账号、过期、断网、文件写失败清理部分文件；当前 Account 跨服务契约未闭环，需保持待验收 |

视觉流程另检查 320 级窄屏布局、大字号、键盘、长文件名、长文本、列表空态、底栏、弹窗与保存按钮。无障碍标识只用于测试定位，仍需实际核对朗读标签、焦点与点击区域；关闭动画后页面必须可操作。预览不按展示时长自动关闭。

## 命令与执行记录

以下命令展示真实测试入口，本次未执行。服务端命令在 App 仓库；Account 测试应进入其当前维护仓库另行运行：

```sh
node --test chat-service/tests/native-auth.test.mjs chat-service/tests/chat.test.mjs
node --test chat-service/tests/media.test.mjs chat-service/tests/moments.test.mjs
node --test chat-service/tests/calls.test.mjs chat-service/tests/call-media.test.mjs chat-service/tests/live-location.test.mjs
zsh ios-app/scripts/test-native-auth.sh
```

```sh
cd android-app
./gradlew :app:testDebugUnitTest --max-workers=1 \
  -Dorg.gradle.jvmargs=-Xmx1g -Pkotlin.compiler.execution.strategy=in-process
```

```sh
cd identity
npm run typecheck
npm test
npm --prefix client test
```

设备用例必须先指定允许使用的 QA 目标，串行执行，不卸载现有 App，不 wipe 模拟器或清 Keychain。历史医疗 QA 记录了 iOS 37 项本机测试、Android 69 项单测及专项 UI；原生授权记录包含 iOS 16 项测试与 Android 85 项 JVM、5 项隔离设备测试。Android 另有一次真实授权与 force-stop 冷启动记录；iOS 完整真实账号回跳、审核 iPad、生产推送、跨网音视频、正式包和手表独立版仍需新的证据。

每条验收结果填写：场景 ID、当前基线、期望、实得、退出码、日志摘要、目标设备、是否改变夹具、复验结果与未覆盖项。失败记录保留并关联修复后的结果；统计数量只能帮助追溯，不能替代具体断言或成为生产功能保证。发行门禁见[发行清单](https://document.thestarline.cn/app/release-checklist.md)。
