refactor: 后端架构加固 — 泛型化、批量查询、bootstrap 统一、路径修复与 pageSize 上限
- CheckerDefinition 泛型化,HTTP/Command checker 移除 resolved target 断言 - 新增 ProbeStore.getAllRecentSamples 消除 targets 路由 N+1 查询 - 统一 getAllTargetStats 与 getTargetStats 的 availability 精度 - Engine rejected 结果写入 internal error 记录,提升可观测性 - 新增 bootstrap.ts 统一 dev/production 启动序列 - dataDir 相对路径改为基于配置文件目录解析 - validatePagination 增加 pageSize 上限 200 校验 - 修复 ErrorBoundary override 标记 - 更新 README/DEVELOPMENT 文档,新增完整测试覆盖
This commit is contained in:
@@ -280,6 +280,71 @@ describe("ProbeStore", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("getAllRecentSamples 返回每个 target 的最近采样数据", () => {
|
||||
const sampleStore = new ProbeStore(join(tempDir, "all-samples.db"));
|
||||
const httpA: ResolvedHttpTarget = { ...httpTarget, name: "sample-http-a" };
|
||||
const httpB: ResolvedHttpTarget = {
|
||||
...httpTarget,
|
||||
http: { ...httpTarget.http, url: "https://example.com/other" },
|
||||
name: "sample-http-b",
|
||||
};
|
||||
const httpEmpty: ResolvedHttpTarget = {
|
||||
...httpTarget,
|
||||
http: { ...httpTarget.http, url: "https://example.com/empty" },
|
||||
name: "sample-http-empty",
|
||||
};
|
||||
sampleStore.syncTargets([httpA, httpB, httpEmpty]);
|
||||
const targets = sampleStore.getTargets();
|
||||
const targetAId = targets.find((t) => t.name === "sample-http-a")!.id;
|
||||
const targetBId = targets.find((t) => t.name === "sample-http-b")!.id;
|
||||
const emptyTargetId = targets.find((t) => t.name === "sample-http-empty")!.id;
|
||||
|
||||
for (const [index, timestamp] of [
|
||||
"2025-01-01T00:00:00.000Z",
|
||||
"2025-01-01T00:01:00.000Z",
|
||||
"2025-01-01T00:02:00.000Z",
|
||||
].entries()) {
|
||||
sampleStore.insertCheckResult({
|
||||
durationMs: 100 + index,
|
||||
failure: null,
|
||||
matched: index !== 1,
|
||||
statusDetail: "200 OK",
|
||||
targetId: targetAId,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
sampleStore.insertCheckResult({
|
||||
durationMs: 200,
|
||||
failure: null,
|
||||
matched: true,
|
||||
statusDetail: "200 OK",
|
||||
targetId: targetBId,
|
||||
timestamp: "2025-01-01T00:03:00.000Z",
|
||||
});
|
||||
sampleStore.insertCheckResult({
|
||||
durationMs: null,
|
||||
failure: { kind: "error", message: "fail", path: "request", phase: "request" },
|
||||
matched: false,
|
||||
statusDetail: null,
|
||||
targetId: targetBId,
|
||||
timestamp: "2025-01-01T00:04:00.000Z",
|
||||
});
|
||||
|
||||
const samples = sampleStore.getAllRecentSamples(2);
|
||||
|
||||
expect(samples.get(targetAId)).toEqual([
|
||||
{ duration_ms: 102, matched: 1, timestamp: "2025-01-01T00:02:00.000Z" },
|
||||
{ duration_ms: 101, matched: 0, timestamp: "2025-01-01T00:01:00.000Z" },
|
||||
]);
|
||||
expect(samples.get(targetBId)).toEqual([
|
||||
{ duration_ms: null, matched: 0, timestamp: "2025-01-01T00:04:00.000Z" },
|
||||
{ duration_ms: 200, matched: 1, timestamp: "2025-01-01T00:03:00.000Z" },
|
||||
]);
|
||||
expect(samples.has(emptyTargetId)).toBe(false);
|
||||
|
||||
sampleStore.close();
|
||||
});
|
||||
|
||||
test("关闭后操作不报错", () => {
|
||||
const closedStore = new ProbeStore(join(tempDir, "closed.db"));
|
||||
closedStore.close();
|
||||
@@ -420,6 +485,33 @@ describe("ProbeStore", () => {
|
||||
freshStore.close();
|
||||
});
|
||||
|
||||
test("getAllTargetStats 与 getTargetStats 的 availability 精度一致", () => {
|
||||
const statsStore = new ProbeStore(join(tempDir, "stats-precision.db"));
|
||||
const target: ResolvedHttpTarget = { ...httpTarget, name: "stats-precision" };
|
||||
statsStore.syncTargets([target]);
|
||||
const targetId = statsStore.getTargets()[0]!.id;
|
||||
|
||||
for (const [index, matched] of [true, true, false].entries()) {
|
||||
statsStore.insertCheckResult({
|
||||
durationMs: 100,
|
||||
failure: null,
|
||||
matched,
|
||||
statusDetail: matched ? "200 OK" : "500 ERROR",
|
||||
targetId,
|
||||
timestamp: `2025-01-01T00:0${index}:00.000Z`,
|
||||
});
|
||||
}
|
||||
|
||||
const targetStats = statsStore.getTargetStats(targetId);
|
||||
const allStats = statsStore.getAllTargetStats().get(targetId)!;
|
||||
|
||||
expect(targetStats.availability).toBe(66.67);
|
||||
expect(allStats.availability).toBe(66.67);
|
||||
expect(allStats.availability).toBe(targetStats.availability);
|
||||
|
||||
statsStore.close();
|
||||
});
|
||||
|
||||
test("prune 删除过期数据", () => {
|
||||
const pruneStore = new ProbeStore(join(tempDir, "prune.db"));
|
||||
pruneStore.syncTargets([httpTarget]);
|
||||
|
||||
Reference in New Issue
Block a user