## Purpose 定义拨测调度引擎的行为:按 interval 分组定时、组内并发拨测、expect 结果校验和结果持久化。 ## Requirements ### Requirement: 按 interval 分组调度 系统 SHALL 将拨测目标按 interval 值分组,每组使用独立的定时器进行调度。 #### Scenario: 相同 interval 的目标共享定时器 - **WHEN** 多个 target 配置了相同的 interval(如 30s) - **THEN** 系统 SHALL 使用同一个 `setInterval` 定时器,每次 tick 并发拨测所有该组目标 #### Scenario: 不同 interval 的目标各自调度 - **WHEN** target A 配置 15s interval,target B 配置 30s interval - **THEN** 系统 SHALL 创建两个独立定时器,分别按各自频率调度 ### Requirement: 组内并发拨测 系统 SHALL 在每次调度 tick 时并发执行同组内目标的检查,但实际同时运行的检查数 MUST 受全局 `runtime.maxConcurrentChecks` 限制。 #### Scenario: 同组目标并发执行 - **WHEN** 调度器触发一次 tick,该组有 3 个目标,且全局并发余量至少为 3 - **THEN** 系统 SHALL 同时执行 3 个 checker,而非顺序执行 #### Scenario: 单个目标失败不影响同组其他目标 - **WHEN** 同组中某个目标的检查请求超时或失败 - **THEN** 其他目标的检查 SHALL 正常完成并记录结果 #### Scenario: 全局并发限制生效 - **WHEN** 调度器同时触发 10 个目标且 runtime.maxConcurrentChecks 为 3 - **THEN** 系统 MUST 同时最多运行 3 个检查,其余检查等待并发槽位释放 ### Requirement: HTTP 拨测执行 系统 SHALL 对 `type: http` 的目标执行 HTTP 请求,支持 GET、POST、PUT、DELETE、PATCH、HEAD 方法,并携带 `http.headers` 和 `http.body`。 #### Scenario: 执行 GET 请求 - **WHEN** 目标配置 method 为 GET - **THEN** 系统 SHALL 发送 GET 请求到目标 URL #### Scenario: 执行 POST 请求带 body - **WHEN** 目标配置 method 为 POST 且指定了 body 和 Content-Type header - **THEN** 系统 SHALL 发送带指定 body 的 POST 请求 #### Scenario: 携带自定义 headers - **WHEN** 目标配置了 headers(如 Authorization) - **THEN** 系统 SHALL 在请求中包含所有配置的 headers #### Scenario: HTTP body 读取上限 - **WHEN** HTTP response body 超过该 target 的 maxBodyBytes - **THEN** 系统 MUST 停止读取并记录 `matched=false` 和结构化输出超限错误 ### Requirement: 请求超时控制 系统 SHALL 对每次 checker 执行实施超时控制,超时时间使用目标配置的 timeout 值。 #### Scenario: HTTP 请求超时 - **WHEN** HTTP 请求在 timeout 时间内未收到响应 - **THEN** 系统 SHALL 中止该请求,记录为失败并标注超时错误 #### Scenario: command 执行超时 - **WHEN** command 进程在 timeout 时间内未退出 - **THEN** 系统 MUST 终止该子进程,记录为失败并标注超时错误 #### Scenario: 请求在超时前完成 - **WHEN** checker 在超时前完成执行 - **THEN** 系统 SHALL 正常记录执行结果并进入 expect 校验 ### Requirement: expect 校验 系统 SHALL 在 checker 执行完成后根据目标类型的 expect 配置校验观测结果,校验结果和首个失败原因记入 check result。 #### Scenario: HTTP 默认状态码 - **WHEN** HTTP target 未配置 `expect.status` - **THEN** 系统 SHALL 按默认 `status: [200]` 校验响应状态码 #### Scenario: 校验 HTTP 状态码 - **WHEN** HTTP target 配置了 `expect.status: [200, 201]` - **THEN** 系统 SHALL 检查响应状态码是否在列表中,将匹配结果记录到 matched 字段 #### Scenario: 校验 HTTP 响应头 - **WHEN** HTTP target 配置了 `expect.headers: {"Content-Type": {contains: "application/json"}}` - **THEN** 系统 SHALL 检查响应头是否符合指定规则,全部匹配时继续后续阶段 #### Scenario: 校验 HTTP 响应体 - **WHEN** HTTP target 配置了有序 `expect.body` 规则数组 - **THEN** 系统 SHALL 按数组顺序执行 body 规则,任一失败立即记录 failure 并停止后续规则 #### Scenario: command 默认 exitCode - **WHEN** command target 未配置 `expect.exitCode` - **THEN** 系统 SHALL 按默认 `exitCode: [0]` 校验命令退出码 #### Scenario: 校验 command stdout - **WHEN** command target 配置了有序 `expect.stdout` 规则数组 - **THEN** 系统 SHALL 按数组顺序执行 stdout 规则,任一失败立即记录 failure 并停止后续规则 #### Scenario: 校验耗时阈值 - **WHEN** 目标配置了 `expect.maxDurationMs` - **THEN** 系统 SHALL 检查实际 durationMs 是否超过阈值,将匹配结果记录到 matched 字段 #### Scenario: 多条 expect 规则 - **WHEN** 目标同时配置状态、duration、元数据和内容规则 - **THEN** 系统 SHALL 所有规则全部通过时 matched 为 true,任一不通过则为 false 并记录首个失败原因 ### Requirement: Body 校验按需解析 系统 SHALL 仅在 HTTP target 配置了 body 校验且 status、duration、headers 阶段均通过时才读取并解析响应体,避免不必要的读取和解析开销。 #### Scenario: status 失败时不读取 body - **WHEN** HTTP target 的 status 阶段不匹配 - **THEN** 系统 SHALL 立即返回 matched=false,且 MUST NOT 读取 response body #### Scenario: 仅配置 contains 时不解析 JSON - **WHEN** HTTP target 仅配置 body contains 规则而未配置 json/css/xpath 规则 - **THEN** 系统 SHALL 不执行 JSON.parse 或 HTML/XML 解析 #### Scenario: 配置 json 时解析 JSON 失败 - **WHEN** HTTP target 配置了 body json 规则但响应体不是合法 JSON - **THEN** 系统 SHALL 判定 matched 为 false,并记录 json 规则对应的 failure.path ### Requirement: 拨测结果记录 系统 SHALL 在每次 checker 完成后,将结果写入 SQLite 数据存储,包含 target_id、timestamp、matched、duration_ms、status_detail、failure 字段。 #### Scenario: 成功检查结果记录 - **WHEN** checker 成功执行且 expect 全部匹配 - **THEN** 系统 SHALL 记录 matched=true、duration_ms、status_detail,failure 为 null #### Scenario: 执行失败结果记录 - **WHEN** checker 执行失败(网络错误、超时、命令启动失败、输出超限等) - **THEN** 系统 SHALL 记录 matched=false、failure.kind="error" 和具体错误信息 #### Scenario: expect 不匹配结果记录 - **WHEN** checker 执行成功但 expect 不匹配 - **THEN** 系统 SHALL 记录 matched=false、failure.kind="mismatch" 和具体不匹配信息 ### Requirement: runner 选择 系统 SHALL 根据 target.type 选择对应 runner 执行检查。 #### Scenario: 选择 HTTP runner - **WHEN** target.type 为 `http` - **THEN** 系统 SHALL 使用 HTTP runner 执行该目标 #### Scenario: 选择 command runner - **WHEN** target.type 为 `command` - **THEN** 系统 SHALL 使用 command runner 执行该目标