From f3df3a203b3d75e1586e89bc8489c26fcae48c2f Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Wed, 20 May 2026 17:08:12 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E4=BC=98=E5=8C=96=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=EF=BC=8C=E7=A6=81=E6=AD=A2=20subage?= =?UTF-8?q?nt=20=E8=AF=BB=E5=8F=96=E6=96=87=E4=BB=B6=EF=BC=8C=E6=98=8E?= =?UTF-8?q?=E7=A1=AE=20apply=20=E9=98=B6=E6=AE=B5=E4=B8=8D=E5=8A=A8?= =?UTF-8?q?=E4=B8=BB=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config.yaml: subagent 限定为计算密集/多步骤任务,文件读取用 Read 工具 prompt-proposal-review.md: 收集阶段加入读取约束和分步策略,复核补全待澄清清单 prompt-apply-review.md: 禁止同步主规范,新增 Spec 覆盖完整性扫描与补充流程 --- docs/prompts/prompt-apply-review.md | 60 ++++++++++++------- docs/prompts/prompt-proposal-review.md | 25 +++++--- openspec/config.yaml | 2 +- .../specs/test-output-cleanliness/spec.md | 29 +++++++++ openspec/specs/vite-frontend-bundling/spec.md | 16 ++++- src/server/checker/engine.ts | 2 +- src/web/app.tsx | 37 +++++++----- tests/server/app.test.ts | 46 +++++++------- tests/server/checker/engine.test.ts | 3 + tests/server/helpers.test.ts | 12 +++- 10 files changed, 161 insertions(+), 71 deletions(-) create mode 100644 openspec/specs/test-output-cleanliness/spec.md diff --git a/docs/prompts/prompt-apply-review.md b/docs/prompts/prompt-apply-review.md index a34f3b2..377506a 100644 --- a/docs/prompts/prompt-apply-review.md +++ b/docs/prompts/prompt-apply-review.md @@ -4,6 +4,7 @@ - 先审查再修复;未经用户确认,不修改代码或变更文档 - 默认按 `spec-driven` workflow 审查;识别 change 后先确认 `schemaName`;若实际 schema 不同,说明差异,仅对实际存在的 artifacts 做审查 +- 禁止同步或修改 `openspec/specs/` 下的主规范——主规范同步属于 archive 阶段,不在此提示词范围内;本次 change 目录下的 `specs/*.md`、代码、测试、README 等均可修改 - 优先使用当前会话中的实现说明、测试结论、手动修补记录和已生成的变更文档;仅在无法明确 change、`schemaName`、改动范围或修补来源时,再用提问工具或 OpenSpec 命令补充定位 - 不要因为代码已经存在就自动以代码为准;先判断差异属于"文档要求未实现"、"测试后新增修补"还是"意外偏离/回归" - 每批代码或文档修改执行前用提问工具获得用户确认 @@ -12,14 +13,27 @@ ## 1. 收集 -并行读取: +读取约束: -- 本次 change 的实际 artifacts;在 `spec-driven` 下通常包括 `proposal.md`、`design.md`、`tasks.md`、`specs/*.md` -- 当前会话中与本次变更相关的实现说明、apply 过程中的偏离、测试失败、手动修补原因、待确认事项 -- 与本次变更相关的代码和测试文件;优先依据 `git diff --name-only`、`git diff --name-only --cached`、`tasks.md`、Impact、失败测试栈定位;若工作区已干净,再结合文档和代码模块反推 -- 最近一次相关测试命令、测试结果、失败信息和修补后的验证结果 +- 直接使用 Read 工具并行读取文件,禁止使用 subagent/Task 工具做文件读取和内容转发 +- 不原样输出文件内容,仅在步骤 2 输出审查结论 + +分步收集: + +a) 先并行读取核心入口和配置,确定范围: + +- 本次 change 的 `proposal.md` - `openspec/config.yaml` -- 与本次改动相关的 README、架构文档,以及现有 `openspec/specs/**/spec.md` 中与本次变更相关的规范,相关性来源包括:`proposal.md` 的 `Capabilities` / `Modified Capabilities`、手动修补涉及的受影响能力、`design.md` / Impact 中提到的模块、相关代码对应的现有能力 + +b) 从 `proposal.md` 提取 `Capabilities` / `Modified Capabilities`,确定 proposal 已声明的 spec 列表 + +c) 并行获取改动范围:`git diff --name-only`、`git diff --name-only --cached`;若工作区已干净,再结合文档和代码模块反推 + +d) 对比 git diff 涉及的模块与 proposal 已声明的 spec,识别 apply 阶段新增改动触及但 proposal 未覆盖的现有 spec,合并为完整 spec 读取列表;相关性来源还包括:手动修补涉及的受影响能力、`design.md` Impact 中提到的模块、相关代码对应的现有能力 + +e) 并行读取完整 spec 列表和其余 artifacts(`design.md`、`tasks.md`、相关源码、测试文件、README、架构文档) + +f) 收集当前会话中与本次变更相关的实现说明、apply 过程中的偏离、测试失败、手动修补原因、待确认事项 若当前上下文无法明确 change 或文档路径: @@ -34,12 +48,13 @@ 按以下优先级检查: -| 优先级 | 维度 | 检查点 | -| ------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| P0 | 实际实现与测试结论 | 当前代码的真实行为是什么;apply 后是否有手动改动或测试后修补;测试是否证明这些实现有效;若缺少测试结果,标记相关结论为"未验证";检查是否存在回归、未覆盖场景或被掩盖的问题 | -| P1 | 文档同步性 | 对实际存在的 artifacts 检查:已落地的实现、测试后新增修补、边界处理、异常路径、验证结论是否已同步回变更文档;若影响模块结构、API、实体或用户可见行为,再检查 README 是否同步 | -| P2 | 文档要求覆盖 | 对实际存在的 artifacts 检查:文档中承诺的目标、方案、Requirement、Scenario 是否都已实现;在 `spec-driven` 下重点检查 `proposal.md`、`design.md`、`specs/*.md`、`tasks.md` | -| P3 | 实现质量 | 代码结构、复用、命名、复杂度、错误处理、测试质量、与项目现有模式的一致性是否存在明显问题或可优化点 | +| 优先级 | 维度 | 检查点 | +| ------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| P0 | 实际实现与测试结论 | 当前代码的真实行为是什么;apply 后是否有手动改动或测试后修补;测试是否证明这些实现有效;若缺少测试结果,标记相关结论为"未验证";检查是否存在回归、未覆盖场景或被掩盖的问题 | +| P1 | 文档同步性 | 对本次 change 目录下实际存在的 artifacts 检查:已落地的实现、测试后新增修补、边界处理、异常路径、验证结论是否已同步回变更文档;若影响模块结构、API、实体或用户可见行为,再检查 README 是否同步 | +| P2 | Spec 覆盖完整性 | 对比实际代码改动范围与 proposal 中定义的 `Capabilities` / `Modified Capabilities`,识别 apply 阶段新增的功能是否触及了更多现有 spec;若触及,列入补充清单,在本次 change 的 specs 中新增对应的 spec 文件,并更新 `proposal.md` 的 `Modified Capabilities` | +| P3 | 文档要求覆盖 | 对实际存在的 artifacts 检查:文档中承诺的目标、方案、Requirement、Scenario 是否都已实现;在 `spec-driven` 下重点检查 `proposal.md`、`design.md`、`specs/*.md`、`tasks.md` | +| P4 | 实现质量 | 代码结构、复用、命名、复杂度、错误处理、测试质量、与项目现有模式的一致性是否存在明显问题或可优化点 | 分析时区分三类差异: @@ -58,7 +73,7 @@ - 文档要求但未落地的功能、场景、异常处理或验证步骤 - apply 完成后新增的代码修补、边界处理、接口调整、行为变化未同步到文档 - `tasks.md` 标记完成,但代码、测试或文档未闭环 -- `Modified Capabilities` 应更新但未更新的现有 spec +- `Modified Capabilities` 在本次 change 的 specs 中是否已更新(注意:仅更新 change 目录下的 specs,不动 `openspec/specs/`);apply 阶段新增功能触及的未覆盖 spec 是否已补充到本次 change 的 `specs/` 目录 - 代码存在明显的重复、复杂度过高、命名不清、错误处理薄弱、测试质量不足等问题 输出审查结果: @@ -68,10 +83,11 @@ 3. **未覆盖清单**:文档要求但未在代码中实现或未充分验证的内容 4. **需回写文档清单**:代码和测试中已确认、但文档未体现的实现、修补或约束变化 5. **方向待确认清单**:代码与文档不一致,且无法判断应以哪边为准的事项 -6. **任务状态问题清单**:未真正完成、状态错误或需补充的新任务 -7. **测试问题清单**:缺失覆盖、掩盖错误、验证不足或修补后未回归验证的测试问题 -8. **代码质量/优化清单**:可优化的实现问题和建议 -9. **逐项分析**:每个问题说明位置、问题、影响、建议和建议修复方向 +6. **Spec 补充清单**:apply 阶段新增功能触及但 proposal 未覆盖的现有 spec,列出需新增的 spec 文件名、对应的主 spec 路径、需描述的变更内容 +7. **任务状态问题清单**:未真正完成、状态错误或需补充的新任务 +8. **测试问题清单**:缺失覆盖、掩盖错误、验证不足或修补后未回归验证的测试问题 +9. **代码质量/优化清单**:可优化的实现问题和建议 +10. **逐项分析**:每个问题说明位置、问题、影响、建议和建议修复方向 若所有清单均为空,输出"审查通过,未发现问题",跳至步骤 5。 @@ -82,7 +98,8 @@ 再整理完整修复方案,按类别列出: - 代码或测试补充:补实现、补异常处理、补回归测试、修复掩盖错误的测试 -- 文档回写:同步 `proposal.md`、`design.md`、`tasks.md`、`specs/*.md`、README 中遗漏或过时的内容 +- 文档回写:同步本次 change 目录下的 `proposal.md`、`design.md`、`tasks.md`、`specs/*.md`、README 中遗漏或过时的内容(禁止同步到 `openspec/specs/`) +- Spec 补充:将 apply 阶段新增功能触及的现有 spec 复制到本次 change 的 `specs/` 目录并更新,同步更新 `proposal.md` 的 `Modified Capabilities` - 任务状态修正:修正已完成/未完成状态,补充 apply 后新增但已完成的修补任务或验证任务 - 代码质量优化:在不改变目标行为的前提下优化结构、复用、命名或可维护性 @@ -112,13 +129,16 @@ 若修改了文档: -- 确认实际存在的变更文档之间保持一致;在 `spec-driven` 下重点检查 `proposal.md`、`design.md`、`tasks.md`、`specs/*.md` -- 若 apply 后新增修补改变了能力边界或行为约束,同步更新 `Capabilities` / `Modified Capabilities` +- 确认本次 change 目录下的变更文档之间保持一致;重点检查 `proposal.md`、`design.md`、`tasks.md`、`specs/*.md` +- 若 apply 后新增修补改变了能力边界或行为约束,同步更新本次 change 的 `Capabilities` / `Modified Capabilities` +- 若"Spec 补充清单"中有需新增的 spec:先从 `openspec/specs/` 复制对应的原 spec 到本次 change 的 `specs/` 目录,再基于实际改动更新其内容;禁止修改 `openspec/specs/` 下的原文件 +- 禁止将本次 change 的 specs 同步到 `openspec/specs/`,该操作属于 archive 阶段 执行后重新读取所有被修改的代码、测试和文档,并复核: - "未覆盖清单" 是否已清空或已标注保留原因 - "需回写文档清单" 是否已清空 +- "Spec 补充清单" 是否已清空或已标注保留原因 - "方向待确认清单" 是否已清空或已记录用户决策 - "任务状态问题清单" 和 "测试问题清单" 是否已清空或已标注残留原因 - "代码质量/优化清单" 中哪些已处理,哪些有意延期 diff --git a/docs/prompts/prompt-proposal-review.md b/docs/prompts/prompt-proposal-review.md index 9fa8f1b..4ebb5f2 100644 --- a/docs/prompts/prompt-proposal-review.md +++ b/docs/prompts/prompt-proposal-review.md @@ -10,13 +10,21 @@ ## 1. 收集 -并行读取: +读取约束: -- 本次 change 的实际 artifacts;在 `spec-driven` 下通常包括 `proposal.md`、`design.md`、`tasks.md`、`specs/*.md` -- 当前会话中与本次变更相关的讨论、澄清、边界约束、非目标、待确认事项 -- 与本次变更直接相关的源码、测试、README、架构文档 +- 直接使用 Read 工具并行读取文件,禁止使用 subagent/Task 工具做文件读取和内容转发 +- 不原样输出文件内容,仅在步骤 2 输出审查结论 + +分步收集: + +a) 先并行读取核心入口和配置,确定范围: + +- 本次 change 的 `proposal.md` - `openspec/config.yaml` -- 现有 `openspec/specs/**/spec.md` 中与本次变更相关的规范,相关性来源包括:`proposal.md` 的 `Capabilities` / `Modified Capabilities`、讨论中提到的受影响能力、`design.md` / Impact 中提到的模块、相关代码对应的现有能力 + +b) 从 `proposal.md` 提取 `Capabilities` / `Modified Capabilities`,确定需要读取的 spec 列表;相关性来源还包括:讨论中提到的受影响能力、`design.md` Impact 中提到的模块、相关代码对应的现有能力 + +c) 并行读取已确定的 spec 和其余 artifacts(`design.md`、`tasks.md`、相关源码、测试、README、架构文档) 若当前上下文无法明确 change 或文档路径: @@ -48,8 +56,7 @@ - 讨论中已确定但文档未记录的内容 - 文档基于错误现状做出的设计或任务拆分 - 文档之间相互冲突的目标、方案、约束、任务 -- `proposal -> specs -> design -> tasks` 链路中的断点 -- `Modified Capabilities` 应更新但未更新的现有 spec +- `Modified Capabilities` 在本次 change 的 specs 中是否已更新(注意:仅更新 change 目录下的 specs,不动 `openspec/specs/`) 输出审查结果: @@ -90,7 +97,9 @@ - "讨论遗漏清单" 是否已清空或已标注保留原因 - "现实性问题清单" 是否已清空或已标注为预期变更 -- "文档冲突清单" 和 "OpenSpec 规范问题清单" 是否已清空 +- "文档冲突清单" 是否已清空 +- "OpenSpec 规范问题清单" 是否已清空 +- "待澄清清单" 是否已清空或已记录用户决策 ## 5. 收尾 diff --git a/openspec/config.yaml b/openspec/config.yaml index 4938a64..2b804b8 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -15,7 +15,7 @@ context: | - 前端严禁:组件内联style属性、CSS覆盖TD内部类名、使用!important、硬编码色值 - Git提交: 仅中文; 格式"类型: 简短描述", 类型: feat/fix/refactor/docs/style/test/chore; 多行描述空行后写详细说明 - 禁止创建git操作task - - 积极使用subagents精心设计并行任务,节省上下文空间,加速任务执行 + - 使用subagents处理计算密集或多步骤的并行任务(如代码实现、测试执行);文件读取直接使用Read工具并行调用,禁止用subagent转发文件内容 - 优先使用提问工具对用户进行提问 - (当前项目未上线,不需要考虑向前兼容) diff --git a/openspec/specs/test-output-cleanliness/spec.md b/openspec/specs/test-output-cleanliness/spec.md new file mode 100644 index 0000000..5729a4e --- /dev/null +++ b/openspec/specs/test-output-cleanliness/spec.md @@ -0,0 +1,29 @@ +# Test Output Cleanliness + +## Purpose + +确保测试运行时输出干净、无噪音,便于开发者快速定位问题。 + +## Requirements + +### Requirement: 测试不应产生无关 console 输出 +测试运行时,由测试用例预期的容错行为(如 JSON 解析失败、checker rejected)触发的 `console.warn` 输出 SHALL 在测试代码中被抑制,不污染测试报告。 + +#### Scenario: 容错测试抑制 console.warn +- **WHEN** 测试用例故意注入损坏数据或触发异常以验证系统容错行为 +- **THEN** 测试 SHALL 在执行前临时覆盖 `console.warn` 为空函数,在 finally 块中恢复原始函数 + +#### Scenario: 非预期 console.warn 不被抑制 +- **WHEN** 测试用例并非专门测试容错行为 +- **THEN** 测试 SHALL NOT 抑制 `console.warn`,确保意外 warn 可被观测 + +### Requirement: 探针执行失败日志输出单行消息 +ProbeEngine 在捕获 checker rejected 时,`console.warn` SHALL 输出单行错误消息文本,MUST NOT 输出 Error 对象(会导致多行堆栈噪音)。 + +#### Scenario: checker rejected 输出单行日志 +- **WHEN** checker 执行抛出未捕获异常(Promise rejected) +- **THEN** `console.warn` SHALL 输出格式为 `探针执行失败: ` 的单行文本,其中 message 使用 `formatReason()` 提取 + +#### Scenario: formatReason 复用 +- **WHEN** 构建失败日志消息和写入 CheckFailure +- **THEN** 两者 SHALL 共用同一个 `formatReason` 函数提取错误消息 diff --git a/openspec/specs/vite-frontend-bundling/spec.md b/openspec/specs/vite-frontend-bundling/spec.md index bc4c963..04fdb7b 100644 --- a/openspec/specs/vite-frontend-bundling/spec.md +++ b/openspec/specs/vite-frontend-bundling/spec.md @@ -20,7 +20,7 @@ - **THEN** `assets/` 目录下的 JS 和 CSS 文件名 SHALL 包含 content hash(如 `index-a1b2c3.js`) ### Requirement: Code Splitting 策略 -系统 SHALL 配置 Vite 的 Rolldown code splitting,将 vendor 库分离为独立 chunks。 +系统 SHALL 配置 Vite 的 Rolldown code splitting,将 vendor 库分离为独立 chunks,并通过 `React.lazy()` 动态导入实现按需加载。 #### Scenario: React 相关库分离 - **WHEN** Vite 构建完成 @@ -34,8 +34,16 @@ - **WHEN** Vite 构建完成 - **THEN** `recharts` 和 `d3-*` 相关模块 SHALL 被打包到名为 `vendor-chart` 的独立 chunk +#### Scenario: TargetDetailDrawer 延迟加载 +- **WHEN** Vite 构建完成 +- **THEN** `TargetDetailDrawer` 及其依赖(recharts、D3、DateRangePicker 等)SHALL 通过 `React.lazy()` 动态导入,被 Rolldown 自动拆分为异步 chunk,不包含在初始加载的 JS 中 + +#### Scenario: Drawer 首次渲染无闪烁 +- **WHEN** 用户首次点击目标触发 Drawer 渲染 +- **THEN** Drawer SHALL 通过 `` 包裹,利用其默认 visible=false 状态避免加载期间的视觉闪烁 + ### Requirement: CSS 处理 -系统 SHALL 通过 Vite 处理 CSS 导入,产出独立的 CSS 文件。 +系统 SHALL 通过 Vite 处理 CSS 导入,产出独立的 CSS 文件。TDesign 组件样式 SHALL 保持全量导入方式。 #### Scenario: CSS 文件产出 - **WHEN** Vite 构建完成 @@ -44,3 +52,7 @@ #### Scenario: CSS 压缩 - **WHEN** Vite 执行生产构建 - **THEN** 产出的 CSS 文件 SHALL 经过压缩处理 + +#### Scenario: TDesign CSS 全量导入 +- **WHEN** 前端入口文件初始化样式 +- **THEN** 系统 SHALL 通过 `tdesign-react/dist/reset.css` 和 `tdesign-react/dist/tdesign.min.css` 全量导入 TDesign 组件样式 diff --git a/src/server/checker/engine.ts b/src/server/checker/engine.ts index ee72bdc..6898ad9 100644 --- a/src/server/checker/engine.ts +++ b/src/server/checker/engine.ts @@ -70,7 +70,7 @@ export class ProbeEngine { this.writeResult(result.value); } else { const target = targets[index]; - console.warn("探针执行失败:", result.reason); + console.warn(`探针执行失败: ${formatReason(result.reason)}`); if (!target) continue; this.writeResult({ detail: null, diff --git a/src/web/app.tsx b/src/web/app.tsx index c7dd4fa..82f0669 100644 --- a/src/web/app.tsx +++ b/src/web/app.tsx @@ -1,16 +1,19 @@ import type { SkeletonProps } from "tdesign-react"; -import { useState } from "react"; +import { lazy, Suspense, useState } from "react"; import { Alert, Layout, Menu, RadioGroup, Skeleton } from "tdesign-react"; import { RefreshCountdown } from "./components/RefreshCountdown"; import { SummaryCards } from "./components/SummaryCards"; import { TargetBoard } from "./components/TargetBoard"; -import { TargetDetailDrawer } from "./components/TargetDetailDrawer"; import { useDashboard } from "./hooks/use-queries"; import { useTargetDetail } from "./hooks/use-target-detail"; import { type ThemePreference, useThemePreference } from "./hooks/use-theme-preference"; +const TargetDetailDrawer = lazy(() => + import("./components/TargetDetailDrawer").then((m) => ({ default: m.TargetDetailDrawer })), +); + const { Content, Header } = Layout; const DEFAULT_REFRESH_INTERVAL_MS = 30000; const DASHBOARD_SKELETON_ROW_COL: SkeletonProps["rowCol"] = [ @@ -122,20 +125,22 @@ export function App() { )} - + + + ); } diff --git a/tests/server/app.test.ts b/tests/server/app.test.ts index 4bc0530..9558f48 100644 --- a/tests/server/app.test.ts +++ b/tests/server/app.test.ts @@ -421,29 +421,35 @@ describe("API 路由", () => { }); test("损坏的 failure JSON 返回 null 而不崩溃", async () => { - const targets = store.getTargets(); - const t1Id = targets[0]!.id; + const originalWarn = console.warn; + console.warn = () => undefined; + try { + const targets = store.getTargets(); + const t1Id = targets[0]!.id; - store.insertCheckResult({ - durationMs: 100, - failure: { kind: "error", message: "test", path: "$", phase: "body" }, - matched: false, - observation: null, - targetId: t1Id, - timestamp: "2025-06-01T00:00:00.000Z", - }); + store.insertCheckResult({ + durationMs: 100, + failure: { kind: "error", message: "test", path: "$", phase: "body" }, + matched: false, + observation: null, + targetId: t1Id, + timestamp: "2025-06-01T00:00:00.000Z", + }); - (store as unknown as { db: { prepare: (sql: string) => { run: (...args: unknown[]) => void } } }).db - .prepare("UPDATE check_results SET failure = ? WHERE target_id = ? AND timestamp = ?") - .run("{invalid json!!!", t1Id, "2025-06-01T00:00:00.000Z"); + (store as unknown as { db: { prepare: (sql: string) => { run: (...args: unknown[]) => void } } }).db + .prepare("UPDATE check_results SET failure = ? WHERE target_id = ? AND timestamp = ?") + .run("{invalid json!!!", t1Id, "2025-06-01T00:00:00.000Z"); - const from = "2025-06-01T00:00:00.000Z"; - const to = "2025-06-01T23:59:59.999Z"; - const response = await fetch(`${baseUrl}/api/targets/${t1Id}/history?from=${from}&to=${to}`); - const body = (await response.json()) as HistoryResponse; + const from = "2025-06-01T00:00:00.000Z"; + const to = "2025-06-01T23:59:59.999Z"; + const response = await fetch(`${baseUrl}/api/targets/${t1Id}/history?from=${from}&to=${to}`); + const body = (await response.json()) as HistoryResponse; - expect(response.status).toBe(200); - expect(body.items).toHaveLength(1); - expect(body.items[0]!.failure).toBeNull(); + expect(response.status).toBe(200); + expect(body.items).toHaveLength(1); + expect(body.items[0]!.failure).toBeNull(); + } finally { + console.warn = originalWarn; + } }); }); diff --git a/tests/server/checker/engine.test.ts b/tests/server/checker/engine.test.ts index bd8f666..c9fa025 100644 --- a/tests/server/checker/engine.test.ts +++ b/tests/server/checker/engine.test.ts @@ -165,6 +165,8 @@ describe("ProbeEngine", () => { return originalExecute(target, ctx); }; + const originalWarn = console.warn; + console.warn = () => undefined; try { const rejectTarget = makeCommandTarget("reject-cmd"); const goodTarget = makeCommandTarget("good-cmd"); @@ -192,6 +194,7 @@ describe("ProbeEngine", () => { expect(results[1]!["targetId"]).toBe("good-cmd"); expect(results[1]!["matched"]).toBe(true); } finally { + console.warn = originalWarn; checker.execute = originalExecute; } }); diff --git a/tests/server/helpers.test.ts b/tests/server/helpers.test.ts index c55bbdb..ec02a04 100644 --- a/tests/server/helpers.test.ts +++ b/tests/server/helpers.test.ts @@ -143,8 +143,14 @@ describe("mapCheckResult", () => { }); test("损坏 observation JSON 返回 null observation", () => { - const result = mapCheckResult(makeRow({ observation: "{invalid json" }), "http"); - expect(result.detail).toBeNull(); - expect(result.observation).toBeNull(); + const originalWarn = console.warn; + console.warn = () => undefined; + try { + const result = mapCheckResult(makeRow({ observation: "{invalid json" }), "http"); + expect(result.detail).toBeNull(); + expect(result.observation).toBeNull(); + } finally { + console.warn = originalWarn; + } }); });