update doc

This commit is contained in:
2026-07-13 10:47:58 +08:00
parent df23caf7d9
commit 6b42853c0b
5 changed files with 304 additions and 1321 deletions
-243
View File
@@ -1,243 +0,0 @@
# QA Report Round 6 — Worker Redis Stream Migration Verification
> Date: 2026-07-10
> Commit tested: 762de6a (DB polling → Redis Stream dispatch)
> Test plan: `docs/QA_TEST_PLAN_2026-07-10_cdp_round6.md`
---
## 1. Summary
| Metric | Value |
|--------|-------|
| Pages tested via CDP | 11 pages (Dashboard, Conversation, Contacts, Reports×4, Settings×4, Help Center) |
| Worker Redis paths verified | 6 job types across 6 queues |
| Pass | 11 pages + 6 worker paths |
| Findings | 4 new findings (1 P1, 3 P3) |
| WebSocket /cable | Connected, stable, no 401 |
| Overall verdict | Redis Stream migration PASSED — all user-visible functionality intact; 1 pre-existing P1 bug surfaced |
---
## 2. Worker Redis Stream Migration Verification
### 2.1 Redis Consumer Group Init
All 10 queue streams created with `gochat-workers` consumer group:
`default`, `high`, `medium`, `low`, `events`, `automation`, `search`, `scheduled_jobs`, `deferred` — all with consumer group `gochat-workers`, pending=0.
`purgable` stream: `ERR no such key` — no job has ever been enqueued to this queue, so the stream was never created by `XGROUP CREATE MKSTREAM`. This is expected behavior (stream is created lazily on first `XADD`).
### 2.2 Concurrency
- `config.dev.yaml` sets `concurrency: 5`
- Backend uses `NewWorkerPoolWithOptions(db, WithWorkerCount(cfg.Worker.Concurrency))`
- Redis `XINFO CONSUMERS` shows 1 consumer named `worker-{nanoseconds}`
- **Root cause:** All goroutines share the same `wp.workerID` (set once at pool creation), so Redis groups them as a single consumer
- **Impact:** Concurrency IS working (5 goroutines launched), but observability is limited — cannot distinguish per-goroutine activity in Redis
- **Severity:** P3 (observability, no functional impact)
### 2.3 Core Job Path Verification
| Job Type | Queue | Triggered By | DB Status | Redis Stream | Verdict |
|----------|-------|-------------|-----------|--------------|---------|
| `message:send_reply` | high | Agent reply via API (conversation 8) | completed, attempts=1 | XLEN=1, pending=0 | PASS |
| `event:dispatch_async` | events | Triggered by message reply | completed, attempts=1 | XLEN=0, pending=0 | PASS |
| `search:index` | search | Contact/message changes | dead, attempts=3 | XLEN=6, pending=0 | PASS (dead = Meilisearch not configured, known) |
| `conversation:reopen_snoozed` | low | Sweep (scheduled maintenance) | completed, attempts=1 | XLEN=2, pending=0 | PASS |
| `account:conversations_resolution_scheduler` | scheduled_jobs | Scheduled sweep | completed, attempts=1 | XLEN=11, pending=0 | PASS |
| `sla:trigger_accounts` (delayed) | scheduled_jobs | Scheduled (5min cycle) | completed after sweep | pending=0 | PASS |
### 2.4 Delayed Job Sweep Verification
- Job `sla:trigger_accounts` (id=1266) had `scheduled_at = 2026-07-10 06:22:33 UTC`
- At `06:21:51 UTC` the job was `queued` (not yet due)
- At `06:23:00 UTC` the job was `completed` (sweep picked it up after maturity)
- Sweep interval = 15s (dev config) — confirmed via backend log showing `worker.go:603` sweep query every ~15s
- `RequeueStaleJobs` also running in sweep loop (`worker.go:375`) — confirmed via backend logs
### 2.5 Message Send End-to-End (Browser Verified)
1. API call: `POST /api/v1/accounts/1/conversations/8/messages` with content "Redis Stream迁移测试-客服回复"
2. API returned message id=113, status=sent
3. DB job id=1263: `message:send_reply`, status=completed, queue=high, attempts=1
4. Redis high stream: XLEN=1, pending=0 (consumed and ACKed)
5. Browser: conversation 8 detail view shows 3 messages, including "Redis Stream迁移测试-客服回复" with timestamp "Jul 10, 2:16 PM"
6. No console errors on conversation detail page
**Verdict:** Redis Stream dispatch path fully functional for message sending.
### 2.6 FakeMessagePlatform Inbound (Blocked by BUG-F)
- FakeMessagePlatform sends to `/webhooks/fake/fake_test_1` with `X-Fake-Token: fake_test_token`
- GoChat returns `{"status":"ignored"}` — message not persisted
- Root cause: `fake_webhook.go:167` uses `channel_config @> '{"identifier":"..."}'` but `channel_config` is a TEXT column, not JSONB — the `@>` operator fails
- This is a **pre-existing bug** (not introduced by the Redis migration), confirmed by the fact that the last successful fake webhook was in round 5 before this column type issue would have manifested
- See BUG-F below for details
---
## 3. WebSocket /cable Verification
- After login: `/cable` connected successfully
- Backend logs show `ws: message command from user=1, data={"action":"update_presence"}` every ~1-2s (heartbeat)
- No `ws: authentication failed` in backend logs
- No reconnect storm — WS connection stable for entire test duration (~30 min)
- Backend log: `ws hub: client unregistered` appears only on page navigation (expected)
**Verdict:** WebSocket /cable healthy. Real-time features functional.
---
## 4. Per-Page Results
| # | Page | Click Path | Result | Findings |
|---|------|------------|--------|----------|
| 1 | Dashboard | (post-login) | PASS | Clean console, conversation list loads, sidebar visible |
| 2 | Conversation detail | Click "测试客户A" conversation | PASS | Messages render including new reply; 3 onClose warnings (allowlisted) |
| 3 | Contacts | Sidebar: 联系人 | PASS | 8 contacts loaded, edit forms visible; clean console |
| 4 | Reports Overview | Sidebar: 报告 | PASS | Stats cards, charts, agent table, team stats; clean console |
| 5 | Reports SLA | Reports sub-tab: SLA | PASS | SLA metrics (100% hit rate), "no SLA applied conversations"; clean console |
| 6 | Reports Bot | Reports sub-tab: 机器人 | PASS | BUG-A regression: sidebar nav from within Reports WORKS (FIXED) |
| 7 | Settings General | Sidebar: 设置 | PASS | Account settings form loads; clean console |
| 8 | Settings Agent Bots | 设置 sub-link: 机器人 | PASS | BUG-2 regression: sidebar click navigates correctly (FIXED) |
| 9 | Settings Inboxes | 设置 sub-link: 收件箱 | PASS | Inbox list loads (shows 1 of 2 inboxes — see BUG-H) |
| 10 | Inbox Pre-chat Form | Inbox settings → 预聊天表单 tab | PASS | BUG-C regression: no Vue error on null pre_chat_form_options (FIXED) |
| 11 | Help Center (Portals) | Sidebar: 帮助中心 | PASS | Articles/categories/locales/settings tabs visible; clean console |
| 12 | Activity (Campaigns) | Sidebar: 活动 | PASS | BUG-1 regression: route resolves to /campaigns/live_chat (FIXED); 1 new Vue Router warning (BUG-G) |
---
## 5. Findings
### BUG-F (P1) — Fake webhook lookupInbox uses JSONB @> on TEXT column
- **Severity:** P1
- **Symptom:** All fake channel webhooks return `{"status":"ignored"}` — no inbound messages persisted.
- **Root cause:** `backend/internal/handler/webhook/fake_webhook.go:167` constructs the query:
```sql
channel_type = 'fake' AND channel_config @> '{"identifier":"fake_test_1"}'
```
But `inboxes.channel_config` is a `TEXT` column (confirmed via `\d inboxes`), not `JSONB`. The `@>` operator does not exist for `TEXT @> unknown`, causing a PostgreSQL error that gets caught and returns "inbox not found".
- **Evidence:**
- Direct psql: `SELECT ... WHERE channel_config @> '{"identifier":"fake_test_1"}'` → `ERROR: operator does not exist: text @> unknown`
- With cast: `SELECT ... WHERE channel_config::jsonb @> '{"identifier":"fake_test_1"}'` → returns inbox id=3 correctly
- curl to webhook: `{"status":"ignored"}`
- **Affected files:** `backend/internal/handler/webhook/fake_webhook.go:167`
- **Fix direction:** Either (a) cast to jsonb in the query: `channel_config::jsonb @> ...`, or (b) change the `channel_config` column type to `JSONB` via a migration. Option (a) is the minimal fix; option (b) is the correct fix (JSONB is the right type for structured config).
- **Status:** NEW in round 6. Pre-existing (not introduced by Redis migration), but first time surfaced via CDP testing.
### BUG-G (P3) — Vue Router history.state warning on Activity page
- **Severity:** P3
- **Symptom:** Console warning: `[Vue Router warn]: history.state seems to have been manually replaced without preserving the necessary values.`
- **Context:** Appears when navigating to the Activity (Campaigns) page via sidebar click.
- **Root cause:** Something in the route navigation chain calls `history.replaceState` without preserving the existing state object. Likely related to the campaign list component or a router guard.
- **Affected files:** Likely in `frontend/app/javascript/dashboard/routes/` or campaign-related route guards.
- **Status:** NEW in round 6. No functional impact — page loads correctly.
### BUG-H (P3) — Inbox list page shows only 1 of 2 inboxes
- **Severity:** P3
- **Symptom:** Settings → Inboxes list shows "1 inbox" (Test Website Inbox only), but DB has 2 enabled fake+web inboxes (id=1 web_widget, id=3 fake).
- **Context:** The Fake Test Inbox (id=3) is enabled (`enabled=true`, `deleted_at=NULL`) but doesn't appear in the UI list.
- **Root cause:** Unknown — the inbox list API may filter by account_id or the frontend may not render fake-type inboxes in the settings list. Needs investigation of the inbox list API endpoint and frontend component.
- **Affected files:** Backend inbox list handler or frontend `SettingsInboxList.vue` equivalent.
- **Status:** NEW in round 6. Pre-existing (not introduced by Redis migration).
### BUG-I (P3) — All worker goroutines share same Redis consumer name
- **Severity:** P3 (observability)
- **Symptom:** Redis `XINFO CONSUMERS` shows 1 consumer even though `concurrency=5`.
- **Root cause:** `WorkerPool.workerID` is set once at pool creation (`fmt.Sprintf("worker-%d", time.Now().UnixNano())`) and shared by all goroutines. `Start()` launches `workerCount` goroutines all calling `go wp.run(ctx)`, but they all use `wp.workerID` in `XReadGroup.Consumer`.
- **Affected files:** `backend/internal/worker/worker.go:62` (workerID assignment), `worker.go:398` (XReadGroup Consumer field)
- **Fix direction:** Generate a unique consumer ID per goroutine (e.g., `fmt.Sprintf("worker-%d-%d", time.Now().UnixNano(), i)`) and pass it to `run()`.
- **Status:** NEW in round 6. Introduced by the Redis migration commit (762de6a).
---
## 6. Known Issues Re-Verification
| Bug | Description | Status (Round 6) | Evidence |
|-----|-------------|-------------------|----------|
| BUG-1 | Activity route missing | FIXED | Sidebar "活动" → `/campaigns/live_chat` loads correctly |
| BUG-2 | Bots sidebar link not navigating | FIXED | Settings → "机器人" → `/settings/agent-bots` loads |
| BUG-3/B | intlify empty key warnings | NOT TESTED | Did not create a label in this round |
| BUG-A | Bot Reports sidebar link from Reports | FIXED | Reports SLA → "机器人" → `/reports/bot` navigates |
| BUG-C | Pre-chat Form null error | FIXED | Inbox settings → 预聊天表单 tab → no Vue error |
| BUG-D | ChannelItem isActive whitelist (fake) | NOT TESTED | Did not navigate to inbox creation page |
| BUG-E | inbox_members not written | NOT TESTED | Did not create a new inbox |
| BUG-7 | 6 missing tables / 500 | FIXED | All 6 endpoints return 200 |
---
## 7. Backend Errors Observed
- No HTTP 500s during testing
- No panics
- No WebSocket authentication failures
- GORM logger noise from sweep queries (expected — worker.go:603 SELECT every 15s)
- `XGroupCreate` warnings did not appear (consumer groups created successfully on first Start)
---
## 8. Redis Worker State Snapshot
```
# Consumer Groups (all 10 queues)
gochat:jobs:default → group: gochat-workers, consumers: 1, pending: 0
gochat:jobs:high → group: gochat-workers, consumers: 1, pending: 0
gochat:jobs:medium → group: gochat-workers, consumers: 1, pending: 0
gochat:jobs:low → group: gochat-workers, consumers: 1, pending: 0
gochat:jobs:events → group: gochat-workers, consumers: 1, pending: 0
gochat:jobs:automation → group: gochat-workers, consumers: 1, pending: 0
gochat:jobs:search → group: gochat-workers, consumers: 1, pending: 0
gochat:jobs:scheduled_jobs → group: gochat-workers, consumers: 1, pending: 0
gochat:jobs:deferred → group: gochat-workers, consumers: 1, pending: 0
gochat:jobs:purgable → ERR no such key (lazy creation, expected)
```
---
## 9. Testing Notes
### What was tested
- Backend startup with Redis worker config (config.dev.yaml: concurrency=5, sweep=15s)
- DB schema integrity (6 endpoints return 200)
- Redis consumer group creation (10 queues)
- Message send reply via API → Redis high queue → worker → DB completed
- Event dispatch async → Redis events queue → completed
- Delayed job (sla:trigger_accounts) sweep pickup after maturity
- Conversation maintenance jobs → completed via sweep
- WebSocket /cable stability (30 min, no reconnect storm)
- 11 CDP browser pages with click navigation + console error capture
- 5 known bug regressions (BUG-1, BUG-2, BUG-A, BUG-C, BUG-7 — all FIXED)
### What was skipped / not tested
- 28-page full matrix (tested 11 key pages — browser tools had session timeout issues mid-run)
- FakeMessagePlatform inbound message flow (blocked by BUG-F — webhook lookupInbox fails on TEXT column)
- FakeMessagePlatform outbound message delivery (dependent on inbound working first)
- Cross-tab real-time delivery (Phase 5 skipped due to iteration limit)
- Direct WS test via websocat (Phase 5 skipped)
- BUG-3/B (intlify empty key) — did not create a label
- BUG-D (ChannelItem isActive) — did not navigate to inbox creation page
- BUG-E (inbox_members) — did not create a new inbox
- Profile page — not reached
- Settings: Teams, Labels, Custom Attributes, Automation, Macros, Canned Responses, Integrations, Conversation Workflow — not reached
### Blockers
- BUG-F (P1): Fake webhook `lookupInbox` fails because `channel_config` is TEXT, not JSONB. This blocks FakeMessagePlatform integration testing but does NOT affect the Redis Stream migration verification — the migration is about job dispatch, not webhook processing.
---
## 10. Conclusion
The Redis Stream migration (commit 762de6a) is **functionally verified**:
1. Worker jobs dispatch correctly through Redis Streams (XADD → XREADGROUP → DB claim → perform → XACK)
2. All tested job types complete successfully through the Redis path
3. Delayed jobs are picked up by the sweep mechanism when they mature
4. The concurrency fix (NewWorkerPoolWithOptions with WithWorkerCount) is applied — 5 goroutines are launched
5. No user-visible functionality regression — pages load, messages send, WebSocket connects, no new console errors from the migration itself
The migration is safe to keep. The findings (BUG-F through BUG-I) are either pre-existing or minor observability issues that should be addressed separately.
-393
View File
@@ -1,393 +0,0 @@
# FakeMessagePlatform 手工测试指南
> 基于 `docs/plans/2026-07-09-brainstorming-fake-message-platform.md` 和 `docs/qa/2026-07-09-test-plan-round5.md`
> 适用于开发者本地手工验证 FakeMessagePlatform 全链路消息流。
---
## 前置条件
1. PostgreSQL 16 + pgvector 运行在 `localhost:5444`,数据库 `gochat_dev` 已初始化
2. Redis 运行在 `localhost:6379`
3. Go 1.24+ 和 Node.js 20+ / pnpm 10+ 已安装
4. 仓库根目录执行过 `pnpm install`
5. 种子数据已加载(`admin@gochat.local / changeme` 账号存在)
---
## Step 1:启动三个服务
打开三个终端窗口:
```bash
# 终端 1GoChat 后端 (:3000)
cd /home/yanghao05/Projects/gochat
export GOROOT=/usr/lib/go-1.24 && export PATH=$GOROOT/bin:/home/yanghao05/.local/node-v22.20.0-linux-x64/bin:$PATH
export GOMODCACHE=/home/yanghao05/go/pkg/mod
pnpm dev:backend
# 终端 2:前端 Vite (:3036)
cd /home/yanghao05/Projects/gochat
export PATH="/home/yanghao05/.local/node-v22.20.0-linux-x64/bin:$PATH"
cd frontend && npx vite --port 3036
# 终端 3FakeMessagePlatform (:9100)
cd /home/yanghao05/Projects/gochat
export PATH="/home/yanghao05/.local/node-v22.20.0-linux-x64/bin:$PATH"
cd channels/fake && npx tsx src/index.ts
```
等待 10-15 秒,然后验证三个服务健康:
```bash
curl http://127.0.0.1:3000/health # 预期: {"status":"ok",...}
curl -o /dev/null -w '%{http_code}' http://127.0.0.1:3036/ # 预期: 200
curl http://127.0.0.1:9100/health # 预期: {"status":"ok","service":"fake-message-platform"}
```
三个都通过才能继续。
---
## Step 2:通过前端 UI 创建 Fake 渠道 Inbox
1. 浏览器打开 `http://127.0.0.1:3036/app/login`
2. 登录:`admin@gochat.local` / `changeme`
3. 左侧栏点击「设置」展开子菜单
4. 点击「收件箱」
5. 点击「添加收件箱」
6. 在渠道选择页面找到「Fake 测试平台」卡片,点击
7. 填写表单:
- 频道名称:`Fake Test Inbox`
- 标识符 (Identifier)`fake_test_1`
- Webhook URL`http://127.0.0.1:9100/receive`
- Token`fake_test_token`
8. 点击「创建 Fake 频道」
9. 在 agent 分配页面添加 admin 到此 inbox
10. 返回收件箱列表,确认 "Fake Test Inbox" 出现在列表中
如果前端 UI 因浏览器问题不稳定,可以用 API 替代:
```bash
curl -X POST http://127.0.0.1:3000/api/v1/accounts/1/inboxes \
-H "Content-Type: application/json" \
-H "X-User-ID: 1" -H "X-Account-ID: 1" \
-d '{
"name": "Fake Test Inbox",
"channel": {
"type": "fake",
"identifier": "fake_test_1",
"webhook_url": "http://127.0.0.1:9100/receive",
"token": "fake_test_token"
}
}'
```
预期返回 JSON 中 `channel_type: "fake"``id: 2`(或更大)。
---
## Step 3:配置 FakeMessagePlatform 的 GoChat webhook URL
FakeMessagePlatform 需要知道 GoChat 的 webhook 端点和 token
```bash
curl -X POST http://127.0.0.1:9100/api/config \
-H 'Content-Type: application/json' \
-d '{"webhook_url":"http://127.0.0.1:3000/webhooks/fake/fake_test_1","token":"fake_test_token"}'
```
预期返回:`{"status":"ok"}`
> 如果启动 FakeMessagePlatform 时已经设置了环境变量 `GOCHAT_WEBHOOK_URL` 和 `GOCHAT_FAKE_TOKEN`
> 则此步可跳过。但默认 URL 用的是 `fake_inbox_1`,需要改成你实际创建的 identifier。
---
## Step 4:连通性测试
发送一条测试消息,验证 FakeMessagePlatform → GoChat 的链路通畅:
```bash
curl -X POST http://127.0.0.1:9100/api/send \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"smoke_test","sender_name":"连通性测试","content":"ping"}'
```
预期返回:
```json
{"status":"sent","message_id":"fake_msg_...","gochat_status":200}
```
检查 FakeMessagePlatform 状态:
```bash
curl http://127.0.0.1:9100/api/status
```
预期 `total_sent >= 1`
如果 `gochat_status` 不是 200,说明 webhook 未正确接收。检查:
- FakeMessagePlatform 的 webhook_url 是否指向正确的 identifier
- GoChat 后端日志是否出现 "Fake webhook received"(注意:后端 worker pool 日志很多,需要过滤查找)
---
## Step 5:入站消息 — 客户发消息 → GoChat 创建会话
模拟客户"测试客户A"发一条消息:
```bash
curl -X POST http://127.0.0.1:9100/api/send \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_001","sender_name":"测试客户A","content":"你好,我需要帮助"}'
```
验证:
1. 前端 Dashboard 应出现新会话(如果前端打开了的话)
2. 通过 API 确认会话和消息已创建:
```bash
# 查看最新会话(替换 ID 为实际的会话 ID)
curl -s "http://127.0.0.1:3000/api/v1/accounts/1/conversations/4" \
-H "X-User-ID: 1" -H "X-Account-ID: 1" | python3 -m json.tool | head -30
```
3. 或直接查数据库确认:
```bash
PGPASSWORD=xiha02 psql -h 127.0.0.1 -p 5444 -U postgres -d gochat_dev -c \
"SELECT id, content, sender_type, source_id FROM messages WHERE inbox_id=2 ORDER BY id DESC LIMIT 5"
```
预期:看到 content="你好,我需要帮助"sender_type="contact"source_id 以 "fake_msg_" 开头。
---
## Step 6:出站消息 — 客服回复 → FakeMessagePlatform 收到
模拟客服在会话中回复(替换 `4` 为实际的会话 ID):
```bash
curl -X POST "http://127.0.0.1:3000/api/v1/accounts/1/conversations/4/messages" \
-H "X-User-ID: 1" -H "X-Account-ID: 1" \
-H "Content-Type: application/json" \
-d '{"content":"您好,有什么可以帮您?","message_type":"outgoing","private":false}'
```
验证 FakeMessagePlatform 收到了出站消息:
```bash
curl http://127.0.0.1:9100/api/messages?inbox_identifier=fake_test_1
```
预期返回的 `received` 数组中包含 content="您好,有什么可以帮您?"sender.type 为 "agent"。
这一步验证了完整的双向消息流:
```
客户消息 → FakeMsgPlatform → GoChat webhook → 创建会话/消息
客服回复 → GoChat API → FakeProvider.SendMessage → POST /receive → FakeMsgPlatform 存储
```
---
## Step 7:多客户并发会话
模拟两个不同客户同时发消息:
```bash
# 客户 B
curl -X POST http://127.0.0.1:9100/api/send \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_002","sender_name":"测试客户B","content":"退款咨询"}'
# 客户 C
curl -X POST http://127.0.0.1:9100/api/send \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_003","sender_name":"测试客户C","content":"技术支持"}'
```
验证创建了独立的会话:
```bash
PGPASSWORD=xiha02 psql -h 127.0.0.1 -p 5444 -U postgres -d gochat_dev -c \
"SELECT c.id, c.status, ct.name FROM conversations c JOIN contacts ct ON c.contact_id=ct.id WHERE c.inbox_id=2 ORDER BY c.id"
```
预期:每个 sender_id 对应一个独立的会话和联系人。
---
## Step 8:打字状态指示
模拟客户正在打字:
```bash
# 开始打字
curl -X POST http://127.0.0.1:9100/api/typing \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_001","typing":true}'
# 停止打字
curl -X POST http://127.0.0.1:9100/api/typing \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_001","typing":false}'
```
预期:两次请求都返回 `{"status":"sent","typing":true/false}`
---
## Step 9:关闭聊天窗口
模拟客户关闭聊天窗口(发送 session.end 事件):
```bash
curl -X POST http://127.0.0.1:9100/api/close \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_001"}'
```
验证 GoChat 创建了 "[session ended]" 系统消息:
```bash
PGPASSWORD=xiha02 psql -h 127.0.0.1 -p 5444 -U postgres -d gochat_dev -c \
"SELECT id, content, source_id FROM messages WHERE content='[session ended]' AND inbox_id=2"
```
预期:至少一条记录,source_id 以 "fake_close_" 开头。
---
## Step 10:消息附件
发送带图片附件的消息:
```bash
curl -X POST http://127.0.0.1:9100/api/send \
-H 'Content-Type: application/json' \
-d '{
"inbox_identifier":"fake_test_1",
"sender_id":"customer_001",
"sender_name":"测试客户A",
"content":"请看这张截图",
"content_type":"image",
"attachments":[{
"url":"http://example.com/screenshot.png",
"content_type":"image/png",
"filename":"screenshot.png",
"file_size":102400
}]
}'
```
验证消息以 image 类型创建:
```bash
PGPASSWORD=xiha02 psql -h 127.0.0.1 -p 5444 -U postgres -d gochat_dev -c \
"SELECT id, content_type, content_attributes FROM messages WHERE inbox_id=2 AND content_type='image' ORDER BY id DESC LIMIT 3"
```
预期:content_type 为 "image"content_attributes 中包含附件 URL。
---
## Step 11:客服上下线状态(FakeMessagePlatform 侧记录)
模拟客服上线和下线(这些是 FakeMessagePlatform 内存中记录的状态,供测试脚本查询):
```bash
# 客服上线
curl -X POST http://127.0.0.1:9100/api/agent/online \
-H 'Content-Type: application/json' \
-d '{"agent_id":"1","agent_name":"Admin"}'
# 查看状态
curl http://127.0.0.1:9100/api/status
# 预期:online_agents 中包含 agent_id="1"
# 客服下线
curl -X POST http://127.0.0.1:9100/api/agent/offline \
-H 'Content-Type: application/json' \
-d '{"agent_id":"1"}'
# 再次查看状态
curl http://127.0.0.1:9100/api/status
# 预期:online_agents 为空
```
---
## Step 12:重置状态(可选)
在每次测试前重置 FakeMessagePlatform 的内存状态:
```bash
curl -X POST http://127.0.0.1:9100/api/reset
```
预期返回 `{"status":"ok"}`,之后 `/api/status` 显示所有计数为 0。
---
## 验证清单
- [ ] 三个服务全部启动且健康检查通过
- [ ] Fake Inbox 成功创建(channel_type=fake
- [ ] FakeMessagePlatform webhook URL 已正确配置
- [ ] 连通性测试:发消息 → GoChat 返回 200
- [ ] 入站消息:客户发消息 → 创建 Contact + Conversation + Message
- [ ] 出站消息:客服回复 → FakeMessagePlatform /receive 收到
- [ ] 多客户并发:每个客户独立会话
- [ ] 打字状态:typing true/false 事件成功发送
- [ ] 会话关闭:"[session ended]" 消息创建
- [ ] 消息附件:image 类型消息正确创建
- [ ] 客服上下线:FakeMessagePlatform 状态正确记录
---
## 常见问题排查
### Q: FakeMessagePlatform 发消息返回 gochat_status 非 200
检查 FakeMessagePlatform 的 webhook URL
```bash
curl http://127.0.0.1:9100/api/status
```
确认 webhook_url 指向 `http://127.0.0.1:3000/webhooks/fake/<你的identifier>`
### Q: GoChat webhook 返回 200 但没有创建会话
FakeWebhookHandler 在出错时也返回 200(遵循 webhook 惯例)。检查后端日志:
```bash
# 在后端终端中查找 "Fake webhook" 相关日志
# 注意 worker pool 的 "record not found" 日志是正常噪音,不影响消息流
```
或直接查数据库确认消息是否已写入。
### Q: 客服回复没有到达 FakeMessagePlatform
确认 Fake Inbox 的 channel_config 中 webhook_url 指向 FakeMessagePlatform 的 /receive
```bash
PGPASSWORD=xiha02 psql -h 127.0.0.1 -p 5444 -U postgres -d gochat_dev -c \
"SELECT channel_config FROM inboxes WHERE channel_type='fake'"
```
预期 webhook_url 为 `http://127.0.0.1:9100/receive`
### Q: Meilisearch 连接失败
后端日志中可能出现 `meilisearch index document: ... connection refused`。这是因为 Meilisearch 未运行,搜索索引后台 job 会失败但**不影响消息收发**。如需完整搜索功能:
```bash
# 可选:启动 Meilisearch
cd /home/yanghao05/Projects/gochat/deploy/quickstart && docker compose up -d meilisearch
```
---
## 停止服务
测试完成后,在各终端按 Ctrl+C 停止服务。或批量停止:
```bash
kill $(lsof -t -i:3000 -i:3036 -i:9100) 2>/dev/null
```
-250
View File
@@ -1,250 +0,0 @@
# QA Report — Round 5: FakeMessagePlatform 全链路测试
**日期:** 2026-07-09
**环境:** backend (:3000) + frontend Vite (:3036) + FakeMessagePlatform (:9100) + CDP Chrome DevTools (:9222)
**登录:** admin@gochat.local / changeme (CDP 浏览器登录,click navigation)
**前置条件:** FakeMessagePlatform 已实现(Plan Task 1-13 全部完成),Round 4 的 28 页功能测试已通过。
---
## 1. Summary
本轮 QA 通过 CDP Chrome DevTools 浏览器验证了 FakeMessagePlatform 与 GoChat 的全链路消息集成。所有核心消息流通过浏览器 UI 操作验证(非 curl),包括前端 UI 创建 Fake Inbox、客户消息在 dashboard 实时显示、客服回复通过 FakeProvider 发回 FakeMessagePlatform。
| 维度 | 结果 | 验证方式 |
|------|------|----------|
| 服务健康检查 | ✓ 3/3 通过 | curl /health |
| DB schema integrity | ✓ 6/6 端点返回 401(非 500 | curl |
| Fake Inbox 创建 | ✓ 通过前端 UI 表单创建 | CDP click navigation |
| 渠道选择器渲染 | ✓ "Fake 测试平台" 卡片可见且可点击 | CDP snapshot |
| Fake.vue 表单渲染 | ✓ 标题/描述/4个字段/提交按钮全部正确 | CDP snapshot |
| i18n 翻译 | ✓ zh_CN 翻译全部正确显示 | CDP snapshot |
| 连通性测试 | ✓ FakeMessagePlatform → GoChat webhook 双向通信 | curl + CDP |
| 入站消息 → 创建会话 | ✓ "测试客户A" 会话出现在 dashboard | CDP 浏览器可见 |
| 出站消息 → FakePlatform 接收 | ✓ 客服 Ctrl+Enter 回复 → /receive 收到 | CDP UI 操作 + FakePlatform API |
| 28 页前端回归 | ⚠ 跳过(时间限制,核心场景已验证) | — |
| WebSocket /cable | ✓ 后端日志无 401presence 心跳正常 | 后端日志 |
---
## 2. 服务启动验证
| 服务 | 端口 | 健康检查 | 状态 |
|------|------|----------|------|
| GoChat Backend | :3000 | `GET /health` → 200 `{"status":"ok"}` | ✓ |
| Frontend Vite | :3036 | `GET /` → 200 | ✓ |
| FakeMessagePlatform | :9100 | `GET /health` → 200 `{"status":"ok","service":"fake-message-platform"}` | ✓ |
---
## 3. DB Schema Integrity Check
| 端点 | 状态码 | 结论 |
|------|--------|------|
| conversations/unread_counts | 401 | ✓ (非 500) |
| notifications | 401 | ✓ |
| custom_attribute_definitions/ | 401 | ✓ |
| agent_bots | 401 | ✓ |
| custom_filters/?filter_type=conversation | 401 | ✓ |
| custom_filters/?filter_type=contact | 401 | ✓ |
---
## 4. Fake Inbox 创建验证(CDP 浏览器 UI 操作)
### 4.1 渠道选择器验证
通过 CDP 浏览器 click navigation 导航到收件箱创建页面:
1. Dashboard → sidebar 点击「设置」→ 点击「收件箱」→ 点击「添加收件箱」
2. 渠道选择页面渲染 11 个渠道卡片,包括 **"Fake 测试平台"** 卡片
3. 卡片描述:"创建用于自动化测试的 Fake 消息渠道"
4. 卡片可点击(非 disabled 状态)
**发现并修复的问题:** `ChannelItem.vue``isActive` computed 中,`fake` 不在白名单数组里,导致卡片初始为 disabled。已修复:在 `isActive``return [...].includes(key)` 数组中添加 `'fake'`
**影响文件:** `frontend/app/javascript/dashboard/components/widgets/ChannelItem.vue:59-70`
### 4.2 Fake.vue 表单验证
点击 "Fake 测试平台" 卡片后,页面跳转到 `/settings/inboxes/new/fake`,渲染 Fake.vue 组件:
- 标题:"Fake 测试频道"i18n: `FAKE_CHANNEL.TITLE`
- 描述:"创建一个 Fake 消息渠道,用于自动化集成测试。"(i18n: `FAKE_CHANNEL.DESC`
- 表单字段:
- 频道名称(必填,i18n: `FAKE_CHANNEL.CHANNEL_NAME`
- 标识符(必填,i18n: `FAKE_CHANNEL.IDENTIFIER`
- Webhook URL(带说明文字,i18n: `FAKE_CHANNEL.WEBHOOK_URL`
- Token(可选,带说明文字,i18n: `FAKE_CHANNEL.TOKEN`
- 提交按钮:"创建 Fake 频道"i18n: `FAKE_CHANNEL.SUBMIT_BUTTON`
### 4.3 表单提交
填写表单:
- 频道名称:`Fake Test Inbox`
- 标识符:`fake_test_1`
- Webhook URL`http://127.0.0.1:9100/receive`
- Token`fake_test_token`
点击「创建 Fake 频道」→ 页面跳转到 `/settings/inboxes/new/3/agents`(inbox_id=3),表单提交成功。
### 4.4 Agent 分配
在 agent 分配页面点击「添加客服代理」按钮。通过 API 补充添加 admin 为 inbox 成员(前端 UI 的添加操作未正确写入 inbox_members,通过 `POST /api/v1/accounts/1/inboxes/3/members` 补充)。
---
## 5. FakeMessagePlatform 全链路测试结果(CDP 浏览器验证)
### 5.1 客户发消息 → GoChat 创建会话 → 前端 dashboard 显示
通过 FakeMessagePlatform 发送消息:
```
POST http://127.0.0.1:9100/api/send
{"inbox_identifier":"fake_test_1","sender_id":"customer_001","sender_name":"测试客户A","content":"你好,我需要帮助"}
```
**CDP 浏览器验证结果:**
- Dashboard 「所有的」标签下出现会话 "测试客户A"
- 会话消息预览显示 "你好,我需要帮助"
- 点击会话后,聊天窗口显示完整消息 "你好,我需要帮助",时间 "Jul 9, 5:28 PM"
- 回复框可见,带 "发送 (CTRL + ↵)" 按钮
**入站消息流通过浏览器 UI 验证**
### 5.2 客服回复 → FakeMessagePlatform 收到出站消息
在 CDP 浏览器中操作:
1. 点击回复框(contenteditable div
2. 输入 "您好,有什么可以帮您?"
3. 按 Ctrl+Enter 发送
**验证结果:**
- 消息即时出现在聊天窗口
- FakeMessagePlatform `/api/messages` 返回 received 数组包含:
- msg_id=112, content="您好,有什么可以帮您?", sender.type="user"
**出站消息流通过浏览器 UI 验证**
### 5.3 多客户并发会话
通过 FakeMessagePlatform 发送 customer_002 ("退款咨询") 和 customer_003 ("技术支持")
- 各自创建独立会话和联系人
- DB 确认 6 个独立会话(inbox_id=3
### 5.4 打字状态指示
- `POST /api/typing` typing=true → GoChat 收到 typing.start 事件 ✓
- `POST /api/typing` typing=false → GoChat 收到 typing.stop 事件 ✓
### 5.5 聊天窗口关闭(session end
- `POST /api/close` → GoChat 创建 "[session ended]" 消息 ✓
### 5.6 消息附件
- 发送 content_type=image + attachments → Message 创建为 image 类型 ✓
---
## 6. WebSocket /cable 验证
- 后端日志无 `ws: authentication failed`
- ActionCable JS 库加载正常
- WS presence heartbeat 正常运行(后端日志可见 `ws: message command from user=1, data={"action":"update_presence"}`
- Dashboard 登录后正常渲染
---
## 7. 28 页功能回归测试
**状态:** 跳过
**原因:** 本轮 QA 重点是 FakeMessagePlatform 全链路验证(Phase 3),该目标已通过 CDP 浏览器验证达成。28 页回归测试应在后续单独执行。
已验证的页面(通过 click navigation):
- Login → Dashboard ✓
- Dashboard → Settings → Inboxes ✓
- Inboxes → Add Inbox → Channel List ✓
- Channel List → Fake.vue 表单 ✓
- Fake.vue 表单提交 → Agent 分配页 ✓
- Dashboard → 会话列表 → 会话详情 ✓
- 会话详情 → 输入回复 → Ctrl+Enter 发送 ✓
---
## 8. Findings
### BUG-D (P2) — ChannelItem.vue isActive 白名单未包含 fake
- **Severity:** P2
- **Symptom:** Fake 渠道卡片在选择页面显示为 disabled,无法点击
- **Root cause:** `ChannelItem.vue``isActive` computed 中,最终的 `return [...].includes(key)` 白名单数组不包含 `'fake'`
- **Fix:** 在白名单数组中添加 `'fake'`
- **文件:** `frontend/app/javascript/dashboard/components/widgets/ChannelItem.vue:59-70`
- **状态:** FIXED(本轮修复)
### BUG-E (P3) — 前端 agent 分配页面未正确写入 inbox_members
- **Severity:** P3
- **Symptom:** 在 agent 分配页面点击「添加客服代理」后,inbox_members 表中无记录,导致 admin 无法看到 fake inbox 的会话
- **Workaround:** 通过 API `POST /api/v1/accounts/1/inboxes/3/members` 手动添加
- **状态:** 未修复(非 fake 渠道特有问题,是 inbox member 前端流程的通用问题)
---
## 9. Known Issues 回归验证
| Bug | Round 4 状态 | Round 5 状态 |
|-----|-------------|-------------|
| BUG-1 (P2) Activity 路由 | FIXED | 未测试 |
| BUG-2 (P3) Bots sidebar 导航 | FIXED | 未测试 |
| BUG-3 (P4) intlify empty key | PERSISTING | 未测试 |
| BUG-A (P3) Bot Reports 侧栏导航 | NEW | 未测试 |
| BUG-C (P2) 预聊天表单 null | FIXED | 未测试 |
---
## 10. 后端错误观察
| 类别 | 观察 |
|------|------|
| 500s | 无 |
| Panics | 无 |
| WS auth failures | 无 |
| Meilisearch | 未运行 (port 7700 connection refused),后台 job 累积为 dead 状态。非阻断性。 |
---
## 11. 测试备注
**测试了什么(CDP 浏览器验证):**
- 前端 UI 创建 Fake Inbox:渠道选择器 → Fake.vue 表单 → 提交 → agent 分配
- 入站消息:FakeMessagePlatform 发消息 → GoChat 创建会话 → dashboard "所有的" 标签显示会话 "测试客户A" + 消息 "你好,我需要帮助"
- 出站消息:客服在聊天窗口输入 "您好,有什么可以帮您?" → Ctrl+Enter → FakeMessagePlatform /receive 收到消息
- Console hooks 注入:登录后 console 干净(0 errors / 0 warnings
**测试了什么(API + DB 验证):**
- 多客户并发、打字状态、会话关闭、附件(通过 curl + psql 验证 DB 持久化)
- 连通性测试、FakeMessagePlatform REST API
**跳过了什么:**
- 28 页前端 click-navigation 回归矩阵
- 跨标签页实时推送测试
- 直接 WS welcome+ping 测试
- 多客服轮询分配测试(Phase 4)
**修复的 bug**
- BUG-D: ChannelItem.vue isActive 白名单未包含 fakeP2,已修复)
---
## 12. Go 后端/TS 单元测试结果
| 测试套件 | 测试数 | 通过 | 失败 |
|----------|--------|------|------|
| Go fake_test.go (ChannelProvider) | 14 | 14 | 0 |
| TS integration.test.ts (FakeMessagePlatform) | 10 | 10 | 0 |
| Go router_test.go (route registration) | 1 | 1 | 0 |
| Go build ./... | — | ✓ | — |
| Go vet ./... | — | ✓ | — |
-435
View File
@@ -1,435 +0,0 @@
# Test Plan — CDP 自动化全链路测试 (Round 5)
**日期:** 2026-07-09
**环境:** backend (:3000) + frontend Vite (:3036) + FakeMessagePlatform (:9100) + CDP browser (:9222)
**登录:** admin@gochat.local / changeme
**前置条件:** FakeMessagePlatform 已实现(Plan 文档 Task 1-13 全部完成),Round 4 的 28 页功能测试已通过,WebSocket 认证修复已验证。
---
## 背景
Round 4 验证了 28 页功能页面 + WebSocket 认证修复 + 实时消息推送。但以下场景因未对接消息收发平台而未被实际验证:
- 外部渠道 → GoChat:客户通过外部平台发消息,webhook 接收并创建会话
- GoChat → 外部渠道:客服回复后消息通过 provider 发回外部平台
- 多客服同时在线的消息分配
- 客服上下线状态切换对消息路由的影响
- 聊天窗口关闭/重开的对话连续性
- 打字状态指示跨渠道传递
本轮引入 FakeMessagePlatform 作为可编程的假消息平台,覆盖上述全部场景。同时保留 Round 4 的 28 页回归测试。
---
## 测试规则(继承 Round 4,无放松)
### Rule 1: Console warnings ARE errors
- `console.warn` / Vue warnings / i18n fallback / deprecation notices 全部记录为 finding
- 允许的 pre-existing warnings(不变):onClose deprecated、WootInput deprecated、Lit dev mode、Vue Router "Discarded invalid param(s)"、SW registration SecurityError
- 任何不在 allowlist 中的新 warning IS a bug
### Rule 2: Network infinite request loop detection
- 每页加载并 settle 后,监控网络 10 秒
- 标记任何在无用户交互下被调用超过 3 次的端点
- 特别关注 /cable 重连循环、轮询模式、4xx/5xx 端点重试
- `cache_keys` 端点多次调用是 false-positive(各组件独立获取,settle 后停止)
### Rule 3: Click-only navigation — NO URL input
- 所有页面切换通过 UI 点击(sidebar links、sub-menus、tabs、buttons
- 唯一例外:初始登录页 `/app/login`
- 折叠的 Settings sub-links 用 JS `.click()` on `<a>` 元素(算 UI click
- `window.location.href = '...'` 禁止用于页面导航
### Rule 4: Transition error capture
- 离开页面前 capture console buffer,到达下一页后 capture
- 过渡期间出现的 error/warning 是 transition-error finding
### Rule 5: CRUD interaction where applicable
- 不只是"页面加载"——在支持 CRUD 的页面至少执行一次 create/edit 操作
- 验证操作成功 + 无新 console errors + 无 infinite refetch loop
---
## Pre-Test Setup
### Step 0a: 启动全部服务
```bash
# Terminal 1: GoChat 后端 (:3000)
cd /home/yanghao05/Projects/gochat && pnpm dev:backend
# Terminal 2: Frontend Vite (:3036)
cd /home/yanghao05/Projects/gochat && pnpm dev:frontend
# Terminal 3: FakeMessagePlatform (:9100)
cd /home/yanghao05/Projects/gochat && pnpm fake:start
```
- [ ] `curl http://127.0.0.1:3000/health` → 200
- [ ] `curl http://127.0.0.1:3036/` → 200
- [ ] `curl http://127.0.0.1:9100/health``{"status":"ok","service":"fake-message-platform"}`
### Step 0b: DB schema integrity checkRound 2 BUG-7 教训)
```bash
for ep in conversations/unread_counts notifications custom_attribute_definitions/ agent_bots "custom_filters/?filter_type=conversation" "custom_filters/?filter_type=contact"; do
code=$(curl -s -o /dev/null -w '%{http_code}' -H 'X-Account-Id: 1' "http://127.0.0.1:3000/api/v1/accounts/1/$ep")
echo "$ep$code"
done
```
全部返回 200(或 401 未认证),不可 500。任何 500 = 停下修 schema。
### Step 0c: CDP browser + console hooks
- [ ] Chrome :9222 alive — `curl http://127.0.0.1:9222/json/version`
- [ ] 打开 `http://127.0.0.1:3036/app/login`
- [ ] 注入 console hooks`window.__consoleErrors` / `__consoleWarnings` / `__resetConsole`
- [ ] 验证返回 `'ok'`
### Step 0d: Login
- [ ] 填写 admin@gochat.local / changeme,点击登录
- [ ] 验证重定向到 dashboard
- [ ] Capture console — post-login 干净
- [ ] 验证 cookie `cw_d_session_info` 包含 `access-token`
### Step 0e: 创建 Fake 渠道 Inbox
通过前端 UI 创建(验证 Task 7-9 的前端兼容性):
- [ ] Sidebar → 设置 → 收件箱 → 点击"创建新收件箱"
- [ ] 渠道选择列表中找到"Fake 测试平台"卡片,点击
- [ ] 填写表单:
- 频道名称:`Fake Test Inbox`
- Identifier`fake_test_1`
- Webhook URL`http://127.0.0.1:9100/receive`
- Token`fake_test_token`
- [ ] 点击"创建 Fake 频道"
- [ ] 验证跳转到 agent 分配页面
- [ ] 添加 admin 到此 inbox
- [ ] 验证 inbox 出现在收件箱列表中
通过 API 验证:
```bash
curl -s -H "X-Account-Id: 1" -H "Authorization: Bearer <JWT>" \
http://127.0.0.1:3000/api/v1/accounts/1/inboxes | jq '.[] | select(.channel_type=="fake")'
```
### Step 0f: 验证 FakeMessagePlatform ↔ GoChat 连通性
```bash
# 从 FakeMessagePlatform 发一条测试消息到 GoChat
curl -X POST http://127.0.0.1:9100/api/send \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"smoke_test","sender_name":"连通性测试","content":"ping"}'
# 预期:GoChat 收到 webhook,创建 contact + conversation + message
# 验证 GoChat 日志中出现 "Fake webhook received" + "message persisted"
# 验证 FakeMessagePlatform 状态
curl http://127.0.0.1:9100/api/status
# 预期:sent >= 1
```
如果连通性测试失败,停下排查——后续所有测试依赖这条链路。
---
## Phase 1: WebSocket /cable 验证(回归 Round 4
- [ ] 登录后等 5s,检查后端日志 `ws: connection established`
- [ ] 后端日志无 `ws: authentication failed`
- [ ] 浏览器 console 无 WebSocket errors、无 /cable 401
- [ ] 网络 15s:/cable 连接一次并保持,不重复重连
- [ ] Presence indicator 显示状态(online/busy/offline
- [ ] 直接 WS 测试:连 `ws://127.0.0.1:3036/cable?access-token=<JWT>`,收到 `{"type":"welcome"}` + 5s ping
---
## Phase 2: 28 页功能回归测试(click navigation
继承 Round 4 的 28 页矩阵。本轮重点验证回归——Round 4 发现的 bug 是否修复、是否回退。每页执行完整 per-page checklistreset buffer → click → settle → console → network 10s → CRUD → backend logs → transition capture)。
| # | 页面 | 点击路径 | CRUD 测试 | 回归关注 |
|---|------|----------|-----------|----------|
| 1 | Dashboard | (post-login) | 验证会话列表 + 在线状态 | — |
| 2 | 会话详情 | 点击会话 | 发消息,验证即时送达 | — |
| 3 | 联系人 | Sidebar: 联系人 | 编辑联系人,保存 | — |
| 4 | 报告 Overview | Sidebar: 报告 → Overview | 日期选择器变化 | — |
| 5 | 报告 会话 | 报告 sub-tab: 会话 | 筛选变化 | — |
| 6 | 报告 客服 | 报告 sub-tab: 客服 | 验证表格 | — |
| 7 | 报告 标签 | 报告 sub-tab: 标签 | 验证加载 | — |
| 8 | 报告 收件箱 | 报告 sub-tab: 收件箱 | 验证加载 | — |
| 9 | 报告 团队 | 报告 sub-tab: 团队 | 验证加载 | — |
| 10 | 报告 CSAT | 报告 sub-tab: 客户满意度 | 验证加载 | — |
| 11 | 报告 SLA | 报告 sub-tab: SLA | 验证 SLA 指标 | — |
| 12 | 报告 机器人 | 报告 sub-tab: 机器人 | 验证加载 | **BUG-A** 回归:从 Reports 页面内点"机器人" sidebar link 是否跳转 |
| 13 | 活动 | Sidebar: 活动 | 验证页面加载 | **BUG-1** 回归:路由是否正常 |
| 14 | 帮助中心 | Sidebar: 帮助中心 | 点击 portal,验证文章 | — |
| 15 | 设置 账户设置 | Settings → 账户设置 | 更新字段,保存 | — |
| 16 | 设置 客服代理 | Settings sub: 客服代理 | 验证列表 + 搜索 | — |
| 17 | 设置 团队 | Settings sub: 团队 | 打开创建团队向导 | — |
| 18 | 设置 收件箱 | Settings sub: 收件箱 | 点击 inbox config,加载全部 tab | **BUG-C** 回归:预聊天表单 tab |
| 19 | 设置 标签 | Settings sub: 标签 | 创建标签 | **BUG-3** 回归:intlify empty key warning |
| 20 | 设置 自定义属性 | Settings sub: 自定义属性 | 切换 tab | — |
| 21 | 设置 自动化 | Settings sub: 自动化 | 验证列表 | — |
| 22 | 设置 机器人 | Settings sub: 机器人 | 验证列表 | **BUG-2** 回归:sidebar click nav |
| 23 | 设置 宏 | Settings sub: 宏 | 验证列表 | — |
| 24 | 设置 预设回复 | Settings sub: 预设回复 | 创建预设回复 | — |
| 25 | 设置 集成方式 | Settings sub: 集成方式 | 点击配置一个 | — |
| 26 | 设置 会话工作流 | Settings sub: 会话工作流 | Toggle switch | — |
| 27 | 设置 客服分配 | Settings sub: 客服分配 | 验证表单 | — |
| 28 | 个人资料 | Avatar dropdown → profile | 更新资料,保存 | — |
### 回归判定标准
| Bug | Round 4 状态 | Round 5 判定 |
|-----|-------------|-------------|
| BUG-1 (P2) Activity 路由缺失 | FIXED | 点击"活动" → campaigns 页面加载 → FIXED |
| BUG-2 (P3) Bots sidebar 导航 | FIXED | Settings → 机器人 → 导航到 agent-bots → FIXED |
| BUG-3 (P4) intlify empty key | PERSISTING | 创建标签时无 `[intlify] Not found ''` → FIXED;仍有 → PERSISTING |
| BUG-A (P3) Bot Reports 侧栏导航 | NEW | 从 Reports 页面点"机器人" → 跳转到 /reports/bot → FIXED;不跳转 → PERSISTING |
| BUG-C (P2) 预聊天表单 null | FIXED | inbox settings → 预聊天表单 tab → 无 Vue warn → FIXED |
---
## Phase 3: FakeMessagePlatform 全链路消息测试(本轮新增核心)
这是 Round 5 的核心差异——通过 FakeMessagePlatform 驱动真实的外部渠道消息流。
### 3.1 客户发消息 → GoChat 创建会话
- [ ] 通过 FakeMessagePlatform 发送消息:
```bash
curl -X POST http://127.0.0.1:9100/api/send \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_001","sender_name":"测试客户A","content":"你好,我需要帮助"}'
```
- [ ] 前端 Dashboard 看到新会话出现(Fake Test Inbox 渠道)
- [ ] 会话列表显示客户名"测试客户A"和消息预览"你好,我需要帮助"
- [ ] 点击会话,消息出现在聊天窗口
- [ ] 后端日志:`Fake webhook received` + `message persisted`
- [ ] Console 无新错误
### 3.2 客服回复 → FakeMessagePlatform 收到出站消息
- [ ] 在前端会话详情页,客服输入回复"您好,有什么可以帮您?"Ctrl+Enter 发送
- [ ] 消息即时出现在聊天窗口(WS 实时推送)
- [ ] FakeMessagePlatform 收到出站消息:
```bash
curl http://127.0.0.1:9100/api/messages?inbox_identifier=fake_test_1
```
预期:返回包含 `"content":"您好,有什么可以帮您?"` 的消息
- [ ] 消息 sender.type 为 "agent"
- [ ] Console 无新错误
### 3.3 客户回复 → 消息追加到同一会话
- [ ] 通过 FakeMessagePlatform 回复:
```bash
curl -X POST http://127.0.0.1:9100/api/reply \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_001","content":"我的订单有问题","reply_to_id":"<agent_msg_source_id>"}'
```
- [ ] 前端会话详情页:新消息追加到聊天窗口底部(不创建新会话)
- [ ] 消息 sender 为客户"测试客户A"
- [ ] Console 无新错误
### 3.4 多客户并发会话
- [ ] 同时发送两个不同客户的消息:
```bash
curl -X POST http://127.0.0.1:9100/api/send \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_002","sender_name":"测试客户B","content":"退款咨询"}' &
curl -X POST http://127.0.0.1:9100/api/send \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_003","sender_name":"测试客户C","content":"技术支持"}' &
```
- [ ] 前端会话列表出现 3 个独立会话(A/B/C)
- [ ] 每个会话的消息内容正确对应
- [ ] Console 无新错误
### 3.5 打字状态指示
- [ ] 触发客户打字状态:
```bash
curl -X POST http://127.0.0.1:9100/api/typing \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_001","typing":true}'
```
- [ ] 前端会话详情页显示"正在输入..."指示器(如果 UI 支持)
- [ ] 停止打字:
```bash
curl -X POST http://127.0.0.1:9100/api/typing \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_001","typing":false}'
```
- [ ] 指示器消失
- [ ] Console 无新错误
### 3.6 聊天窗口关闭(session end
- [ ] 关闭客户会话:
```bash
curl -X POST http://127.0.0.1:9100/api/close \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","conversation_id":"<external_conv_id>","sender_id":"customer_001"}'
```
- [ ] GoChat 收到 session.end 事件,创建 "[session ended]" 消息
- [ ] 前端会话显示系统消息或状态变化
- [ ] Console 无新错误
- [ ] 同一客户再次发消息 → 创建新会话(如果 inbox 配置 lock_to_single_conversation=false
### 3.7 消息附件
- [ ] 发送带附件的消息:
```bash
curl -X POST http://127.0.0.1:9100/api/send \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_001","sender_name":"测试客户A","content":"请看这张截图","content_type":"image","attachments":[{"url":"http://example.com/screenshot.png","content_type":"image/png","filename":"screenshot.png","file_size":102400}]}'
```
- [ ] 前端会话详情页显示附件预览(图片缩略图或文件链接)
- [ ] Console 无新错误
---
## Phase 4: 多客服消息分配测试(本轮新增)
### 4.1 单客服在线 — 所有消息分配给该客服
- [ ] 确保 Fake Test Inbox 启用 auto_assignment
- [ ] 确保只有一个客服(admin)在线
- [ ] FakeMessagePlatform 模拟客服上线:
```bash
curl -X POST http://127.0.0.1:9100/api/agent/online \
-H 'Content-Type: application/json' \
-d '{"agent_id":"1","agent_name":"Admin"}'
```
- [ ] 发送新客户消息 → 验证会话自动分配给 admin
- [ ] 前端会话列表中该会话 assignee 为 admin
### 4.2 多客服在线 — 轮询分配
- [ ] 创建第二个客服用户(通过 Settings → 客服代理)
- [ ] 将第二个客服添加到 Fake Test Inbox
- [ ] 两个客服同时在线
- [ ] 连续发送 3 条不同客户的消息
- [ ] 验证会话按 round-robin 或 least_busy 策略分配给不同客服
- [ ] 前端各客服的会话列表显示分配给自己的会话
### 4.3 客服下线 — 消息不分配给离线客服
- [ ] 第二个客服下线:
```bash
curl -X POST http://127.0.0.1:9100/api/agent/offline \
-H 'Content-Type: application/json' \
-d '{"agent_id":"2"}'
```
- [ ] 发送新客户消息 → 验证会话只分配给在线的 admin
- [ ] 不分配给已下线的客服
### 4.4 全部客服下线 — 消息进入未分配队列
- [ ] admin 也下线
- [ ] 发送新客户消息 → 验证会话创建但 assignee 为空(未分配)
- [ ] 客服上线后 → 验证是否自动补分配(取决于 assignment policy 配置)
---
## Phase 5: 实时 WebSocket 功能验证(含跨渠道)
### 5.1 基础实时推送(回归 Round 4)
- [ ] 打开会话,发消息 → 即时出现
- [ ] 后端日志:`message.created` event dispatched
- [ ] Presence indicator 显示正确状态
- [ ] 网络 30s/cable 稳定,无 401,无重连风暴
- [ ] 直接 WS 测试:welcome + 5s ping
### 5.2 跨标签页实时推送(回归 Round 4)
- [ ] Tab 2 同账号登录,reload dashboard
- [ ] Tab 1 发消息 → Tab 2 会话列表预览更新 + 会话详情消息出现
- [ ] 无需手动刷新
### 5.3 FakeMessagePlatform 触发的实时推送(新增)
- [ ] Tab 1 打开 customer_001 的会话
- [ ] 通过 FakeMessagePlatform 发送 customer_001 的新消息
- [ ] Tab 1 聊天窗口即时显示新消息(WS 推送,非轮询)
- [ ] Tab 2 如果也打开同一会话 → 同样即时显示
- [ ] 后端日志:`Fake webhook received` → `message persisted` → `ws: message command`WS 推送)
---
## Phase 6: 收集与报告
### Issue severity(继承 Round 4
| Sev | 定义 |
|-----|------|
| P0 | 功能损坏,阻断核心工作流,或无限循环导致资源耗尽 |
| P1 | 核心功能损坏但有 workaround;或重复 console errors 降低 UX |
| P2 | 非核心功能损坏,或 warnings 指向真实代码问题 |
| P3 | 轻微 cosmetic/edge-case,无功能影响 |
| P4 | 纯噪声(dev-mode warnings、deprecation 无用户影响) |
### 报告文件
写入 `docs/qa/2026-07-09-qa-report-round5.md`。
必须包含的章节:
1. **Summary** — 总页数/通过/失败数 + FakeMessagePlatform 集成测试结果概要
2. **服务启动验证** — backend/frontend/fake-platform 三个服务的健康检查结果
3. **Fake Inbox 创建验证** — 前端 UI 创建 fake 渠道 inbox 的过程和结果
4. **WebSocket 验证** — /cable 状态、welcome/ping、无 401
5. **28 页回归测试结果表** — 页面、点击路径、通过/失败、findings
6. **FakeMessagePlatform 全链路测试结果**Phase 3 每个子项)
7. **多客服消息分配测试结果**(Phase 4 每个子项)
8. **实时 WebSocket 功能验证**Phase 5 每个子项)
9. **Findings** — 每个 issueseverity、复现步骤(click path + curl command)、预期 vs 实际、console errors、screenshot path、根因方向、影响文件
10. **Known issues 回归验证** — BUG-1/2/3/A/C 的 Round 5 状态
11. **后端错误观察** — 500s、panics、WS auth failures
12. **测试备注** — 测试了什么、跳过了什么、blockers
### 截图
每个 finding 截图:
```
browser_vision(question="Capture the issue: <description>", annotate=false)
```
在 CLI 模式下陈述截图的绝对路径(不使用 MEDIA: tag)。
### 文档清理
按 known-bugs.md 的 cleanup policy
- [ ] 将 Round 4 的 `docs/qa/2026-07-09-qa-report-round4.md` 和 `docs/qa/2026-07-09-test-plan-round4.md` 删除
- [ ] 将 Round 5 报告中的新发现合并到 `references/known-bugs.md`
- [ ] 更新 known-bugs.md 的 "Consolidated from QA rounds 15" 行
- [ ] 保留 Round 5 的报告和测试计划作为当前版本
---
## 验证 Checklist
- [ ] 三个服务(backend :3000 + frontend :3036 + fake :9100)全部启动并健康
- [ ] DB schema integrity check6 个历史端点全部 200(非 500)
- [ ] Console hooks 注入成功
- [ ] Login 成功,cookie 包含 access-token
- [ ] Fake 渠道 Inbox 通过前端 UI 成功创建
- [ ] FakeMessagePlatform → GoChat 连通性测试通过
- [ ] /cable WebSocket 连接稳定(welcome + ping + 无 401
- [ ] 28 页全部通过 click navigation 测试(或标注 exception
- [ ] 每页:reset buffer → navigate → settle → console → network 10s → CRUD → logs → transition
- [ ] Phase 3 全链路消息测试:发消息/回复/多客户/打字/关窗口/附件 全部验证
- [ ] Phase 4 多客服分配:单客服/多客服轮询/下线/全下线 全部验证
- [ ] Phase 5 实时推送:基础/跨标签页/FakePlatform 触发 全部验证
- [ ] 报告写入 `docs/qa/2026-07-09-qa-report-round5.md`
- [ ] 每个 finding 有:severity、复现步骤(click path + curl)、console errors、screenshot path、根因
- [ ] Known bugs 回归验证:BUG-1/2/3/A/C 状态更新
- [ ] 旧轮报告清理(Round 4 report + test plan 删除)
- [ ] known-bugs.md 更新(新发现 + 状态变更 + "rounds 15"
+304
View File
@@ -0,0 +1,304 @@
# QA Test Plan — CDP Full-Page Functional Testing Round 7
> **Date:** 2026-07-10
> **Commit tested:** 690796a (fix: resolve 4 bugs from QA round 6 — BUG-F/G/H/I)
> **Previous round:** Round 6 (Redis Stream migration verification, 11 pages, 4 findings)
> **Round 7 focus:** BUG-F/G/H/I fix verification + full 28-page matrix completion + FakeMessagePlatform E2E
---
## 1. Round 7 Goals
### 1.1 Primary: Verify 4 Bug Fixes from Round 6
| Bug | Sev | Fix Summary | Verification Method |
|-----|-----|-------------|---------------------|
| BUG-F | P1 | `fake_webhook.go:168` now casts `channel_config::jsonb` before `@>` operator | Send fake webhook → verify message persisted in DB + appears in browser conversation list |
| BUG-G | P3 | `Settings.vue:544` passes `window.history.state` (not `null`) to `replaceState`; same fix in Instagram.vue, Tiktok.vue, portalThemeHelper.js | Navigate to Activity page → check console for Vue Router history.state warning (should be absent) |
| BUG-H | P3 | `account_service.go` CacheKeys() now derives real inbox/label/team keys from DB state (count + max(updated_at)) instead of hardcoded `"0000000000"` | Navigate to Settings → Inboxes → verify both inboxes (web_widget + fake) appear in list |
| BUG-I | P3 | `worker.go:386` generates unique `consumerID := fmt.Sprintf("%s-%d", wp.workerID, index)` per goroutine | Redis `XINFO CONSUMERS` for any queue → verify 5 consumers (not 1) |
### 1.2 Secondary: Complete the 28-Page Matrix
Round 6 tested only 11 pages (browser session timeout). Round 7 must cover ALL 28 pages
in the standard matrix, with special attention to pages skipped in round 6:
- Settings: Teams, Labels, Custom Attributes, Automation, Macros, Canned Responses,
Integrations, Conversation Workflow
- Profile page
- Reports: Conversations, Agents, Labels, Inboxes, Teams, CSAT (round 6 only tested
Overview, SLA, Bot)
### 1.3 Tertiary: FakeMessagePlatform E2E Integration
BUG-F blocked all fake channel testing in round 6. With the fix, perform the full
inbound → outbound message flow:
1. Start FakeMessagePlatform (:9100)
2. Send customer message via `POST /api/send` → GoChat webhook → DB persist
3. Verify in browser: conversation appears in dashboard, message renders in chat
4. Agent reply in browser (Ctrl+Enter) → FakeMessagePlatform `/receive` → verify via
`GET /api/messages`
5. Verify real-time delivery: message appears instantly (WebSocket)
### 1.4 Quaternary: Complete Phase 5 Real-time Verification
Round 6 skipped Phase 5 (iteration limit). Round 7 must:
- Send message → instant render in chat UI
- Verify `message.created` event in backend logs
- Presence indicator shows status (online/busy/offline)
- 30s stable /cable monitoring
- Direct WS test via shell (websocat or Python)
- Cross-tab real-time delivery (2 tabs, same credentials)
### 1.5 Quinary: Redis Worker Observability (BUG-I fix)
- Start backend with `concurrency: 5` (config.dev.yaml)
- Trigger at least one job (send a message)
- Redis `XINFO CONSUMERS gochat:jobs:high gochat-workers` → verify 5 consumers
- Verify each consumer has a unique name (`worker-<timestamp>-0` through `-4`)
---
## 2. Test Execution Plan
### Phase 1 — Start Dev Services + Pre-checks
1. Start backend (air hot-reload, :3000) and frontend (Vite, :3036) as background processes
2. Health check: `curl :3000/health` and `curl :3036/` → both OK
3. DB schema integrity check: hit 6 historically-missing endpoints → all 200 (not 500)
4. Capture backend log baseline
5. Start FakeMessagePlatform: `cd channels/fake && npx tsx src/index.ts` (background)
6. Verify FakeMessagePlatform health: `curl :9100/health`
### Phase 2 — Browser Login + Console Hook Injection
1. Navigate to `http://127.0.0.1:3036/app/login` (only allowed direct URL)
2. Inject console hooks (error + warn capture, `__resetConsole`)
3. Fill login form: `admin@gochat.local` / `changeme`
4. Submit → verify redirect to `/app/` (dashboard)
5. Re-inject hooks if full-page reload occurred
6. Read console buffer → verify clean
### Phase 3 — WebSocket /cable Verification
1. Wait 35s for WS connect
2. Read console buffer for `/cable` 401 or WS errors
3. Poll backend logs for `ws: connection established`
4. Monitor network 10s — `/cable` stable, no reconnect loop
5. If /cable broken → record P0, continue page tests (note degraded real-time)
### Phase 4 — Full 28-Page Matrix (Click Navigation)
**Per-page protocol (EVERY page, non-negotiable):**
1. `window.__resetConsole()` — reset buffer
2. Click to navigate (record click path)
3. Wait to settle — `browser_vision` confirms content rendered
4. Read console buffer → any error or non-allowlisted warning is a finding
5. Monitor network 10s → flag any endpoint called >3x (except `cache_keys`)
6. Verify core content renders (not blank, not error boundary)
7. Attempt one CRUD interaction where applicable
8. Poll backend logs for errors during this page
9. Capture transition errors before leaving
**Page matrix:**
| # | Page | Click Path | Key Checks | Round 6 Status |
|---|------|------------|------------|----------------|
| 1 | Dashboard | (post-login) | Conversation list, sidebar, online status | ✅ PASS |
| 2 | Conversation detail | Click a conversation | Messages, reply box, send msg, real-time | ✅ PASS |
| 3 | Contacts | Sidebar: 联系人 | Edit, save, search; no refetch loop | ✅ PASS |
| 4 | Reports Overview | Sidebar: 报告 → Overview | Stats cards, charts, date picker | ✅ PASS |
| 5 | Reports Conversations | Reports sub-tab: 会话 | Table, filter change | ⬜ NOT TESTED |
| 6 | Reports Agents | Reports sub-tab: 客服 | Agent table | ⬜ NOT TESTED |
| 7 | Reports Labels | Reports sub-tab: 标签 | Verify load | ⬜ NOT TESTED |
| 8 | Reports Inboxes | Reports sub-tab: 收件箱 | Verify load | ⬜ NOT TESTED |
| 9 | Reports Teams | Reports sub-tab: 团队 | Verify load | ⬜ NOT TESTED |
| 10 | Reports CSAT | Reports sub-tab: 客户满意度 | Verify load | ⬜ NOT TESTED |
| 11 | Reports SLA | Reports sub-tab: SLA | SLA metrics | ✅ PASS |
| 12 | Reports Bot | Reports sub-tab: 机器人 | BUG-A regression | ✅ PASS (FIXED) |
| 13 | Activity | Sidebar: 活动 (JS click) | BUG-1 + **BUG-G regression** | ✅ PASS (BUG-G warning) |
| 14 | Help Center | Sidebar: 帮助中心 (JS click) | Portal list, articles, tabs | ✅ PASS |
| 15 | Settings General | 设置 → 账户设置 | Update a field, save | ✅ PASS |
| 16 | Settings Agents | 设置: 客服代理 | List, search | ⬜ NOT TESTED |
| 17 | Settings Teams | 设置: 团队 | List, create team wizard | ⬜ NOT TESTED |
| 18 | Settings Inboxes | 设置: 收件箱 | **BUG-H regression: 2 inboxes visible** | ✅ PASS (BUG-H: 1 of 2) |
| 19 | Settings Labels | 设置: 标签 | Create label, **BUG-3/B regression** | ⬜ NOT TESTED |
| 20 | Settings Custom Attributes | 设置: 自定义属性 | List, tabs | ⬜ NOT TESTED |
| 21 | Settings Automation | 设置: 自动化 | List | ⬜ NOT TESTED |
| 22 | Settings Agent Bots | 设置: 机器人 | BUG-2 regression | ✅ PASS (FIXED) |
| 23 | Settings Macros | 设置: 宏 | List | ⬜ NOT TESTED |
| 24 | Settings Canned Responses | 设置: 预设回复 | Create canned response | ⬜ NOT TESTED |
| 25 | Settings Integrations | 设置: 集成方式 | Cards, configure one | ⬜ NOT TESTED |
| 26 | Settings Conv. Workflow | 设置: Conversation Workflows | Toggle switch | ⬜ NOT TESTED |
| 27 | Settings Assignment | 设置: Agent Assignment | Forms, add buttons | ⬜ NOT TESTED |
| 28 | Profile | Avatar dropdown → profile | Update profile, save | ⬜ NOT TESTED |
**Inbox Settings Deep Dive (page 18 sub-tabs):**
| Sub-tab | Key Check | Round 6 |
|---------|-----------|---------|
| 收件箱设置 | Config form loads | ✅ |
| 协作 | Agent assignment list | ⬜ |
| 预聊天表单 | BUG-C regression (null pre_chat_form_options) | ✅ FIXED |
| 后聊天表单 | Form renders | ⬜ |
| 消息标签 | Message labels config | ⬜ |
| 自动化规则 | Auto-assign rules | ⬜ |
| 业务时间 | Business hours config | ⬜ |
### Phase 4b — BUG-F Verification: FakeMessagePlatform E2E
**This is the highest-priority test in round 7 (P1 bug fix verification).**
1. Navigate to Settings → Inboxes → verify Fake Test Inbox (id=3) is visible (BUG-H fix)
2. Record the Fake Test Inbox identifier (e.g., `fake_test_1`)
3. Configure FakeMessagePlatform webhook URL to match:
```
curl -X POST http://127.0.0.1:9100/api/config \
-H 'Content-Type: application/json' \
-d '{"webhook_url":"http://127.0.0.1:3000/webhooks/fake/fake_test_1","token":"fake_test_token"}'
```
4. **Inbound test:** Send customer message via FakeMessagePlatform:
```
curl -X POST http://127.0.0.1:9100/api/send \
-H 'Content-Type: application/json' \
-d '{"inbox_identifier":"fake_test_1","sender_id":"customer_round7","sender_name":"Round7测试客户","content":"你好,这是第7轮测试消息"}'
```
5. Verify GoChat response: `{"status":"success"}` (NOT `{"status":"ignored"}`)
6. Verify in DB:
```sql
SELECT id, content, sender_type, inbox_id, conversation_id
FROM messages WHERE inbox_id=3 ORDER BY id DESC LIMIT 5;
```
7. **Browser verification:** Navigate to Dashboard → verify new conversation appears
in conversation list with "Round7测试客户" and message preview "你好,这是第7轮测试消息"
8. Click the conversation → verify message renders in chat window
9. **Outbound test:** In the browser, click reply box, type "您好,有什么可以帮您?",
Ctrl+Enter to send
10. Verify FakeMessagePlatform received the outbound message:
```
curl http://127.0.0.1:9100/api/messages?inbox_identifier=fake_test_1
```
11. Verify the outbound message appears in the chat UI with timestamp "now"
12. **Real-time verification:** If another browser tab is open, verify the message
appears in that tab without manual refresh
### Phase 4c — BUG-D Regression: Channel Creation Page
1. Navigate to Settings → Inboxes → click "添加收件箱" (Add Inbox)
2. Verify the "Fake 测试平台" channel card is visible and NOT disabled (BUG-D)
3. Do NOT create a new inbox unless testing BUG-E (inbox_members)
### Phase 5 — Real-time WebSocket Feature Verification
1. Open a conversation, send a message → verify instant render with "now" timestamp
2. Poll backend logs for `ws: connection established` and `message.created` event
3. Verify presence/availability indicator shows status (not empty)
4. Monitor network 30s — `/cable` stable, no 401s, no reconnect storm
5. **Direct WS test:** Extract JWT via shell curl → connect with websocat/Python →
expect `{"type":"welcome"}` then `{"type":"ping",...}` every 5s
6. **Cross-tab real-time delivery:** Open 2nd tab, login, send message from tab 1 →
verify it appears in tab 2 without refresh
### Phase 6 — Redis Worker Observability (BUG-I Fix)
1. Trigger a job: send an agent reply via API or browser
2. Redis CLI:
```
redis-cli XINFO CONSUMERS gochat:jobs:high gochat-workers
```
3. Verify 5 consumers with unique names: `worker-<timestamp>-0` through `worker-<timestamp>-4`
4. Verify at least 1 consumer has a pending=0 after ACK (job consumed successfully)
### Phase 7 — Collect, Report, Teardown
1. Kill background dev servers + FakeMessagePlatform
2. Write report to `docs/qa/2026-07-10-qa-report-round7.md`
3. Delete previous round's report from `docs/` (round 6 report at `docs/QA_REPORT_2026-07-10_cdp_round6.md`)
4. Update `references/known-bugs.md` with round 7 statuses
---
## 3. Known Bug Re-Verification Matrix
Every bug in the registry must be re-verified. Mark FIXED / PERSISTING / REGRESSED
with evidence.
| Bug | Sev | Description | Round 6 Status | Round 7 Check |
|-----|-----|-------------|----------------|---------------|
| BUG-1 | P2 | Activity page route missing | FIXED | Sidebar "活动" → `/campaigns/live_chat` |
| BUG-2 | P3 | Bots sidebar link not navigating | FIXED | Settings → "机器人" → `/settings/agent-bots` |
| BUG-3/B | P4 | intlify empty key warnings | NOT TESTED | **Create a label** → check console |
| BUG-4 | P3 | Vue prop type mismatch on message ID | (assumed fixed) | Open conversation with messages → no prop warnings |
| BUG-5 | P1 | NotificationSetting handler not registered | (assumed fixed) | `curl notification_settings` → 200 |
| BUG-7 | P0 | Missing DB tables → 500 on 6 endpoints | FIXED | All 6 endpoints → 200 |
| BUG-A | P3 | Bot Reports sidebar nav from Reports | FIXED | Reports SLA → "机器人" → `/reports/bot` |
| BUG-C | P2 | Pre-chat Form tab null error | FIXED | Inbox settings → 预聊天表单 → no Vue error |
| BUG-D | P2 | ChannelItem isActive whitelist (fake) | NOT TESTED | Add Inbox page → Fake card NOT disabled |
| BUG-E | P3 | inbox_members not written | NOT TESTED | Create new inbox → verify inbox_members |
| BUG-F | P1 | Fake webhook JSONB @> on TEXT | NEW → **FIX PENDING VERIFY** | FakeMessagePlatform E2E (Phase 4b) |
| BUG-G | P3 | Vue Router history.state warning | NEW → **FIX PENDING VERIFY** | Activity page → console clean |
| BUG-H | P3 | Inbox list shows 1 of 2 | NEW → **FIX PENDING VERIFY** | Settings → Inboxes → 2 inboxes visible |
| BUG-I | P3 | Worker consumer name shared | NEW → **FIX PENDING VERIFY** | Redis XINFO → 5 consumers |
---
## 4. Console Warning Allowlist
Warnings in this list are acceptable and do NOT count as findings unless they
increase in frequency or appear in new contexts:
| Warning | Where | Sev |
|---------|-------|-----|
| `[DEPRECATED] The 'onClose' prop is deprecated` | Widget/UI components | P4 |
| `[DEPRECATED] <WootInput> has be deprecated` | Old WootInput components | P4 |
| `Lit is in dev mode` / `Multiple versions of Lit loaded` | Initial load | P4 |
| `[Vue Router warn]: Discarded invalid param(s) "page"` | Route nav | P4 |
| `SW registration failed (SecurityError)` | Vite dev server | P4 |
**BUG-G warning (`history.state seems to have been manually replaced`) should NOT
appear after the fix.** If it does, BUG-G is PERSISTING.
---
## 5. Network False-Positive: `cache_keys` Endpoint
`/api/v1/.../cache_keys` is called multiple times on page load. This is NOT an
infinite loop. Do not flag unless it continues repeating after the 10s settle window.
**BUG-H context:** The fix changes `cache_keys` response values from hardcoded
`"0000000000"` to real DB-derived keys. The frontend uses these to invalidate
IndexedDB cache. After the fix, navigating to Settings → Inboxes should trigger
a fresh API call (not cached) and return both inboxes.
---
## 6. Testing Principles (Non-Negotiable)
1. **Console warnings ARE errors** — zero tolerance for non-allowlisted warnings
2. **Watch for infinite network loops** — 10s monitor per page, >3 calls = finding
(except `cache_keys`)
3. **Navigate by clicking, never by URL** — login is the sole exception
4. **Capture transition errors** — reset buffer before leaving, read after arriving
5. **CRUD interaction where applicable** — not just "page loads"
6. **Use `chrome_devtools` MCP if `browser_*` tools lose context 2+ times** (round 5 lesson)
---
## 7. Success Criteria
Round 7 is a PASS if:
- ✅ All 28 pages tested via click navigation with per-page console/network checks
- ✅ BUG-F verified: FakeMessagePlatform E2E (inbound + outbound) works end-to-end
- ✅ BUG-G verified: Activity page has no `history.state` warning
- ✅ BUG-H verified: Settings → Inboxes shows both web_widget and fake inboxes
- ✅ BUG-I verified: Redis `XINFO CONSUMERS` shows 5 unique consumers
- ✅ WebSocket /cable connected and stable (30s, no reconnect storm)
- ✅ Direct WS test: welcome + ping frames received
- ✅ Cross-tab real-time delivery works
- ✅ All known bugs re-verified (FIXED / PERSISTING / REGRESSED status recorded)
- ✅ Zero new P0/P1 findings
- ✅ Report written to `docs/qa/2026-07-10-qa-report-round7.md`