# 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 三条交互链路联调。
- 所有关键用户场景、边界条件、错误处理场景均有执行记录。
- 已知阻断性问题清零，或被明确降级并获负责人确认。
