1
0
Files
DiAL/tests/server/checker/config-loader.test.ts
lanyuanxiaoyao 599d973cbd feat: 增强 expect 规则系统,支持多种 body 校验方法和操作符
- 新增 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
2026-05-10 00:10:42 +08:00

232 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
});
});
});