613 lines
31 KiB
Markdown
613 lines
31 KiB
Markdown
|
|
# 测试用例 - 选项选择持久化
|
|||
|
|
|
|||
|
|
> **REQ 编号**: REQ-通用-005
|
|||
|
|
> **版本**: v1.0
|
|||
|
|
> **日期**: 2026-07-29
|
|||
|
|
> **作者**: 严过关(QA)
|
|||
|
|
> **状态**: ✅ 编写完成(35 用例,待工程师静态审查 + AST 后给出最终结论)
|
|||
|
|
> **关联文档**:
|
|||
|
|
> - PRD:`docs/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_select,6 字段完整 |
|
|||
|
|
| **关联 AC** | AC-01 |
|
|||
|
|
| **前置条件** | 1) H5 已建立 WS 连接;2) AI 已发出 `ai_structured` 含 options;3) `messages` 表初始无 option_select |
|
|||
|
|
| **测试步骤** | 1. 员工点击"网络中断"选项<br>2. H5 发送 6 字段 `option_select`<br>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_original`;3) `content` 为原始 label;4) `GET /conversations/{id}/messages` 返回该行 |
|
|||
|
|
| **优先级** | P0 |
|
|||
|
|
| **自动化建议** | ✅ `test_handle_option_select_persists_first_message` |
|
|||
|
|
| **静态审查锚点** | ws.py:374-385(构造 Message) + ws.py:365-373(extra_data 5+1 字段) |
|
|||
|
|
|
|||
|
|
#### TC-通用-005-002
|
|||
|
|
|
|||
|
|
| 字段 | 内容 |
|
|||
|
|
|------|------|
|
|||
|
|
| **用例编号** | TC-通用-005-002 |
|
|||
|
|
| **用例标题** | 同一选项重选两次,形成两行 option_select |
|
|||
|
|
| **关联 AC** | AC-02 |
|
|||
|
|
| **前置条件** | 1) 已完成 TC-001 的首次点选;2) 5 秒后再点同选项 |
|
|||
|
|
| **测试步骤** | 1. 员工再次点击同选项(新 UUID)<br>2. 等待 200ms |
|
|||
|
|
| **预期结果** | 1) `messages` 表累计 2 行 `option_select`,`message_id` 互不相同;2) 第一行不被覆盖;3) REST 历史按时间正序返回两行 |
|
|||
|
|
| **优先级** | P0 |
|
|||
|
|
| **自动化建议** | ✅ `test_two_reselects_persist_two_rows` |
|
|||
|
|
| **静态审查锚点** | ws.py:386(db.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-421(broadcast 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-173(REST mask 分支) |
|
|||
|
|
|
|||
|
|
#### TC-通用-005-005
|
|||
|
|
|
|||
|
|
| 字段 | 内容 |
|
|||
|
|
|------|------|
|
|||
|
|
| **用例编号** | TC-通用-005-005 |
|
|||
|
|
| **用例标题** | 坐席 MessageBubble 渲染"✓ 选项"灰色徽标 |
|
|||
|
|
| **关联 AC** | AC-04 |
|
|||
|
|
| **前置条件** | 1) 坐席前端加载会话;2) REST 返回 1 条 option_select |
|
|||
|
|
| **测试步骤** | 1. 渲染 MessageBubble;2. 检查 DOM |
|
|||
|
|
| **预期结果** | 1) 渲染 `option-select-badge` 节点;2) 文案为 `✓ {content}`;3) 默认不带 `--latest` 类(灰色) |
|
|||
|
|
| **优先级** | P0 |
|
|||
|
|
| **自动化建议** | ✅ `MessageBubble.spec.ts → option_select renders badge` |
|
|||
|
|
| **静态审查锚点** | MessageBubble.vue:84-94(option_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-483(isLatestSelection) + 386-403(latestSelectionPerQuestion) |
|
|||
|
|
|
|||
|
|
#### 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:1567(safeRandomUUID 调用) + 1571-1579(6 字段 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-336(5 秒窗口幂等查询 + return) + ws.py:300-313(advisory 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_id`、`option_id`、`option_value`、`option_label`(mask 后);3) `query` 字段为 option_label 原值;4) 不影响现有 `user`/`conversation_id` |
|
|||
|
|
| **优先级** | P0 |
|
|||
|
|
| **自动化建议** | ✅ `test_dify_inputs_contains_feedback_context` |
|
|||
|
|
| **静态审查锚点** | ws.py:433-439(feedback_context dict) + ai_service.py:279(inputs 透传) |
|
|||
|
|
|
|||
|
|
#### 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_updated`;3. 校验 `data.selected_options` |
|
|||
|
|
| **预期结果** | 1) `conversation_updated.data.selected_options` 是 list;2) 每项含 `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-628(tags 注入) + h5_ai_task.py:743-755(conversation_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****后N`;2) DB `messages.content` 为原值;3) `question_id`/`option_id`/`client_msg_id` 不被 mask |
|
|||
|
|
| **优先级** | P0 |
|
|||
|
|
| **自动化建议** | ✅ `test_mask_preserves_db_value` |
|
|||
|
|
| **静态审查锚点** | sensitive.py:38-66(mask_sensitive_text) + ws.py:397,415(mask 应用) + messages.py:165(mask 应用) |
|
|||
|
|
|
|||
|
|
#### TC-通用-005-012
|
|||
|
|
|
|||
|
|
| 字段 | 内容 |
|
|||
|
|
|------|------|
|
|||
|
|
| **用例编号** | TC-通用-005-012 |
|
|||
|
|
| **用例标题** | 全链路:AI 发选项→员工点选→坐席实时 ✓→坐席重连→REST 历史仍可见,全程无重复记录 |
|
|||
|
|
| **关联 AC** | AC-10 |
|
|||
|
|
| **前置条件** | 1) AI 已发 ai_structured;2) 坐席 WS 已连接 |
|
|||
|
|
| **测试步骤** | 1. H5 点选;2. 坐席收到 new_message;3. 坐席 WS 断开 10s 后重连;4. 调用 REST 历史 |
|
|||
|
|
| **预期结果** | 1) 实时收到 type=new_message,message_id=X;2) REST 返回含相同 message_id=X 的 option_select;3) DB 中该 message_id 仅 1 行;4) 整个流程无重复 INSERT、无重复广播 |
|
|||
|
|
| **优先级** | P0 |
|
|||
|
|
| **自动化建议** | ✅ `test_e2e_no_duplicate_across_ws_and_rest` |
|
|||
|
|
| **静态审查锚点** | ws.py:407(广播)+ messages.py:163(REST 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-356(hist_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_select`;2. 完成后统计 |
|
|||
|
|
| **预期结果** | 1) DB 仅 1 行;2) 广播 1 次;3) Dify 任务 1 次;4) 其余 19 次命中 5 秒窗口 return |
|
|||
|
|
| **优先级** | P0 |
|
|||
|
|
| **自动化建议** | ✅ `test_concurrent_20_workers_single_uuid` |
|
|||
|
|
| **静态审查锚点** | ws.py:300-313(pg_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. 等待 200ms;3. 坐席调用 REST 历史 |
|
|||
|
|
| **预期结果** | 1) `messages` 表有 option_select 行;2) WS 广播异常被吞掉(仅 warning);3) REST 历史可读 |
|
|||
|
|
| **优先级** | P1 |
|
|||
|
|
| **自动化建议** | ✅ `test_ws_broadcast_failure_graceful` |
|
|||
|
|
| **静态审查锚点** | ws.py:422-423(try/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=text`;3) 不产生 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_structured(question_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_select;2. 检查 `--latest` 类归属 |
|
|||
|
|
| **预期结果** | 1) A3 与 B2 各带 `--latest`;2) A1/A2/B1 无高亮;3) 排序按 (created_at desc, id desc) |
|
|||
|
|
| **优先级** | P0 |
|
|||
|
|
| **自动化建议** | ✅ `MessageBubble.spec.ts → multi-question latest only` |
|
|||
|
|
| **静态审查锚点** | MessageBubble.vue:392-403(latest map 更新条件) |
|
|||
|
|
|
|||
|
|
#### TC-通用-005-020
|
|||
|
|
|
|||
|
|
| 字段 | 内容 |
|
|||
|
|
|------|------|
|
|||
|
|
| **用例编号** | TC-通用-005-020 |
|
|||
|
|
| **用例标题** | 重选不更新旧行(数据库与 WS 均为追加语义) |
|
|||
|
|
| **关联 AC** | PRD §6 撤回/重选 + §7 非目标 #2 |
|
|||
|
|
| **前置条件** | 1) 第一条 option_select 已落库(行 1) |
|
|||
|
|
| **测试步骤** | 1. SQL:检查行 1 的 `created_at`、`content`、`extra_data`;2. 员工重选(行 2) |
|
|||
|
|
| **预期结果** | 1) 行 1 内容与时间不变;2) 行 2 是新 INSERT;3) 没有 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)<br>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")` <br>2. `mask_sensitive_text("123")` <br>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中文")` <br>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")`(不应误伤)<br>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-314(snapshot 不调用 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=null`;2) H5 调用 `sendOptionSelect` |
|
|||
|
|
| **测试步骤** | 1. 检查发送后 store 状态 |
|
|||
|
|
| **预期结果** | 1) `pendingOptionSelect.value !== null`;2) `clientMsgId` 为合法 UUID v4;3) `attempts=1`;4) `sentAt` 为 Date.now() |
|
|||
|
|
| **优先级** | P0 |
|
|||
|
|
| **自动化建议** | ✅ `conversation.spec.ts → first click sets pending` |
|
|||
|
|
| **静态审查锚点** | conversation.ts:1560-1589 |
|
|||
|
|
|
|||
|
|
#### TC-通用-005-027
|
|||
|
|
|
|||
|
|
| 字段 | 内容 |
|
|||
|
|
|------|------|
|
|||
|
|
| **用例编号** | TC-通用-005-027 |
|
|||
|
|
| **用例标题** | 重试复用:失败重发使用同 client_msg_id(attempts+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()` 偏移 2s;2. 调用 sendOptionSelect |
|
|||
|
|
| **预期结果** | 1) 新 client_msg_id ≠ 上次;2) DB 累计 +1 行;3) 旧行不被覆盖 |
|
|||
|
|
| **优先级** | P0 |
|
|||
|
|
| **自动化建议** | ✅ `conversation.spec.ts → user reselect generates new UUID` |
|
|||
|
|
| **静态审查锚点** | conversation.ts:1555-1568(isUserReselection 判断) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 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 行数 += 1;2) broadcast 计数 = 1;3) Dify 任务计数 = 1;4) 其他 19 次命中 5 秒幂等 |
|
|||
|
|
| **优先级** | P1 |
|
|||
|
|
| **自动化建议** | ✅ `test_concurrent_20_single_uuid` |
|
|||
|
|
| **静态审查锚点** | ws.py:300-336(lock + 5s 查询) |
|
|||
|
|
|
|||
|
|
#### TC-通用-005-030
|
|||
|
|
|
|||
|
|
| 字段 | 内容 |
|
|||
|
|
|------|------|
|
|||
|
|
| **用例编号** | TC-通用-005-030 |
|
|||
|
|
| **用例标题** | 1000 条 option_select 的 latestSelectionPerQuestion 计算耗时 |
|
|||
|
|
| **关联 AC** | 技术方案 §9 |
|
|||
|
|
| **前置条件** | 1) 构造 1000 条 mock;2) `performance.now()` 测时 |
|
|||
|
|
| **测试步骤** | 1. 构造 1000 条 `msg_type=option_select`;2. 计算 `latestSelectionPerQuestion`;3. 100 次循环取平均 |
|
|||
|
|
| **预期结果** | 平均耗时 < 10ms(典型 N=1000, Vue computed 单帧计算) |
|
|||
|
|
| **优先级** | P2 |
|
|||
|
|
| **自动化建议** | ✅ `MessageBubble.spec.ts → 1000 rows performance` |
|
|||
|
|
| **静态审查锚点** | MessageBubble.vue:382-405(O(N) 单次扫描) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2.F Dify 集成
|
|||
|
|
|
|||
|
|
#### TC-通用-005-031
|
|||
|
|
|
|||
|
|
| 字段 | 内容 |
|
|||
|
|
|------|------|
|
|||
|
|
| **用例编号** | TC-通用-005-031 |
|
|||
|
|
| **用例标题** | option_select 触发:5 字段 inputs 完整透传 |
|
|||
|
|
| **关联 AC** | AC-07 |
|
|||
|
|
| **前置条件** | 1) mock Dify `/v1/chat-messages`;2) 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 Dify;2. 检查 payload |
|
|||
|
|
| **预期结果** | 1) `payload.inputs == {}`;2) `feedback_type` 字段不存在;3) 仅 query / user / response_mode |
|
|||
|
|
| **优先级** | P1 |
|
|||
|
|
| **自动化建议** | ✅ `test_dify_plain_text_empty_inputs` |
|
|||
|
|
| **静态审查锚点** | ai_service.py:279(`inputs or {}`) + h5_ai_task.py:1609(普通文本传 None) |
|
|||
|
|
|
|||
|
|
#### TC-通用-005-033
|
|||
|
|
|
|||
|
|
| 字段 | 内容 |
|
|||
|
|
|------|------|
|
|||
|
|
| **用例编号** | TC-通用-005-033 |
|
|||
|
|
| **用例标题** | 代理 fallback(dify2openai):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-479(metadata 注入) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 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 为最后一次的 ID;3) `selected_at` 准确;4) label 已 mask |
|
|||
|
|
| **优先级** | P0 |
|
|||
|
|
| **自动化建议** | ✅ `test_snapshot_latest_per_question` |
|
|||
|
|
| **静态审查锚点** | h5_ai_task.py:277-298(row_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.tags;2. 抓取坐席 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-628(tags)+ h5_ai_task.py:743-755(data 注入) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 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+options;2) 旧客户端与新客户端并存 |
|
|||
|
|
| **测试步骤** | 1. 检查 ai_reply 推送;2. 检查 dynamic_recommend;3. 检查 new_message 广播 |
|
|||
|
|
| **预期结果** | 1) `extra_data.action` 仍含旧字段;2) `extra_data.options` 仍含旧字段;3) 不因 option_select 改造而丢失 |
|
|||
|
|
| **优先级** | P1 |
|
|||
|
|
| **自动化建议** | ✅ `test_recommend_event_compat` |
|
|||
|
|
| **静态审查锚点** | h5_ai_task.py:557-590(extra_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-001~TC-004, TC-008~TC-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-005~TC-006, TC-018~TC-019, TC-030 |
|
|||
|
|
|
|||
|
|
> **注**:自动化测试代码尚未在仓库中提交(`Glob` 未发现 `test_option_select.py` 与 `test_sensitive.py`),需工程师在 T02/T03 验收前补齐。
|