Files
wecom_it_smart_desk/docs/03-测试文档/03-功能测试用例/TC-REQ-通用-005-选项选择持久化-v1.0.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

31 KiB
Raw Blame History

测试用例 - 选项选择持久化

REQ 编号: REQ-通用-005 版本: v1.0 日期: 2026-07-29 作者: 严过关(QA 状态: 编写完成(35 用例,待工程师静态审查 + AST 后给出最终结论) 关联文档:

  • PRDdocs/01-产品文档/00-产品规划/PRD-REQ-通用-005-选项选择持久化-v1.0.md
  • 技术方案:docs/02-技术文档/技术架构/技术方案-REQ-通用-005-选项选择持久化-v1.0.md
  • 代码真相:src/backend/app/api/ws.py + src/backend/app/api/messages.py + src/backend/app/utils/sensitive.py + src/backend/app/tasks/h5_ai_task.py + src/backend/app/services/ai_service.py + src/frontend-agent/src/components/chat/MessageBubble.vue + src/frontend-h5/src/stores/conversation.ts

一、用例汇总

类别 用例数 覆盖重点 关联 PRD
A. 功能验收(AC-01~AC-10 10 PRD §5 验收标准 §5
B. 异常场景 8 历史 UUID 复用 / 多 worker 并发 / Dify 超时 / WS 失败 / 缺字段降级 / 跨卡同名 / 多题最新 / 不更新旧行 §6 + 工程师报告 §八
C. 敏感词边界 5 16 位 / 18 位 / 短值 / 中文边界 / 不动业务键 §4.8
D. H5 UUID 生命周期 3 首次生成 / 重试复用 / 主动重选 §4.4 + §4.5
E. 性能压测 2 20 次并发 / 1000 条 latest 计算 技术方案 §9
F. Dify 集成 3 5 字段注入 / 普通文本无 inputs / 代理 fallback 不拼原值 §4.6 + 技术方案 §5
G. 转人工快照 2 每题最新 / 双处注入(tags + conversation_updated.data §4.7 + 技术方案 §6
H. 回归兼容 2 普通文本链路 / ai_structured + recommend_event 技术方案 §9 回归
总计 35 - -

优先级分布P0 = 23 条 / P1 = 9 条 / P2 = 3 条


二、功能测试用例

2.A 功能验收(直接对应 PRD §5 AC-01~AC-10

TC-通用-005-001

字段 内容
用例编号 TC-通用-005-001
用例标题 首次点选后 messages 表新增 1 行 option_select6 字段完整
关联 AC AC-01
前置条件 1) H5 已建立 WS 连接;2) AI 已发出 ai_structured 含 options3) messages 表初始无 option_select
测试步骤 1. 员工点击"网络中断"选项
2. H5 发送 6 字段 option_select
3. 等待 200ms
预期结果 1) messages 表新增 1 行:msg_type="option_select"sender_type="employee"2) extra_data 字段含 option_value/selected_from_message_id/client_msg_id/question_id/option_id + option_label_original3) content 为原始 label4) GET /conversations/{id}/messages 返回该行
优先级 P0
自动化建议 test_handle_option_select_persists_first_message
静态审查锚点 ws.py:374-385(构造 Message + ws.py:365-373extra_data 5+1 字段)

TC-通用-005-002

字段 内容
用例编号 TC-通用-005-002
用例标题 同一选项重选两次,形成两行 option_select
关联 AC AC-02
前置条件 1) 已完成 TC-001 的首次点选;2) 5 秒后再点同选项
测试步骤 1. 员工再次点击同选项(新 UUID)
2. 等待 200ms
预期结果 1) messages 表累计 2 行 option_selectmessage_id 互不相同;2) 第一行不被覆盖;3) REST 历史按时间正序返回两行
优先级 P0
自动化建议 test_two_reselects_persist_two_rows
静态审查锚点 ws.py:386db.add + 不存在 UPDATE 旧行的 SQL

TC-通用-005-003

字段 内容
用例编号 TC-通用-005-003
用例标题 坐席在线时收到标准 new_message 事件
关联 AC AC-03
前置条件 1) 坐席 WebSocket 已连接;2) H5 发起点选
测试步骤 1. 抓取坐席 WS 收到的消息;2. 校验 payload
预期结果 1) 收到 type=="new_message"2) data.msg_type=="option_select"3) data.message_id 与 DB 行一致;4) data.content 为 mask 后文本
优先级 P0
自动化建议 test_ws_broadcasts_new_message_type
静态审查锚点 ws.py:407-421broadcast payload

TC-通用-005-004

字段 内容
用例编号 TC-通用-005-004
用例标题 坐席断线重连后,REST 历史可见相同 message_id
关联 AC AC-03
前置条件 1) 已完成 TC-001 的首次点选;2) 坐席 WS 断开;3) 重新调用 GET 历史接口
测试步骤 1. 调用 GET /api/conversations/{id}/messages;2. 在返回列表中查找该 option_select
预期结果 1) 找到 1 条 msg_type=="option_select"2) message_id 与 TC-001 落库 ID 完全一致;3) content 为 mask 后文本
优先级 P0
自动化建议 test_rest_history_returns_same_message_id
静态审查锚点 messages.py:158-173REST mask 分支)

TC-通用-005-005

字段 内容
用例编号 TC-通用-005-005
用例标题 坐席 MessageBubble 渲染"✓ 选项"灰色徽标
关联 AC AC-04
前置条件 1) 坐席前端加载会话;2) REST 返回 1 条 option_select
测试步骤 1. 渲染 MessageBubble2. 检查 DOM
预期结果 1) 渲染 option-select-badge 节点;2) 文案为 ✓ {content}3) 默认不带 --latest 类(灰色)
优先级 P0
自动化建议 MessageBubble.spec.ts → option_select renders badge
静态审查锚点 MessageBubble.vue:84-94option_select 模板分支)

TC-通用-005-006

字段 内容
用例编号 TC-通用-005-006
用例标题 同 question_id 仅最新一条高亮,旧记录仍可见
关联 AC AC-04
前置条件 1) 同一 question_id 有 2 条 option_select(不同 message_id
测试步骤 1. 渲染该 question 的所有 option_select 徽标;2. 检查 --latest 类归属
预期结果 1) 仅 1 个徽标带 option-select-badge--latest 与"最新"标签;2) 旧徽标仍显示但无高亮;3) 排序稳定(按 created_at + id 倒序)
优先级 P0
自动化建议 MessageBubble.spec.ts → only latest highlighted
静态审查锚点 MessageBubble.vue:477-483isLatestSelection + 386-403latestSelectionPerQuestion

TC-通用-005-007

字段 内容
用例编号 TC-通用-005-007
用例标题 H5 首次点选生成合法 UUID v4
关联 AC AC-05
前置条件 1) H5 加载完成;2) pendingOptionSelect 为 null
测试步骤 1. 员工首次点击选项;2. 抓取 WS 发送的 data.client_msg_id
预期结果 1) client_msg_id 符合 UUID v4 格式(8-4-4-4-12,第 13 位为 4);2) 6 字段全部存在;3) selected_from_message_id 与 AI 消息 ID 一致
优先级 P0
自动化建议 conversation.spec.ts → first click generates UUID
静态审查锚点 conversation.ts:1567safeRandomUUID 调用) + 1571-15796 字段 payload

TC-通用-005-008

字段 内容
用例编号 TC-通用-005-008
用例标题 5 秒内同 client_msg_id 重发 3 次,仅落库/广播/Dify 各 1 次
关联 AC AC-06
前置条件 1) H5 已首次点选;2) WS 偶发重连
测试步骤 1. 在 5 秒窗口内用同一 client_msg_id 重发 3 次;2. 统计落库次数、广播次数、Dify 调用次数
预期结果 1) messages 表仅 +1 行;2) ws_manager.broadcast 仅 1 次;3) process_h5_ai_reply 仅 1 次;4) 后续 2 次返回首次 message 不报错
优先级 P0
自动化建议 test_idempotency_three_repeats
静态审查锚点 ws.py:316-3365 秒窗口幂等查询 + return + ws.py:300-313advisory lock

TC-通用-005-009

字段 内容
用例编号 TC-通用-005-009
用例标题 Dify 收到 feedback_type=option_select 及题目/选项字段
关联 AC AC-07
前置条件 1) H5 发起点选;2) Dify Workflow 已声明 5 个 input variables
测试步骤 1. mock 拦截 Dify /v1/chat-messages 请求;2. 解析 payload.inputs
预期结果 1) payload.inputs.feedback_type=="option_select"2) 字段含 question_idoption_idoption_valueoption_labelmask 后);3) query 字段为 option_label 原值;4) 不影响现有 user/conversation_id
优先级 P0
自动化建议 test_dify_inputs_contains_feedback_context
静态审查锚点 ws.py:433-439feedback_context dict + ai_service.py:279inputs 透传)

TC-通用-005-010

字段 内容
用例编号 TC-通用-005-010
用例标题 员工选择后立即转人工,坐席上下文含各 question_id 最新选择
关联 AC AC-08
前置条件 1) 员工已对 2 个不同 question_id 各选 1 次;2) Dify 返回 diagnosis_stage=escalating
测试步骤 1. 等待 Dify 触发转人工;2. 坐席 WS 抓取 conversation_updated3. 校验 data.selected_options
预期结果 1) conversation_updated.data.selected_options 是 list2) 每项含 question_id/option_id/label(message_id/selected_at)3) 每题仅 1 条;4) label 为 mask 后文本;5) conversation.tags.selected_options 同步写入
优先级 P0
自动化建议 test_snapshot_each_question_latest
静态审查锚点 h5_ai_task.py:253-318_build_selected_options_snapshot + h5_ai_task.py:620-628tags 注入) + h5_ai_task.py:743-755conversation_updated.data 注入)

TC-通用-005-011

字段 内容
用例编号 TC-通用-005-011
用例标题 选项含账号/身份证时,中间四位为 ****DB 原值不变
关联 AC AC-09
前置条件 1) 选项 label 含 16 位数字串
测试步骤 1. H5 点选;2. 抓 WS 广播、REST 历史、Dify inputs、转人工快照;3. 直查 DB
预期结果 1) WS/REST/Dify/快照中均为 前4****后N2) DB messages.content 为原值;3) question_id/option_id/client_msg_id 不被 mask
优先级 P0
自动化建议 test_mask_preserves_db_value
静态审查锚点 sensitive.py:38-66mask_sensitive_text + ws.py:397,415mask 应用) + messages.py:165mask 应用)

TC-通用-005-012

字段 内容
用例编号 TC-通用-005-012
用例标题 全链路:AI 发选项→员工点选→坐席实时 ✓→坐席重连→REST 历史仍可见,全程无重复记录
关联 AC AC-10
前置条件 1) AI 已发 ai_structured2) 坐席 WS 已连接
测试步骤 1. H5 点选;2. 坐席收到 new_message3. 坐席 WS 断开 10s 后重连;4. 调用 REST 历史
预期结果 1) 实时收到 type=new_messagemessage_id=X2) REST 返回含相同 message_id=X 的 option_select3) DB 中该 message_id 仅 1 行;4) 整个流程无重复 INSERT、无重复广播
优先级 P0
自动化建议 test_e2e_no_duplicate_across_ws_and_rest
静态审查锚点 ws.py:407(广播)+ messages.py:163REST mask 分支) + ws.py:316-336(去重)

2.B 异常场景

TC-通用-005-013

字段 内容
用例编号 TC-通用-005-013
用例标题 历史 client_msg_id 复用(>5s)应被拒绝并不新增行
关联 AC PRD §6 撤回/重选 + 工程师报告 §八 偏离项 #2
前置条件 1) 已完成首次点选 client_msg_id=A 已落库;2) 时间流逝 >5s
测试步骤 1. 员工用同 client_msg_id=A 再次发起(模拟客户端异常复用);2. 等待 200ms
预期结果 1) messages 表行数不变;2) 服务端日志告警"拒绝历史 client_msg_id 复用"3) 不广播、不调 Dify
优先级 P0
自动化建议 test_historical_uuid_reuse_rejected
静态审查锚点 ws.py:339-356hist_stmt 检查)

TC-通用-005-014

字段 内容
用例编号 TC-通用-005-014
用例标题 多 worker 并发同 UUID 20 次,仅 1 次落库/广播/Dify
关联 AC PRD §8 + 技术方案 §3.2
前置条件 1) 启动 ≥2 个 worker 模拟;2) 同一 client_msg_id
测试步骤 1. 20 个协程同时调用 _handle_option_select2. 完成后统计
预期结果 1) DB 仅 1 行;2) 广播 1 次;3) Dify 任务 1 次;4) 其余 19 次命中 5 秒窗口 return
优先级 P0
自动化建议 test_concurrent_20_workers_single_uuid
静态审查锚点 ws.py:300-313pg_advisory_xact_lock

TC-通用-005-015

字段 内容
用例编号 TC-通用-005-015
用例标题 Dify 30 秒超时,但 option_select 消息已落库,不反向删除
关联 AC 工程师报告 §八 偏离项 #2/3
前置条件 1) H5 已点选;2) mock Dify 30 秒不返回
测试步骤 1. 等待 Dify 超时;2. 走关键词降级;3. 检查 messages 表
预期结果 1) messages 表 option_select 行保留;2) AI 回复走关键词降级(不影响 option_select 持久化);3) 后续 REST 仍可见该行
优先级 P1
自动化建议 test_dify_timeout_does_not_delete_option_select
静态审查锚点 h5_ai_task.py:1613-1639(超时分支) + ws.py:426-441(异步任务不阻塞 commit

TC-通用-005-016

字段 内容
用例编号 TC-通用-005-016
用例标题 WS 广播失败(ws_manager.broadcast 抛异常),消息已落库且可由 REST 重连读取
关联 AC PRD §8 风险"广播失败"
前置条件 1) mock ws_manager.broadcast 抛 RuntimeError
测试步骤 1. H5 点选;2. 等待 200ms3. 坐席调用 REST 历史
预期结果 1) messages 表有 option_select 行;2) WS 广播异常被吞掉(仅 warning);3) REST 历史可读
优先级 P1
自动化建议 test_ws_broadcast_failure_graceful
静态审查锚点 ws.py:422-423try/except ws_err

TC-通用-005-017

字段 内容
用例编号 TC-通用-005-017
用例标题 缺字段(question_id/option_id/sourceMessageId)降级为普通文本
关联 AC 工程师报告 §八 偏离项 #2
前置条件 1) H5 sendOptionSelect 传入空 questionId=""
测试步骤 1. 观察 H5 行为;2. 后端落库消息类型
预期结果 1) H5 走 sendMessage({content: optionLabel, msg_type: 'text'}) 降级;2) messages 新增 1 行 msg_type=text3) 不产生 option_select 行
优先级 P1
自动化建议 conversation.spec.ts → missing question_id falls back to text
静态审查锚点 conversation.ts:1538-1544(降级分支)

TC-通用-005-018

字段 内容
用例编号 TC-通用-005-018
用例标题 跨卡同名 label 不串扰(依靠 question_id+option_id 归属)
关联 AC PRD §6 跨卡归属
前置条件 1) 会话有 2 张不同 ai_structuredquestion_id=A 与 question_id=B),均含 label="是"
测试步骤 1. H5 在 A 卡选"是"option_id=A_yes);2. H5 在 B 卡选"是"option_id=B_yes);3. 落库 2 行
预期结果 1) 2 行均落库;2) latestSelectionPerQuestion 中 A→{label: "是", messageId: A 行 id}B→{label: "是", messageId: B 行 id}3) 不会互相覆盖
优先级 P1
自动化建议 MessageBubble.spec.ts → cross-card same label no interference
静态审查锚点 MessageBubble.vue:387-394(按 question_id 分组)

TC-通用-005-019

字段 内容
用例编号 TC-通用-005-019
用例标题 多 question_id 各自多次重选,最新选择仅高亮最新一条
关联 AC PRD §6 + AC-04
前置条件 1) question_id=A 选了 3 次(A1/A2/A3);question_id=B 选了 2 次(B1/B2
测试步骤 1. 渲染所有 option_select2. 检查 --latest 类归属
预期结果 1) A3 与 B2 各带 --latest2) A1/A2/B1 无高亮;3) 排序按 (created_at desc, id desc)
优先级 P0
自动化建议 MessageBubble.spec.ts → multi-question latest only
静态审查锚点 MessageBubble.vue:392-403latest map 更新条件)

TC-通用-005-020

字段 内容
用例编号 TC-通用-005-020
用例标题 重选不更新旧行(数据库与 WS 均为追加语义)
关联 AC PRD §6 撤回/重选 + §7 非目标 #2
前置条件 1) 第一条 option_select 已落库(行 1
测试步骤 1. SQL:检查行 1 的 created_atcontentextra_data2. 员工重选(行 2
预期结果 1) 行 1 内容与时间不变;2) 行 2 是新 INSERT3) 没有 UPDATE 语句
优先级 P0
自动化建议 test_reselect_never_updates_old_row(检查 SQL 日志无 UPDATE
静态审查锚点 ws.py:374-385(仅 db.add + flush + 全文无 Message.update

2.C 敏感词边界

TC-通用-005-021

字段 内容
用例编号 TC-通用-005-021
用例标题 16 位连续数字:保留前 4 + **** + 后 8
关联 AC AC-09
前置条件 1) 单元测试 mask_sensitive_text
测试步骤 1. 调用 mask_sensitive_text("6222123456789012")
预期结果 返回 "6222****56789012"
优先级 P0
自动化建议 test_mask_16_digit
静态审查锚点 sensitive.py:28_MASK_PATTERN_16 + 57(替换)

TC-通用-005-022

字段 内容
用例编号 TC-通用-005-022
用例标题 18 位身份证(数字串)按通用规则:前 4 + **** + 后 10
关联 AC AC-09
前置条件 单元测试
测试步骤 1. mask_sensitive_text("11010119900101001X")(含字母 X
2. mask_sensitive_text("110101199001010011")(纯数字)
预期结果 1) 含 X 时不匹配纯数字正则,前 14 位中数字部分应 mask(如 "1101****0101001X");2) 纯数字 18 位按 4+4+10 切分,前 4 + **** + 后 10
优先级 P1
自动化建议 test_mask_18_idcard
静态审查锚点 sensitive.py:32_MASK_PATTERN_GENERIC

TC-通用-005-023

字段 内容
用例编号 TC-通用-005-023
用例标题 短敏感值(<4 位数字)全部替换为 *
关联 AC AC-09
前置条件 单元测试
测试步骤 1. mask_sensitive_text("6222")
2. mask_sensitive_text("123")
3. mask_sensitive_text("9")
预期结果 1) "****"2) "***"3) "*"
优先级 P1
自动化建议 test_mask_short_value
静态审查锚点 sensitive.py:35_MASK_PATTERN_SHORT

TC-通用-005-024

字段 内容
用例编号 TC-通用-005-024
用例标题 中文边界:数字前后为中文应正确切分
关联 AC AC-09
前置条件 单元测试
测试步骤 1. mask_sensitive_text("账号62221234567中文")
2. mask_sensitive_text("余额是1234元")
预期结果 1) "账号6222****4567中文"(11 位按通用规则:4+4+3);2) "余额是****元"(4 位按短规则但通用先匹配为 4+4+0... 实际为 "余额是****元"
优先级 P1
自动化建议 test_mask_chinese_boundary
静态审查锚点 sensitive.py:32(前后 (?<!\d)/(?!\d) 数字边界断言)

TC-通用-005-025

字段 内容
用例编号 TC-通用-005-025
用例标题 业务键(question_id/option_id/client_msg_id)不被 mask
关联 AC AC-09
前置条件 单元测试
测试步骤 1. mask_sensitive_text("fault_type_network_down_12345678")(不应误伤)
2. 验证 question_id="fault_type" 等保持原值(在 _build_selected_options_snapshot 输出中验证)
预期结果 1) 含 _ 分隔的数字串不被破坏(按单词边界);2) snapshot 中 question_id 与 option_id 完整保留
优先级 P1
自动化建议 test_mask_skips_business_keys
静态审查锚点 h5_ai_task.py:305-314snapshot 不调用 mask on qid/oid

2.D H5 UUID 生命周期

TC-通用-005-026

字段 内容
用例编号 TC-通用-005-026
用例标题 首次点选:pendingOptionSelect 由 null → 含 client_msg_id
关联 AC AC-05
前置条件 1) pendingOptionSelect.value=null2) H5 调用 sendOptionSelect
测试步骤 1. 检查发送后 store 状态
预期结果 1) pendingOptionSelect.value !== null2) clientMsgId 为合法 UUID v43) attempts=14) sentAt 为 Date.now()
优先级 P0
自动化建议 conversation.spec.ts → first click sets pending
静态审查锚点 conversation.ts:1560-1589

TC-通用-005-027

字段 内容
用例编号 TC-通用-005-027
用例标题 重试复用:失败重发使用同 client_msg_idattempts+1
关联 AC AC-05 + §4.4
前置条件 1) pendingOptionSelect.value 已存在;2) WS send 失败
测试步骤 1. 模拟 WS 未连接,重发同一选项;2. 抓 client_msg_id
预期结果 1) 第二次发送复用同一 UUID;2) attempts=2;3) 服务端 5 秒窗口去重,DB 仅 1 行
优先级 P0
自动化建议 conversation.spec.ts → retry reuses UUID
静态审查锚点 conversation.ts:1561-1564(复用分支)

TC-通用-005-028

字段 内容
用例编号 TC-通用-005-028
用例标题 主动重选:同 question_id + 时间间隔 >1s 生成新 UUID
关联 AC AC-05
前置条件 1) pendingOptionSelect.value 已存在;2) 同 question_id 选项再次点击
测试步骤 1. mock Date.now() 偏移 2s2. 调用 sendOptionSelect
预期结果 1) 新 client_msg_id ≠ 上次;2) DB 累计 +1 行;3) 旧行不被覆盖
优先级 P0
自动化建议 conversation.spec.ts → user reselect generates new UUID
静态审查锚点 conversation.ts:1555-1568isUserReselection 判断)

2.E 性能压测

TC-通用-005-029

字段 内容
用例编号 TC-通用-005-029
用例标题 20 个协程并发同 UUID:仅 1 次 INSERT / 1 次 broadcast / 1 次 Dify
关联 AC PRD §8 + 技术方案 §9
前置条件 1) 集成测试环境;2) mock ws_manager + Dify
测试步骤 1. asyncio.gather(*[_handle_option_select(...) for _ in range(20)])2. 统计
预期结果 1) DB 行数 += 12) broadcast 计数 = 13) Dify 任务计数 = 14) 其他 19 次命中 5 秒幂等
优先级 P1
自动化建议 test_concurrent_20_single_uuid
静态审查锚点 ws.py:300-336lock + 5s 查询)

TC-通用-005-030

字段 内容
用例编号 TC-通用-005-030
用例标题 1000 条 option_select 的 latestSelectionPerQuestion 计算耗时
关联 AC 技术方案 §9
前置条件 1) 构造 1000 条 mock2) performance.now() 测时
测试步骤 1. 构造 1000 条 msg_type=option_select2. 计算 latestSelectionPerQuestion3. 100 次循环取平均
预期结果 平均耗时 < 10ms(典型 N=1000, Vue computed 单帧计算)
优先级 P2
自动化建议 MessageBubble.spec.ts → 1000 rows performance
静态审查锚点 MessageBubble.vue:382-405O(N) 单次扫描)

2.F Dify 集成

TC-通用-005-031

字段 内容
用例编号 TC-通用-005-031
用例标题 option_select 触发:5 字段 inputs 完整透传
关联 AC AC-07
前置条件 1) mock Dify /v1/chat-messages2) H5 点选
测试步骤 1. 检查 payload.inputs 字段
预期结果 1) feedback_type=="option_select"2) question_id 与 H5 一致;3) option_id/option_value 一致;4) option_label 是 mask 后文本
优先级 P0
自动化建议 test_dify_inputs_feedback_context
静态审查锚点 ws.py:433-439 + ai_service.py:279

TC-通用-005-032

字段 内容
用例编号 TC-通用-005-032
用例标题 普通文本消息 inputs 仍为空 dict
关联 AC AC-07(普通文本链路不受影响)
前置条件 1) H5 发送普通 text 消息
测试步骤 1. mock Dify2. 检查 payload
预期结果 1) payload.inputs == {}2) feedback_type 字段不存在;3) 仅 query / user / response_mode
优先级 P1
自动化建议 test_dify_plain_text_empty_inputs
静态审查锚点 ai_service.py:279inputs or {} + h5_ai_task.py:1609(普通文本传 None

TC-通用-005-033

字段 内容
用例编号 TC-通用-005-033
用例标题 代理 fallbackdify2openai):feedback_context 放入 metadata,不拼原值
关联 AC 工程师报告 §八 偏离项 #5
前置条件 1) mock 原生 API 失败;2) 走 dify2openai 路径
测试步骤 1. 检查代理 payload
预期结果 1) payload.metadata.feedback_context 含 5 字段;2) payload.messages[0].content 不含 option 原值(仅原 query 内容);3) 不破坏 OpenAI 兼容层
优先级 P1
自动化建议 test_proxy_fallback_metadata
静态审查锚点 ai_service.py:476-479metadata 注入)

2.G 转人工快照

TC-通用-005-034

字段 内容
用例编号 TC-通用-005-034
用例标题 转人工快照:每 question_id 仅 1 条最新选择,按 (created_at desc, id desc) 取
关联 AC AC-08
前置条件 1) 同一 question_id 选 3 次;2) Dify 返回 escalating
测试步骤 1. 触发转人工;2. 校验 snapshot
预期结果 1) 该 question_id 仅 1 条;2) message_id 为最后一次的 ID3) selected_at 准确;4) label 已 mask
优先级 P0
自动化建议 test_snapshot_latest_per_question
静态审查锚点 h5_ai_task.py:277-298row_number window function

TC-通用-005-035

字段 内容
用例编号 TC-通用-005-035
用例标题 转人工快照同时注入:conversation.tags.selected_options + conversation_updated.data.selected_options
关联 AC PRD §4.7 + 技术方案 §6
前置条件 1) Dify 触发 escalating
测试步骤 1. 检查 conversation.tags2. 抓取坐席 WS 的 conversation_updated
预期结果 1) conversation.tags.selected_options 写入;2) conversation.tags.selected_options_updated_at ISO 时间存在;3) conversation_updated.data.selected_options 含相同条目;4) 两处结构一致
优先级 P0
自动化建议 test_snapshot_dual_inject
静态审查锚点 h5_ai_task.py:620-628tags+ h5_ai_task.py:743-755data 注入)

2.H 回归兼容

TC-通用-005-036

字段 内容
用例编号 TC-通用-005-036
用例标题 普通文本消息链路不受 option_select 改造影响
关联 AC 技术方案 §9 回归
前置条件 1) H5 发送普通 text
测试步骤 1. 检查落库消息类型;2. 检查 Dify 调用;3. 检查 WS 广播
预期结果 1) messages.msg_type=="text"2) Dify inputs 为空;3) WS 仍广播 type=new_message 含 text 消息
优先级 P0
自动化建议 test_plain_text_chain_unaffected
静态审查锚点 h5_ai_task.py:1609(普通文本 None+ messages.py:163(仅 option_select 分支 mask

TC-通用-005-037

字段 内容
用例编号 TC-通用-005-037
用例标题 ai_structured 推荐卡 + recommend_event 兼容(不删除旧字段)
关联 AC PRD §7 非目标 #6 + 技术方案 §9 回归
前置条件 1) Dify 返回 action+options2) 旧客户端与新客户端并存
测试步骤 1. 检查 ai_reply 推送;2. 检查 dynamic_recommend3. 检查 new_message 广播
预期结果 1) extra_data.action 仍含旧字段;2) extra_data.options 仍含旧字段;3) 不因 option_select 改造而丢失
优先级 P1
自动化建议 test_recommend_event_compat
静态审查锚点 h5_ai_task.py:557-590extra_data.action/options 构造未改)

三、覆盖矩阵

PRD/技术方案要点 覆盖用例
PRD §4.1 落库 schema TC-001, TC-002, TC-011
PRD §4.2 WS 标准广播 TC-003, TC-012, TC-016
PRD §4.3 坐席渲染 TC-005, TC-006, TC-018, TC-019
PRD §4.4 H5 字段 TC-007, TC-026~TC-028
PRD §4.5 幂等 TC-008, TC-013, TC-014, TC-029
PRD §4.6 Dify inputs TC-009, TC-031~TC-033
PRD §4.7 转人工快照 TC-010, TC-034, TC-035
PRD §4.8 敏感词 mask TC-011, TC-021~TC-025
PRD §5 AC-01~AC-10 TC-001~TC-012(含 AC-10 E2E
PRD §6 边界场景 TC-013~TC-020
PRD §8 风险 TC-014, TC-016, TC-029

四、自动化落库建议(pytest/vitest 目录)

文件 覆盖
src/backend/tests/test_option_select.py TC-001TC-004, TC-008TC-014, TC-016, TC-020, TC-029, TC-034~TC-035
src/backend/tests/test_sensitive.py TC-021~TC-025
src/backend/tests/test_dify_inputs.py TC-031~TC-033
src/frontend-h5/src/stores/conversation.spec.ts TC-007, TC-017, TC-026~TC-028
src/frontend-agent/src/components/chat/MessageBubble.spec.ts TC-005TC-006, TC-018TC-019, TC-030

:自动化测试代码尚未在仓库中提交(Glob 未发现 test_option_select.pytest_sensitive.py),需工程师在 T02/T03 验收前补齐。