# M1.2 集成测试计划 ## 1. 目标与范围 - 目标:完成 ShowenV2 在 M1.2 阶段的端到端集成测试、旧功能对齐验证、边界条件验证、错误处理验证与回归基线建立。 - 范围覆盖:`device`、`screen`、`wifi`、`video`、`ble`、`http` 六个内置插件,动态插件系统,Flutter App 通过 HTTP/WebSocket/BLE 的交互链路。 - 重点链路:插件注册与启动、消息路由、配置热重载、视频控制、WiFi 配网、BLE 配网、文件管理、动态插件生命周期。 - 验收对齐:覆盖 `docs/MILESTONES.md` 中 M1.2 的全部任务项,并为 M1.3 性能优化提供可重复的基准输入。 ## 2. 当前实现观察与 M1.2 风险 - `src/main.rs` 按 `device -> screen -> wifi -> video -> ble -> http` 注册静态插件,再扫描 `plugin_store/` 挂载动态插件。 - `src/core/service_manager.rs` 负责依赖排序、`init -> self_test -> start` 生命周期、消息路由、动态插件错误阈值、自动回退、启停与热替换。 - `src/plugins/http/routes.rs` 已暴露播放、配置、视频、文件、WiFi、BLE、插件管理、App 下载等 API。 - 风险 1:HTTP 插件管理 API 通过 `Message::Custom` 向 Manager 发命令,但 `ServiceManager::handle_manager_message()` 当前未处理 `plugin_enable`、`plugin_disable`、`plugin_rollback`、`plugin_switch`、`plugin_install`、`plugin_check_updates`。 - 风险 2:`/api/plugins` 依赖 `plugin_states` 自定义消息更新状态,但当前源码中未看到 Manager 侧生产该消息,插件列表接口可能返回空状态。 - 风险 3:`WifiProvisioned`、`DeviceEvent`、部分 `Custom` 消息在当前主链路中无明确生产者/消费者,测试时应区分“未实现”与“回归缺陷”。 - 风险 4:BLE、WiFi、显示、OpenCV、动态插件 `.so` 装载均依赖 ARM64 实机环境,CI 只能承担部分替身测试。 ## 3. 测试策略 ### 3.1 分层策略 - 单元测试:验证纯逻辑、解析、状态转换、命令拼装、路径校验、manifest 校验、消息序列化。 - 集成测试:验证 `ServiceManager` 与插件间消息流、配置重载、插件生命周期、HTTP 路由到消息总线的桥接。 - 端到端测试:以真实进程启动 `showen_v2`,通过 HTTP/WebSocket/BLE/文件系统操作驱动系统,校验插件协同与用户可见结果。 ### 3.2 层级分工 - 单元:优先放在 `src/core/*`、`src/plugins/*` 内部测试,补齐未覆盖的解析和错误分支。 - 集成:新增 `tests/m1_2_*.rs`,使用临时目录、测试配置、fake backend、fake dynamic plugin store 驱动系统。 - E2E:以 ARM64 实机为主,CI 仅跑“无硬件替身版”流程;WebSocket/HTTP 使用 `curl`、`websocat`、Flutter 测试桩执行。 ### 3.3 覆盖原则 - 所有非 `-` 消息交互单元至少有 1 条自动化验证。 - 所有用户入口 API 至少有 1 条成功场景和 1 条失败场景。 - 所有动态插件生命周期动作至少覆盖:加载、必需能力自测失败、错误阈值禁用、自动回退、热替换恢复。 - Flutter 侧至少覆盖:首次连接、实时状态、WiFi 配网、配置读取/保存、APK 下载入口。 ## 4. src 模块视图 - `src/main.rs`:程序入口、配置加载、静态/动态插件注册、主循环。 - `src/core/`:消息模型、插件 trait、ServiceManager、PluginLoader、VersionManager。 - `src/plugins/device/`:统一设备能力入口,响应 `DeviceCommand` 并广播 `DeviceResponse`。 - `src/plugins/screen/`:DevicePlugin thin wrapper,负责防息屏与光标隐藏。 - `src/plugins/wifi/`:nmcli 驱动的 WiFi 扫描/连接/AP 管理。 - `src/plugins/video/`:OpenCV 播放器、状态机、状态广播。 - `src/plugins/ble/`:BlueZ GATT 配网服务,接收 WiFi 结果并向核心转发 WiFi 指令。 - `src/plugins/http/`:REST/WebSocket/Web UI/App 下载与文件管理桥接层。 ## 5. 插件 × Message 覆盖矩阵 说明:`Rx`=直接处理,`Tx`=直接发送/广播,`Bridge`=对外桥接或间接映射,`Gap`=已有入口但当前实现未闭环,`-`=无直接关系。 | 插件/系统 | PlayerCommand | PlayerStatus | Trigger | StateChanged | ScreenLockRequest | CursorVisibility | WifiCommand | WifiResult | WifiProvisioned | ConfigReloaded | ConfigReloadRequest | Shutdown | PluginReady | DeviceCommand | DeviceResponse | DeviceEvent | Custom | |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| | DevicePlugin | - | - | - | - | - | - | - | - | - | - | - | Rx | Rx | Rx | Tx | - | - | | ScreenPlugin | - | - | - | - | Rx/Tx | Rx/Tx | - | - | - | - | - | Rx | - | Tx | - | - | - | | WifiPlugin | - | - | - | - | - | - | Rx | Tx | - | - | - | - | - | - | - | - | - | | VideoPlugin | Rx | Tx | Rx | Tx | Tx | - | - | - | - | Rx | - | Rx | Tx | - | - | - | - | | BlePlugin | - | - | - | - | - | - | Tx(经 GATT) | Rx | - | - | - | Rx | Tx | - | - | - | - | | HttpPlugin | Bridge | Rx/Bridge | Bridge | Rx/Bridge | - | - | Bridge | Rx/Bridge | - | Rx/Bridge | Tx/Bridge | Rx | Rx(ble) | - | - | - | Rx(`plugin_states`) | | 动态插件系统 | 视插件而定 | 视插件而定 | 视插件而定 | 视插件而定 | 视插件而定 | 视插件而定 | 视插件而定 | 视插件而定 | 视插件而定 | 视插件而定 | - | Rx | 视插件而定 | 视插件而定 | 视插件而定 | 视插件而定 | Rx/Tx | | Flutter App | Bridge | Bridge | Bridge | Bridge | - | - | Bridge | Bridge | Bridge(BLE 配网结果) | Bridge | Bridge | - | Bridge(状态映射) | - | - | - | Bridge(插件管理入口) | ### 5.1 重点补测项 - `DeviceResponse`:补足 DevicePlugin 与 ScreenPlugin/业务插件的联动验证,确认 thin wrapper 没有只发不收的问题。 - `PluginReady`:验证 `video`、`http`、`ble` 的 ready 事件被 Manager 广播后,HTTP/Flutter 状态同步是否准确。 - `Custom`:动态插件自定义消息已有基础;HTTP 插件管理类 `Custom` 当前缺少 Manager 闭环,应作为 M1.2 对齐缺陷优先验证。 ## 6. HTTP API 覆盖范围 ### 6.1 播放与状态 - `GET /api/status` - `POST /api/play` - `POST /api/pause` - `POST /api/next` - `POST /api/previous` - `POST /api/goto/:index` - `GET /api/playlist` - `POST /api/scene/:name` - `POST /api/trigger/:name` - `POST /api/trigger/:name/:value` ### 6.2 配置管理 - `GET /api/config` - `GET /api/config/display` - `POST /api/config` - `GET /api/config/available` - `POST /api/config/switch` ### 6.3 视频/文件/App - `GET /api/videos` - `POST /api/videos/upload` - `DELETE /api/videos/:filename` - `GET /api/app/info` - `GET /download/:filename` - `GET /api/files/:dir` - `POST /api/files/:dir/upload` - `GET /api/files/:dir/download` - `POST /api/files/:dir/delete` - `POST /api/files/move` - `POST /api/files/:dir/mkdir` ### 6.4 WiFi / BLE / WebSocket / 插件管理 - `GET|POST /api/wifi/scan` - `GET /api/wifi/status` - `POST /api/wifi/connect` - `POST /api/wifi/ap/start` - `POST /api/wifi/ap/stop` - `POST /api/wifi/hotspot/start` - `POST /api/wifi/hotspot/stop` - `POST /api/ble/start` - `POST /api/ble/stop` - `GET /api/ble/status` - `GET /ws` - `GET /api/plugins` - `GET /api/plugins/:id` - `POST /api/plugins/:id/enable` - `POST /api/plugins/:id/disable` - `POST /api/plugins/:id/rollback` - `POST /api/plugins/:id/switch` - `POST /api/plugins/install` - `POST /api/plugins/check-updates` ## 7. 端到端场景清单 每个场景均要求记录:日志、HTTP 响应、WebSocket 事件、关键文件变化、插件状态快照。 ### 场景 1:系统冷启动与插件注册 - 前置条件:ARM64 实机;有效配置文件;`plugin_store/` 为空或仅含稳定动态插件。 - 操作步骤:启动主程序;观察启动日志;调用 `GET /api/status`;建立 `/ws` 连接。 - 预期结果:6 个内置插件按依赖顺序启动;若动态插件存在则完成自测;`/api/status` 可返回;WebSocket 首包包含 `status_update`、`config_update`、`ble_update`。 ### 场景 2:播放控制主链路 - 前置条件:播放列表至少 2 个有效视频;HTTP 服务启用。 - 操作步骤:依次调用 `/api/play`、`/api/pause`、`/api/play`、`/api/next`、`/api/previous`。 - 预期结果:VideoPlugin 正确切换状态;WebSocket 推送 `status_update`;暂停时触发 ScreenPlugin 释放防息屏,恢复播放时重新加锁。 ### 场景 3:按索引跳转视频 - 前置条件:播放列表长度 >= 3。 - 操作步骤:调用 `POST /api/goto/2`,随后调用 `GET /api/status`。 - 预期结果:当前索引变为 2;当前视频与目标条目一致;无越界异常。 ### 场景 4:状态机触发器切换场景 - 前置条件:配置内存在多个 state/trigger 规则。 - 操作步骤:调用 `POST /api/trigger/voice/name` 或 WebSocket `{"cmd":"trigger","name":"voice","value":"name"}`。 - 预期结果:VideoPlugin 接收 `Trigger`;若状态变化则广播 `StateChanged`;HTTP/WebSocket 观察到 `state_update`。 ### 场景 5:配置热重载 - 前置条件:当前配置文件合法,可修改显示或播放参数。 - 操作步骤:`POST /api/config` 提交合法 JSON;观察日志与 `/ws`;再次调用 `/api/config`。 - 预期结果:配置文件落盘;Manager 处理 `ConfigReloadRequest` 并广播 `ConfigReloaded`;VideoPlugin 与 HttpPlugin 更新内部状态;新配置立即可见。 ### 场景 6:切换配置文件 - 前置条件:`configs/` 下至少 2 个合法配置文件。 - 操作步骤:调用 `GET /api/config/available`,选择另一个配置后调用 `POST /api/config/switch`。 - 预期结果:目标配置通过校验后覆盖当前活动配置;系统广播配置重载;`active` 配置更新。 ### 场景 7:视频文件上传与删除 - 前置条件:视频目录可写;准备 1 个小视频文件。 - 操作步骤:调用 `/api/videos/upload` 上传;调用 `/api/videos` 检查列表;随后调用 `DELETE /api/videos/:filename`。 - 预期结果:上传成功且文件可见;删除后列表消失;不会因文件名注入逃逸目录。 ### 场景 8:文件管理器跨目录操作 - 前置条件:`videos/`、`configs/`、`plugin_store/` 可访问;创建测试子目录。 - 操作步骤:调用 `/api/files/:dir` 浏览、`mkdir` 新建目录、`upload` 上传、`move` 移动、`download` 下载、`delete` 删除。 - 预期结果:仅允许受管目录;子路径校验生效;跨文件系统移动可回退到 copy+delete;非法路径返回拒绝。 ### 场景 9:WiFi 扫描与状态查询 - 前置条件:实机具备 WiFi 网卡与 `nmcli`;附近存在可扫描网络。 - 操作步骤:调用 `GET /api/wifi/scan`;随后调用 `GET /api/wifi/status`。 - 预期结果:WiFiPlugin 返回去重后的网络列表;状态接口返回连接状态、SSID、IP;WebSocket 有 `wifi_update`。 ### 场景 10:WiFi 连接成功 - 前置条件:准备可连接的测试 WiFi;账号密码正确。 - 操作步骤:调用 `POST /api/wifi/connect`;等待 1~10 秒后查询 `/api/wifi/status`。 - 预期结果:连接命令进入 WifiPlugin;返回成功消息;状态转为 connected;IP 地址可读。 ### 场景 11:AP 热点启停 - 前置条件:设备支持热点模式。 - 操作步骤:调用 `POST /api/wifi/ap/start`;确认热点启动;随后调用 `POST /api/wifi/ap/stop`。 - 预期结果:热点名称和密码按请求生效;停止成功;兼容别名 `/api/wifi/hotspot/*`。 ### 场景 12:BLE 配网成功链路 - 前置条件:BLE 在配置中启用;手机或测试脚本可写 GATT 特征;目标 WiFi 可用。 - 操作步骤:启动程序;通过 BLE 写入 SSID、密码、`connect` 命令;观察 BLE 状态特征、日志、WiFi 状态。 - 预期结果:BLE 将凭据转发为 `WifiCommand::Connect`;WifiPlugin 返回结果后 BLE 状态特征更新;HttpPlugin 反映 `ble_update` 与 `wifi_update`。 ### 场景 13:WebSocket 实时控制链路 - 前置条件:WebSocket 已连接。 - 操作步骤:发送 `play`、`pause`、`goto`、`trigger`、`connect`、`ap_start` 命令 JSON。 - 预期结果:合法命令被解析为内部消息并入队;响应 `{"ok":true}`;状态更新推送到客户端。 ### 场景 14:动态插件挂载与自测通过 - 前置条件:`plugin_store/registry.json` 指向一个合法动态插件版本;manifest 声明 capability 且自测通过。 - 操作步骤:启动程序;查看日志与插件状态接口。 - 预期结果:动态插件被加载、`init`、`self_test`、`start`;必需能力通过;插件状态显示 enabled。 ### 场景 15:动态插件必需能力失败并自动回退 - 前置条件:准备 active 版本失败、stable 版本可回退的动态插件仓库;错误策略为 `auto_rollback`。 - 操作步骤:启动程序或发送触发失败的消息直至达到错误阈值。 - 预期结果:当前版本被禁用并触发回退;若稳定版本可重载则恢复运行;否则 `needs_rollback=true` 并记录日志。 ### 场景 16:动态插件热替换恢复旧版本 - 前置条件:已加载动态插件;准备一个启动失败的新版本。 - 操作步骤:通过测试钩子或后续管理命令触发热替换。 - 预期结果:旧插件先 stop;新插件 init/start 失败后恢复旧插件;资源无双开窗口。 ### 场景 17:Flutter App 首次连接与实时状态 - 前置条件:Flutter App 安装完成;手机与设备网络可达。 - 操作步骤:Flutter App 输入设备地址并连接;进入主控页;触发播放/暂停;保持 WebSocket 连接。 - 预期结果:App 能读取 `/api/status`、`/api/playlist`、`/api/ble/status`;能接收 `status_update`、`state_update`、`wifi_update`;UI 与设备状态一致。 ### 场景 18:Flutter App 配网与 APK 下载入口 - 前置条件:HTTP 服务与下载目录启用;存在 `downloads/showen-app.apk`。 - 操作步骤:Flutter 通过 BLE 或 HTTP 执行配网;访问 App 下载信息接口;点击下载 APK。 - 预期结果:`/api/app/info` 返回正确版本、大小、下载地址;APK 下载成功;Flutter 侧配网链路完成且错误可回显。 ## 8. 边界条件清单 - 1. 空 `playlist` 启动:`/api/status`、`/api/videos`、WebSocket 快照均不崩溃。 - 2. `goto` 越界:返回 `400`,不改变当前播放状态。 - 3. 上传 100MB 边界:恰好上限通过,超过上限返回 `413`。 - 4. 文件名包含 `..`、`/`、`\\`:上传、下载、删除、配置切换均拒绝。 - 5. WiFi SSID/密码包含空格、引号、反斜杠、冒号:命令参数保真。 - 6. BLE 写入空 SSID 或空命令:状态特征返回错误,不向核心发送无效命令。 - 7. 动态插件 manifest `id/version` 与目录不匹配:加载应失败且不污染注册表。 - 8. 动态插件 required capability 缺失于自测结果:视为失败并按策略处理。 - 9. `ConfigReloadRequest` 指向损坏 JSON:Manager 记录失败,保留旧配置。 - 10. WebSocket 收到非法 JSON 或缺少 `cmd`:返回结构化错误字符串,不影响连接。 - 11. `remote_control.enabled=false`:HTTP 插件不启动监听,但系统其他插件正常工作。 - 12. 关闭中的广播消息:`Shutdown` 广播后所有启用插件按逆序停止。 ## 9. 错误处理场景清单 - 1. HTTP 发送到关闭的消息通道:播放/WiFi/插件管理 API 返回 `500`。 - 2. WiFi 10 秒无响应:`/api/wifi/*` 返回 `504`。 - 3. WiFi 返回非 JSON:HTTP 层返回 `502`。 - 4. WiFi 返回 `{ok:false}`:HTTP 层透传为 `500` 业务错误。 - 5. 配置更新请求体非 UTF-8:返回 `400`。 - 6. 配置切换目标文件不存在:返回 `404`。 - 7. 文件下载缺少 `path` 参数:返回 `400`。 - 8. 文件移动目标已存在:返回 `409`。 - 9. 静态插件 init/start 失败:启动整体失败并退出。 - 10. 动态插件 init/start 失败:仅该插件禁用,其他插件继续运行。 - 11. 动态插件错误阈值达到上限:`disable_and_log` 时禁用;`auto_rollback` 时回退。 - 12. 热替换新插件启动失败:恢复旧插件;若恢复失败则明确标记 disabled。 - 13. BLE worker 崩溃:3 秒重试;停止信号下应及时退出。 - 14. `/api/plugins*` 命令当前无 Manager 闭环:应在 M1.2 先作为已知功能对齐缺陷立项验证并修复。 ## 10. 性能基准定义 M1.2 不做深度优化,但必须建立可重复基准,供 M1.3 追踪。 - 启动时间:从进程启动到 `http` 与 `video` 均发出 `PluginReady`,目标 `<= 3s`(无动态插件时)。 - HTTP 控制延迟:`POST /api/play` 到首个 `status_update`,P95 `<= 200ms`。 - WebSocket 状态推送延迟:内部消息到客户端收到事件,P95 `<= 150ms`。 - 配置热重载耗时:`POST /api/config` 到 `config_update` 推送完成,P95 `<= 500ms`。 - WiFi 状态查询:单次 `/api/wifi/status` 完成时间 P95 `<= 3s`,超时阈值 10s。 - 视频上传:100MB 文件上传不出现进程 OOM,峰值 RSS 不超过基线版本 120%。 - 长稳冒烟:连续 4 小时播放 + WebSocket 连接 + 周期性 WiFi 状态查询,无崩溃、无句柄泄漏迹象。 - 动态插件回退:达到错误阈值到完成禁用/回退标记,P95 `<= 1s`(不含磁盘 I/O 抖动)。 ## 11. 测试环境要求 ### 11.1 ARM64 实机 - Linux ARM64。 - 安装 `nmcli`、BlueZ、OpenCV 运行时、`websocat`、`curl`。 - 具备显示输出、WiFi 网卡、BLE 适配器。 - 可访问测试路由器,并可创建 AP 热点。 - 支持动态插件 `.so` 装载与回退仓库读写。 ### 11.2 CI 环境 - 运行 Rust 单元/集成测试。 - 使用 fake backend/fake plugin store 替代硬件。 - 执行 HTTP 路由级测试、消息序列化测试、ServiceManager 生命周期测试。 - 不承担真实 OpenCV 显示、真实 WiFi/BLE、真实动态 `.so` 装载回归。 ### 11.3 Flutter 联调环境 - Android 真机优先,至少 1 台;若支持则补 1 台 iOS 设备做网络连通冒烟。 - Flutter App 使用与服务端同版本 API 模型。 - 同时验证 HTTP 轮询回退与 WebSocket 实时模式。 ## 12. 自动化落地建议 - 新增 `tests/m1_2_service_manager.rs`:启动顺序、关闭顺序、广播、配置重载、动态插件错误策略。 - 新增 `tests/m1_2_http.rs`:播放/配置/文件/WiFi/BLE/插件管理 API 路由级验证。 - 新增 `tests/m1_2_dynamic_plugin.rs`:registry、manifest、热替换、回退、必需能力失败。 - 新增 `scripts/e2e/`:实机冒烟脚本,串联 `curl + websocat + 日志断言`。 - 新增 `clients/flutter/integration_test/`:设备发现、状态同步、WiFi 配网、配置保存。 ## 13. 预估工作量与排期建议 ### 13.1 工作量 - 测试基建与 fake fixture:2 人日。 - HTTP/ServiceManager 集成测试补齐:3 人日。 - 动态插件系统回退/热替换测试:2 人日。 - ARM64 实机 WiFi/BLE/视频链路验证:3 人日。 - Flutter 联调与回归:2 人日。 - 缺陷修复回归缓冲:2~4 人日。 - 合计:12~16 人日。 ### 13.2 两周建议排期 - 第 1-2 天:补齐测试基建,冻结 M1.2 测试配置与样本数据。 - 第 3-5 天:完成 ServiceManager、HTTP、文件管理、配置热重载自动化测试。 - 第 6-7 天:完成动态插件系统自动化测试,并确认插件管理 API 闭环缺陷。 - 第 8-10 天:ARM64 实机执行视频/WiFi/BLE/Flutter E2E,集中提单。 - 第 11-12 天:修复缺陷并回归,补齐性能基线数据。 - 第 13-14 天:做旧版本功能对齐复盘、输出测试报告与遗留风险清单。 ## 14. M1.2 完成判定 - 六个内置插件均有成功链路与失败链路验证证据。 - 动态插件系统完成加载、自测、禁用、回退、热替换验证。 - Flutter App 完成 HTTP/WebSocket/BLE 三条交互链路联调。 - 所有关键用户场景、边界条件、错误处理场景均有执行记录。 - 已知阻断性问题清零,或被明确降级并获负责人确认。