- 新增 body 分组校验:contains、regex、json(JSONPath)、css(CSS选择器)、xpath - 新增操作符系统:equals、contains、match、empty、exists、gte、lte、gt、lt - 新增 headers 响应头校验 - 引入 cheerio、xpath、@xmldom/xmldom 依赖 - BREAKING: expect.bodyContains 迁移至 expect.body.contains
232 lines
6.0 KiB
TypeScript
232 lines
6.0 KiB
TypeScript
import { beforeAll, afterAll, describe, expect, test } from "bun:test";
|
||
import { loadConfig, parseDuration } from "../../../src/server/checker/config-loader";
|
||
import { readRuntimeConfig } from "../../../src/server/config";
|
||
import { mkdir, rm, writeFile } from "node:fs/promises";
|
||
import { join } from "node:path";
|
||
import { tmpdir } from "node:os";
|
||
|
||
describe("parseDuration", () => {
|
||
test("解析秒", () => {
|
||
expect(parseDuration("30s")).toBe(30000);
|
||
expect(parseDuration("1s")).toBe(1000);
|
||
});
|
||
|
||
test("解析分钟", () => {
|
||
expect(parseDuration("5m")).toBe(300000);
|
||
expect(parseDuration("1m")).toBe(60000);
|
||
});
|
||
|
||
test("解析毫秒", () => {
|
||
expect(parseDuration("500ms")).toBe(500);
|
||
expect(parseDuration("100ms")).toBe(100);
|
||
});
|
||
|
||
test("解析小数", () => {
|
||
expect(parseDuration("1.5s")).toBe(1500);
|
||
});
|
||
|
||
test("无效格式抛出错误", () => {
|
||
expect(() => parseDuration("30")).toThrow("无效的时长格式");
|
||
expect(() => parseDuration("abc")).toThrow("无效的时长格式");
|
||
expect(() => parseDuration("30x")).toThrow("无效的时长格式");
|
||
expect(() => parseDuration("")).toThrow("无效的时长格式");
|
||
});
|
||
});
|
||
|
||
describe("readRuntimeConfig", () => {
|
||
test("返回配置文件路径", () => {
|
||
expect(readRuntimeConfig(["./probes.yaml"])).toEqual({ configPath: "./probes.yaml" });
|
||
});
|
||
|
||
test("未提供参数抛出错误", () => {
|
||
expect(() => readRuntimeConfig([])).toThrow("需要指定 YAML 配置文件路径");
|
||
});
|
||
});
|
||
|
||
describe("loadConfig", () => {
|
||
let tempDir: string;
|
||
|
||
beforeAll(async () => {
|
||
tempDir = join(tmpdir(), `gc-test-${Date.now()}`);
|
||
await mkdir(tempDir, { recursive: true });
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await rm(tempDir, { recursive: true, force: true });
|
||
});
|
||
|
||
test("解析完整配置", async () => {
|
||
const configPath = join(tempDir, "full.yaml");
|
||
await writeFile(
|
||
configPath,
|
||
`server:
|
||
host: "0.0.0.0"
|
||
port: 8080
|
||
dataDir: "./my-data"
|
||
defaults:
|
||
interval: "15s"
|
||
timeout: "5s"
|
||
method: "POST"
|
||
targets:
|
||
- name: "test"
|
||
url: "http://example.com"
|
||
`,
|
||
);
|
||
|
||
const config = await loadConfig(configPath);
|
||
expect(config.host).toBe("0.0.0.0");
|
||
expect(config.port).toBe(8080);
|
||
expect(config.dataDir).toBe("./my-data");
|
||
expect(config.targets).toHaveLength(1);
|
||
expect(config.targets[0]).toEqual({
|
||
name: "test",
|
||
url: "http://example.com",
|
||
method: "POST",
|
||
headers: {},
|
||
body: undefined,
|
||
intervalMs: 15000,
|
||
timeoutMs: 5000,
|
||
expect: undefined,
|
||
});
|
||
});
|
||
|
||
test("解析最简配置(只有 targets)", async () => {
|
||
const configPath = join(tempDir, "minimal.yaml");
|
||
await writeFile(
|
||
configPath,
|
||
`targets:
|
||
- name: "t1"
|
||
url: "http://a.com"
|
||
- name: "t2"
|
||
url: "http://b.com"
|
||
interval: "1m"
|
||
`,
|
||
);
|
||
|
||
const config = await loadConfig(configPath);
|
||
expect(config.host).toBe("127.0.0.1");
|
||
expect(config.port).toBe(3000);
|
||
expect(config.dataDir).toBe("./data");
|
||
expect(config.targets).toHaveLength(2);
|
||
expect(config.targets[0]!.intervalMs).toBe(30000);
|
||
expect(config.targets[1]!.intervalMs).toBe(60000);
|
||
});
|
||
|
||
test("per-target 覆盖 defaults", async () => {
|
||
const configPath = join(tempDir, "override.yaml");
|
||
await writeFile(
|
||
configPath,
|
||
`defaults:
|
||
interval: "30s"
|
||
timeout: "10s"
|
||
method: "GET"
|
||
headers:
|
||
Authorization: "Bearer token"
|
||
targets:
|
||
- name: "override-all"
|
||
url: "http://example.com"
|
||
method: "POST"
|
||
interval: "5m"
|
||
timeout: "30s"
|
||
headers:
|
||
X-Custom: "value"
|
||
`,
|
||
);
|
||
|
||
const config = await loadConfig(configPath);
|
||
const target = config.targets[0]!;
|
||
expect(target.method).toBe("POST");
|
||
expect(target.intervalMs).toBe(300000);
|
||
expect(target.timeoutMs).toBe(30000);
|
||
expect(target.headers).toEqual({ Authorization: "Bearer token", "X-Custom": "value" });
|
||
});
|
||
|
||
test("配置文件不存在抛出错误", async () => {
|
||
await expect(loadConfig("/nonexistent/file.yaml")).rejects.toThrow("配置文件不存在");
|
||
});
|
||
|
||
test("target 缺少 name 抛出错误", async () => {
|
||
const configPath = join(tempDir, "no-name.yaml");
|
||
await writeFile(
|
||
configPath,
|
||
`targets:
|
||
- url: "http://example.com"
|
||
`,
|
||
);
|
||
|
||
await expect(loadConfig(configPath)).rejects.toThrow("缺少 name 字段");
|
||
});
|
||
|
||
test("target 缺少 url 抛出错误", async () => {
|
||
const configPath = join(tempDir, "no-url.yaml");
|
||
await writeFile(
|
||
configPath,
|
||
`targets:
|
||
- name: "test"
|
||
`,
|
||
);
|
||
|
||
await expect(loadConfig(configPath)).rejects.toThrow("缺少 url 字段");
|
||
});
|
||
|
||
test("target name 重复抛出错误", async () => {
|
||
const configPath = join(tempDir, "dup-name.yaml");
|
||
await writeFile(
|
||
configPath,
|
||
`targets:
|
||
- name: "dup"
|
||
url: "http://a.com"
|
||
- name: "dup"
|
||
url: "http://b.com"
|
||
`,
|
||
);
|
||
|
||
await expect(loadConfig(configPath)).rejects.toThrow("target name 重复");
|
||
});
|
||
|
||
test("targets 为空数组抛出错误", async () => {
|
||
const configPath = join(tempDir, "empty-targets.yaml");
|
||
await writeFile(configPath, `targets: []`);
|
||
|
||
await expect(loadConfig(configPath)).rejects.toThrow("至少一个 target");
|
||
});
|
||
|
||
test("无效端口号抛出错误", async () => {
|
||
const configPath = join(tempDir, "bad-port.yaml");
|
||
await writeFile(
|
||
configPath,
|
||
`server:
|
||
port: 99999
|
||
targets:
|
||
- name: "t"
|
||
url: "http://a.com"
|
||
`,
|
||
);
|
||
|
||
await expect(loadConfig(configPath)).rejects.toThrow("无效端口号");
|
||
});
|
||
|
||
test("解析 expect 配置", async () => {
|
||
const configPath = join(tempDir, "expect.yaml");
|
||
await writeFile(
|
||
configPath,
|
||
`targets:
|
||
- name: "with-expect"
|
||
url: "http://example.com"
|
||
expect:
|
||
status: [200, 201]
|
||
body:
|
||
contains: "ok"
|
||
maxLatencyMs: 3000
|
||
`,
|
||
);
|
||
|
||
const config = await loadConfig(configPath);
|
||
expect(config.targets[0]!.expect).toEqual({
|
||
status: [200, 201],
|
||
body: { contains: "ok" },
|
||
maxLatencyMs: 3000,
|
||
});
|
||
});
|
||
});
|