新增 YAML 配置解析(Bun 内置 YAML)、SQLite 数据存储(bun:sqlite)、按 interval 分组并发拨测引擎、REST API(/api/summary、/api/targets、/api/targets/:id/history、/api/targets/:id/trend)、React 前端 Dashboard(统计卡片、目标表格、可展开详情面板、recharts 趋势图)。CLI 简化为仅接受配置文件路径。移除 /api/demo 路由和相关 demo 代码。保留 /health、静态资源服务和 SPA fallback。
93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
import { ProbeStore } from "../../../src/server/checker/store";
|
|
import { ProbeEngine } from "../../../src/server/checker/engine";
|
|
import type { ResolvedTarget } from "../../../src/server/checker/types";
|
|
import { mkdir, rm } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
|
|
describe("ProbeEngine", () => {
|
|
let tempDir: string;
|
|
let store: ProbeStore;
|
|
|
|
const target: ResolvedTarget = {
|
|
name: "httpbin",
|
|
url: "https://httpbin.org/get",
|
|
method: "GET",
|
|
headers: {},
|
|
intervalMs: 60000,
|
|
timeoutMs: 10000,
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
tempDir = join(tmpdir(), `gc-engine-test-${Date.now()}`);
|
|
await mkdir(tempDir, { recursive: true });
|
|
store = new ProbeStore(join(tempDir, "test.db"));
|
|
store.syncTargets([target]);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
store.close();
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
});
|
|
|
|
test("groupByInterval 分组逻辑", () => {
|
|
const targets: ResolvedTarget[] = [
|
|
{ name: "a", url: "http://a.com", method: "GET", headers: {}, intervalMs: 30000, timeoutMs: 10000 },
|
|
{ name: "b", url: "http://b.com", method: "GET", headers: {}, intervalMs: 30000, timeoutMs: 10000 },
|
|
{ name: "c", url: "http://c.com", method: "GET", headers: {}, intervalMs: 60000, timeoutMs: 10000 },
|
|
];
|
|
|
|
const engine = new ProbeEngine(store, targets);
|
|
engine.start();
|
|
engine.stop();
|
|
|
|
// 只要能启动和停止不出错就行
|
|
expect(true).toBe(true);
|
|
});
|
|
|
|
test("engine start/stop 不抛错", () => {
|
|
const engine = new ProbeEngine(store, [target]);
|
|
engine.start();
|
|
engine.stop();
|
|
expect(true).toBe(true);
|
|
});
|
|
|
|
test("单次拨测写入数据库", async () => {
|
|
const engine = new ProbeEngine(store, [target]);
|
|
// 手动调用 probeGroup 不启动 timer
|
|
const probeGroup = (engine as unknown as { probeGroup: (t: ResolvedTarget[]) => Promise<void> }).probeGroup.bind(engine);
|
|
await probeGroup([target]);
|
|
|
|
const dbTargets = store.getTargets();
|
|
const latest = store.getLatestCheck(dbTargets[0]!.id);
|
|
expect(latest).not.toBeNull();
|
|
expect(latest!.success === 1 || latest!.success === 0).toBe(true);
|
|
});
|
|
|
|
test("单目标失败隔离", async () => {
|
|
const badTarget: ResolvedTarget = {
|
|
name: "bad-target",
|
|
url: "http://127.0.0.1:1/impossible",
|
|
method: "GET",
|
|
headers: {},
|
|
intervalMs: 60000,
|
|
timeoutMs: 2000,
|
|
};
|
|
|
|
store.syncTargets([target, badTarget]);
|
|
|
|
const engine = new ProbeEngine(store, [target, badTarget]);
|
|
const probeGroup = (engine as unknown as { probeGroup: (t: ResolvedTarget[]) => Promise<void> }).probeGroup.bind(engine);
|
|
await probeGroup([target, badTarget]);
|
|
|
|
const dbTargets = store.getTargets();
|
|
const goodResult = store.getLatestCheck(dbTargets.find((t) => t.name === "httpbin")!.id);
|
|
const badResult = store.getLatestCheck(dbTargets.find((t) => t.name === "bad-target")!.id);
|
|
|
|
expect(goodResult).not.toBeNull();
|
|
expect(badResult).not.toBeNull();
|
|
expect(badResult!.success).toBe(0);
|
|
});
|
|
});
|