1
0

feat: 重构为多类型 checker 通用框架,支持 HTTP 与命令检查

- 引入 typed target 判别联合,支持 http 与 command 两种 checker
- expect 重构为有序规则数组,按配置顺序快速失败并生成结构化 failure
- 新增 command runner,支持 exec + args 本地命令执行
- 引入全局并发限制 maxConcurrentChecks 和 size 解析 (KB/MB/GB)
- HTTP/command 各自独立 expect pipeline,应用领域默认成功语义
- SQLite schema、API、Dashboard 全链路调整为 checker 通用契约
- 补充完整测试覆盖(192 tests),更新 README 与示例配置
This commit is contained in:
2026-05-10 22:25:21 +08:00
parent 599d973cbd
commit b8810f1182
46 changed files with 3562 additions and 1062 deletions

View File

@@ -0,0 +1,122 @@
import type { BodyRule, CheckFailure, HeaderExpect, HttpExpectConfig } from "../types";
import { checkBodyExpect } from "./body";
import { applyOperator } from "./body";
import { mismatchFailure, errorFailure } from "./failure";
export interface HttpObservation {
statusCode: number;
headers: Record<string, string>;
body: string | null;
durationMs: number;
}
function checkStatus(obs: HttpObservation, allowed: number[]): { matched: boolean; failure: CheckFailure | null } {
if (!allowed.includes(obs.statusCode)) {
return {
matched: false,
failure: mismatchFailure(
"status",
"status",
allowed,
obs.statusCode,
`status ${obs.statusCode} not in [${allowed}]`,
),
};
}
return { matched: true, failure: null };
}
function checkDuration(
obs: HttpObservation,
maxDurationMs?: number,
): { matched: boolean; failure: CheckFailure | null } {
if (maxDurationMs === undefined) return { matched: true, failure: null };
if (obs.durationMs > maxDurationMs) {
return {
matched: false,
failure: mismatchFailure(
"duration",
"duration",
`<=${maxDurationMs}ms`,
obs.durationMs,
`duration ${obs.durationMs}ms > ${maxDurationMs}ms`,
),
};
}
return { matched: true, failure: null };
}
function checkHeaders(
obs: HttpObservation,
headerExpects?: Record<string, HeaderExpect>,
): { matched: boolean; failure: CheckFailure | null } {
if (!headerExpects) return { matched: true, failure: null };
for (const [key, expected] of Object.entries(headerExpects)) {
const actualValue = obs.headers[key.toLowerCase()];
const path = `headers.${key}`;
if (typeof expected === "string") {
if (actualValue !== expected) {
return {
matched: false,
failure: mismatchFailure("headers", path, expected, actualValue, `header ${key} mismatch`),
};
}
} else {
if (actualValue === undefined) {
if (expected.exists !== false) {
return {
matched: false,
failure: mismatchFailure("headers", path, "defined", undefined, `header ${key} not found`),
};
}
continue;
}
if (!applyOperator(actualValue, expected)) {
return {
matched: false,
failure: mismatchFailure("headers", path, expected, actualValue, `header ${key} mismatch`),
};
}
}
}
return { matched: true, failure: null };
}
function checkBody(obs: HttpObservation, bodyRules?: BodyRule[]): { matched: boolean; failure: CheckFailure | null } {
if (!bodyRules || bodyRules.length === 0) return { matched: true, failure: null };
if (obs.body === null) {
return {
matched: false,
failure: errorFailure("body", "body", "body is null but body rules are configured"),
};
}
return checkBodyExpect(obs.body, bodyRules);
}
export function checkHttpExpect(
obs: HttpObservation,
expect?: HttpExpectConfig,
): { matched: boolean; failure: CheckFailure | null } {
if (!expect) {
return checkStatus(obs, [200]);
}
const statusResult = checkStatus(obs, expect.status ?? [200]);
if (!statusResult.matched) return statusResult;
const durationResult = checkDuration(obs, expect.maxDurationMs);
if (!durationResult.matched) return durationResult;
const headersResult = checkHeaders(obs, expect.headers);
if (!headersResult.matched) return headersResult;
const bodyResult = checkBody(obs, expect.body);
if (!bodyResult.matched) return bodyResult;
return { matched: true, failure: null };
}