1
0

fix: 安全性与代码质量加固(异常保护、外键级联、竞态修复、优雅关机)

This commit is contained in:
2026-05-11 14:24:12 +08:00
parent 35ba56888b
commit 0ee10b47c9
17 changed files with 132 additions and 33 deletions

View File

@@ -254,4 +254,33 @@ describe("API 路由", () => {
const asset = await fetchHandler(new Request("http://localhost/assets/app.js"));
expect(asset.status).toBe(200);
});
test("损坏的 failure JSON 返回 null 而不崩溃", async () => {
const targets = store.getTargets();
const t1Id = targets[0]!.id;
store.insertCheckResult({
targetId: t1Id,
timestamp: "2025-06-01T00:00:00.000Z",
matched: false,
durationMs: 100,
statusDetail: "200 OK",
failure: { kind: "error", phase: "body", path: "$", message: "test" },
});
(store as unknown as { db: { prepare: (sql: string) => { run: (...args: unknown[]) => void } } }).db
.prepare("UPDATE check_results SET failure = ? WHERE target_id = ? AND timestamp = ?")
.run("{invalid json!!!", t1Id, "2025-06-01T00:00:00.000Z");
const from = "2025-06-01T00:00:00.000Z";
const to = "2025-06-01T23:59:59.999Z";
const response = await fetchHandler(
new Request(`http://localhost/api/targets/${t1Id}/history?from=${from}&to=${to}`),
);
const body = (await response.json()) as HistoryResponse;
expect(response.status).toBe(200);
expect(body.items).toHaveLength(1);
expect(body.items[0]!.failure).toBeNull();
});
});

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { parseSize, DEFAULT_MAX_BODY_BYTES, DEFAULT_MAX_OUTPUT_BYTES } from "../../../src/server/checker/size";
import { parseSize } from "../../../src/server/checker/size";
describe("parseSize", () => {
test("解析 B", () => {
@@ -35,9 +35,4 @@ describe("parseSize", () => {
expect(() => parseSize("abc")).toThrow("无效的 size 格式");
expect(() => parseSize("")).toThrow("无效的 size 格式");
});
test("默认值", () => {
expect(DEFAULT_MAX_BODY_BYTES).toBe(104857600);
expect(DEFAULT_MAX_OUTPUT_BYTES).toBe(104857600);
});
});

View File

@@ -256,4 +256,45 @@ describe("ProbeStore", () => {
expect(closedStore.getTargets()).toHaveLength(0);
expect(closedStore.getTargetById(1)).toBeNull();
});
test("删除 target 级联删除 check_results", () => {
const cascadeStore = new ProbeStore(join(tempDir, "cascade.db"));
const cascadeTarget: ResolvedTarget = {
type: "http",
name: "cascade-test",
group: "default",
http: { url: "http://cascade.test", method: "GET", headers: {}, maxBodyBytes: 104857600 },
intervalMs: 30000,
timeoutMs: 10000,
};
cascadeStore.syncTargets([cascadeTarget]);
const t = cascadeStore.getTargets()[0]!;
cascadeStore.insertCheckResult({
targetId: t.id,
timestamp: "2025-01-01T00:00:00.000Z",
matched: true,
durationMs: 100,
statusDetail: "200 OK",
failure: null,
});
cascadeStore.insertCheckResult({
targetId: t.id,
timestamp: "2025-01-01T00:01:00.000Z",
matched: false,
durationMs: null,
statusDetail: null,
failure: { kind: "error", phase: "status", path: "$", message: "fail" },
});
expect(cascadeStore.getLatestCheck(t.id)).not.toBeNull();
cascadeStore.syncTargets([]);
expect(cascadeStore.getTargets()).toHaveLength(0);
expect(cascadeStore.getLatestCheck(t.id)).toBeNull();
cascadeStore.close();
});
});