- failure actual 截断格式改为 …(共 N 字符),标量不序列化直接返回 - 新增 redos.ts 实现 ReDoS 静态检测(嵌套量词/重叠交替),启动期拒绝危险正则 - JSON body rules 共享同一次 JSON.parse 结果,避免重复解析 - checkCssRule 重构为线性流程,消除 exist:true 与无 operator 的冗余分支 - extract checkEarlyTimeout 辅助函数,明确提前 duration 检查意图 - 补充 303/307/308 重定向、相对路径 Location、混合 body rules 集成测试
266 lines
14 KiB
Markdown
266 lines
14 KiB
Markdown
## Purpose
|
||
|
||
定义 HTTP 拨测中响应体校验方法集(contains/regex/json/css/xpath)、操作符系统和响应头校验的行为规范。
|
||
|
||
## Requirements
|
||
|
||
### Requirement: 响应体多种校验方法
|
||
系统 SHALL 支持对 HTTP 响应体进行五种可组合的校验方法:contains(子串)、regex(正则)、json(JSONPath)、css(CSS 选择器)、xpath(XPath)。这些方法 MUST 配置在 `expect.body` 有序数组中。
|
||
|
||
#### Scenario: contains 子串匹配
|
||
- **WHEN** HTTP target 配置 `expect.body: [{contains: "healthy"}]`,且响应体包含 `"healthy"`
|
||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||
|
||
#### Scenario: contains 不匹配
|
||
- **WHEN** HTTP target 配置 `expect.body: [{contains: "healthy"}]`,且响应体不包含该文本
|
||
- **THEN** 系统 SHALL 判定 matched 为 false,并记录该规则的 failure.path
|
||
|
||
#### Scenario: regex 正则匹配
|
||
- **WHEN** HTTP target 配置 `expect.body: [{regex: '"status"\\s*:\\s*"ok"'}]`,且响应体匹配该正则
|
||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||
|
||
#### Scenario: regex 不匹配
|
||
- **WHEN** HTTP target 配置 regex body 规则,且响应体不匹配该正则
|
||
- **THEN** 系统 SHALL 判定 matched 为 false,并记录该规则的 failure.path
|
||
|
||
#### Scenario: json JSONPath 等值匹配
|
||
- **WHEN** HTTP target 配置 `expect.body: [{json: {path: "$.status", equals: "ok"}}]`,且响应 JSON 中 `$.status` 值为 `"ok"`
|
||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||
|
||
#### Scenario: json JSONPath 值不匹配
|
||
- **WHEN** HTTP target 配置 json body 规则,且提取值不符合期望
|
||
- **THEN** 系统 SHALL 判定 matched 为 false,并记录包含 JSONPath 的 failure.path
|
||
|
||
#### Scenario: json 解析失败
|
||
- **WHEN** HTTP target 配置了 json body 规则但响应体不是合法 JSON
|
||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||
|
||
#### Scenario: css 选择器匹配
|
||
- **WHEN** HTTP target 配置 `expect.body: [{css: {selector: "div#health", equals: "OK"}}]`,且 HTML 中存在 `div#health` 元素文本为 `"OK"`
|
||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||
|
||
#### Scenario: css 选择器匹配属性值
|
||
- **WHEN** HTTP target 配置 css 规则带 `attr: "content"` 用于提取属性,且属性值匹配期望
|
||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||
|
||
#### Scenario: css 选择器无匹配元素
|
||
- **WHEN** HTTP target 配置了 css 选择器但 HTML 中无匹配元素
|
||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||
|
||
#### Scenario: xpath 表达式匹配
|
||
- **WHEN** HTTP target 配置 `expect.body: [{xpath: {path: "/root/status/text()", equals: "ok"}}]`,且 XML 中 `/root/status` 节点文本为 `"ok"`
|
||
- **THEN** 系统 SHALL 判定该 body 规则通过
|
||
|
||
#### Scenario: xpath 表达式无匹配节点
|
||
- **WHEN** HTTP target 配置了 xpath 表达式但 XML 中无匹配节点
|
||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||
|
||
### Requirement: 多种 body 校验方法 AND 组合
|
||
系统 SHALL 支持在 `expect.body` 数组中同时配置多种 body 校验方法,所有方法均通过时 matched 方为 true。
|
||
|
||
#### Scenario: 多种方法全部通过
|
||
- **WHEN** HTTP target 的 `expect.body` 数组依次配置 contains、json、regex,且全部通过
|
||
- **THEN** 系统 SHALL 判定 matched 为 true
|
||
|
||
#### Scenario: 多种方法任一失败
|
||
- **WHEN** HTTP target 的 `expect.body` 数组第一条 contains 不通过,后续还有 json 规则
|
||
- **THEN** 系统 SHALL 判定 matched 为 false,且不再检查后续 json 规则
|
||
|
||
### Requirement: 操作符系统
|
||
系统 SHALL 支持对提取值和文本值使用以下操作符进行比较:equals(默认等值)、contains(子串包含)、match(正则匹配)、empty(空值判断)、exists(存在性判断)、gte/lte/gt/lt(数值比较)。
|
||
|
||
#### Scenario: 标量值隐式 equals
|
||
- **WHEN** 配置的期望值为标量(字符串/数字/布尔/null),如 `equals: "ok"`
|
||
- **THEN** 系统 SHALL 使用 equals 操作符,对实际值做严格相等比较
|
||
|
||
#### Scenario: 显式 contains 操作符
|
||
- **WHEN** 配置 `{contains: "success"}`,且实际值包含 `"success"`
|
||
- **THEN** 系统 SHALL 判定该规则通过
|
||
|
||
#### Scenario: 显式 match 操作符
|
||
- **WHEN** 配置 `{match: '\\d+\\.\\d+\\.\\d+'}`,且实际值匹配该正则
|
||
- **THEN** 系统 SHALL 判定该规则通过
|
||
|
||
#### Scenario: empty 操作符判断为空
|
||
- **WHEN** 配置 `{empty: true}`,且实际值为空数组 `[]`
|
||
- **THEN** 系统 SHALL 判定该规则通过
|
||
|
||
#### Scenario: empty 操作符判断非空
|
||
- **WHEN** 配置 `{empty: false}`,且实际值为 `[1, 2]`
|
||
- **THEN** 系统 SHALL 判定该规则通过
|
||
|
||
#### Scenario: exists 操作符判断存在
|
||
- **WHEN** 配置 `{exists: false}`,且实际值不存在
|
||
- **THEN** 系统 SHALL 判定该规则通过
|
||
|
||
#### Scenario: gte 数值比较
|
||
- **WHEN** 配置 `{gte: 10}`,且实际值为 `15`(数字)
|
||
- **THEN** 系统 SHALL 判定该规则通过
|
||
|
||
#### Scenario: gt/lt 数值比较
|
||
- **WHEN** 配置 `{gt: 0, lt: 1000}`,且实际值为 `500`
|
||
- **THEN** 系统 SHALL 对同一字段进行多操作符复合比较,全部通过则该规则通过
|
||
|
||
### Requirement: 响应头校验
|
||
系统 SHALL 支持通过 `expect.headers` 配置对 HTTP 响应头进行键值规则校验,header 名称匹配 MUST 不区分大小写。
|
||
|
||
#### Scenario: 响应头匹配
|
||
- **WHEN** HTTP target 配置 `expect.headers: {"Content-Type": {contains: "application/json"}}`,且响应包含该 header 且值匹配
|
||
- **THEN** 系统 SHALL 判定 headers 阶段通过
|
||
|
||
#### Scenario: 响应头不匹配
|
||
- **WHEN** HTTP target 配置 `expect.headers: {"Content-Type": {equals: "application/json"}}`,且响应 header 值为 `"text/html"`
|
||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||
|
||
#### Scenario: 响应头缺失
|
||
- **WHEN** HTTP target 配置了某个 header 但响应中不存在该 header
|
||
- **THEN** 系统 SHALL 判定 matched 为 false
|
||
|
||
### Requirement: 结构化 expect 失败信息
|
||
系统 SHALL 在任一 expect 规则失败时生成结构化 failure,用于标识失败阶段、规则路径、期望值、实际值和可读错误信息。actual 值 SHALL 在构造时截断至不超过 200 字符,超出部分以省略标记和总字符数替代。expected 值不截断。
|
||
|
||
#### Scenario: body 规则失败信息
|
||
- **WHEN** HTTP target 的 `expect.body[1].json` 规则失败
|
||
- **THEN** failure SHALL 包含 kind=`mismatch`、phase=`body`、path 指向 `expect.body[1]`,并包含 message
|
||
|
||
#### Scenario: actual 值截断
|
||
- **WHEN** 失败规则的实际值为字符串且长度超过 200 字符
|
||
- **THEN** failure.actual SHALL 为前 200 字符加 `…(共 N 字符)` 后缀,其中 N 为原始总字符数
|
||
|
||
#### Scenario: actual 值未超限
|
||
- **WHEN** 失败规则的实际值为字符串且长度不超过 200 字符
|
||
- **THEN** failure.actual SHALL 保留完整原始值,不做截断
|
||
|
||
#### Scenario: actual 值为对象或数组
|
||
- **WHEN** 失败规则的实际值为对象或数组,且 JSON 序列化后长度超过 200 字符
|
||
- **THEN** failure.actual SHALL 为序列化后前 200 字符加 `…(共 N 字符)` 后缀
|
||
|
||
#### Scenario: actual 值为标量
|
||
- **WHEN** 失败规则的实际值为 number、boolean、null 或 undefined
|
||
- **THEN** failure.actual SHALL 保留原始值,不做截断
|
||
|
||
### Requirement: 状态码范围匹配
|
||
系统 SHALL 支持在 `expect.status` 数组中使用范围模式字符串(`"1xx"`、`"2xx"`、`"3xx"`、`"4xx"`、`"5xx"`),与精确数字混合使用。范围模式 SHALL 匹配对应百位段内的所有状态码。其他范围模式 SHALL 在启动期配置校验失败。
|
||
|
||
#### Scenario: 1xx 范围匹配
|
||
- **WHEN** HTTP target 配置 `expect.status: ["1xx"]`,且响应状态码为 101
|
||
- **THEN** 系统 SHALL 判定状态码匹配
|
||
|
||
#### Scenario: 2xx 范围匹配
|
||
- **WHEN** HTTP target 配置 `expect.status: ["2xx"]`,且响应状态码为 200
|
||
- **THEN** 系统 SHALL 判定状态码匹配
|
||
|
||
#### Scenario: 2xx 范围匹配 204
|
||
- **WHEN** HTTP target 配置 `expect.status: ["2xx"]`,且响应状态码为 204
|
||
- **THEN** 系统 SHALL 判定状态码匹配
|
||
|
||
#### Scenario: 2xx 范围不匹配 301
|
||
- **WHEN** HTTP target 配置 `expect.status: ["2xx"]`,且响应状态码为 301
|
||
- **THEN** 系统 SHALL 判定状态码不匹配
|
||
|
||
#### Scenario: 混合精确值与范围模式
|
||
- **WHEN** HTTP target 配置 `expect.status: ["2xx", 301]`,且响应状态码为 301
|
||
- **THEN** 系统 SHALL 判定状态码匹配(精确值 301 匹配)
|
||
|
||
#### Scenario: 混合精确值与范围模式范围命中
|
||
- **WHEN** HTTP target 配置 `expect.status: ["2xx", 301]`,且响应状态码为 204
|
||
- **THEN** 系统 SHALL 判定状态码匹配(2xx 范围命中)
|
||
|
||
#### Scenario: 5xx 范围匹配
|
||
- **WHEN** HTTP target 配置 `expect.status: ["5xx"]`,且响应状态码为 503
|
||
- **THEN** 系统 SHALL 判定状态码匹配
|
||
|
||
#### Scenario: 非 HTTP 范围模式启动失败
|
||
- **WHEN** HTTP target 配置 `expect.status: ["6xx"]`
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败
|
||
|
||
### Requirement: HTTP expect 规则启动期校验
|
||
系统 SHALL 在启动期校验 HTTP expect 中已支持字段的类型、格式、未知字段和可编译表达式。HTTP expect、body rule、json/css/xpath rule 和 operator 对象中的未知字段 SHALL 导致启动期配置失败。每个 body rule 对象 MUST 恰好包含 contains、regex、json、css、xpath 中的一种规则类型。纯 operator 对象 MUST 至少包含一个已知 operator;body 提取规则可以不配置 operator,并以路径、元素或节点存在作为通过语义。`equals` operator SHALL 支持任意 JSON value,包括数组和对象。系统 SHALL 在启动期对 regex body 规则和 match operator 的正则表达式进行 ReDoS 安全检测,含有嵌套量词等危险模式的正则 SHALL 导致启动期配置失败。
|
||
|
||
#### Scenario: body rule 使用 regex 字段
|
||
- **WHEN** HTTP target 配置 `expect.body: [{regex: "ok|healthy"}]` 且 regex 可编译且无 ReDoS 风险
|
||
- **THEN** 系统 SHALL 接受该配置,并在运行期按 regex body 规则匹配响应体
|
||
|
||
#### Scenario: body rule 不支持 match 字段
|
||
- **WHEN** HTTP target 配置 `expect.body: [{match: "ok"}]` 且该规则没有 contains、regex、json、css、xpath 任一支持字段
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败
|
||
|
||
#### Scenario: body rule 忽略未知字段 → body rule 未知字段启动失败
|
||
- **WHEN** HTTP target 配置 `expect.body: [{contains: "ok", note: "ignored"}]`
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示 `note` 是未知字段
|
||
|
||
#### Scenario: body rule 多支持字段非法
|
||
- **WHEN** HTTP target 的同一条 body rule 同时配置 contains 和 regex
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败
|
||
|
||
#### Scenario: operator match 正则非法
|
||
- **WHEN** HTTP target 的 expect.headers、json、css 或 xpath operator 配置了不可编译的 match 正则
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败
|
||
|
||
#### Scenario: operator 数值比较类型非法
|
||
- **WHEN** HTTP target 的 expect operator 配置 gt、gte、lt 或 lte,且对应值不是有限数字
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败
|
||
|
||
#### Scenario: operator 布尔类型非法
|
||
- **WHEN** HTTP target 的 expect operator 配置 empty 或 exists,且对应值不是布尔值
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败
|
||
|
||
#### Scenario: JSONPath 子集非法
|
||
- **WHEN** HTTP target 的 json body rule path 不符合系统支持的 JSONPath 子集
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败
|
||
|
||
#### Scenario: operator 未知字段非法
|
||
- **WHEN** HTTP target 的 expect operator 配置了 `foo: "bar"` 等未知 operator 字段
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败
|
||
|
||
#### Scenario: equals 支持对象
|
||
- **WHEN** HTTP target 配置 `expect.body: [{json: {path: "$.payload", equals: {status: "ok"}}}]`
|
||
- **THEN** 系统 SHALL 接受该配置,并在运行期使用深度相等比较提取值和对象期望
|
||
|
||
#### Scenario: equals 支持数组
|
||
- **WHEN** HTTP target 配置 `expect.body: [{json: {path: "$.items", equals: ["a", "b"]}}]`
|
||
- **THEN** 系统 SHALL 接受该配置,并在运行期使用深度相等比较提取值和数组期望
|
||
|
||
#### Scenario: 纯 operator 对象不能为空
|
||
- **WHEN** HTTP target 的 `expect.headers` 中某个 header 期望配置为空对象 `{}`
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败,要求显式配置至少一个 operator
|
||
|
||
#### Scenario: json rule 允许存在性语义
|
||
- **WHEN** HTTP target 配置 `expect.body: [{json: {path: "$.status"}}]`
|
||
- **THEN** 系统 SHALL 接受该配置,并在运行期以 JSONPath 值存在作为通过语义
|
||
|
||
#### Scenario: css rule 未知字段非法
|
||
- **WHEN** HTTP target 配置 `expect.body: [{css: {selector: "h1", unknown: true}}]`
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示未知字段
|
||
|
||
#### Scenario: xpath rule 未知字段非法
|
||
- **WHEN** HTTP target 配置 `expect.body: [{xpath: {path: "/html/body", unknown: true}}]`
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示未知字段
|
||
|
||
#### Scenario: regex body 规则含嵌套量词启动失败
|
||
- **WHEN** HTTP target 配置 `expect.body: [{regex: "(a+)+$"}]`
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示正则存在 ReDoS 风险
|
||
|
||
#### Scenario: match operator 含嵌套量词启动失败
|
||
- **WHEN** HTTP target 的 expect operator 配置 `{match: "(\\d+)*x"}`
|
||
- **THEN** 系统 SHALL 在启动期配置校验失败,提示正则存在 ReDoS 风险
|
||
|
||
#### Scenario: 安全正则通过校验
|
||
- **WHEN** HTTP target 配置 `expect.body: [{regex: "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"}]`
|
||
- **THEN** 系统 SHALL 接受该配置(无嵌套量词,无 ReDoS 风险)
|
||
|
||
### Requirement: HTTP body 运行期失败结构化
|
||
系统 SHALL 将 HTTP body 运行期失败记录为结构化 CheckFailure,并保留与具体规则相关的 phase 和 path。响应内容不符合配置 SHALL 记录为 mismatch;响应内容无法按配置解析或解码 SHALL 记录为 error。
|
||
|
||
#### Scenario: JSON 响应不是合法 JSON
|
||
- **WHEN** HTTP target 配置 json body rule,但响应体不是合法 JSON
|
||
- **THEN** 系统 SHALL 记录 `failure.kind="error"`、`failure.phase="body"`,且 failure.path SHALL 指向对应 json 规则
|
||
|
||
#### Scenario: CSS selector 无匹配元素
|
||
- **WHEN** HTTP target 配置 css body rule,但响应 HTML 中无匹配元素
|
||
- **THEN** 系统 SHALL 记录 `failure.kind="mismatch"`、`failure.phase="body"`,且 failure.path SHALL 指向对应 css 规则
|
||
|
||
#### Scenario: XPath 无匹配节点
|
||
- **WHEN** HTTP target 配置 xpath body rule,但响应 XML/HTML 中无匹配节点
|
||
- **THEN** 系统 SHALL 记录 `failure.kind="mismatch"`、`failure.phase="body"`,且 failure.path SHALL 指向对应 xpath 规则
|