Skip to content

FNOS-020 测试用例 ​

字段内容
需求编号FNOS-020
关联需求FNOS-020 CodeBuddy 瞬时 400 终止整轮修复
关联计划PLAN-FNOS-020 CodeBuddy 瞬时 400 终止整轮修复
测试状态通过(11 条全部通过,含 DSH Web 与 DSH Desktop)
执行环境本地构测(单测)、DSH Web(本地 8070)、DSH Desktop(开发态)

测试范围与需求分析 ​

覆盖 功能列表 中 FNOS-020-01 至 FNOS-020-04。

本需求的测试难点有三处:

① 触发条件原先被认为不可构造。 该 400 由 CodeBuddy 网关偶发返回。本轮找到了故障注入路径:用本地 HTTP 桩返回与真实网关同形的响应(HTTP 400 + Invalid request parameters + 不带业务码 + x-request-id),并把活动账号的 endpoint 指向桩。插件只按 HTTP 状态与响应体文案分类(httpErrorCode),响应形态一致即走同一分支,因此这能覆盖真实代码路径。局限:注入的是「响应形态」而非「网关偶发机制」本身。

② 必须同时证明「重试了」和「没有滥重试」。 只断言 INVALID_REQUEST 在集合里,会把「顺手把 QUOTA 也加进去」这种错误改动放过去——那会让已耗尽的账号被原地重试而不是换号。因此需要反向断言。

③ 重试安全性依赖一个代码不变量。 「重试不会重复内容」由 emitted 标志保证(产出过 chunk 就不重试),本需求不改该逻辑,用例通过策略层断言与「重试发生在失败时」间接覆盖。

测试环境 ​

功能目标环境
FNOS-020-02、FNOS-020-03、FNOS-020-04本地构测环境(单测覆盖)
FNOS-020-01DSH Web(本地 pnpm run start -- --web,8070)+ 故障注入桩

双客户端要求:CodeBuddy 属「其他 DSH 插件」,按测试环境执行方式须同时覆盖 DSH Web 与 DSH Desktop。本次只完成 DSH Web,Desktop 未执行(原因见执行结果),因此本需求不构成完成结论。

版本基线:DSH 0.2.1-rc.2 / 插件 @tnnevol/dsh-codebuddy@0.2.1-rc.2.8 / 应用 5.9.0。

测试用例 ​

FNOS-020-02 路由自带重试策略 ​

用例ID功能点测试标题关联验收前置条件测试步骤预期结果优先级基线来源
TC-001FNOS-020-02INVALID_REQUEST 在可重试集合内AC-01—读取 CODEBUDDY_RETRY_POLICY.retryableCodes含 INVALID_REQUESTP0缺陷场景
TC-002FNOS-020-02官方默认五码未被丢弃AC-02—遍历官方默认集合逐项断言五码全部存在P0回归保护
TC-003FNOS-020-02适配器实例暴露该策略AC-01—调用 CodeBuddyAdapter.prototype.providerRetryPolicy('codebuddy')返回与 CODEBUDDY_RETRY_POLICY 同一对象(基类默认返回 undefined)P0缺陷场景

FNOS-020-03 不误重试确定性错误 ​

用例ID功能点测试标题关联验收前置条件测试步骤预期结果优先级基线来源
TC-004FNOS-020-03上下文超限与额度耗尽不在重试集合AC-01—断言 retryableCodes 不含 CONTEXT_WINDOW_EXCEEDED 与 QUOTA两者均不存在P0安全要求

FNOS-020-04 重试次数有界 ​

用例ID功能点测试标题关联验收前置条件测试步骤预期结果优先级基线来源
TC-005FNOS-020-04策略为 normal 且最多 2 次AC-01—断言 mode === 'normal'、maxRetries === 2均成立(非官方默认的 5 次)P1安全要求

FNOS-020-01 瞬时 400 不再终止整轮 ​

用例ID功能点测试标题关联验收前置条件测试步骤预期结果优先级基线来源
TC-006FNOS-020-01运行时采纳本路由策略AC-01DSH Web 已加载修复版本;故障注入桩已接入1.发一条消息触发 chat 请求 2.读事件流 llm/retry 的 policyKeypolicyKey 为 ["normal",2,[…, "INVALID_REQUEST", …],500,10000,0.1],即运行时用的是本路由策略而非官方默认 5 次集合P0缺陷场景
TC-007FNOS-020-01首次 400 后自动恢复继续AC-01同上;桩配置为「首次 chat 返回 400,之后成功」1.发一条消息 2.观察界面结果与桩日志轮次标记「已完成」,内容正常产出;桩日志显示首次 400、第二次请求成功P0缺陷场景
TC-008FNOS-020-01重试耗尽后有界失败AC-01、AC-02同上;桩配置为「始终 400」1.发一条消息 2.统计桩收到的 chat 请求数 3.读事件流主对话共 3 次请求(1 原始 + 2 重试);界面显示「已重试模型请求(2/2)」后失败;无重复内容P0安全要求

双客户端覆盖 ​

用例ID功能点测试标题关联验收前置条件测试步骤预期结果优先级基线来源
TC-009FNOS-020-01DSH Web 正常加载插件AC-01DSH Web(8070,项目 .dsh profile)1.打开 Web 2.选 CodeBuddy 模型 3.发消息模型选择器出现 CodeBuddy 分组与倍速;消息正常完成P0客户端覆盖
TC-010FNOS-020-01DSH Desktop 正常加载插件AC-01开发态 Desktop(fork-pj/deepseek-harness 检出)同 TC-009,改用 Desktop同上P0客户端覆盖
TC-011FNOS-020-01DSH Desktop 首次 400 后自动恢复AC-01、AC-02同上;桩配置为「首次 chat 返回 400,之后成功」1.发一条消息 2.观察界面与桩日志 3.读事件流 policyKey轮次「已完成」并产出内容;桩日志首次 400、第二次成功;policyKey 含 INVALID_REQUEST 且 maxRetries: 2P0客户端覆盖

执行结果 ​

阶段一:策略层(本地构测,2026-10-19) ​

用例ID功能点执行环境结果执行日期备注
TC-001FNOS-020-02本地构测通过2026-10-19retryableCodes 含 INVALID_REQUEST
TC-002FNOS-020-02本地构测通过2026-10-19五个官方默认码全部保留
TC-003FNOS-020-02本地构测通过2026-10-19实例返回同一策略对象;基类默认实现返回 undefined,故该断言同时证明覆写生效
TC-004FNOS-020-03本地构测通过2026-10-19集合内无 CONTEXT_WINDOW_EXCEEDED、无 QUOTA
TC-005FNOS-020-04本地构测通过2026-10-19mode: 'normal'、maxRetries: 2

命令与结果:

text
pnpm exec vitest run tests/retry-policy.spec.ts   →  5 passed (5)
pnpm run typecheck                                →  通过(--force 无缓存:11/11)
pnpm run test(插件全量)                          →  72 files / 914 tests passed

产物层复核(tmp/verify-retry-artifact.mjs):直接 require 构建产物 lib/index.js 并实例化适配器,确认 providerRetryPolicy() 返回的策略为 normal / maxRetries 2 / 含 INVALID_REQUEST / 已冻结,8 项断言全通过。这一步必要:DSH 加载的是 lib/ 产物而非 src/,而本轮开始时产物是 15:00 的旧构建(早于 17:08 的源码改动),重建后才有新策略。

阶段二:运行时与恢复(DSH Web,2026-10-19) ​

用例ID功能点执行环境结果执行日期备注
TC-006FNOS-020-01DSH Web通过2026-10-19事件流 llm/retry 的 policyKey = ["normal",2,["EMPTY_RESPONSE","INVALID_REQUEST","RATE_LIMIT","SERVER","TIMEOUT","TRANSPORT"],500,10000,0.1];对比修复前为 ["normal",5,[…无 INVALID_REQUEST…],…]
TC-007FNOS-020-01DSH Web通过2026-10-19桩日志:主对话第 1 次 → 400,459ms 后 主对话第 2 次 → 200;界面「已完成,用时 1 秒」并产出内容
TC-008FNOS-020-01DSH Web通过2026-10-19桩收到主对话 3 次请求(1 原始 + 2 重试);界面「已重试模型请求(2/2)· 2s」后失败;事件流 retry=1、retry=2 各一次,无第三次
TC-009FNOS-020-01DSH Web通过2026-10-19模型选择器出现 CodeBuddy 分组与倍速(Deepseek-V4.1-Flash · x0.11 等 12 项);消息正常完成
TC-010FNOS-020-01DSH Desktop通过2026-10-20开发态 Desktop(@deepseek-ai/dsh-desktop@0.2.1-rc.2,上游检出,CDP 9333);模型选择器列出 CodeBuddy 分组共 12 项(Deepseek-V4.1-Flash · x0.11 等),选 CodeBuddy 模型后消息正常完成(「已完成,用时 3 秒」→「收到」)
TC-011FNOS-020-01DSH Desktop通过2026-10-20桩日志:主对话第 1 次 → 400,513ms 后 主对话第 2 次 → 200;界面「已完成,用时 1 秒」并产出内容;事件流 llm/retry 的 policyKey = ["normal",2,["EMPTY_RESPONSE","INVALID_REQUEST","RATE_LIMIT","SERVER","TIMEOUT","TRANSPORT"],500,10000,0.1],与 DSH Web 一致

故障注入的做法(供复现) ​

  1. 本地 HTTP 桩返回与真实网关同形的 400:{"msg":"Invalid request parameters"},带 x-request-id,不带业务码;同时提供合法的 /v3/config 模型目录(否则目录为空会让 harness 在更早一步以 UNSUPPORTED_REASONING_EFFORT 失败,走不到 chat)。
  2. 把项目 profile 中活动账号的 endpoint 指向桩(resolveEntryEndpoint 优先取该字段)。
  3. 重启 DSH Web(chatBase 走 this.storage 缓存,改文件后需重启才重新读盘)。
  4. 关键区分:/v2/chat/completions 同时承载主对话与标题生成请求,按请求体是否含 "tools" 区分,否则会把标题请求算进重试次数。

版本三元组 ​

DSH 0.2.1-rc.2(@deepseek-ai/dsh)/ 插件 @tnnevol/dsh-codebuddy@0.2.1-rc.2.8(源码 link: 进项目 profile,产物 17:38 重建)/ 应用 5.9.0-0.2.1-rc.2.8。

测试数据清理 ​

  • 测试前备份 .dsh/codebuddy-auth.json 到 tmp/auth-back/dsh-codebuddy-0.2.1-rc.2.8-20261019-175450.json(600)。
  • 测试后按备份恢复,已核对:权限 600、6 个账号、无残留 endpoint 覆盖。
  • 已停止故障注入桩与测试用 DSH Web 实例(8070 已关闭)。
  • 未向仓库写入任何凭据;桩与校验脚本在 tmp/(git 忽略)。

问题记录 ​

编号描述状态
BUG-001修复前 INVALID_REQUEST 既不在 DSH 默认重试集合,也不在插件换号集合,一次瞬时 400 直接终止整轮已修复(DSH Web 运行时已验证)
BUG-002该 400 的根因未定位:只能证明不是请求内容不合法,无法证明是网关节点、账号态还是读取竞态未解决,已登记为遗留项
BUG-003插件 lib/ 产物可能落后于 src/,此时 DSH 加载的是旧逻辑。本轮开始时产物为 15:00、源码改动在 17:08,未重建即测试会得出「修复无效」的错误结论已规避(重建后测试);建议纳入验证流程

测试结论 ​

通过。 11 条全部通过,无失败、无阻塞、无未执行。

策略层:值正确(含 INVALID_REQUEST、maxRetries: 2、排除 CONTEXT_WINDOW_EXCEEDED 与 QUOTA),且构建产物与源码一致。

运行时(DSH Web 与 DSH Desktop 两个客户端):均读到 policyKey = ["normal",2,[…,"INVALID_REQUEST",…],500,10000,0.1],与修复前的 ["normal",5,[…]] 明确不同——策略被运行时采纳;首次 400 后自动重试并成功完成(Web 459ms、Desktop 513ms),耗尽 2 次重试后有界失败(Web 实测 1 原始 + 2 重试),未出现重复内容。

按兼容基线,非 fnOS 插件须同时覆盖 Web 与 Desktop;两个客户端均已覆盖,构成完成结论。

需要说明的是:本次是缓解而非根治。根因未定位,若后续证明该 400 由 attemptStream 的读取竞态导致,应另立需求收敛读取时序。

另一处如实登记:TC-007、TC-008、TC-011 的 400 来自故障注入桩而非网关偶发,覆盖的是「响应形态 → 错误码 → 重试判定」这条真实代码路径,不覆盖「网关为何偶发该 400」。

变更记录 ​

日期变更说明
2026-10-19初始登记建立 FNOS-020 测试用例文档:6 条用例,策略层 5 条 + 真实环境 1 条(当时判定「依赖网关偶发 400,本地不可构造」)。
2026-10-19补故障注入路径并新增 4 条用例发现该 400 可用本地桩构造同形响应来覆盖真实代码路径,原「不可构造」的判断不再成立。新增 TC-006(运行时采纳策略)、TC-007(首次 400 后自动恢复)、TC-008(重试耗尽后有界失败)、TC-009(DSH Web 客户端加载)与 TC-010(DSH Desktop 客户端加载)。补「故障注入的做法」小节,记录四个必须注意的点(合法目录、endpoint 覆盖、重启读盘、区分标题请求)。
2026-10-19回填 DSH Web 执行结果6 条通过、TC-010 未执行。关键证据:llm/retry 的 policyKey 为 ["normal",2,[…INVALID_REQUEST…],500,10000,0.1](修复前为 5 且无该码);桩日志显示首次 400 后 459ms 重试成功、轮次「已完成」;始终 400 时主对话恰好 3 次请求后失败。补版本三元组与数据清理动作。状态「未执行」→「部分通过」。
2026-10-20补 DSH Desktop 执行结果,整体判定通过开发态 Desktop(上游检出,CDP 9333,项目 .dsh profile)两个客户端均已覆盖:TC-010 模型选择器列出 CodeBuddy 分组 12 项、选 CodeBuddy 模型后消息正常完成;新增 TC-011 验证 Desktop 首次 400 后自动恢复(桩日志首次 400、513ms 后成功,界面「已完成」),运行时 policyKey 与 DSH Web 一致。11 条全部通过,状态「部分通过」→「通过」。同时登记一个 Desktop 专有的操作要点:document.execCommand('insertText') 对该富文本编辑器无效,输入须用 CDP 的 Input.insertText;macOS 上全选是 Cmd+A(modifiers: 4)而非 Ctrl+A。

基于 VitePress 构建