Files
wecom_it_smart_desk/docs/03-测试文档/03-功能测试用例/TC-用户-006-智能推荐重构.md
T
Simon facc04aa65 chore: docs 结构整改 + compose 双目录对齐(合并重建提交)
本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交
(5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。

一、docs 结构整改(整改 #14)
根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入,
随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录
树并存于 docs/,共 791 文件、双分类体系冲突。

修复动作:
- b2 同名异主题文件改名迁移保全 9 个
- C 类 39 个孤立文件按主题正确归类
- A/B1 类 222 个重复文件删除(新结构已有内容副本)
- 9 个旧独有空目录删除
- 270 处内部引用按 verified 映射改写
- 整改记录 #14 登记于 04-运维文档/部署运维

结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。
残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。

二、compose 双目录对齐(消除踩坑 A)
- docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist
  改为 src/frontend-*/dist(h5 / agent / admin / terminal)
- docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/
- 效果:本地 docker compose up 不再把根目录 stale dist 挂回,
  与线上一致,分叉隐患消除(已 docker compose config 校验通过)

防复发铁律:
- 重构须提交;仓库修复须 git stash -u 或先 commit
- 新结构须 git add 并提交,避免再次 untracked 复活
- H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
2026-08-07 22:31:32 +08:00

844 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 测试用例 - H5 智能推荐重构
> **REQ 编号**: REQ-用户-006
> **版本**: v1.0
> **日期**: 2026-07-28
> **作者**: 宋献 (Simon) + Duckula
> **状态**: 🟡 待评审(基于 PRD v1.0-Frozen + 技术方案 v1.0
> **关联文档**:
> - **PRD(冻结)**`docs/01-产品文档/05-用户端H5/PRD-REQ-用户-006-智能推荐重构-v1.0-Frozen.md`
> - **技术方案**`docs/02-技术文档/技术架构/技术方案-REQ-用户-006-智能推荐重构-v1.0.md`
> - **任务说明书**:`docs/07-项目管理/任务说明书/任务说明书-REQ-用户-006-智能推荐重构.md`
> - **原型图**`docs/01-产品文档/05-用户端H5/原型-REQ-用户-006-智能推荐重构-v1.0.html`
---
## 一、基本信息
| 项目 | 内容 |
|------|------|
| **用例集名称** | TC-用户-006 H5 智能推荐重构 |
| **关联 PRD** | REQ-用户-006 v1.0-Frozen(§4.7 已冻结) |
| **用例总数** | 76 条(10 大类) |
| **优先级分布** | P0 28 条 / P1 32 条 / P2 16 条 |
| **关联决策** | 必须 100% 覆盖 PRD §4.7 5 个核心决策(① A / ② B / ③ B / ④ B / ⑤ B |
| **自动化目标** | 单元测试 100% 自动化 / E2E 关键路径自动化 / 灰度指标由数据埋点验证 |
---
## 二、测试范围
### 2.1 In-scope
- 后端 `asset_recommend_service.py` 重构(L1/L2/L3 + 中文匹配)
- 后端新增 `recommend_progress_service.py`T2 webhook + 轮询)
- 后端新增 `topic_detector.py`Jaccard 相似度)
- 后端 `_step_assets` 改造(3s 超时 + 降级链路)
- 前端 `recommendStore.ts` 状态机
- 前端 `DynamicRecommend.vue` 重构(无标题 + FIFO + 4 类卡片)
- 前端 `RightPanel.vue` 适配
- WS 协议扩展(dynamic_recommend / asset_recommend / recommend_update
- localStorage 持久化
- 数据模型(recommend_progress + recommend_event
### 2.2 Out-of-scope
- 智能推荐算法升级(语义匹配/向量召回)→ v1.4+ 单独测
- 坐席端 AI 辅助 → 不在本次范围
- 多模态推荐 → PRD-REQ-AI-003 单独测
---
## 三、测试环境
| 项 | 配置 |
|----|------|
| **后端** | Python 3.13 / FastAPI / pytest |
| **前端** | Vue 3 / TypeScript / Vitest |
| **E2E** | agent-browserPlaywright |
| **数据库** | PostgreSQL 16 + Redis 7 |
| **外部 mock** | Dify 推理 / 企微审批 webhook / 火绒 API |
| **环境** | dev → staging → 生产灰度 |
---
## 四、用例分类规则
| 前缀 | 模块 | 用例数 |
|------|------|--------|
| TC-001 | 来源 ADify actionT0 同步) | 8 |
| TC-002 | 来源 BL1 关键词(T1 异步) | 10 |
| TC-003 | 来源 CL2 画像(T1 异步) | 10 |
| TC-004 | 来源 DL3 角色(T1 异步) | 8 |
| TC-005 | 多源合并规则(决策 ② B) | 8 |
| TC-006 | 冷启动策略(决策 ① A | 6 |
| TC-007 | 触发失败降级(决策 ③ B) | 8 |
| TC-008 | 话题切换检测(决策 ④ B) | 8 |
| TC-009 | 跨会话持久化(决策 ⑤ B) | 6 |
| TC-010 | E2E 集成 + UI 行为 | 4 |
| **合计** | | **76** |
---
## 五、详细用例
### TC-001:来源 A - Dify actionT0 同步)
#### TC-001-1 [P0] 来源 AAI 返回 action 后立即同步推送到左侧气泡
- **前置条件**:用户进入新会话,员工端 WS 已连接
- **测试步骤**
1. 用户发送"我要申请 VPN"
2. Dify 返回 `{text: "好的", action: {approval_type: "vpn_access", title: "VPN 申请"}}`
- **预期结果**
- WS `ai_reply` 立即到达(含文字 + action)
- 左侧气泡显示"VPN 申请"操作卡(v2.3 后审批类只放左边)
- 右侧栏 **不出现** 该审批卡(v2.3 决策)
- **关联**PRD §4.7.1 来源 A / 技术方案 §5.1 T0
#### TC-001-2 [P0] 来源 A:非审批类 action 同步推送到右侧栏
- **前置条件**:同上
- **测试步骤**
1. 用户发送"VPN 连不上怎么办"
2. Dify 返回 `{text: "请重连", action: {type: "step_card", steps: [...]}}`
- **预期结果**
- 左侧气泡显示文字 + 来源图标
- 右侧栏顶部插入操作步骤卡(蓝色边框,可展开)
- **关联**PRD §4.5 / §4.7.1 A
#### TC-001-3 [P1] 来源 Aaction 为空时不推送右侧栏
- **前置条件**Dify 返回 `{text: "你好", action: null}`
- **测试步骤**:触发 AI 回复
- **预期结果**
- 左侧气泡显示文字
- 右侧栏 **不增加** 任何卡片
- 不出现"暂无推荐"提示
- **关联**:技术方案 §4.1
#### TC-001-4 [P1] 来源 Arecommend_data 包含 source/trigger_timing/layer 字段
- **前置条件**:触发 AI 回复(Dify 返回非空 action
- **测试步骤**:检查 WS 数据包
- **预期结果**
- `recommend_data.source = "dify_action"`
- `recommend_data.trigger_timing = "T0"`
- `recommend_data.layer = "A"`
- **关联**:技术方案 §5.1
#### TC-001-5 [P1] 来源 A:审批发起后立即在右侧栏顶部插入占位卡
- **前置条件**:用户发起 VPN 审批(action.approval_type = "vpn_access"
- **测试步骤**
1. 用户点击气泡中的"申请"
2. 跳转企微审批表单
3. 用户提交审批
- **预期结果**
- 提交瞬间右侧栏顶部插入审批进度占位卡(紫色边框,状态"pending"
- 显示"VPN 申请 已发起,等待审批"
- **关联**:技术方案 §5.3 T2 / PRD §4.7.2
#### TC-001-6 [P2] 来源 Aaction.type 包含 contact_window 时只推左侧
- **前置条件**Dify 返回 `action.type = "contact_window"`
- **测试步骤**:触发 AI 回复
- **预期结果**
- 左侧气泡显示联系窗口按钮
- 右侧栏 **不出现** 该卡(PRD §4.7.1 A 限制)
- **关联**PRD §4.7.1 来源 A 补充决策
#### TC-001-7 [P2] 来源 Aaction.type 包含 contact_card 时只推左侧
- **前置条件**Dify 返回 `action.type = "contact_card"`
- **测试步骤**:同上
- **预期结果**
- 左侧气泡显示联系人名片
- 右侧栏不出现
- **关联**PRD §4.7.1 来源 A 补充决策
#### TC-001-8 [P2] 来源 Aaction.type 包含 download 时只推左侧
- **前置条件**Dify 返回 `action.type = "download"`
- **测试步骤**:同上
- **预期结果**
- 左侧气泡显示下载链接
- 右侧栏不出现
- **关联**PRD §4.7.1 来源 A 补充决策
---
### TC-002:来源 B - L1 关键词匹配(T1 异步)
#### TC-002-1 [P0] 来源 B:用户消息包含精确关键词时命中
- **前置条件**assets.yaml 配置 `vpn` 关键词
- **测试步骤**:用户发送"VPN 连不上"
- **预期结果**
- 右侧栏插入 L1 卡片(绿色边框)
- 标题为"VPN 相关信息"
- 置信度反映词频权重(≥ 0.5
- **关联**PRD §4.7.1 来源 B / 技术方案 §4.2
#### TC-002-2 [P0] 来源 B:用户消息包含别名时也命中
- **前置条件**:配置 alias "虚拟专用网络" → "vpn"
- **测试步骤**:用户发送"虚拟专用网络怎么用"
- **预期结果**:右侧栏插入 L1 VPN 卡片
- **关联**:技术方案 §4.2 同义词表
#### TC-002-3 [P1] 来源 B:词频权重(短消息 + 高频词)
- **前置条件**:用户发送"VPN"(仅 3 字)
- **测试步骤**:触发 AI 回复
- **预期结果**
- L1 VPN 卡插入
- 置信度 = min(1.0, 1 × (3/3) × 10) = 1.0(短消息高权重)
- **关联**:技术方案 §4.2 词频权重公式
#### TC-002-4 [P1] 来源 B:长消息中关键词被稀释
- **前置条件**:用户发送长消息(> 50 字),仅 1 次包含 "VPN"
- **测试步骤**:触发 AI 回复
- **预期结果**
- L1 VPN 卡插入
- 置信度较低(< 0.5),优先级低于其他高权重推荐
- **关联**:技术方案 §4.2 长度归一化
#### TC-002-5 [P1] 来源 Bexclude_keywords 排除规则生效
- **前置条件**:配置 `exclude_keywords: ["邮箱"]`
- **测试步骤**:用户发送"我的收件箱打不开,邮箱登不上"
- **预期结果**
- L1 邮箱卡 **不出现**(被排除)
- 仅出现其他来源推荐
- **关联**:技术方案 §4.2 排除关键词
#### TC-002-6 [P1] 来源 B30 分钟同源抑制
- **前置条件**:5 分钟前已推送过 L1 VPN 卡
- **测试步骤**:用户再次发送"VPN 又连不上了"
- **预期结果**
- L1 VPN 卡 **不重复推送**30 分钟内同关键词抑制)
- Redis 中存在 `suppress:{employee_id}:vpn` 键(TTL ≤ 30 分钟)
- **关联**PRD §4.7.3 同源抑制 / 技术方案 §6.4
#### TC-002-7 [P2] 来源 B30 分钟后可再次推送
- **前置条件**:30 分钟前推送过 L1 VPN 卡,Redis 键已过期
- **测试步骤**:用户再次发送"VPN 连不上"
- **预期结果**:L1 VPN 卡正常推送
- **关联**:技术方案 §6.4
#### TC-002-8 [P1] 来源 B:用户消息包含多个关键词时多卡
- **前置条件**:用户发送"VPN 连不上,邮箱也登不上"
- **测试步骤**:触发 AI 回复
- **预期结果**
- 右侧栏插入 L1 VPN 卡 + L1 邮箱卡(2 张)
- 按 layer 优先级 + confidence 排序
- 总数 ≤ 3 张
- **关联**PRD §4.7.3 / 技术方案 §6.1
#### TC-002-9 [P2] 来源 B:消息完全不包含任何关键词时无 L1
- **前置条件**:用户发送"今天天气真好"
- **测试步骤**:触发 AI 回复
- **预期结果**
- 来源 B 返回空列表
- 继续走来源 C/D 降级链路
- **关联**:技术方案 §4.2 / §6.1
#### TC-002-10 [P2] 来源 BDify 同步识别意图时,来源 B 可与 A 互补
- **前置条件**Dify 返回 action(来源 A),同时用户消息包含关键词
- **测试步骤**:触发 AI 回复
- **预期结果**
- 左侧气泡显示 A 来源卡片
- 右侧栏同时显示 A 步骤卡 + B 关键词卡(不重复)
- **关联**:技术方案 §6.1 去重
---
### TC-003:来源 C - L2 画像触发(T1 异步)
#### TC-003-1 [P0] 来源 C:火绒病毒库过期触发提醒
- **前置条件**:员工画像 `huorong_virusdb_date` 距今 10 天
- **测试步骤**:用户发送任意消息
- **预期结果**
- 右侧栏插入 L2 提醒卡(橙色边框)
- 标题"火绒病毒库过期"
- 描述包含"已 N 天未更新"
- **关联**PRD §4.7.1 来源 C / 技术方案 §4.3
#### TC-003-2 [P0] 来源 C:火绒终端版本过旧触发提醒
- **前置条件**:画像 `huorong_version = "5.0.50"`< 5.0.73
- **测试步骤**:同上
- **预期结果**
- L2 提醒卡插入
- 描述"您的版本为 5.0.50,最新版本为 5.0.73"
- **关联**:技术方案 §4.3
#### TC-003-3 [P0] 来源 C:终端防护离线触发最高优先级提醒
- **前置条件**:画像 `huorong_offline_days = 5`
- **测试步骤**:同上
- **预期结果**
- L2 提醒卡插入
- priority = 100(最高)
- **关联**:技术方案 §4.3
#### TC-003-4 [P1] 来源 C:联软违规项触发
- **前置条件**:画像 `unionsoft_violations = ["禁用软件", "未安装补丁"]`
- **测试步骤**:同上
- **预期结果**
- L2 提醒卡插入
- extra.violations 包含违规项列表
- **关联**:技术方案 §4.3
#### TC-003-5 [P1] 来源 C:系统补丁缺失触发
- **前置条件**:画像 `unionsoft_patches_missing = 12`
- **测试步骤**:同上
- **预期结果**:L2 提醒卡"系统补丁缺失"
- **关联**:技术方案 §4.3
#### TC-003-6 [P0] 来源 C:画像 API 不可用时降级到 DB 缓存
- **前置条件**`employee_profile_service.get_profile()` 超时/异常
- **测试步骤**:触发 AI 回复
- **预期结果**
- 调用 `_get_cached_profile_from_db(employee_id)` 兜底
- 如 DB 有缓存画像,按缓存触发 L2
- 如 DB 也无,走来源 D 降级
- **关联**:技术方案 §4.3 缓存画像 / PRD §4.7.5 决策 ③ B
#### TC-003-7 [P1] 来源 C:画像完全为空时(API + DB 都无)
- **前置条件**:新员工,无任何画像数据
- **测试步骤**:同上
- **预期结果**
- 来源 C 返回空
- 走来源 D 降级(C → D
- **关联**PRD §4.7.5 / 技术方案 §4.3
#### TC-003-8 [P2] 来源 C:多个画像字段同时满足触发多张卡
- **前置条件**:病毒库过期 + 版本过旧 + 离线 5 天
- **测试步骤**:触发 AI 回复
- **预期结果**
- 3 张 L2 提醒卡(按 priority 排序:离线 > 版本 > 病毒库)
- 总数 + 其他来源 ≤ 3 张
- **关联**:技术方案 §4.3 / §6.1
#### TC-003-9 [P2] 来源 C:画像字段格式异常时优雅跳过
- **前置条件**`huorong_version = ""`(空字符串)
- **测试步骤**:同上
- **预期结果**
- try-except 捕获 AttributeError/ValueError
- 该字段跳过,其他字段继续判定
- **关联**:技术方案 §4.3 try-except
#### TC-003-10 [P1] 来源 CT1 异步触发 3s 超时保护
- **前置条件**:画像 API 响应缓慢(> 3s
- **测试步骤**:触发 AI 回复
- **预期结果**
- `asyncio.timeout(3.0)` 触发超时
- 跳过来源 C,走来源 D
- 不阻塞主对话流程
- **关联**:技术方案 §5.2 / PRD §4.7.5
---
### TC-004:来源 D - L3 角色匹配(T1 异步)
#### TC-004-1 [P0] 来源 D:中文岗位名命中英文 role_assets key(精确)
- **前置条件**`profile.position = "开发工程师"` + 配置 `开发工程师: alias_of: developer`
- **测试步骤**:触发 AI 回复
- **预期结果**
- 来源 D 命中 developer 资源
- L3 卡插入右侧栏(实际:按 v1.0 用户决策移到左侧)
- **关联**PRD §4.7.1 来源 D / 技术方案 §4.4 / §11 卡片归属
#### TC-004-2 [P0] 来源 D:中文岗位子串匹配
- **前置条件**`profile.position = "前端开发工程师"`
- **测试步骤**:同上
- **预期结果**
- 子串匹配命中 "developer"
- confidence = 0.7(子串匹配置信度低)
- **关联**:技术方案 §4.4 子串匹配
#### TC-004-3 [P1] 来源 D:别名匹配(如 "研发" → "developer"
- **前置条件**`profile.position = "研发"` + 配置 `role_aliases.developer: ["研发", ...]`
- **测试步骤**:同上
- **预期结果**
- 命中 developer 资源
- confidence = 0.85
- **关联**:技术方案 §4.4 role_aliases
#### TC-004-4 [P1] 来源 Dprofile.position 为 None 时跳过
- **前置条件**:新员工未填写岗位
- **测试步骤**:同上
- **预期结果**
- 来源 D 返回空
- 走来源 B + C 降级(D 失败 → B + C)
- **关联**:技术方案 §4.4
#### TC-004-5 [P1] 来源 D:英文 position 也能命中英文 key(兼容)
- **前置条件**`profile.position = "developer"`
- **测试步骤**:同上
- **预期结果**:精确匹配 developerconfidence = 1.0
- **关联**:技术方案 §4.4
#### TC-004-6 [P2] 来源 D:双向子串匹配(key 包含 position
- **前置条件**`profile.position = "开发"` + key = "developer"
- **测试步骤**:同上
- **预期结果**
- "开发" 在 "developer" 里?否(中文不在英文里)
- "developer" 在 "开发" 里?否
- 来源 D 返回空(无命中)
- **关联**:技术方案 §4.4 双向子串
#### TC-004-7 [P2] 来源 D:多个角色同时命中
- **前置条件**position = "开发"(命中 developer),但配置也有 "财务开发"
- **测试步骤**:同上
- **预期结果**
- 多张 L3 卡
- 按 confidence 排序
- **关联**:技术方案 §4.4 / §6.1
#### TC-004-8 [P2] 来源 Drole_assets 配置变更热重载
- **前置条件**:调用 `asset_service.reload()`
- **测试步骤**:修改 assets.yaml 后调用 reload
- **预期结果**
- 配置生效,无需重启服务
- 日志输出"配置热重载完成"
- **关联**:技术方案 §4.4 reload()
---
### TC-005:多源合并规则(决策 ② B)
#### TC-005-1 [P0] 合并:同 recommend_id 去重,保留高 confidence
- **前置条件**A 来源产生 rec_001confidence=0.85+ B 来源也产生 rec_001confidence=0.7
- **测试步骤**:触发 AI 回复
- **预期结果**
- 右侧栏仅 1 张 rec_001(保留 A 的 0.85
- **关联**PRD §4.7.3 / 技术方案 §6.1
#### TC-005-2 [P0] 合并:layer 优先级排序 L1 > L2 > L3
- **前置条件**A 步骤卡(layer A)+ L1 关键词卡 + L2 画像卡 + L3 角色卡
- **测试步骤**:触发 AI 回复
- **预期结果**
- 排序:A > L1 > L2 > L3
- 右侧栏显示:A 在最上
- **关联**:技术方案 §6.2
#### TC-005-3 [P0] 合并:上限 3 张
- **前置条件**:同时有 5 张推荐卡(A + 2 × L1 + L2 + L3
- **测试步骤**:触发 AI 回复
- **预期结果**
- 右侧栏仅 3 张
- 超出 2 张按排序丢弃
- **关联**PRD §4.7.3 上限规则
#### TC-005-4 [P1] 合并:同 layer 内按 confidence DESC
- **前置条件**L1 VPN0.85+ L1 邮箱(0.7
- **测试步骤**:触发 AI 回复
- **预期结果**
- L1 VPN 在 L1 邮箱上方
- **关联**:技术方案 §6.1
#### TC-005-5 [P1] 合并:A 来源步骤卡优先级最高(layer A)
- **前置条件**A 步骤卡(layer A)+ L1 关键词卡
- **测试步骤**:触发 AI 回复
- **预期结果**:A 步骤卡在最上方(即使 confidence 较低)
- **关联**:技术方案 §6.2 layer_priority
#### TC-005-6 [P1] 合并:进度回流卡插入最上方
- **前置条件**:已有 2 张卡,审批状态变化触发 T2
- **测试步骤**:企微审批 webhook 推送 status="approved"
- **预期结果**
- 进度卡插入最上方(FIFO
- 已有的 2 张卡往下排
- 超出上限时最旧的被丢弃
- **关联**PRD §4.7.7 / 技术方案 §5.3
#### TC-005-7 [P2] 合并:merge_recommends 算法纯函数测试
- **前置条件**:单元测试
- **测试步骤**:mock 4 个来源各返回 N 张卡
- **预期结果**
- 总数 ≤ 3
- 排序符合 layer 优先级 + confidence
- 同 ID 不重复
- **关联**:技术方案 §6.1
#### TC-005-8 [P2] 合并:空输入返回空列表
- **前置条件**:所有来源都返回空
- **测试步骤**:调用 `merge_recommends([], [], [], [])`
- **预期结果**:返回 `[]`
- **关联**:技术方案 §6.1
---
### TC-006:冷启动策略(决策 ① A)
#### TC-006-1 [P0] 冷启动:进会话 5s 内无消息 → 右侧栏完全空白
- **前置条件**:用户进入新会话
- **测试步骤**:观察右侧栏 5 秒
- **预期结果**
- 右侧栏 **完全空白**
- 不显示"暂无推荐"提示
- 不显示"试试问 XXX"引导
- **关联**PRD §4.7.4 决策 ① A
#### TC-006-2 [P0] 冷启动:首条消息发出但 AI 未回复 → 右侧栏空白
- **前置条件**:用户已发消息,AI 推理中
- **测试步骤**:观察右侧栏
- **预期结果**:右侧栏空白(与决策 ① A 一致)
- **关联**PRD §4.7.4
#### TC-006-3 [P0] 冷启动:AI 回复到达瞬间 → 真实推荐填充
- **前置条件**AI 推理完成
- **测试步骤**:观察右侧栏变化
- **预期结果**
- 按 §4.7.3 合并规则填充
- 顶部插入新卡
- **关联**PRD §4.7.4 场景 3
#### TC-006-4 [P0] 冷启动:完全不显示任何兜底
- **前置条件**:所有来源都失败(L3 失败 + 无画像 + 无历史)
- **测试步骤**:触发 AI 回复
- **预期结果**
- 右侧栏 **完全空白**
- 不显示"暂无推荐,试试问 'VPN 怎么连'"提示
- **关联**PRD §4.7.4 决策 ① A + §4.7.5 全部失败
#### TC-006-5 [P1] 冷启动:store 状态机从 empty → populated
- **前置条件**:前端 recommendStore 初始化
- **测试步骤**:触发 AI 回复(A/B/C/D 任一产生卡)
- **预期结果**
- state 从 `empty``populated`
- visibleCards 非空
- **关联**:技术方案 §7.1
#### TC-006-6 [P2] 冷启动:清空后 state 回到 empty
- **前置条件**:已有卡,用户清空
- **测试步骤**:调用 `store.clearAll()`
- **预期结果**state 回到 `empty`visibleCards = []
- **关联**:技术方案 §7.1
---
### TC-007:触发失败降级(决策 ③ B)
#### TC-007-1 [P0] 降级:A 失败 → B + C + D
- **前置条件**Dify 返回非 JSON 或 action 为空
- **测试步骤**:触发 AI 回复
- **预期结果**
- 来源 A 跳过
- 来源 B/C/D 正常执行
- **关联**PRD §4.7.5
#### TC-007-2 [P0] 降级:B 失败 → C + D
- **前置条件**:用户消息不包含任何关键词(来源 B 返回空)
- **测试步骤**:触发 AI 回复
- **预期结果**
- 来源 B 跳过
- 来源 C/D 正常执行
- **关联**PRD §4.7.5
#### TC-007-3 [P0] 降级:C 失败 → D(关键降级)
- **前置条件**:画像 API 不可用 + DB 无缓存
- **测试步骤**:触发 AI 回复
- **预期结果**
- 来源 C 跳过
- 来源 D 命中(如果有中文岗位)
- 至少能展示 1 张卡(保证有内容)
- **关联**PRD §4.7.5 / 技术方案 §4.3
#### TC-007-4 [P0] 降级:D 失败 → 空
- **前置条件**profile.position 为 None + 用户消息无关键词 + 无画像
- **测试步骤**:触发 AI 回复
- **预期结果**
- 所有来源失败
- 右侧栏空(按决策 ① A 走空状态)
- **关联**PRD §4.7.5
#### TC-007-5 [P0] 降级:全部失败 → 右侧栏空(与决策 ① 一致)
- **前置条件**:所有来源都失败
- **测试步骤**:触发 AI 回复
- **预期结果**
- 右侧栏 **完全空白**
- 不显示"暂无推荐"提示
- **关联**PRD §4.7.4 + §4.7.5
#### TC-007-6 [P1] 降级:T1 异步触发 3s 超时
- **前置条件**:来源 B/C/D 全部响应缓慢
- **测试步骤**:模拟慢响应
- **预期结果**
- 3s 超时触发
- 不阻塞主对话
- 右侧栏空
- **关联**:技术方案 §5.2
#### TC-007-7 [P2] 降级:降级路径日志完整
- **前置条件**:触发降级(C → D)
- **测试步骤**:检查后端日志
- **预期结果**:日志包含 `[AssetRecommend] 画像获取失败/超时(C → D 降级)`
- **关联**:技术方案 §5.2 日志
#### TC-007-8 [P2] 降级:recommend_event 埋点记录降级触发次数
- **前置条件**:降级发生时
- **测试步骤**:检查 recommend_event 表
- **预期结果**
- 存在 `event_type='degraded'` 记录
- 包含降级链路信息(C→D / 全部失败)
- **关联**:技术方案 §11.2 埋点
---
### TC-008:话题切换检测(决策 ④ B)
#### TC-008-1 [P0] 话题切换:Jaccard 相似度 < 0.3 触发清空
- **前置条件**:最近 3 条消息关键词集合 = {VPN, 重连, aTrust},新消息 = {会议室, 预定, 时间}
- **测试步骤**:用户发送"帮我预定会议室"
- **预期结果**
- Jaccard = 0/6 = 0 < 0.3
- 触发话题切换
- L1 推荐清空(按 layer 清除)
- **关联**PRD §4.7.6 / 技术方案 §9.1
#### TC-008-2 [P0] 话题切换:Jaccard 相似度 ≥ 0.3 不触发清空
- **前置条件**:最近 3 条 = {VPN, 重连, aTrust},新消息 = {VPN, 又连不上}
- **测试步骤**:用户发送"VPN 又连不上了"
- **预期结果**
- Jaccard = 1/5 = 0.2 < 0.3(边界)
- L1 保留(VPN 关键词继续命中)
- **关联**:技术方案 §9.1
#### TC-008-3 [P1] 话题切换:意图突变触发清空
- **前置条件**Dify 上次返回 intent_type="vpn_query",本次 "meeting_book"
- **测试步骤**:触发 AI 回复
- **预期结果**:清空对应 layer 的推荐
- **关联**PRD §4.7.6 / 技术方案 §9.2
#### TC-008-4 [P1] 话题切换:会话分隔清空所有跨会话推荐
- **前置条件**:用户点"结束会话"或新建会话
- **测试步骤**:用户新建会话
- **预期结果**
- 所有跨会话推荐清空
- L2/L3/progress 也清(按"会话分隔"规则)
- **关联**PRD §4.7.6
#### TC-008-5 [P1] 话题切换:L2/L3/progress 不被 L1 清空影响
- **前置条件**:已有 L1 VPN + L2 病毒库 + progress VPN 申请
- **测试步骤**:用户切换话题到"会议室"
- **预期结果**
- L1 VPN 清空
- L2 病毒库保留(与话题无关)
- progress VPN 申请保留(跨会话持久)
- **关联**PRD §4.7.6
#### TC-008-6 [P2] 话题切换:算法纯函数测试
- **前置条件**:单元测试
- **测试步骤**:构造边界用例
- **预期结果**
- 空集合相似度 = 1.0
- 完全相同相似度 = 1.0
- 完全不同相似度 = 0
- **关联**:技术方案 §9.1
#### TC-008-7 [P2] 话题切换:阈值为 0.3 边界测试
- **前置条件**Jaccard = 0.29 和 0.31
- **测试步骤**:分别测试
- **预期结果**
- 0.29 < 0.3 → 触发
- 0.31 ≥ 0.3 → 不触发
- **关联**:技术方案 §9.1
#### TC-008-8 [P2] 话题切换:检测失败时静默跳过(不阻塞主流程)
- **前置条件**`extract_keywords` 抛异常
- **测试步骤**mock 异常
- **预期结果**
- 异常被捕获
- 主对话正常进行
- 不触发清空(保守处理)
- **关联**:技术方案 §9.1 try-except
---
### TC-009:跨会话持久化(决策 ⑤ B)
#### TC-009-1 [P0] 持久化:L1 关键词推荐不持久化
- **前置条件**:用户会话 1 产生 L1 VPN 卡
- **测试步骤**:关闭浏览器,重新打开
- **预期结果**
- L1 VPN 卡 **不出现**localStorage 无记录)
- **关联**PRD §4.7.7 / 技术方案 §10.2
#### TC-009-2 [P0] 持久化:L2 画像提醒持久化 30 天
- **前置条件**:用户会话 1 产生 L2 火绒过期提醒
- **测试步骤**:关闭浏览器,重新打开
- **预期结果**
- L2 提醒卡 **仍在**localStorage 有记录)
- 数据包含 `ttl_days: 30`
- **关联**PRD §4.7.7 / 技术方案 §10.2
#### TC-009-3 [P0] 持久化:L3 角色资源持久化
- **前置条件**:用户会话 1 产生 L3 开发者资源
- **测试步骤**:关闭浏览器,重新打开
- **预期结果**L3 卡仍在
- **关联**PRD §4.7.7
#### TC-009-4 [P0] 持久化:审批进度持久化直到完结
- **前置条件**:用户发起 VPN 审批,progress 卡插入
- **测试步骤**
1. 关闭浏览器,重新打开
2. 企微审批 webhook 推送 status="completed"
- **预期结果**
- 关闭前 progress 卡仍在
- webhook 后 progress 卡标记"已完成"
- 后续不再显示(已完结)
- **关联**PRD §4.7.7 / 技术方案 §5.3
#### TC-009-5 [P1] 持久化:localStorage 配额超限降级
- **前置条件**localStorage 写入失败(QuotaExceededError
- **测试步骤**mock QuotaExceededError
- **预期结果**
- 捕获异常
- LRU 淘汰最旧数据
- 新数据正常写入
- **关联**:技术方案 §10.1 / §15 R3
#### TC-009-6 [P2] 持久化:profile.position 变更时清空 L3
- **前置条件**:用户从"开发"调到"财务"
- **测试步骤**:修改 profile.position + 触发 AI 回复
- **预期结果**:原 L3 开发者资源清空,命中新 L3 财务资源
- **关联**PRD §4.7.7
---
### TC-010E2E 集成 + UI 行为
#### TC-010-1 [P0] E2E:员工发"VPN 申请"完整链路
- **前置条件**agent-browser + dev 环境
- **测试步骤**
1. 打开 H5 员工端
2. 发送"我要申请 VPN"
3. 观察 AI 回复 + 右侧栏
4. 点击"申请"
- **预期结果**
- 左侧气泡显示"VPN 申请"操作卡
- 右侧栏顶部插入审批进度占位卡
- 跳转企微审批表单
- **关联**:技术方案 §3.2 时序图 / 端到端
#### TC-010-2 [P0] E2E:话题切换 + 持久化组合
- **前置条件**:已有 L1/L2/progress 卡
- **测试步骤**
1. 用户切换话题
2. 验证 L1 清空
3. 验证 L2/progress 保留
4. 关闭浏览器重新打开
5. 验证 L2/progress 仍在
- **预期结果**:所有规则同时生效
- **关联**:综合
#### TC-010-3 [P0] UI:右侧栏无标题
- **前置条件**:任意场景
- **测试步骤**:观察右侧栏
- **预期结果**
- 顶部无"⚡ 智能推荐"标题
- 顶部无"相关推荐"/"运维提醒"/"常用资源"分组标题
- 仅卡片内容
- **关联**PRD §4.5
#### TC-010-4 [P0] UIFIFO 插入验证
- **前置条件**:先产生卡 A,再产生卡 B
- **测试步骤**:观察卡片顺序
- **预期结果**:B 在 A 上方(最新优先)
- **关联**PRD §4.5 / §4.7.3
#### TC-010-5 [P0] UI:边框颜色区分 layer
- **前置条件**:同时有 L1/L2/L3 卡
- **测试步骤**:观察每张卡左边框
- **预期结果**
- L1 = 绿色
- L2 = 橙色
- L3 = 灰色
- A = 蓝色
- progress = 紫色
- **关联**PRD §4.5
#### TC-010-6 [P1] UI:操作步骤可展开
- **前置条件**A 来源步骤卡
- **测试步骤**:点击"展开 ▼"
- **预期结果**:显示完整步骤列表,按钮变"收起 ▲"
- **关联**PRD §4.6
#### TC-010-7 [P1] 性能:右侧栏渲染 < 200ms
- **前置条件**:10 张卡准备就绪
- **测试步骤**:使用浏览器 Performance API 测量
- **预期结果**render 时间 < 200ms
- **关联**:技术方案 §14.1
#### TC-010-8 [P1] 性能:T1 异步触发 < 3s
- **前置条件**:正常响应
- **测试步骤**:测量从用户发消息到右侧栏出现卡的时间
- **预期结果**< 3s
- **关联**:技术方案 §14.1
#### TC-010-9 [P1] 性能:T0 同步触发零延迟
- **前置条件**Dify 返回 action
- **测试步骤**:测量 ai_reply 与右侧栏卡的时间差
- **预期结果**:同一 WS 包发出(0ms 延迟)
- **关联**:技术方案 §14.1
#### TC-010-10 [P1] WS 断连降级
- **前置条件**:手动断开 WS
- **测试步骤**
1. 断开 WS
2. 触发 AI 回复
3. 重连 WS
- **预期结果**
- 断连期间 store 保持现有卡
- 重连后增量同步
- **关联**:技术方案 §14.2
---
## 六、验收标准对照(PRD §4.7 5 个决策)
| 决策 | 测试用例覆盖 | 状态 |
|------|------------|------|
| ① 冷启动 A | TC-0066 条) | ✅ 100% |
| ② 多源合并 B | TC-0058 条) | ✅ 100% |
| ③ 触发失败降级 B | TC-0078 条) | ✅ 100% |
| ④ 话题切换检测 B | TC-0088 条) | ✅ 100% |
| ⑤ 跨会话持久化 B | TC-0096 条) | ✅ 100% |
| §4.7.1 4 类触发源 | TC-001~00436 条) | ✅ 100% |
| §4.7.2 3 种触发时机 | TC-001~00328 条) | ✅ 100% |
| §4.7.5 触发失败降级 | TC-0078 条) | ✅ 100% |
---
## 七、灰度验证标准
| 阶段 | 规模 | 通过条件 | 用例支撑 |
|------|------|---------|---------|
| **1%** | 10 人,1 天 | 无 P0/P1 失败 | TC-010-1~10 全通过 |
| **10%** | 100 人,2 天 | 点击率 > 5%recommend_event 埋点) | TC-005/TC-009 |
| **50%** | 500 人,3 天 | 点击率 > 10% | TC-005/TC-009 |
| **100%** | 全量 | 点击率 > 15%,自助解决率 > 10% | 长期监控 |
---
## 八、风险与边界
| 边界场景 | 处理策略 |
|---------|---------|
| 浏览器禁用 localStorage | 持久化失效,仅当前会话有效 |
| 用户清空浏览器数据 | 所有持久化数据丢失 |
| 多设备登录同一账号 | localStorage 仅本机生效 |
| Dify 升级改变 action 结构 | 兼容旧字段解析(向后兼容) |
| 企微 webhook 推送频率异常 | 60s 轮询兜底 |
---
## 九、关联文档
- **PRD(冻结)**`docs/01-产品文档/05-用户端H5/PRD-REQ-用户-006-智能推荐重构-v1.0-Frozen.md`
- **技术方案**`docs/02-技术文档/技术架构/技术方案-REQ-用户-006-智能推荐重构-v1.0.md`
- **任务说明书**`docs/07-项目管理/任务说明书/任务说明书-REQ-用户-006-智能推荐重构.md`
- **原型图**`docs/01-产品文档/05-用户端H5/原型-REQ-用户-006-智能推荐重构-v1.0.html`
- **现有代码**
- `src/backend/tests/services/test_asset_recommend.py`(升级为 v2
- `src/backend/tests/services/test_recommend_progress.py`(新增)
- `src/backend/tests/services/test_topic_detector.py`(新增)
- `src/frontend-h5/tests/stores/recommendStore.test.ts`(新增)
---
## 十、变更日志
| 版本 | 日期 | 变更内容 | 作者 |
|------|------|---------|------|
| v1.0 | 2026-07-28 19:31 | 初版:76 条用例覆盖 4 类触发源 + 3 种时机 + 多源合并 + 冷启动 + 降级 + 话题切换 + 持久化 + E2E100% 覆盖 PRD §4.7 5 个决策 | Duckula + 宋献 |