1
0

chore: 强化代码质量与风格检查体系

ESLint 升级到 recommended-type-checked + stylistic-type-checked,
引入 perfectionist 导入排序和 import 插件导入验证。

Prettier 显式声明全部格式化参数,消除跨环境差异。
TypeScript 启用 noUnusedLocals 和 noPropertyAccessFromIndexSignature。
完善 ignore 列表,排除 .agents/、bun.lock、data/ 等。
引入 husky + lint-staged(pre-commit)+ commitlint(commit-msg)。
更新 DEVELOPMENT.md 代码质量章节。
修复所有新增规则检测到的类型和风格违规。
This commit is contained in:
2026-05-12 18:44:59 +08:00
parent ce8baae3d1
commit a5cf6065c2
83 changed files with 2654 additions and 1824 deletions

View File

@@ -1,12 +1,13 @@
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 { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { mkdir, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { loadConfig, parseDuration } from "../../../src/server/checker/config-loader";
import { checkerRegistry } from "../../../src/server/checker/runner";
import { HttpChecker } from "../../../src/server/checker/runner/http/runner";
import { CommandChecker } from "../../../src/server/checker/runner/command/runner";
import { HttpChecker } from "../../../src/server/checker/runner/http/runner";
import { readRuntimeConfig } from "../../../src/server/config";
function ensureRegistered() {
if (!checkerRegistry.supportedTypes.includes("http")) {
@@ -66,7 +67,7 @@ describe("loadConfig", () => {
});
afterAll(async () => {
await rm(tempDir, { recursive: true, force: true });
await rm(tempDir, { force: true, recursive: true });
});
test("解析最简 HTTP 配置", async () => {
@@ -125,7 +126,7 @@ describe("loadConfig", () => {
expect(t.command.args).toEqual(["nginx"]);
expect(t.command.cwd).toBe(subdir);
expect(t.command.maxOutputBytes).toBe(104857600);
expect(t.command.env.PATH).toBeDefined();
expect(t.command.env["PATH"]).toBeDefined();
}
});
@@ -230,6 +231,7 @@ targets:
});
test("配置文件不存在抛出错误", async () => {
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig("/nonexistent/file.yaml")).rejects.toThrow("配置文件不存在");
});
@@ -243,6 +245,7 @@ targets:
url: "http://example.com"
`,
);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("缺少 name 字段");
});
@@ -256,6 +259,7 @@ targets:
url: "http://example.com"
`,
);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("缺少 type 字段");
});
@@ -269,6 +273,7 @@ targets:
http: {}
`,
);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("缺少 http.url 字段");
});
@@ -282,6 +287,7 @@ targets:
command: {}
`,
);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("缺少 command.exec 字段");
});
@@ -294,6 +300,7 @@ targets:
type: dns
`,
);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("不支持的 type");
});
@@ -312,12 +319,14 @@ targets:
url: "http://b.com"
`,
);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("target name 重复");
});
test("targets 为空数组抛出错误", async () => {
const configPath = join(tempDir, "empty-targets.yaml");
await writeFile(configPath, `targets: []`);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("至少一个 target");
});
@@ -334,6 +343,7 @@ targets:
url: "http://a.com"
`,
);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("无效端口号");
});
@@ -350,6 +360,7 @@ targets:
url: "http://a.com"
`,
);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("maxConcurrentChecks 必须为正整数");
});
@@ -367,6 +378,7 @@ targets:
url: "http://a.com"
`,
);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("无效的 size 格式");
});
@@ -382,6 +394,7 @@ targets:
url: "http://a.com"
`,
);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("无效的时长格式");
});
@@ -409,9 +422,9 @@ targets:
const t = config.targets[0]!;
if (t.type === "http") {
expect(t.expect).toEqual({
status: [200, 201],
body: [{ contains: "ok" }, { json: { path: "$.status", equals: "ok" } }],
body: [{ contains: "ok" }, { json: { equals: "ok", path: "$.status" } }],
maxDurationMs: 3000,
status: [200, 201],
});
}
});
@@ -441,9 +454,9 @@ targets:
if (t.type === "command") {
expect(t.expect).toEqual({
exitCode: [0, 2],
stdout: [{ contains: "ok" }, { match: "done" }],
stderr: [{ empty: true }],
maxDurationMs: 5000,
stderr: [{ empty: true }],
stdout: [{ contains: "ok" }, { match: "done" }],
});
}
});
@@ -488,9 +501,9 @@ targets:
const config = await loadConfig(configPath);
const t = config.targets[0]!;
if (t.type === "command") {
expect(t.command.env.LANG).toBe("C");
expect(t.command.env.CUSTOM_VAR).toBe("test");
expect(t.command.env.PATH).toBeDefined();
expect(t.command.env["LANG"]).toBe("C");
expect(t.command.env["CUSTOM_VAR"]).toBe("test");
expect(t.command.env["PATH"]).toBeDefined();
}
});
@@ -540,6 +553,7 @@ targets:
`,
);
// eslint-disable-next-line @typescript-eslint/await-thenable
await expect(loadConfig(configPath)).rejects.toThrow("group 字段必须为字符串");
});
});

View File

@@ -1,10 +1,38 @@
import { describe, expect, test } from "bun:test";
import { ProbeEngine } from "../../../src/server/checker/engine";
import type { ProbeStore } from "../../../src/server/checker/store";
import type { ResolvedCommandTarget, ResolvedHttpTarget, ResolvedTarget } from "../../../src/server/checker/types";
import { ProbeEngine } from "../../../src/server/checker/engine";
import { checkerRegistry } from "../../../src/server/checker/runner";
import { HttpChecker } from "../../../src/server/checker/runner/http/runner";
import { CommandChecker } from "../../../src/server/checker/runner/command/runner";
import { HttpChecker } from "../../../src/server/checker/runner/http/runner";
function createMockStore(targetNames: string[]) {
let nextId = 1;
const targets = targetNames.map((name) => ({ id: nextId++, name }));
const results: Array<Record<string, unknown>> = [];
return {
_results: results,
getTargets() {
return targets.map(({ id, name }) => ({
config: "",
expect: null,
grp: "default",
id,
interval_ms: 60000,
name,
target: "",
timeout_ms: 5000,
type: "command" as const,
}));
},
insertCheckResult(result: Record<string, unknown>) {
results.push(result);
},
};
}
function ensureRegistered() {
if (!checkerRegistry.supportedTypes.includes("http")) {
@@ -13,46 +41,20 @@ function ensureRegistered() {
}
}
function createMockStore(targetNames: string[]) {
let nextId = 1;
const targets = targetNames.map((name) => ({ id: nextId++, name }));
const results: Array<Record<string, unknown>> = [];
return {
getTargets() {
return targets.map(({ id, name }) => ({
id,
name,
type: "command" as const,
target: "",
config: "",
interval_ms: 60000,
timeout_ms: 5000,
expect: null,
grp: "default",
}));
},
insertCheckResult(result: Record<string, unknown>) {
results.push(result);
},
_results: results,
};
}
function makeCommandTarget(name: string, overrides?: Partial<ResolvedCommandTarget>): ResolvedCommandTarget {
return {
type: "command",
name,
group: "default",
command: {
exec: "echo",
args: ["hello"],
cwd: "/tmp",
env: {},
exec: "echo",
maxOutputBytes: 1024 * 1024,
},
group: "default",
intervalMs: 60000,
name,
timeoutMs: 5000,
type: "command",
...overrides,
};
}
@@ -80,16 +82,16 @@ describe("ProbeEngine", () => {
const results = (mockStore as unknown as { _results: Array<Record<string, unknown>> })._results;
expect(results.length).toBe(1);
expect(results[0]!.matched).toBe(true);
expect(results[0]!.statusDetail).toBe("exitCode=0");
expect(results[0]!["matched"]).toBe(true);
expect(results[0]!["statusDetail"]).toBe("exitCode=0");
});
test("多个目标并发执行", async () => {
const targetA = makeCommandTarget("echo-a", {
command: { exec: "echo", args: ["a"], cwd: "/tmp", env: {}, maxOutputBytes: 1024 * 1024 },
command: { args: ["a"], cwd: "/tmp", env: {}, exec: "echo", maxOutputBytes: 1024 * 1024 },
});
const targetB = makeCommandTarget("echo-b", {
command: { exec: "echo", args: ["b"], cwd: "/tmp", env: {}, maxOutputBytes: 1024 * 1024 },
command: { args: ["b"], cwd: "/tmp", env: {}, exec: "echo", maxOutputBytes: 1024 * 1024 },
});
const mockStore = createMockStore(["echo-a", "echo-b"]) as unknown as ProbeStore;
@@ -106,7 +108,7 @@ describe("ProbeEngine", () => {
test("失败目标不阻塞其他目标", async () => {
const badTarget = makeCommandTarget("bad-cmd", {
command: { exec: "false", args: [], cwd: "/tmp", env: {}, maxOutputBytes: 1024 * 1024 },
command: { args: [], cwd: "/tmp", env: {}, exec: "false", maxOutputBytes: 1024 * 1024 },
});
const goodTarget = makeCommandTarget("good-cmd");
@@ -121,8 +123,8 @@ describe("ProbeEngine", () => {
const results = (mockStore as unknown as { _results: Array<Record<string, unknown>> })._results;
expect(results.length).toBe(2);
const badResult = results.find((r) => r.matched === false);
const goodResult = results.find((r) => r.matched === true);
const badResult = results.find((r) => r["matched"] === false);
const goodResult = results.find((r) => r["matched"] === true);
expect(badResult).toBeDefined();
expect(goodResult).toBeDefined();
});
@@ -130,7 +132,7 @@ describe("ProbeEngine", () => {
test("并发限制 maxConcurrentChecks", async () => {
const targets = Array.from({ length: 5 }, (_, i) =>
makeCommandTarget(`cmd-${i}`, {
command: { exec: "echo", args: [String(i)], cwd: "/tmp", env: {}, maxOutputBytes: 1024 * 1024 },
command: { args: [String(i)], cwd: "/tmp", env: {}, exec: "echo", maxOutputBytes: 1024 * 1024 },
}),
);
@@ -145,7 +147,7 @@ describe("ProbeEngine", () => {
const results = (mockStore as unknown as { _results: Array<Record<string, unknown>> })._results;
expect(results.length).toBe(5);
for (const r of results) {
expect(r.matched).toBe(true);
expect(r["matched"]).toBe(true);
}
});
@@ -177,25 +179,25 @@ describe("ProbeEngine", () => {
test("HTTP 目标运行", async () => {
const httpServer = Bun.serve({
port: 0,
fetch() {
return new Response("ok");
},
port: 0,
});
try {
const httpTarget: ResolvedHttpTarget = {
type: "http",
name: "http-test",
group: "default",
http: {
url: `http://localhost:${httpServer.port}/`,
method: "GET",
headers: {},
maxBodyBytes: 1024 * 1024,
method: "GET",
url: `http://localhost:${httpServer.port}/`,
},
intervalMs: 60000,
name: "http-test",
timeoutMs: 5000,
type: "http",
};
const mockStore = createMockStore(["http-test"]) as unknown as ProbeStore;
@@ -208,10 +210,10 @@ describe("ProbeEngine", () => {
const results = (mockStore as unknown as { _results: Array<Record<string, unknown>> })._results;
expect(results.length).toBe(1);
expect(results[0]!.matched).toBe(true);
expect(results[0]!.statusDetail).toBe("HTTP 200");
expect(results[0]!["matched"]).toBe(true);
expect(results[0]!["statusDetail"]).toBe("HTTP 200");
} finally {
httpServer.stop();
void httpServer.stop();
}
});
});

View File

@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test";
import { checkExitCode } from "../../../../../src/server/checker/runner/command/expect";
describe("checkExitCode", () => {

View File

@@ -1,31 +1,11 @@
import { describe, expect, test } from "bun:test";
import { CommandChecker } from "../../../../../src/server/checker/runner/command/runner";
import type { CheckerContext } from "../../../../../src/server/checker/runner/types";
import type { ResolvedCommandTarget } from "../../../../../src/server/checker/types";
const checker = new CommandChecker();
import { CommandChecker } from "../../../../../src/server/checker/runner/command/runner";
function makeTarget(
command: Partial<ResolvedCommandTarget["command"]>,
overrides?: Partial<ResolvedCommandTarget>,
): ResolvedCommandTarget {
return {
type: "command",
name: "test-cmd",
group: "default",
command: {
exec: "echo",
args: ["hello"],
cwd: "/tmp",
env: {},
maxOutputBytes: 1024 * 1024,
...command,
},
intervalMs: 60000,
timeoutMs: 5000,
...overrides,
};
}
const checker = new CommandChecker();
function makeCtx(timeoutMs = 5000): CheckerContext {
const controller = new AbortController();
@@ -33,23 +13,48 @@ function makeCtx(timeoutMs = 5000): CheckerContext {
return { signal: controller.signal };
}
function makeTarget(
command: Partial<ResolvedCommandTarget["command"]>,
overrides?: Partial<ResolvedCommandTarget>,
): ResolvedCommandTarget {
return {
command: {
args: ["hello"],
cwd: "/tmp",
env: {},
exec: "echo",
maxOutputBytes: 1024 * 1024,
...command,
},
group: "default",
intervalMs: 60000,
name: "test-cmd",
timeoutMs: 5000,
type: "command",
...overrides,
};
}
describe("CommandChecker", () => {
test("exitCode=0 成功", async () => {
const result = await checker.execute(makeTarget({ exec: "true", args: [] }), makeCtx());
const result = await checker.execute(makeTarget({ args: [], exec: "true" }), makeCtx());
expect(result.matched).toBe(true);
expect(result.statusDetail).toBe("exitCode=0");
expect(result.failure).toBeNull();
});
test("exitCode=1 不匹配默认 [0]", async () => {
const result = await checker.execute(makeTarget({ exec: "false", args: [] }), makeCtx());
const result = await checker.execute(makeTarget({ args: [], exec: "false" }), makeCtx());
expect(result.matched).toBe(false);
expect(result.statusDetail).toBe("exitCode=1");
expect(result.failure!.phase).toBe("exitCode");
});
test("exitCode=1 匹配自定义 [1]", async () => {
const result = await checker.execute(makeTarget({ exec: "false", args: [] }, { expect: { exitCode: [1] } }), makeCtx());
const result = await checker.execute(
makeTarget({ args: [], exec: "false" }, { expect: { exitCode: [1] } }),
makeCtx(),
);
expect(result.matched).toBe(true);
expect(result.statusDetail).toBe("exitCode=1");
});
@@ -62,54 +67,69 @@ describe("CommandChecker", () => {
});
test("超时返回错误", async () => {
const result = await checker.execute(makeTarget({ exec: "sleep", args: ["10"] }, { timeoutMs: 100 }), makeCtx(100));
const result = await checker.execute(makeTarget({ args: ["10"], exec: "sleep" }, { timeoutMs: 100 }), makeCtx(100));
expect(result.matched).toBe(false);
expect(result.failure!.message).toContain("超时");
});
test("stdout 输出捕获", async () => {
const result = await checker.execute(makeTarget({ exec: "echo", args: ["hello world"] }), makeCtx());
const result = await checker.execute(makeTarget({ args: ["hello world"], exec: "echo" }), makeCtx());
expect(result.matched).toBe(true);
});
test("stdout 匹配 expect", async () => {
const result = await checker.execute(makeTarget({ exec: "echo", args: ["hello"] }, { expect: { stdout: [{ contains: "hello" }] } }), makeCtx());
const result = await checker.execute(
makeTarget({ args: ["hello"], exec: "echo" }, { expect: { stdout: [{ contains: "hello" }] } }),
makeCtx(),
);
expect(result.matched).toBe(true);
});
test("stdout 不匹配 expect", async () => {
const result = await checker.execute(makeTarget({ exec: "echo", args: ["hello"] }, { expect: { stdout: [{ contains: "nonexistent" }] } }), makeCtx());
const result = await checker.execute(
makeTarget({ args: ["hello"], exec: "echo" }, { expect: { stdout: [{ contains: "nonexistent" }] } }),
makeCtx(),
);
expect(result.matched).toBe(false);
expect(result.failure!.phase).toBe("stdout");
});
test("stderr 匹配 expect", async () => {
const result = await checker.execute(makeTarget({ exec: "bash", args: ["-c", "echo error >&2"] }, { expect: { stderr: [{ contains: "error" }] } }), makeCtx());
const result = await checker.execute(
makeTarget({ args: ["-c", "echo error >&2"], exec: "bash" }, { expect: { stderr: [{ contains: "error" }] } }),
makeCtx(),
);
expect(result.matched).toBe(true);
});
test("输出超过 maxOutputBytes", async () => {
const result = await checker.execute(makeTarget({ exec: "bash", args: ["-c", "yes | head -1000"], maxOutputBytes: 10 }), makeCtx());
const result = await checker.execute(
makeTarget({ args: ["-c", "yes | head -1000"], exec: "bash", maxOutputBytes: 10 }),
makeCtx(),
);
expect(result.matched).toBe(false);
expect(result.failure!.message).toContain("超过限制");
});
test("durationMs 非空", async () => {
const result = await checker.execute(makeTarget({ exec: "true", args: [] }), makeCtx());
const result = await checker.execute(makeTarget({ args: [], exec: "true" }), makeCtx());
expect(result.durationMs).not.toBeNull();
expect(result.durationMs!).toBeGreaterThanOrEqual(0);
});
test("不使用 shell通配符不被展开", async () => {
const result = await checker.execute(makeTarget({ exec: "echo", args: ["*"] }, { expect: { stdout: [{ contains: "*" }] } }), makeCtx());
const result = await checker.execute(
makeTarget({ args: ["*"], exec: "echo" }, { expect: { stdout: [{ contains: "*" }] } }),
makeCtx(),
);
expect(result.matched).toBe(true);
});
test("serialize 返回命令摘要和 config JSON", () => {
const target = makeTarget({ exec: "echo", args: ["hello"] });
const target = makeTarget({ args: ["hello"], exec: "echo" });
const s = checker.serialize(target);
expect(s.target).toBe("exec echo hello");
const config = JSON.parse(s.config);
const config = JSON.parse(s.config) as { args: string[]; exec: string };
expect(config.exec).toBe("echo");
expect(config.args).toEqual(["hello"]);
});

View File

@@ -1,18 +1,21 @@
import { describe, expect, test } from "bun:test";
import { checkHttpExpect } from "../../../../../src/server/checker/runner/http/expect";
function obs(overrides: { statusCode?: number; headers?: Record<string, string>; body?: string | null; durationMs?: number } = {}) {
function obs(
overrides: { body?: null | string; durationMs?: number; headers?: Record<string, string>; statusCode?: number } = {},
) {
return {
statusCode: overrides.statusCode ?? 200,
headers: overrides.headers ?? {},
body: overrides.body ?? "",
durationMs: overrides.durationMs ?? 100,
headers: overrides.headers ?? {},
statusCode: overrides.statusCode ?? 200,
};
}
describe("checkHttpExpect", () => {
test("无 expect 配置时默认检查 status [200] 匹配成功", () => {
const r = checkHttpExpect(obs().statusCode, obs().headers, obs().body as string, obs().durationMs);
const r = checkHttpExpect(obs().statusCode, obs().headers, obs().body, obs().durationMs);
expect(r.matched).toBe(true);
expect(r.failure).toBeNull();
});
@@ -65,7 +68,9 @@ describe("checkHttpExpect", () => {
test("headers 操作符格式检查", () => {
const h = { "content-type": "application/json" };
expect(checkHttpExpect(200, h, "", 100, { headers: { "content-type": { contains: "json" } } }).matched).toBe(true);
expect(checkHttpExpect(200, h, "", 100, { headers: { "content-type": { match: "^application/" } } }).matched).toBe(true);
expect(checkHttpExpect(200, h, "", 100, { headers: { "content-type": { match: "^application/" } } }).matched).toBe(
true,
);
expect(checkHttpExpect(200, h, "", 100, { headers: { "content-type": { contains: "xml" } } }).matched).toBe(false);
});
@@ -81,9 +86,9 @@ describe("checkHttpExpect", () => {
});
test("body 规则数组按顺序检查", () => {
const body = JSON.stringify({ status: "ok", count: 5 });
const body = JSON.stringify({ count: 5, status: "ok" });
const r = checkHttpExpect(200, {}, body, 100, {
body: [{ contains: "ok" }, { json: { path: "$.count", gte: 1 } }],
body: [{ contains: "ok" }, { json: { gte: 1, path: "$.count" } }],
});
expect(r.matched).toBe(true);
});
@@ -104,26 +109,26 @@ describe("checkHttpExpect", () => {
test("完整流水线 status->duration->headers->body 全部通过", () => {
const r = checkHttpExpect(200, { "content-type": "application/json" }, JSON.stringify({ status: "healthy" }), 50, {
status: [200],
maxDurationMs: 100,
body: [{ json: { equals: "healthy", path: "$.status" } }],
headers: { "content-type": { contains: "json" } },
body: [{ json: { path: "$.status", equals: "healthy" } }],
maxDurationMs: 100,
status: [200],
});
expect(r.matched).toBe(true);
expect(r.failure).toBeNull();
});
test("完整流水线 status 通过但 duration 失败", () => {
const r = checkHttpExpect(200, {}, "", 500, { status: [200], maxDurationMs: 100 });
const r = checkHttpExpect(200, {}, "", 500, { maxDurationMs: 100, status: [200] });
expect(r.matched).toBe(false);
expect(r.failure!.phase).toBe("duration");
});
test("完整流水线 status 和 duration 通过但 headers 失败", () => {
const r = checkHttpExpect(200, { "x-api": "v1" }, "", 50, {
status: [200],
maxDurationMs: 100,
headers: { "x-api": "v2" },
maxDurationMs: 100,
status: [200],
});
expect(r.matched).toBe(false);
expect(r.failure!.phase).toBe("headers");
@@ -131,10 +136,10 @@ describe("checkHttpExpect", () => {
test("完整流水线 status/duration/headers 通过但 body 失败", () => {
const r = checkHttpExpect(200, { "content-type": "text/plain" }, "error occurred", 50, {
status: [200],
maxDurationMs: 100,
headers: { "content-type": "text/plain" },
body: [{ contains: "success" }],
headers: { "content-type": "text/plain" },
maxDurationMs: 100,
status: [200],
});
expect(r.matched).toBe(false);
expect(r.failure!.phase).toBe("body");

View File

@@ -1,8 +1,10 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { HttpChecker } from "../../../../../src/server/checker/runner/http/runner";
import type { CheckerContext } from "../../../../../src/server/checker/runner/types";
import type { ResolvedHttpTarget } from "../../../../../src/server/checker/types";
import { HttpChecker } from "../../../../../src/server/checker/runner/http/runner";
const checker = new HttpChecker();
describe("HttpChecker", () => {
@@ -11,61 +13,61 @@ describe("HttpChecker", () => {
beforeAll(() => {
server = Bun.serve({
port: 0,
fetch(req) {
const url = new URL(req.url);
switch (url.pathname) {
case "/ok":
return new Response("hello world", {
headers: { "content-type": "text/plain", "x-custom": "test-value" },
case "/echo":
return new Response(JSON.stringify({ body: req.body ? "present" : "empty", method: req.method }), {
headers: { "content-type": "application/json" },
});
case "/json":
return new Response(JSON.stringify({ status: "ok" }), {
headers: { "content-type": "application/json" },
});
case "/echo":
return new Response(JSON.stringify({ method: req.method, body: req.body ? "present" : "empty" }), {
headers: { "content-type": "application/json" },
});
case "/large":
return new Response("x".repeat(2000));
case "/notfound":
return new Response("not found", { status: 404 });
case "/ok":
return new Response("hello world", {
headers: { "content-type": "text/plain", "x-custom": "test-value" },
});
default:
return new Response("ok");
}
},
port: 0,
});
baseUrl = `http://localhost:${server.port}`;
});
afterAll(() => {
server.stop();
void server.stop();
});
function makeTarget(overrides: {
url?: string;
method?: string;
body?: string;
headers?: Record<string, string>;
expect?: Record<string, unknown>;
headers?: Record<string, string>;
maxBodyBytes?: number;
method?: string;
timeoutMs?: number;
url?: string;
}): ResolvedHttpTarget {
return {
type: "http",
name: "test-http",
expect: overrides.expect,
group: "default",
http: {
url: overrides.url ?? `${baseUrl}/ok`,
method: overrides.method ?? "GET",
headers: overrides.headers ?? {},
body: overrides.body,
headers: overrides.headers ?? {},
maxBodyBytes: overrides.maxBodyBytes ?? 1024 * 1024,
method: overrides.method ?? "GET",
url: overrides.url ?? `${baseUrl}/ok`,
},
intervalMs: 60000,
name: "test-http",
timeoutMs: overrides.timeoutMs ?? 5000,
expect: overrides.expect as ResolvedHttpTarget["expect"],
type: "http",
};
}
@@ -91,39 +93,60 @@ describe("HttpChecker", () => {
});
test("404 匹配自定义 status [404]", async () => {
const result = await checker.execute(makeTarget({ url: `${baseUrl}/notfound`, expect: { status: [404] } }), makeCtx());
const result = await checker.execute(
makeTarget({ expect: { status: [404] }, url: `${baseUrl}/notfound` }),
makeCtx(),
);
expect(result.matched).toBe(true);
});
test("headers 检查通过", async () => {
const result = await checker.execute(makeTarget({ url: `${baseUrl}/ok`, expect: { headers: { "x-custom": "test-value" } } }), makeCtx());
const result = await checker.execute(
makeTarget({ expect: { headers: { "x-custom": "test-value" } }, url: `${baseUrl}/ok` }),
makeCtx(),
);
expect(result.matched).toBe(true);
});
test("headers 检查失败", async () => {
const result = await checker.execute(makeTarget({ url: `${baseUrl}/ok`, expect: { headers: { "x-custom": "wrong-value" } } }), makeCtx());
const result = await checker.execute(
makeTarget({ expect: { headers: { "x-custom": "wrong-value" } }, url: `${baseUrl}/ok` }),
makeCtx(),
);
expect(result.matched).toBe(false);
expect(result.failure!.phase).toBe("headers");
});
test("body contains 检查", async () => {
const result = await checker.execute(makeTarget({ url: `${baseUrl}/ok`, expect: { body: [{ contains: "hello" }] } }), makeCtx());
const result = await checker.execute(
makeTarget({ expect: { body: [{ contains: "hello" }] }, url: `${baseUrl}/ok` }),
makeCtx(),
);
expect(result.matched).toBe(true);
});
test("body contains 失败", async () => {
const result = await checker.execute(makeTarget({ url: `${baseUrl}/ok`, expect: { body: [{ contains: "nonexistent" }] } }), makeCtx());
const result = await checker.execute(
makeTarget({ expect: { body: [{ contains: "nonexistent" }] }, url: `${baseUrl}/ok` }),
makeCtx(),
);
expect(result.matched).toBe(false);
expect(result.failure!.phase).toBe("body");
});
test("body json 检查", async () => {
const result = await checker.execute(makeTarget({ url: `${baseUrl}/json`, expect: { body: [{ json: { path: "$.status", equals: "ok" } }] } }), makeCtx());
const result = await checker.execute(
makeTarget({ expect: { body: [{ json: { equals: "ok", path: "$.status" } }] }, url: `${baseUrl}/json` }),
makeCtx(),
);
expect(result.matched).toBe(true);
});
test("响应体超过 maxBodyBytes", async () => {
const result = await checker.execute(makeTarget({ url: `${baseUrl}/large`, maxBodyBytes: 100, expect: { body: [{ contains: "x" }] } }), makeCtx());
const result = await checker.execute(
makeTarget({ expect: { body: [{ contains: "x" }] }, maxBodyBytes: 100, url: `${baseUrl}/large` }),
makeCtx(),
);
expect(result.matched).toBe(false);
expect(result.failure!.phase).toBe("body");
expect(result.failure!.message).toContain("超过限制");
@@ -131,41 +154,59 @@ describe("HttpChecker", () => {
test("请求超时", async () => {
const timeoutServer = Bun.serve({
port: 0,
async fetch() {
await new Promise((resolve) => setTimeout(resolve, 10000));
return new Response("late");
},
port: 0,
});
try {
const result = await checker.execute(makeTarget({ url: `http://localhost:${timeoutServer.port}/`, timeoutMs: 100 }), makeCtx(100));
const result = await checker.execute(
makeTarget({ timeoutMs: 100, url: `http://localhost:${timeoutServer.port}/` }),
makeCtx(100),
);
expect(result.matched).toBe(false);
expect(result.failure!.message).toContain("超时");
} finally {
timeoutServer.stop();
void timeoutServer.stop();
}
});
test("快速失败status 失败时不读取 body", async () => {
const result = await checker.execute(makeTarget({ url: `${baseUrl}/notfound`, expect: { status: [200], body: [{ contains: "something" }] } }), makeCtx());
const result = await checker.execute(
makeTarget({ expect: { body: [{ contains: "something" }], status: [200] }, url: `${baseUrl}/notfound` }),
makeCtx(),
);
expect(result.matched).toBe(false);
expect(result.failure!.phase).toBe("status");
});
test("status 通过但 body 失败", async () => {
const result = await checker.execute(makeTarget({ url: `${baseUrl}/ok`, expect: { status: [200], body: [{ contains: "not-in-body" }] } }), makeCtx());
const result = await checker.execute(
makeTarget({ expect: { body: [{ contains: "not-in-body" }], status: [200] }, url: `${baseUrl}/ok` }),
makeCtx(),
);
expect(result.matched).toBe(false);
expect(result.failure!.phase).toBe("body");
});
test("无 expect 时默认检查 status 200", async () => {
const result = await checker.execute(makeTarget({ url: `${baseUrl}/ok`, expect: undefined }), makeCtx());
const result = await checker.execute(makeTarget({ expect: undefined, url: `${baseUrl}/ok` }), makeCtx());
expect(result.matched).toBe(true);
});
test("POST 请求携带 body", async () => {
const result = await checker.execute(makeTarget({ url: `${baseUrl}/echo`, method: "POST", body: "test-body", headers: { "content-type": "text/plain" }, expect: { status: [200], body: [{ json: { path: "$.body", equals: "present" } }] } }), makeCtx());
const result = await checker.execute(
makeTarget({
body: "test-body",
expect: { body: [{ json: { equals: "present", path: "$.body" } }], status: [200] },
headers: { "content-type": "text/plain" },
method: "POST",
url: `${baseUrl}/echo`,
}),
makeCtx(),
);
expect(result.matched).toBe(true);
});
@@ -173,7 +214,7 @@ describe("HttpChecker", () => {
const target = makeTarget({});
const s = checker.serialize(target);
expect(s.target).toBe(target.http.url);
const config = JSON.parse(s.config);
const config = JSON.parse(s.config) as { method: string; url: string };
expect(config.url).toBe(target.http.url);
expect(config.method).toBe("GET");
});

View File

@@ -1,13 +1,16 @@
import { describe, expect, test } from "bun:test";
import { CheckerRegistry } from "../../../../src/server/checker/runner/registry";
import type { Checker } from "../../../../src/server/checker/runner/types";
import type { CheckResult, ResolvedTarget } from "../../../../src/server/checker/types";
import { CheckerRegistry } from "../../../../src/server/checker/runner/registry";
function createChecker(type: string): Checker {
return {
execute: () => Promise.resolve<CheckResult>({} as unknown as CheckResult),
resolve: () => ({}) as unknown as ResolvedTarget,
serialize: () => ({ config: "", target: "" }),
type,
resolve: () => ({}) as any,
execute: () => Promise.resolve({} as any),
serialize: () => ({ target: "", config: "" }),
};
}

View File

@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test";
import { checkBodyExpect } from "../../../../../src/server/checker/runner/shared/body";
describe("checkBodyExpect (BodyRule[])", () => {
@@ -41,89 +42,89 @@ describe("checkBodyExpect (BodyRule[])", () => {
});
test("json 等值匹配成功", () => {
const body = JSON.stringify({ status: "ok", code: 0 });
const r = checkBodyExpect(body, [{ json: { path: "$.status", equals: "ok" } }]);
const body = JSON.stringify({ code: 0, status: "ok" });
const r = checkBodyExpect(body, [{ json: { equals: "ok", path: "$.status" } }]);
expect(r.matched).toBe(true);
});
test("json 等值匹配失败", () => {
const body = JSON.stringify({ status: "ok" });
const r = checkBodyExpect(body, [{ json: { path: "$.status", equals: "error" } }]);
const r = checkBodyExpect(body, [{ json: { equals: "error", path: "$.status" } }]);
expect(r.matched).toBe(false);
expect(r.failure!.kind).toBe("mismatch");
});
test("json 操作符匹配", () => {
const body = JSON.stringify({ count: 42, version: "v2.1.0" });
expect(checkBodyExpect(body, [{ json: { path: "$.count", gte: 10 } }]).matched).toBe(true);
expect(checkBodyExpect(body, [{ json: { path: "$.version", match: "\\d+\\.\\d+\\.\\d+" } }]).matched).toBe(true);
expect(checkBodyExpect(body, [{ json: { path: "$.count", gte: 100 } }]).matched).toBe(false);
expect(checkBodyExpect(body, [{ json: { gte: 10, path: "$.count" } }]).matched).toBe(true);
expect(checkBodyExpect(body, [{ json: { match: "\\d+\\.\\d+\\.\\d+", path: "$.version" } }]).matched).toBe(true);
expect(checkBodyExpect(body, [{ json: { gte: 100, path: "$.count" } }]).matched).toBe(false);
});
test("json 路径不存在", () => {
const body = JSON.stringify({ status: "ok" });
const r = checkBodyExpect(body, [{ json: { path: "$.notExist", equals: "value" } }]);
const r = checkBodyExpect(body, [{ json: { equals: "value", path: "$.notExist" } }]);
expect(r.matched).toBe(false);
});
test("json 解析失败", () => {
const r = checkBodyExpect("not json", [{ json: { path: "$.status", equals: "ok" } }]);
const r = checkBodyExpect("not json", [{ json: { equals: "ok", path: "$.status" } }]);
expect(r.matched).toBe(false);
expect(r.failure!.kind).toBe("error");
});
test("css 文本内容匹配", () => {
const html = "<div id='health'>OK</div><span class='ver'>1.0</span>";
expect(checkBodyExpect(html, [{ css: { selector: "div#health", equals: "OK" } }]).matched).toBe(true);
expect(checkBodyExpect(html, [{ css: { selector: "span.ver", equals: "1.0" } }]).matched).toBe(true);
expect(checkBodyExpect(html, [{ css: { selector: "div#health", equals: "ERROR" } }]).matched).toBe(false);
expect(checkBodyExpect(html, [{ css: { equals: "OK", selector: "div#health" } }]).matched).toBe(true);
expect(checkBodyExpect(html, [{ css: { equals: "1.0", selector: "span.ver" } }]).matched).toBe(true);
expect(checkBodyExpect(html, [{ css: { equals: "ERROR", selector: "div#health" } }]).matched).toBe(false);
});
test("css 选择器无匹配元素", () => {
const html = "<div>OK</div>";
const r = checkBodyExpect(html, [{ css: { selector: "span.missing", equals: "OK" } }]);
const r = checkBodyExpect(html, [{ css: { equals: "OK", selector: "span.missing" } }]);
expect(r.matched).toBe(false);
});
test("css attr 提取", () => {
const html = '<meta name="version" content="2.0.1">';
expect(
checkBodyExpect(html, [{ css: { selector: 'meta[name="version"]', attr: "content", equals: "2.0.1" } }]).matched,
checkBodyExpect(html, [{ css: { attr: "content", equals: "2.0.1", selector: 'meta[name="version"]' } }]).matched,
).toBe(true);
});
test("css exists 检查", () => {
const html = "<div id='test'>OK</div>";
expect(checkBodyExpect(html, [{ css: { selector: "div#test", exists: true } }]).matched).toBe(true);
expect(checkBodyExpect(html, [{ css: { selector: "span#missing", exists: false } }]).matched).toBe(true);
expect(checkBodyExpect(html, [{ css: { selector: "div#test", exists: false } }]).matched).toBe(false);
expect(checkBodyExpect(html, [{ css: { exists: true, selector: "div#test" } }]).matched).toBe(true);
expect(checkBodyExpect(html, [{ css: { exists: false, selector: "span#missing" } }]).matched).toBe(true);
expect(checkBodyExpect(html, [{ css: { exists: false, selector: "div#test" } }]).matched).toBe(false);
});
test("xpath 节点文本匹配", () => {
const xml = "<root><status>ok</status><code>200</code></root>";
expect(checkBodyExpect(xml, [{ xpath: { path: "/root/status/text()", equals: "ok" } }]).matched).toBe(true);
expect(checkBodyExpect(xml, [{ xpath: { path: "/root/status/text()", equals: "error" } }]).matched).toBe(false);
expect(checkBodyExpect(xml, [{ xpath: { equals: "ok", path: "/root/status/text()" } }]).matched).toBe(true);
expect(checkBodyExpect(xml, [{ xpath: { equals: "error", path: "/root/status/text()" } }]).matched).toBe(false);
});
test("xpath 无匹配节点", () => {
const xml = "<root><status>ok</status></root>";
const r = checkBodyExpect(xml, [{ xpath: { path: "/root/missing/text()", equals: "ok" } }]);
const r = checkBodyExpect(xml, [{ xpath: { equals: "ok", path: "/root/missing/text()" } }]);
expect(r.matched).toBe(false);
});
test("规则数组按顺序检查,第一条失败立即返回", () => {
const body = JSON.stringify({ status: "error" });
const r = checkBodyExpect(body, [{ contains: "healthy" }, { json: { path: "$.status", equals: "error" } }]);
const r = checkBodyExpect(body, [{ contains: "healthy" }, { json: { equals: "error", path: "$.status" } }]);
expect(r.matched).toBe(false);
expect(r.failure!.path).toBe("body[0]");
});
test("多条规则全部通过", () => {
const body = JSON.stringify({ status: "healthy", count: 5 });
const body = JSON.stringify({ count: 5, status: "healthy" });
const r = checkBodyExpect(body, [
{ contains: "healthy" },
{ json: { path: "$.status", equals: "healthy" } },
{ json: { path: "$.count", gte: 1 } },
{ json: { equals: "healthy", path: "$.status" } },
{ json: { gte: 1, path: "$.count" } },
]);
expect(r.matched).toBe(true);
expect(r.failure).toBeNull();
@@ -131,7 +132,7 @@ describe("checkBodyExpect (BodyRule[])", () => {
test("第二条规则失败返回正确索引", () => {
const body = JSON.stringify({ status: "ok" });
const r = checkBodyExpect(body, [{ contains: "ok" }, { json: { path: "$.status", equals: "error" } }]);
const r = checkBodyExpect(body, [{ contains: "ok" }, { json: { equals: "error", path: "$.status" } }]);
expect(r.matched).toBe(false);
expect(r.failure!.path).toContain("body[1]");
});

View File

@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test";
import { checkDuration } from "../../../../../src/server/checker/runner/shared/duration";
describe("checkDuration", () => {

View File

@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test";
import { truncateActual, mismatchFailure, errorFailure } from "../../../../../src/server/checker/runner/shared/failure";
import { errorFailure, mismatchFailure, truncateActual } from "../../../../../src/server/checker/runner/shared/failure";
describe("truncateActual", () => {
test("短字符串不截断", () => {
@@ -43,12 +44,12 @@ describe("mismatchFailure", () => {
test("返回正确的 mismatch 结构", () => {
const f = mismatchFailure("status", "status", [200], 500, "status mismatch");
expect(f).toEqual({
kind: "mismatch",
phase: "status",
path: "status",
expected: [200],
actual: 500,
expected: [200],
kind: "mismatch",
message: "status mismatch",
path: "status",
phase: "status",
});
});
@@ -65,9 +66,9 @@ describe("errorFailure", () => {
const f = errorFailure("body", "body[0].json($.x)", "body is not valid JSON");
expect(f).toEqual({
kind: "error",
phase: "body",
path: "body[0].json($.x)",
message: "body is not valid JSON",
path: "body[0].json($.x)",
phase: "body",
});
});

View File

@@ -1,19 +1,24 @@
import { describe, expect, test } from "bun:test";
import { applyOperator, checkExpectValue, evaluateJsonPath } from "../../../../../src/server/checker/runner/shared/operator";
import {
applyOperator,
checkExpectValue,
evaluateJsonPath,
} from "../../../../../src/server/checker/runner/shared/operator";
describe("evaluateJsonPath", () => {
const obj = {
status: "ok",
code: 0,
active: true,
error: null,
code: 0,
data: {
count: 42,
items: [{ name: "a" }, { name: "b" }],
nested: { deep: "value" },
},
emptyObj: {},
emptyArr: [],
emptyObj: {},
error: null,
status: "ok",
};
test("简单字段访问", () => {

View File

@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test";
import { checkTextRules } from "../../../../../src/server/checker/runner/shared/text";
describe("checkTextRules", () => {
@@ -21,7 +22,11 @@ describe("checkTextRules", () => {
});
test("多条规则全部通过", () => {
const r = checkTextRules("version: 3.2.1, build: ok", [{ contains: "version" }, { match: "\\d+\\.\\d+\\.\\d+" }], "stdout");
const r = checkTextRules(
"version: 3.2.1, build: ok",
[{ contains: "version" }, { match: "\\d+\\.\\d+\\.\\d+" }],
"stdout",
);
expect(r.matched).toBe(true);
});

View File

@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test";
import { parseSize } from "../../../src/server/checker/size";
describe("parseSize", () => {

View File

@@ -1,12 +1,14 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { ProbeStore } from "../../../src/server/checker/store";
import type { CheckFailure, ResolvedTarget } from "../../../src/server/checker/types";
import { mkdir, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { CheckFailure, ResolvedTarget } from "../../../src/server/checker/types";
import { checkerRegistry } from "../../../src/server/checker/runner";
import { HttpChecker } from "../../../src/server/checker/runner/http/runner";
import { CommandChecker } from "../../../src/server/checker/runner/command/runner";
import { HttpChecker } from "../../../src/server/checker/runner/http/runner";
import { ProbeStore } from "../../../src/server/checker/store";
function ensureRegistered() {
if (!checkerRegistry.supportedTypes.includes("http")) {
@@ -20,33 +22,33 @@ beforeAll(() => {
});
const httpTarget: ResolvedTarget = {
type: "http",
name: "test-http",
expect: { maxDurationMs: 3000, status: [200] },
group: "default",
http: {
url: "https://example.com/health",
method: "GET",
headers: { Accept: "application/json" },
maxBodyBytes: 104857600,
method: "GET",
url: "https://example.com/health",
},
intervalMs: 30000,
name: "test-http",
timeoutMs: 10000,
expect: { status: [200], maxDurationMs: 3000 },
type: "http",
};
const commandTarget: ResolvedTarget = {
type: "command",
name: "test-cmd",
group: "default",
command: {
exec: "ping",
args: ["-c", "1", "localhost"],
cwd: "/tmp",
env: {},
exec: "ping",
maxOutputBytes: 104857600,
},
group: "default",
intervalMs: 60000,
name: "test-cmd",
timeoutMs: 5000,
type: "command",
};
describe("ProbeStore", () => {
@@ -61,7 +63,7 @@ describe("ProbeStore", () => {
afterAll(async () => {
store.close();
await rm(tempDir, { recursive: true, force: true });
await rm(tempDir, { force: true, recursive: true });
});
test("初始化后无 targets", () => {
@@ -80,21 +82,26 @@ describe("ProbeStore", () => {
const t = store.getTargets().find((t) => t.name === "test-http")!;
expect(t.type).toBe("http");
expect(t.target).toBe("https://example.com/health");
const config = JSON.parse(t.config);
const config = JSON.parse(t.config) as {
headers: Record<string, string>;
maxBodyBytes: number;
method: string;
url: string;
};
expect(config.url).toBe("https://example.com/health");
expect(config.method).toBe("GET");
expect(config.headers).toEqual({ Accept: "application/json" });
expect(config.maxBodyBytes).toBe(104857600);
expect(t.interval_ms).toBe(30000);
expect(t.timeout_ms).toBe(10000);
expect(JSON.parse(t.expect!)).toEqual({ status: [200], maxDurationMs: 3000 });
expect(JSON.parse(t.expect!)).toEqual({ maxDurationMs: 3000, status: [200] });
});
test("command target 字段正确", () => {
const t = store.getTargets().find((t) => t.name === "test-cmd")!;
expect(t.type).toBe("command");
expect(t.target).toBe("exec ping -c 1 localhost");
const config = JSON.parse(t.config);
const config = JSON.parse(t.config) as { args: string[]; cwd: string; exec: string; maxOutputBytes: number };
expect(config.exec).toBe("ping");
expect(config.args).toEqual(["-c", "1", "localhost"]);
expect(config.cwd).toBe("/tmp");
@@ -144,39 +151,39 @@ describe("ProbeStore", () => {
const t1Id = targets[0]!.id;
store.insertCheckResult({
durationMs: 150.5,
failure: null,
matched: true,
statusDetail: "200 OK",
targetId: t1Id,
timestamp: "2025-01-01T00:00:00.000Z",
matched: true,
durationMs: 150.5,
statusDetail: "200 OK",
failure: null,
});
store.insertCheckResult({
durationMs: 300,
failure: null,
matched: true,
statusDetail: "200 OK",
targetId: t1Id,
timestamp: "2025-01-01T00:00:30.000Z",
matched: true,
durationMs: 300,
statusDetail: "200 OK",
failure: null,
});
const failure: CheckFailure = {
kind: "error",
phase: "duration",
path: "$.maxDurationMs",
expected: 3000,
actual: 5000,
expected: 3000,
kind: "error",
message: "请求耗时 5000ms 超过限制 3000ms",
path: "$.maxDurationMs",
phase: "duration",
};
store.insertCheckResult({
durationMs: null,
failure,
matched: false,
statusDetail: null,
targetId: t1Id,
timestamp: "2025-01-01T00:01:00.000Z",
matched: false,
durationMs: null,
statusDetail: null,
failure,
});
const history = store.getHistory(t1Id, "2025-01-01T00:00:00.000Z", "2025-12-31T23:59:59.999Z", 1, 10);
@@ -198,12 +205,12 @@ describe("ProbeStore", () => {
for (let i = 0; i < 25; i++) {
store.insertCheckResult({
durationMs: 100 + i,
failure: null,
matched: true,
statusDetail: "200 OK",
targetId: t1Id,
timestamp: `2025-01-01T01:${String(i).padStart(2, "0")}:00.000Z`,
matched: true,
durationMs: 100 + i,
statusDetail: "200 OK",
failure: null,
});
}
@@ -274,32 +281,32 @@ describe("ProbeStore", () => {
test("删除 target 级联删除 check_results", () => {
const cascadeStore = new ProbeStore(join(tempDir, "cascade.db"));
const cascadeTarget: ResolvedTarget = {
type: "http",
name: "cascade-test",
group: "default",
http: { url: "http://cascade.test", method: "GET", headers: {}, maxBodyBytes: 104857600 },
http: { headers: {}, maxBodyBytes: 104857600, method: "GET", url: "http://cascade.test" },
intervalMs: 30000,
name: "cascade-test",
timeoutMs: 10000,
type: "http",
};
cascadeStore.syncTargets([cascadeTarget]);
const t = cascadeStore.getTargets()[0]!;
cascadeStore.insertCheckResult({
durationMs: 100,
failure: null,
matched: true,
statusDetail: "200 OK",
targetId: t.id,
timestamp: "2025-01-01T00:00:00.000Z",
matched: true,
durationMs: 100,
statusDetail: "200 OK",
failure: null,
});
cascadeStore.insertCheckResult({
durationMs: null,
failure: { kind: "error", message: "fail", path: "$", phase: "status" },
matched: false,
statusDetail: null,
targetId: t.id,
timestamp: "2025-01-01T00:01:00.000Z",
matched: false,
durationMs: null,
statusDetail: null,
failure: { kind: "error", phase: "status", path: "$", message: "fail" },
});
expect(cascadeStore.getLatestCheck(t.id)).not.toBeNull();
@@ -329,12 +336,12 @@ describe("ProbeStore", () => {
const freshStore = new ProbeStore(join(tempDir, "fresh-map.db"));
freshStore.syncTargets([
{
type: "http",
name: "no-records",
group: "default",
http: { url: "http://no.records", method: "GET", headers: {}, maxBodyBytes: 104857600 },
http: { headers: {}, maxBodyBytes: 104857600, method: "GET", url: "http://no.records" },
intervalMs: 30000,
name: "no-records",
timeoutMs: 10000,
type: "http",
},
]);
@@ -359,8 +366,8 @@ describe("ProbeStore", () => {
const stats2 = stats.get(t2Id);
if (stats2) {
expect(stats2!.totalChecks).toBe(0);
expect(stats2!.availability).toBe(0);
expect(stats2.totalChecks).toBe(0);
expect(stats2.availability).toBe(0);
}
});
@@ -368,12 +375,12 @@ describe("ProbeStore", () => {
const freshStore = new ProbeStore(join(tempDir, "fresh-stats.db"));
freshStore.syncTargets([
{
type: "http",
name: "no-stats",
group: "default",
http: { url: "http://no.stats", method: "GET", headers: {}, maxBodyBytes: 104857600 },
http: { headers: {}, maxBodyBytes: 104857600, method: "GET", url: "http://no.stats" },
intervalMs: 30000,
name: "no-stats",
timeoutMs: 10000,
type: "http",
},
]);