feat: 将 demo 项目转化为 HTTP 拨测监控工具
新增 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。
This commit is contained in:
92
tests/server/checker/engine.test.ts
Normal file
92
tests/server/checker/engine.test.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user