133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { join } from "node:path";
|
|
|
|
import type { ResolvedConfig } from "../../src/server/checker/config-loader";
|
|
import type { ProbeEngine } from "../../src/server/checker/engine";
|
|
import type { ProbeStore } from "../../src/server/checker/store";
|
|
import type { ResolvedTargetBase } from "../../src/server/checker/types";
|
|
|
|
import { bootstrap, type BootstrapDependencies } from "../../src/server/bootstrap";
|
|
|
|
type ShutdownSignal = "SIGINT" | "SIGTERM";
|
|
|
|
const target: ResolvedTargetBase = {
|
|
group: "default",
|
|
intervalMs: 30000,
|
|
name: "test",
|
|
timeoutMs: 5000,
|
|
type: "command",
|
|
};
|
|
|
|
function createHarness(overrides: BootstrapDependencies = {}) {
|
|
const calls: string[] = [];
|
|
const shutdownHandlers = new Map<ShutdownSignal, () => void>();
|
|
const config: ResolvedConfig = {
|
|
configDir: "/tmp",
|
|
dataDir: "/tmp/dial-data",
|
|
host: "127.0.0.1",
|
|
maxConcurrentChecks: 3,
|
|
port: 3000,
|
|
retentionMs: 1000,
|
|
targets: [target],
|
|
};
|
|
const store = {
|
|
close() {
|
|
calls.push("store.close");
|
|
},
|
|
syncTargets(targets: ResolvedTargetBase[]) {
|
|
calls.push(`syncTargets:${targets.length}`);
|
|
},
|
|
} as unknown as ProbeStore;
|
|
const engine = {
|
|
start() {
|
|
calls.push("engine.start");
|
|
},
|
|
stop() {
|
|
calls.push("engine.stop");
|
|
},
|
|
} as unknown as ProbeEngine;
|
|
|
|
const dependencies: BootstrapDependencies = {
|
|
createEngine(actualStore, targets, maxConcurrentChecks, retentionMs) {
|
|
expect(actualStore).toBe(store);
|
|
calls.push(`createEngine:${targets.length}:${maxConcurrentChecks}:${retentionMs}`);
|
|
return engine;
|
|
},
|
|
createStore(dbPath) {
|
|
calls.push(`createStore:${dbPath}`);
|
|
return store;
|
|
},
|
|
exit(code) {
|
|
calls.push(`exit:${code}`);
|
|
throw new Error(`exit:${code}`);
|
|
},
|
|
loadConfig(configPath) {
|
|
calls.push(`loadConfig:${configPath}`);
|
|
return Promise.resolve(config);
|
|
},
|
|
logError(...data) {
|
|
calls.push(`logError:${String(data[1])}`);
|
|
},
|
|
onSignal(signal, handler) {
|
|
calls.push(`onSignal:${signal}`);
|
|
shutdownHandlers.set(signal, handler);
|
|
},
|
|
startServer(options) {
|
|
expect(options.config).toEqual({ host: config.host, port: config.port });
|
|
expect(options.store).toBe(store);
|
|
calls.push(`startServer:${options.mode}`);
|
|
},
|
|
...overrides,
|
|
};
|
|
|
|
return { calls, dependencies, shutdownHandlers };
|
|
}
|
|
|
|
describe("bootstrap", () => {
|
|
test("开发模式执行完整启动序列", async () => {
|
|
const { calls, dependencies } = createHarness();
|
|
|
|
await bootstrap({ configPath: "/tmp/probes.yaml", mode: "development" }, dependencies);
|
|
|
|
expect(calls).toEqual([
|
|
"loadConfig:/tmp/probes.yaml",
|
|
`createStore:${join("/tmp/dial-data", "probe.db")}`,
|
|
"syncTargets:1",
|
|
"createEngine:1:3:1000",
|
|
"engine.start",
|
|
"onSignal:SIGINT",
|
|
"onSignal:SIGTERM",
|
|
"startServer:development",
|
|
]);
|
|
});
|
|
|
|
test("收到退出信号时停止 engine 并关闭 store", async () => {
|
|
const { calls, dependencies, shutdownHandlers } = createHarness();
|
|
|
|
await bootstrap({ configPath: "/tmp/probes.yaml", mode: "development" }, dependencies);
|
|
|
|
expect(() => shutdownHandlers.get("SIGINT")!()).toThrow("exit:0");
|
|
|
|
expect(calls.slice(-3)).toEqual(["engine.stop", "store.close", "exit:0"]);
|
|
});
|
|
|
|
test("启动失败时输出错误并以非零退出", async () => {
|
|
const { calls, dependencies } = createHarness({
|
|
loadConfig() {
|
|
return Promise.reject(new Error("bad config"));
|
|
},
|
|
});
|
|
|
|
let error: unknown;
|
|
try {
|
|
await bootstrap({ configPath: "/tmp/probes.yaml", mode: "development" }, dependencies);
|
|
} catch (caught) {
|
|
error = caught;
|
|
}
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
expect((error as Error).message).toBe("exit:1");
|
|
expect(calls).toEqual(["logError:bad config", "exit:1"]);
|
|
});
|
|
});
|