1
0
Files
DiAL/tests/server/bootstrap.test.ts
lanyuanxiaoyao 7926514986 feat: 配置变量系统与 target id/name 双字段标识
- 新增顶层 variables 段支持 string/number/boolean 字面量
- target 字符串字段支持 、、{...} 转义语法
- 变量解析优先级: variables -> process.env -> 默认值 -> 报错
- 完整引用保留原始类型,部分引用拼接为字符串
- 变量替换在 YAML 解析后、AJV 校验前执行
- 替换仅作用于 targets,跳过 id/type 字段
- target 新增必填 id 字段作为唯一标识,name 改为可选展示名称
- 数据库存储/API/前端全面迁移到 id 标识
- 统一 checker 运行时类型检查为 es-toolkit predicates
- 同步 delta specs 到主 specs,归档 config-variables 变更
2026-05-17 00:37:54 +08:00

134 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",
id: "test",
intervalMs: 30000,
name: "test",
timeoutMs: 5000,
type: "cmd",
};
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"]);
});
});