16 KiB
Purpose
定义共享 expect 断言规则系统的核心概念和基础设施:ValueMatcher 统一匹配器、ContentRules 内容规则数组、KeyValueExpect 键值规则、以及相关的启动期校验和失败路径规范。
Requirements
Requirement: ValueMatcher 统一匹配器
系统 SHALL 提供共享 ValueMatcher 作为所有非状态类 expect 的基础匹配结构。ValueMatcher SHALL 支持 equals、contains、regex、exists、empty、gt、gte、lt 和 lte 字段。equals MUST 支持任意 JSON value,并使用深度相等比较。contains 和 regex SHALL 将实际值转换为字符串后匹配。gt、gte、lt 和 lte SHALL 将实际值转换为有限数字后比较,无法转换为有限数字时 SHALL 判定不匹配。一个 ValueMatcher 对象包含多个 matcher 字段时,系统 SHALL 要求全部 matcher 均通过。
所有类型为 ValueMatcher 的 expect 字段 SHALL 同时接受 primitive 原始值(string / number / boolean / null)作为简写形式。原始值简写 SHALL 等价于 { equals: value }。系统 SHALL 在语义校验入口将 primitive 原始值归一化为 { equals: value } 对象形式,后续 resolve 和运行期逻辑 SHALL 仅处理 ValueMatcher 对象形式。数组和对象 MUST NOT 作为原始值简写;需要对数组或对象执行 equals 匹配时,配置 MUST 显式写成 { equals: value }。
Scenario: equals 匹配对象
- WHEN 实际值为
{status: "ok", count: 1}且 matcher 为{equals: {status: "ok", count: 1}} - THEN 系统 SHALL 使用深度相等判定该 matcher 通过
Scenario: contains 字符串化匹配
- WHEN 实际值为
"service ready"且 matcher 为{contains: "ready"} - THEN 系统 SHALL 判定该 matcher 通过
Scenario: 数字范围组合匹配
- WHEN 实际值为
50且 matcher 为{gte: 0, lte: 100} - THEN 系统 SHALL 判定该 matcher 通过
Scenario: 多 matcher 快速失败
- WHEN 实际值为
"healthy"且 matcher 为{contains: "health", regex: "^ready$"} - THEN 系统 SHALL 在任一 matcher 不满足时判定该 matcher 对象不通过
Scenario: 字符串原始值简写等价 equals
- WHEN expect 字段配置为
finishReason: "stop"且实际值为"stop" - THEN 系统 SHALL 将
"stop"归一化为{equals: "stop"}并判定通过
Scenario: 数字原始值简写等价 equals
- WHEN expect 字段配置为
rowCount: 1且实际值为1 - THEN 系统 SHALL 将
1归一化为{equals: 1}并判定通过
Scenario: 布尔原始值简写等价 equals
- WHEN expect 字段配置为 ValueMatcher 类型且值为
true,实际值为true - THEN 系统 SHALL 将
true归一化为{equals: true}并判定通过
Scenario: null 原始值简写等价 equals
- WHEN expect 字段配置为 ValueMatcher 类型且值为
null,实际值为null - THEN 系统 SHALL 将
null归一化为{equals: null}并判定通过
Scenario: 原始值简写不匹配
- WHEN expect 字段配置为
finishReason: "stop"且实际值为"error" - THEN 系统 SHALL 判定不通过并生成 mismatch failure
Requirement: ValueMatcher 启动期校验
系统 SHALL 在启动期对所有 ValueMatcher 字段执行严格的类型和语义校验。校验 SHALL 同时接受 primitive 原始值和 ValueMatcher 对象两种形式。当输入为 primitive 原始值时,系统 SHALL 视为合法配置(等价于 {equals: value}),无需进一步校验 matcher 字段。当输入为 ValueMatcher 对象时,contains MUST 为 string。equals MAY 为任意 JSON value。exists 和 empty MUST 为 boolean。gt、gte、lt 和 lte MUST 为有限数字(Number.isFinite)。regex MUST 为可编译的 string pattern,并通过 ReDoS 风险校验。ValueMatcher 对象 MUST 至少包含一个合法 matcher 字段,空对象 {} SHALL 导致启动期配置错误。ValueMatcher 对象 MUST NOT 包含未知字段,任何不属于 equals、contains、regex、exists、empty、gt、gte、lt、lte 的字段 SHALL 导致启动期配置错误。
Scenario: 空 matcher 对象被拒绝
- WHEN YAML 配置中任一 matcher 对象为空
{} - THEN 系统 SHALL 在启动期配置校验失败,提示 matcher 必须包含至少一个合法字段
Scenario: 未知 matcher 字段被拒绝
- WHEN YAML 配置中任一 matcher 对象包含
foo: "bar"等未知字段 - THEN 系统 SHALL 在启动期配置校验失败,提示该字段未知
Scenario: 数值 matcher 非有限数字被拒绝
- WHEN YAML 配置中任一 matcher 的
gt、gte、lt或lte值为NaN、Infinity或非数字类型 - THEN 系统 SHALL 在启动期配置校验失败,提示数值 matcher 必须为有限数字
Scenario: 布尔 matcher 非布尔值被拒绝
- WHEN YAML 配置中任一 matcher 的
exists或empty值不是布尔类型 - THEN 系统 SHALL 在启动期配置校验失败,提示该字段必须为布尔值
Scenario: 字符串原始值校验通过
- WHEN YAML 配置中 ValueMatcher 字段值为字符串
"stop" - THEN 系统 SHALL 接受该配置,视为
{equals: "stop"}
Scenario: 数字原始值校验通过
- WHEN YAML 配置中 ValueMatcher 字段值为数字
5000 - THEN 系统 SHALL 接受该配置,视为
{equals: 5000}
Scenario: null 原始值校验通过
- WHEN YAML 配置中 ValueMatcher 字段值为
null - THEN 系统 SHALL 接受该配置,视为
{equals: null}
Scenario: 数组原始值被拒绝
- WHEN YAML 配置中 ValueMatcher 字段值为数组
[1, 2] - THEN 系统 SHALL 在启动期配置校验失败,提示必须为 primitive 原始值或 matcher 对象;如需数组 equals 匹配应写成
{equals: [1, 2]}
Scenario: 对象原始值必须显式 equals
- WHEN YAML 配置中 ValueMatcher 字段值为对象
{foo: "bar"},且foo不是合法 matcher 字段 - THEN 系统 SHALL 在启动期配置校验失败,提示
foo是未知 matcher;如需对象 equals 匹配应写成{equals: {foo: "bar"}}
Requirement: empty matcher 语义
empty: true SHALL 在以下情况判定通过:实际值为 null、undefined、空字符串 ""、空数组 [] 或空对象 {}。empty: false SHALL 在以上条件均不满足时判定通过。数字 0 和布尔 false SHALL NOT 被视为 empty。
Scenario: null 视为 empty
- WHEN 实际值为
null且 matcher 为{empty: true} - THEN 系统 SHALL 判定该 matcher 通过
Scenario: 空字符串视为 empty
- WHEN 实际值为
""且 matcher 为{empty: true} - THEN 系统 SHALL 判定该 matcher 通过
Scenario: 空数组视为 empty
- WHEN 实际值为
[]且 matcher 为{empty: true} - THEN 系统 SHALL 判定该 matcher 通过
Scenario: 空对象视为 empty
- WHEN 实际值为
{}且 matcher 为{empty: true} - THEN 系统 SHALL 判定该 matcher 通过
Scenario: 数字 0 不视为 empty
- WHEN 实际值为
0且 matcher 为{empty: true} - THEN 系统 SHALL 判定该 matcher 不通过
Scenario: 布尔 false 不视为 empty
- WHEN 实际值为
false且 matcher 为{empty: true} - THEN 系统 SHALL 判定该 matcher 不通过
Requirement: exists 与其他 matcher 的组合语义
当 ValueMatcher 同时包含 exists: false 和其他非存在性 matcher(如 contains、regex、equals 等)时,系统 SHALL 在启动期配置校验失败,提示 exists: false 不能与其他 matcher 组合使用。exists: true MAY 与其他 matcher 组合,语义为先确认存在再执行其他 matcher。
Scenario: exists false 与 contains 组合被拒绝
- WHEN YAML 配置中 matcher 为
{exists: false, contains: "foo"} - THEN 系统 SHALL 在启动期配置校验失败,提示
exists: false不能与其他 matcher 组合
Scenario: exists true 与 contains 组合允许
- WHEN 实际值为
"hello foo"且 matcher 为{exists: true, contains: "foo"} - THEN 系统 SHALL 判定该 matcher 通过
Requirement: regex 字段语义
系统 SHALL 使用 regex 作为唯一正则 matcher 字段。regex 值 MUST 为可编译的字符串 pattern。运行期 SHALL 固定使用无 flags 的 new RegExp(pattern).test(String(actual)) 执行匹配。系统 MUST NOT 支持旧 match 字段。系统 SHALL 在启动期对所有 regex pattern 执行可编译校验和 ReDoS 风险校验。
Scenario: regex 任意位置匹配
- WHEN 实际值为
"api status ok"且 matcher 为{regex: "status"} - THEN 系统 SHALL 判定该 matcher 通过,因为无 flags 的 JavaScript 正则仍会搜索整个字符串中的第一次匹配
Scenario: regex 完整匹配由用户声明锚点
- WHEN 实际值为
"OK\n"且 matcher 为{regex: "^OK$"} - THEN 系统 SHALL 判定该 matcher 不通过,因为系统 MUST NOT 默认启用 multiline flags
Scenario: match 字段启动失败
- WHEN YAML 配置中任一 matcher 对象包含
match: "ok" - THEN 系统 SHALL 在启动期配置校验失败,提示
match是未知字段或不支持字段
Scenario: regex ReDoS 风险启动失败
- WHEN YAML 配置中任一
regex为"(a+)+$" - THEN 系统 SHALL 在启动期配置校验失败,提示正则存在 ReDoS 风险
Requirement: ContentRules 内容规则数组
系统 SHALL 提供共享 ContentRules 表达返回内容断言。ContentRules MUST 为有序数组,数组项 SHALL 为直接 ValueMatcher,或 json、css、xpath 三类 extractor 规则之一。系统 SHALL 按数组顺序执行全部规则,任一规则失败时 SHALL 立即停止并返回该规则的 failure。系统 MUST NOT 支持内容字段的非数组对象快捷写法。
Scenario: 直接 matcher 内容规则
- WHEN 内容字段配置
[{contains: "ready"}, {regex: "listening on \\d+"}]且原始内容同时满足两条规则 - THEN 系统 SHALL 判定该内容字段通过
Scenario: 内容规则数组快速失败
- WHEN 内容字段配置三条规则且第二条规则失败
- THEN 系统 SHALL 返回第二条规则的 failure,并 MUST NOT 执行第三条规则
Scenario: 内容字段必须为数组
- WHEN YAML 中内容字段配置为
{contains: "ok"}而不是数组 - THEN 系统 SHALL 在启动期配置校验失败,提示该内容字段必须为规则数组
Requirement: ContentRule 互斥性约束
一条 ContentRule MUST 为直接 ValueMatcher 或恰好一个 extractor(json、css、xpath 之一)。系统 MUST NOT 允许同一条规则同时包含多个 extractor。直接 ValueMatcher 规则 MUST NOT 包含 json、css、xpath 字段。系统 SHALL 在启动期对违反互斥性的规则报错。
Scenario: 多 extractor 被拒绝
- WHEN YAML 中内容规则为
{json: {path: "$.a"}, css: {selector: "div"}} - THEN 系统 SHALL 在启动期配置校验失败,提示一条规则不能同时包含多个 extractor
Scenario: 直接 matcher 混入 extractor 被拒绝
- WHEN YAML 中内容规则为
{contains: "ok", json: {path: "$.a"}} - THEN 系统 SHALL 在启动期配置校验失败,提示直接 matcher 不能与 extractor 混用
Requirement: 空 ContentRules 数组语义
ContentRules 空数组 [] SHALL 被系统接受为合法配置。运行期空数组 SHALL 等价于无规则,即该内容字段的断言直接通过。
Scenario: 空 body 数组通过
- WHEN HTTP target 配置
expect.body: []且响应体为任意内容 - THEN 系统 SHALL 判定 body 阶段通过
Requirement: ContentRules 非字符串值序列化
当 ContentRules 的观测源为非字符串值(如对象或数组)时,直接 ValueMatcher 的 contains 和 regex SHALL 先将值 JSON 序列化为字符串后匹配。equals SHALL 直接在原始结构化值上使用深度相等比较,不进行序列化。
Scenario: 对象序列化后 contains 匹配
- WHEN ContentRules 观测源为
{status: "ok"}且规则为{contains: "ok"} - THEN 系统 SHALL 将对象 JSON 序列化后执行 contains 匹配
Scenario: 对象 equals 不序列化
- WHEN ContentRules 观测源为
{status: "ok"}且规则为{equals: {status: "ok"}} - THEN 系统 SHALL 直接在结构化值上使用深度相等比较
Requirement: ContentRules 提取器
系统 SHALL 支持在 ContentRules 中使用 json、css 和 xpath extractor。json.path MUST 使用现有 JSONPath 子集。css.selector MUST 为非空字符串,并 MAY 配置 attr 提取属性值。xpath.path MUST 为非空字符串,并 SHALL 在启动期进行可编译校验。Extractor 内部 MAY 包含任意 ValueMatcher 字段。Extractor 规则未配置任何 matcher 时 SHALL 等价于 exists: true。
Scenario: json extractor 数字比较
- WHEN 原始内容为 JSON 字符串
{"count": 2}且规则为{json: {path: "$.count", gte: 1}} - THEN 系统 SHALL 解析 JSON、提取
$.count并判定该规则通过
Scenario: json extractor 存在性默认语义
- WHEN 原始内容为 JSON 字符串
{"user": {"id": null}}且规则为{json: {path: "$.user.id"}} - THEN 系统 SHALL 将该规则视为
{json: {path: "$.user.id", exists: true}}并判定通过
Scenario: css attr 存在性默认语义
- WHEN 原始内容包含
<meta name="status" content="ok">且规则为{css: {selector: "meta[name=status]", attr: "content"}} - THEN 系统 SHALL 在属性存在时判定该规则通过
Scenario: xpath 无匹配节点失败
- WHEN XML 内容中不存在 XPath 指向的节点,且规则为
{xpath: {path: "/root/status"}} - THEN 系统 SHALL 判定该规则不通过并生成 phase 对应内容字段的 mismatch failure
Requirement: KeyValueExpect 键值规则
系统 SHALL 提供共享 KeyValueExpect 表达键值型观测值断言。KeyValueExpect SHALL 为动态键对象,每个键对应的值 MAY 为 ValueMatcher 或 JSON 字面量。字面量值 SHALL 等价于 {equals: <literal>}。调用方 MAY 指定 key 规范化策略;HTTP 与 LLM headers MUST 使用大小写不敏感的 key 匹配。
Scenario: headers 字面量快捷写法
- WHEN 响应 headers 中
content-type为application/json,且配置为headers: {Content-Type: "application/json"} - THEN 系统 SHALL 按大小写不敏感 key 匹配并使用 equals 语义判定通过
Scenario: headers matcher 写法
- WHEN 响应 headers 中
content-type为application/json; charset=utf-8,且配置为headers: {Content-Type: {contains: "application/json"}} - THEN 系统 SHALL 判定该 header 规则通过
Scenario: 缺失键 exists false
- WHEN 观测键值表中不存在
x-debug,且配置为{x-debug: {exists: false}} - THEN 系统 SHALL 判定该键规则通过
Requirement: 结构化失败路径
系统 SHALL 在共享 matcher、content 和 key-value 断言失败时生成结构化 CheckFailure。failure SHALL 包含 kind、phase、path、message,并在 mismatch 场景包含 expected 和 actual。内容规则 failure path SHALL 包含数组下标,key-value failure path SHALL 包含键名,extractor failure path SHALL 包含 extractor 类型和 path/selector 信息。
Scenario: ContentRules 失败路径
- WHEN
expect.body[1].json规则失败 - THEN failure.path SHALL 指向
body[1].json($.path)或等价可定位路径,failure.phase SHALL 为body
Scenario: KeyValueExpect 失败路径
- WHEN
expect.headers.Content-Type不匹配 - THEN failure.path SHALL 指向
headers.Content-Type,failure.phase SHALL 为headers
Scenario: actual 截断
- WHEN matcher 失败时 actual 字符串长度超过 200 字符
- THEN 系统 SHALL 使用现有截断策略保存 failure.actual,避免历史记录写入过长内容