# sino-mci 测试补齐计划

> **日期**：2026-07-07  
> **范围**：backend（mci-server）单元/集成测试、API 冒烟测试、前端手工测试、Chrome DevTools 测试场景、Docker 部署验证  
> **执行标准命令**：见第 7 节

---

## 1. 单元测试（已存在）

| 测试类 | 路径 | 覆盖内容 |
|--------|------|---------|
| `PolicyEngineTest` | `send/service/PolicyEngineTest.java` | 频率限制（rate_limit）、时间窗（time_window）、黑名单（blacklist）及组合策略的允许/拒绝判定 |
| `SendServiceFailoverTest` | `send/service/SendServiceFailoverTest.java` | 发送记录主备账号切换、全部账号不可用、无健康账号三种场景下的 `SendService.dispatchRecord` |
| `AccountSelectorTest` | `channel/service/AccountSelectorTest.java` | 会话发送账号选择：主号健康、主号不健康切备用、`backup` 策略优先、无健康账号返回 null |
| `FlowEngineTest` | `inbound/engine/FlowEngineTest.java` | 入站流程引擎：条件分支命中 webhook、未命中执行 reply、无发布流程时标记已处理 |
| `IntentRecognitionServiceTest` | `inbound/intent/IntentRecognitionServiceTest.java` | 规则 + 槽位抽取的意图识别，无规则/无 LLM 时返回 `unknown` |
| `InboundContextServiceTest` | `inbound/service/InboundContextServiceTest.java` | 根据会话/联系人解析业务上下文，合并时优先保留会话维度字段 |
| `WebhookPayloadBuilderTest` | `inbound/webhook/WebhookPayloadBuilderTest.java` | 入站 webhook payload 结构、方向映射、`null` 意图/空白 JSON 处理 |
| `WebhookSignatureServiceTest` | `inbound/webhook/WebhookSignatureServiceTest.java` | HMAC-SHA256/512 签名、默认算法、不同 secret/算法签名差异 |
| `AccountControllerTest` | `admin/controller/AccountControllerTest.java` | 租户创建接口 |
| `ChannelControllerTest` | `channel/controller/ChannelControllerTest.java` | 渠道主体与渠道账号创建、企微回调 echo 校验 |
| `ChannelAccountHeartbeatTest` | `channel/ChannelAccountHeartbeatTest.java` | 心跳更新账号在线/风险状态、超时离线检测 |
| `WeComSessionArchiveTest` | `channel/controller/WeComSessionArchiveTest.java` | 企微 `msgaudit_notify` 回调、会话存档拉取并保存消息事件/媒体记录 |
| `WeComSyncTest` | `channel/controller/WeComSyncTest.java` | 企微通讯录同步（人员+渠道身份）、群聊同步（会话+参与者+账号绑定） |
| `CrmControllerTest` | `crm/controller/CrmControllerTest.java` | 联系人、标签、会话的创建与列表查询 |
| `IvrFlowControllerTest` | `flow/controller/IvrFlowControllerTest.java` | IVR 流程的增删改查、发布、下线生命周期 |
| `InboundFlowControllerTest` | `inbound/controller/InboundFlowControllerTest.java` | 入站流程创建/发布、接收消息事件 |
| `SendControllerTest` | `send/controller/SendControllerTest.java` | 发送接口：单聊发送、规则目标 `and/or` 逻辑、发送记录查询 |
| `SendPreviewControllerTest` | `send/controller/SendPreviewControllerTest.java` | 发送预览：会话/规则目标、渠道拆分、账号用量、黑名单命中但不做限流计数 |
| `SendMessageConsumerTest` | `send/mq/SendMessageConsumerTest.java` | MQ 异步消费：成功回调、失败重试、超过最大重试后的失败回调 |
| `TenantWebhookControllerTest` | `admin/controller/TenantWebhookControllerTest.java` | 租户 webhook 配置查询/更新、测试推送成功/失败 |

---

## 2. 集成测试（补齐）

### 2.1 现有集成测试缺口

- 现有 controller 级测试大多聚焦单接口或局部链路，缺少**从租户创建到发送记录生成**的完整出站链路。
- 缺少**从消息事件接收到 webhook 推送**的完整入站链路验证。

### 2.2 新增集成测试

#### `SendEndToEndTest.java`

- **路径**：`backend/mci-server/src/test/java/com/sino/mci/send/service/SendEndToEndTest.java`
- **技术**：`@SpringBootTest` + `@AutoConfigureMockMvc` + `@Transactional`，直接复用真实数据库（Docker 中 MySQL）。
- **覆盖步骤**：
  1. 创建租户并登录，获取 `authToken`；
  2. 创建渠道主体（wecom）；
  3. 创建渠道账号并关联主体；
  4. 创建会话并关联主体；
  5. 绑定账号为会话发送主号；
  6. 调用 `/api/v1/send/test` 模拟发送；
  7. 查询 `/api/v1/send/{taskId}/records`，校验记录数、状态、渠道类型、会话/账号 ID、内容。

#### `InboundEndToEndTest.java`

- **路径**：`backend/mci-server/src/test/java/com/sino/mci/inbound/engine/InboundEndToEndTest.java`
- **技术**：`@SpringBootTest` + `@AutoConfigureMockMvc` + `@Transactional`，通过 `@TestConfiguration` 将 `RestTemplate` bean 替换为 mock。
- **覆盖步骤**：
  1. 创建租户并登录；
  2. 配置租户入站 webhook URL 与签名密钥；
  3. 创建并发布入站流程：`receive -> intent -> webhook`；
  4. 调用 `/api/v1/inbound/receive` 发送消息事件；
  5. 校验返回事件 `isProcessed=1`；
  6. 验证 `RestTemplate.exchange` 被调用，且目标 URL 为租户配置的 webhook URL。

---

## 3. API 冒烟测试

> 基础地址：`http://localhost:8080/msg-platform`  
> 所有业务接口需携带 `X-Tenant-Code` 与 `Authorization: Bearer <token>`。

### 3.1 健康检查

```bash
curl -fsS http://localhost:8080/msg-platform/api/v1/health
```

### 3.2 创建租户

```bash
curl -X POST http://localhost:8080/msg-platform/api/v1/account/tenant \
  -H 'Content-Type: application/json' \
  -d '{"tenantCode":"rescue","tenantName":"救援业务"}'
```

### 3.3 登录（获取 token）

```bash
TOKEN=$(curl -s -X POST http://localhost:8080/msg-platform/api/v1/account/login \
  -H 'X-Tenant-Code: rescue' \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"admin123"}' | jq -r '.data.token')
echo "Bearer $TOKEN"
```

### 3.4 渠道主体 CRUD

```bash
curl -X POST http://localhost:8080/msg-platform/api/v1/channel-subject \
  -H 'X-Tenant-Code: rescue' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"channelType":"wecom","subjectCode":"sino","subjectName":"Sino"}'

curl -s http://localhost:8080/msg-platform/api/v1/channel-subject/list \
  -H 'X-Tenant-Code: rescue' \
  -H "Authorization: Bearer $TOKEN"
```

### 3.5 渠道账号创建

```bash
curl -X POST http://localhost:8080/msg-platform/api/v1/channel-account \
  -H 'X-Tenant-Code: rescue' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"accountType":1,"channelType":"wecom","accountId":"ZhangSan","accountName":"张三"}'
```

### 3.6 会话创建与账号绑定

```bash
CONV_ID=$(curl -s -X POST http://localhost:8080/msg-platform/api/v1/conversation \
  -H 'X-Tenant-Code: rescue' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"conversationType":2,"channelType":"wecom","channelConversationId":"room-001","conversationName":"测试群"}' | jq -r '.data.id')

curl -X POST "http://localhost:8080/msg-platform/api/v1/conversation/$CONV_ID/bind-account" \
  -H 'X-Tenant-Code: rescue' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"accountId":1,"bindingType":1,"sortOrder":1}'
```

### 3.7 发送消息（模拟发送）

```bash
TASK_ID=$(curl -s -X POST http://localhost:8080/msg-platform/api/v1/send/test \
  -H 'X-Tenant-Code: rescue' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d "{\"targetType\":\"conversation\",\"targetValue\":{\"conversation_id\":$CONV_ID},\"contentType\":\"text\",\"content\":{\"text\":\"hello\"},\"channelStrategy\":{\"channel_type\":\"wecom\",\"strategy\":\"primary\"}}" | jq -r '.data.taskId')

curl -s "http://localhost:8080/msg-platform/api/v1/send/$TASK_ID/records" \
  -H 'X-Tenant-Code: rescue' \
  -H "Authorization: Bearer $TOKEN"
```

### 3.8 入站消息接收

```bash
curl -X POST http://localhost:8080/msg-platform/api/v1/inbound/receive \
  -H 'X-Tenant-Code: rescue' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"eventId":"evt-001","channelType":"wecom","eventType":"message","messageSource":"realtime","conversationId":1,"personId":1,"content":{"type":"text","body":"我要救援"}}'
```

### 3.9 入站流程管理

```bash
curl -X POST http://localhost:8080/msg-platform/api/v1/inbound/flow \
  -H 'X-Tenant-Code: rescue' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"flowCode":"inbound_default","flowName":"默认入站流程","flowType":"inbound","definitionJson":{"nodes":[{"id":"n1","type":"receive"},{"id":"n2","type":"intent"},{"id":"n3","type":"webhook"}],"edges":[{"source":"n1","target":"n2"},{"source":"n2","target":"n3"}]}}'

curl -X POST http://localhost:8080/msg-platform/api/v1/inbound/flow/inbound_default/publish \
  -H 'X-Tenant-Code: rescue' \
  -H "Authorization: Bearer $TOKEN"
```

### 3.10 租户 webhook 配置

```bash
curl -X POST http://localhost:8080/msg-platform/api/v1/account/tenant/webhook \
  -H 'X-Tenant-Code: rescue' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"inbound_webhook_url":"https://example.com/webhook","auth_config":{"sign_algorithm":"hmac-sha256","webhook_secret":"secret-123"}}'
```

---

## 4. 前端手工测试

前端地址：`http://localhost`

| 页面 | 路由 | 测试步骤 | 预期结果 |
|------|------|---------|---------|
| 登录 | `/login` | 输入租户编码、用户名 `admin`、密码 `admin123` 登录 | 跳转 `/dashboard`，本地存储 token |
| 渠道主体 | `/channel-subject` | 新增 wecom 主体 → 保存 → 刷新列表 → 编辑主体名称 | 列表展示新增/更新后的主体，无报错 |
| 渠道账号 | `/channel-account` | 选择主体 → 新增企微内部账号 → 保存 → 触发同步通讯录/群聊 | 账号列表更新，同步返回成功提示 |
| 会话/群聊 | `/conversation` | 新增群聊 → 绑定发送/接收账号 → 绑定标签 | 会话详情正确显示绑定关系 |
| 发送策略 | `/send-policy` | 新建策略组 → 添加限流/时间窗/黑名单规则 → 保存 | 策略列表展示，规则 JSON 可预览 |
| 入站 Webhook | `/inbound-webhook` | 配置 URL 与签名密钥 → 点击测试推送 | 页面提示推送结果（200/失败原因） |
| 入站流程编排 | `/inbound-flow` | 拖拽 receive / intent / condition / webhook 节点 → 连线 → 保存并发布 | 流程定义持久化，发布状态变为已发布 |
| 发送记录 | `/send-record` | 发送消息后进入页面 → 按任务 ID/时间/状态筛选 | 列表展示发送记录及状态 |
| IVR 流程 | `/ivr-flow` | 新建 IVR 流程 → 配置 start / menu 节点 → 保存/发布/下线 | 生命周期状态正常切换 |
| 消息测试 | `/message-test` | 选择会话 → 填写内容 → 点击发送 | 返回任务 ID，发送记录可查询 |

---

## 5. Chrome DevTools 测试场景

### 5.1 通用检查项

1. 打开 `DevTools → Console`，切换页面/提交表单，确认无未处理异常。
2. 打开 `Network` 面板，过滤 `XHR/Fetch`，确认所有 API 请求：
   - 携带 `X-Tenant-Code`；
   - 携带 `Authorization: Bearer <token>`；
   - 返回 `code: 0`（业务成功）。
3. 检查请求/响应 Payload 与架构文档字段是否一致（snake_case / camelCase）。
4. 模拟弱网：在 `Network` 面板选择 `Slow 3G`，观察加载状态与超时提示。
5. 模拟后端 500：在 `Network` 面板对 `/api/v1/send/test` 请求右键 `Block request URL`，观察前端错误处理。

### 5.2 页面级场景

| 页面 | DevTools 操作 | 验证点 |
|------|---------------|--------|
| `/login` | 登录后查看 `Application → Local Storage` | `auth-store` 中保存 token、tenantCode、userInfo |
| `/channel-subject` | 新增主体后查看 Network | POST `/api/v1/channel-subject` 返回 `code: 0`，列表 GET 包含新主体 |
| `/channel-account` | 点击同步通讯录 | POST `/api/v1/channel-account/{id}/sync-contacts` 返回 `syncedCount` |
| `/conversation` | 绑定账号 | POST `/api/v1/conversation/{id}/bind-account` 请求体字段正确 |
| `/send-policy` | 保存策略 | POST `/api/v1/policy-group` 请求体 `policies` JSON 与表单一致 |
| `/inbound-webhook` | 点击测试推送 | POST `/api/v1/account/tenant/webhook/test` 返回 `success` 与 `message` |
| `/inbound-flow` | 保存/发布流程 | POST `/api/v1/inbound/flow` 与 `/publish` 顺序正确，payload 含 nodes/edges |
| `/message-test` | 发送消息 | POST `/api/v1/send/test` 返回 `taskId`，随后 GET `/api/v1/send/{taskId}/records` 返回状态 2 |
| `/send-record` | 筛选/分页 | GET `/api/v1/send/records` 含 page/size 参数，响应 `records` 非空 |
| `/ivr-flow` | 发布/下线 | POST `/api/v1/flow/{code}/publish` 与 `/offline` 后状态码正确 |

---

## 6. Docker 部署测试

### 6.1 启动基础设施

```bash
cd /Users/marsal/Projects/sino-project/sino-mci
cp .env.example .env
# 按需编辑 .env
docker compose --profile infra up -d
```

等待所有 `infra` 服务 healthy：

```bash
docker compose --profile infra ps
```

### 6.2 启动应用服务

```bash
docker compose --profile app up -d
```

### 6.3 验证健康检查

```bash
# MySQL
docker exec mci-mysql mysqladmin ping -h localhost -u root -proot

# Redis
docker exec mci-redis redis-cli ping

# RabbitMQ
docker exec mci-rabbitmq rabbitmq-diagnostics ping

# mci-server
curl -fsS http://localhost:8080/msg-platform/api/v1/health

# channel-service
curl -fsS http://localhost:8000/health

# admin-web（通过首页判断）
curl -fsS -o /dev/null -w '%{http_code}' http://localhost/
```

### 6.4 查看日志

```bash
docker compose --profile app logs -f mci-server
docker compose --profile app logs -f channel-service
```

---

## 7. 标准测试命令与结果

### 7.1 命令

```bash
cd /Users/marsal/Projects/sino-project/sino-mci/backend
docker run --rm --network sino-mci_mci-network --dns 8.8.8.8 \
  -v "$HOME/.m2:/root/.m2" -v "$(pwd):/app" -w /app \
  -e SPRING_DATASOURCE_URL='jdbc:mysql://mci-mysql:3306/sino_mci?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true' \
  -e SPRING_DATASOURCE_USERNAME=root -e SPRING_DATASOURCE_PASSWORD=root \
  -e SPRING_DATA_REDIS_HOST=mci-redis -e SPRING_RABBITMQ_HOST=mci-rabbitmq \
  maven:3.9-eclipse-temurin-21-alpine \
  mvn -B -pl mci-server -am test
```

### 7.2 运行结果

- **总测试数**：59
- **失败**：0
- **错误**：0
- **跳过**：0
- **构建状态**：`BUILD SUCCESS`

新增测试类 `SendEndToEndTest` 与 `InboundEndToEndTest` 均通过。

---

## 8. 后续可补充项

- 引入 Testcontainers 或 H2 以支持无 Docker 环境的本地单元测试运行。
- 为 `channel-service` 增加 Python 侧端到端测试。
- 补充前端自动化测试（Playwright / Cypress）覆盖第 4、5 节手工场景。
- 增加性能/稳定性测试：高并发发送、MQ 堆积恢复、会话存档大批量拉取。
