137 lines
6.5 KiB
Markdown
137 lines
6.5 KiB
Markdown
## Purpose
|
||
|
||
定义后端代码中 es-toolkit 和 Bun 内置 API 的使用规范:类型判断、空值检测、深度比较、错误判断、并发控制、集合分组和 Web API 标准方法,替代手写实现落实库使用优先级规则。
|
||
|
||
## Requirements
|
||
|
||
|
||
### Requirement: 使用 es-toolkit 进行类型判断
|
||
系统 SHALL 使用 es-toolkit 的 `isPlainObject` 替代手写的对象类型判断函数,用于 expect 校验中区分纯值(原始值)和操作符对象。
|
||
|
||
#### Scenario: 识别纯对象为操作符
|
||
- **WHEN** body 校验规则中 expected 配置为 `{ equals: "value" }`(纯对象操作符)
|
||
- **THEN** `isPlainObject(expected)` SHALL 返回 true,系统按操作符语义处理
|
||
|
||
#### Scenario: 排除非纯对象作为操作符
|
||
- **WHEN** body 校验规则中 expected 为原始值如 `"value"` 或数字 `200`
|
||
- **THEN** `isPlainObject(expected)` SHALL 返回 false,系统按 equals 默认操作符处理
|
||
|
||
### Requirement: 使用 es-toolkit 进行空值检测
|
||
系统 SHALL 使用 es-toolkit 的 `isNil` 替代手写的 `actual === null || actual === undefined` 检测,用于 expect 中 `empty` 操作符的空值判断。
|
||
|
||
#### Scenario: null 值判定为空
|
||
- **WHEN** 校验值为 null
|
||
- **THEN** `isNil(null)` SHALL 返回 true
|
||
|
||
#### Scenario: undefined 值判定为空
|
||
- **WHEN** 校验值为 undefined
|
||
- **THEN** `isNil(undefined)` SHALL 返回 true
|
||
|
||
#### Scenario: 非空值判定为非空
|
||
- **WHEN** 校验值为 0、"false"、空数组 `[]` 等非 nil 值
|
||
- **THEN** `isNil(value)` SHALL 返回 false
|
||
|
||
### Requirement: 使用 es-toolkit 进行空对象检测
|
||
系统 SHALL 使用 es-toolkit 的 `isEmptyObject` 替代手写的 `typeof actual === "object" && Object.keys(actual).length === 0` 检测,用于 expect 中 `empty` 操作符的空对象判断。
|
||
|
||
#### Scenario: 空对象判定为空
|
||
- **WHEN** 校验值为 `{}`
|
||
- **THEN** `isEmptyObject({})` SHALL 返回 true
|
||
|
||
#### Scenario: 非空对象判定为非空
|
||
- **WHEN** 校验值为 `{ key: "val" }`
|
||
- **THEN** `isEmptyObject({ key: "val" })` SHALL 返回 false
|
||
|
||
#### Scenario: null 不是空对象
|
||
- **WHEN** 校验值为 null
|
||
- **THEN** `isEmptyObject(null)` SHALL 返回 false(空值由 isNil 前置处理)
|
||
|
||
### Requirement: 使用 es-toolkit 进行深度相等比较
|
||
系统 SHALL 使用 es-toolkit 的 `isEqual` 替代 `!==` 浅比较,用于 expect 中 `equals` 操作符的值比较,支持对象和数组的深度比较。
|
||
|
||
#### Scenario: 原始值浅比较
|
||
- **WHEN** expected 和 actual 均为原始值(字符串、数字、布尔值、null)
|
||
- **THEN** `isEqual(actual, expected)` 的行为 SHALL 与 `actual === expected` 一致
|
||
|
||
#### Scenario: 对象深度比较
|
||
- **WHEN** expected 和 actual 均为对象(如从 JSONPath 提取的结构化数据)
|
||
- **THEN** `isEqual(actual, expected)` SHALL 递归比较所有属性值,而非引用比较
|
||
|
||
### Requirement: 使用 es-toolkit 进行错误类型判断
|
||
系统 SHALL 使用 es-toolkit 的 `isError` 替代 `error instanceof Error`,用于 HTTP runner 和 cmd runner 中的错误类型判断。
|
||
|
||
#### Scenario: Error 实例识别
|
||
- **WHEN** 错误对象为 `new Error("msg")`
|
||
- **THEN** `isError(error)` SHALL 返回 true
|
||
|
||
#### Scenario: Error 子类识别
|
||
- **WHEN** 错误对象为继承 Error 的自定义类型
|
||
- **THEN** `isError(error)` SHALL 返回 true
|
||
|
||
#### Scenario: 非 Error 对象识别
|
||
- **WHEN** 错误对象为字符串或普通对象
|
||
- **THEN** `isError(error)` SHALL 返回 false
|
||
|
||
### Requirement: 使用 es-toolkit Semaphore 实现并发控制
|
||
系统 SHALL 使用 es-toolkit 的 `Semaphore` 类替代手写的信号量实现(计数器 + Promise 队列),用于 ProbeEngine 中的组内并发拨测控制。
|
||
|
||
#### Scenario: 获取并发槽位
|
||
- **WHEN** 当前并发数未达上限
|
||
- **THEN** `semaphore.acquire()` SHALL 立即返回,不阻塞
|
||
|
||
#### Scenario: 等待并发槽位
|
||
- **WHEN** 当前并发数已达上限 maxConcurrentChecks
|
||
- **THEN** `semaphore.acquire()` SHALL 阻塞等待,直到其他任务调用 `semaphore.release()`
|
||
|
||
#### Scenario: 释放并发槽位
|
||
- **WHEN** 调用 `semaphore.release()`
|
||
- **THEN** 系统 SHALL 唤醒一个等待中的 acquire() 调用
|
||
|
||
### Requirement: 使用 es-toolkit groupBy 实现 target 分组
|
||
系统 SHALL 使用 es-toolkit 的 `groupBy` 函数替代手写的 Map 循环分组,用于 ProbeEngine 中按 interval 分组拨测目标。
|
||
|
||
#### Scenario: 按 interval 分组
|
||
- **WHEN** 输入包含不同 intervalMs 值的多个 target
|
||
- **THEN** `groupBy(targets, t => t.intervalMs)` SHALL 返回 key 为 intervalMs 值的分组对象,值为对应 target 数组
|
||
|
||
### Requirement: 使用原生 API 进行数组类型判断
|
||
系统 SHALL 使用原生 `Array.isArray()` 替代 `es-toolkit/compat` 的 `isArray`,用于 checker 模块中所有数组类型判断场景。
|
||
|
||
#### Scenario: 数组值判定为数组
|
||
- **WHEN** 校验值为数组(如 `[1, 2, 3]`)
|
||
- **THEN** `Array.isArray(value)` SHALL 返回 true
|
||
|
||
#### Scenario: 非数组值判定为非数组
|
||
- **WHEN** 校验值为对象 `{}`、字符串 `"abc"`、数字 `123`、null 等
|
||
- **THEN** `Array.isArray(value)` SHALL 返回 false
|
||
|
||
### Requirement: 使用原生 API 进行对象类型判断
|
||
系统 SHALL 使用原生 `typeof x === 'object' && x !== null` 替代 `es-toolkit/compat` 的 `isObject`,用于 checker 模块中需要判断值为对象类型(排除 null)的场景。
|
||
|
||
#### Scenario: 普通对象判定为对象
|
||
- **WHEN** 值为普通对象 `{ key: "val" }`
|
||
- **THEN** `typeof value === 'object' && value !== null` SHALL 返回 true
|
||
|
||
#### Scenario: 数组判定为对象
|
||
- **WHEN** 值为数组 `[1, 2, 3]`
|
||
- **THEN** `typeof value === 'object' && value !== null` SHALL 返回 true
|
||
|
||
#### Scenario: Headers 实例判定为对象
|
||
- **WHEN** 值为 `Headers` 实例
|
||
- **THEN** `typeof value === 'object' && value !== null` SHALL 返回 true
|
||
|
||
#### Scenario: null 判定为非对象
|
||
- **WHEN** 值为 null
|
||
- **THEN** `typeof value === 'object' && value !== null` SHALL 返回 false
|
||
|
||
#### Scenario: 原始值判定为非对象
|
||
- **WHEN** 值为字符串、数字、布尔值、undefined
|
||
- **THEN** `typeof value === 'object' && value !== null` SHALL 返回 false
|
||
|
||
### Requirement: 使用 Bun 内置 API 进行 Headers 转换
|
||
系统 SHALL 使用 `Object.fromEntries(headers)` 标准 Web API 替代手写的 `headersToRecord` 函数,用于将 Fetch API 的 Headers 对象转换为键值对。
|
||
|
||
#### Scenario: 转换响应头
|
||
- **WHEN** HTTP runner 获取到 response headers
|
||
- **THEN** `Object.fromEntries(response.headers)` SHALL 返回以 header 名称为 key、header 值为 value 的对象
|