1
0

refactor: 迁移 Bun fullstack 架构

This commit is contained in:
2026-05-14 00:23:37 +08:00
parent bcfac52112
commit 6e485cc991
36 changed files with 403 additions and 1081 deletions

View File

@@ -11,11 +11,11 @@ import type {
TargetStatus,
} from "../../src/shared/api";
import { createFetchHandler, type StaticAssets } from "../../src/server/app";
import { checkerRegistry } from "../../src/server/checker/runner";
import { CommandChecker } from "../../src/server/checker/runner/command/execute";
import { HttpChecker } from "../../src/server/checker/runner/http/execute";
import { ProbeStore } from "../../src/server/checker/store";
import { startServer } from "../../src/server/server";
import { rmRetry } from "../helpers";
function ensureRegistered() {
@@ -29,19 +29,11 @@ beforeAll(() => {
ensureRegistered();
});
const staticAssets: StaticAssets = {
files: {
"/assets/app.js": new Blob(["console.log('app');"], { type: "text/javascript" }),
},
indexHtml: new Blob(['<!doctype html><title>DiAL</title><div id="root"></div>'], {
type: "text/html",
}),
};
describe("API 路由", () => {
let tempDir: string;
let store: ProbeStore;
let fetchHandler: ReturnType<typeof createFetchHandler>;
let server: ReturnType<typeof startServer>;
let baseUrl: string;
beforeAll(async () => {
tempDir = join(tmpdir(), `dial-api-test-${Date.now()}`);
@@ -104,16 +96,22 @@ describe("API 路由", () => {
timestamp: "2025-01-01T00:00:30.000Z",
});
fetchHandler = createFetchHandler({ mode: "test", staticAssets, store });
server = startServer({
config: { host: "127.0.0.1", port: 0 },
mode: "test",
store,
});
baseUrl = `http://127.0.0.1:${server.port}`;
});
afterAll(async () => {
await server.stop(true);
store.close();
await rmRetry(tempDir);
});
test("/health 返回健康检查", async () => {
const response = fetchHandler(new Request("http://localhost/health"));
const response = await fetch(`${baseUrl}/health`);
const body = (await response.json()) as HealthResponse;
expect(response.status).toBe(200);
@@ -122,7 +120,7 @@ describe("API 路由", () => {
});
test("/api/summary 返回总览统计", async () => {
const response = fetchHandler(new Request("http://localhost/api/summary"));
const response = await fetch(`${baseUrl}/api/summary`);
const body = (await response.json()) as SummaryResponse;
expect(response.status).toBe(200);
expect(body.total).toBe(2);
@@ -133,7 +131,7 @@ describe("API 路由", () => {
});
test("/api/targets 返回目标列表", async () => {
const response = fetchHandler(new Request("http://localhost/api/targets"));
const response = await fetch(`${baseUrl}/api/targets`);
const body = (await response.json()) as TargetStatus[];
expect(response.status).toBe(200);
@@ -158,7 +156,7 @@ describe("API 路由", () => {
});
test("/api/meta 返回 checker 类型列表", async () => {
const response = fetchHandler(new Request("http://localhost/api/meta"));
const response = await fetch(`${baseUrl}/api/meta`);
const body = (await response.json()) as MetaResponse;
expect(response.status).toBe(200);
@@ -167,29 +165,16 @@ describe("API 路由", () => {
expect(body.checkerTypes).toContain("command");
});
test("/api/meta HEAD 请求返回 headers 无 body", async () => {
const response = fetchHandler(new Request("http://localhost/api/meta", { method: "HEAD" }));
const body = await response.text();
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toContain("application/json");
expect(body).toBe("");
});
test("/api/meta 不支持的 method 返回 405", () => {
const response = fetchHandler(new Request("http://localhost/api/meta", { method: "POST" }));
expect(response.status).toBe(405);
expect(response.headers.get("allow")).toBe("GET, HEAD");
test("不支持的 method 在有 API 通配符时返回 404", async () => {
const response = await fetch(`${baseUrl}/api/summary`, { method: "POST" });
expect(response.status).toBe(404);
});
test("/api/targets/:id/history 返回历史记录", async () => {
const targets = store.getTargets();
const from = "2024-01-01T00:00:00.000Z";
const to = "2026-12-31T23:59:59.999Z";
const response = fetchHandler(
new Request(`http://localhost/api/targets/${targets[0]!.id}/history?from=${from}&to=${to}`),
);
const response = await fetch(`${baseUrl}/api/targets/${targets[0]!.id}/history?from=${from}&to=${to}`);
const body = (await response.json()) as HistoryResponse;
expect(response.status).toBe(200);
@@ -205,9 +190,7 @@ describe("API 路由", () => {
const targets = store.getTargets();
const from = "2024-01-01T00:00:00.000Z";
const to = "2026-12-31T23:59:59.999Z";
const response = fetchHandler(
new Request(`http://localhost/api/targets/${targets[0]!.id}/history?from=${from}&to=${to}&pageSize=1`),
);
const response = await fetch(`${baseUrl}/api/targets/${targets[0]!.id}/history?from=${from}&to=${to}&pageSize=1`);
const body = (await response.json()) as HistoryResponse;
expect(response.status).toBe(200);
@@ -219,9 +202,7 @@ describe("API 路由", () => {
const targets = store.getTargets();
const from = "2024-01-01T00:00:00.000Z";
const to = "2026-12-31T23:59:59.999Z";
const response = fetchHandler(
new Request(`http://localhost/api/targets/${targets[0]!.id}/history?from=${from}&to=${to}&pageSize=201`),
);
const response = await fetch(`${baseUrl}/api/targets/${targets[0]!.id}/history?from=${from}&to=${to}&pageSize=201`);
const body = (await response.json()) as Record<string, unknown>;
expect(response.status).toBe(400);
@@ -232,9 +213,7 @@ describe("API 路由", () => {
const targets = store.getTargets();
const from = "2024-01-01T00:00:00.000Z";
const to = "2026-12-31T23:59:59.999Z";
const response = fetchHandler(
new Request(`http://localhost/api/targets/${targets[0]!.id}/trend?from=${from}&to=${to}`),
);
const response = await fetch(`${baseUrl}/api/targets/${targets[0]!.id}/trend?from=${from}&to=${to}`);
const body = (await response.json()) as unknown[];
expect(response.status).toBe(200);
@@ -242,10 +221,8 @@ describe("API 路由", () => {
});
test("查询不存在的目标返回 404", async () => {
const response = fetchHandler(
new Request(
"http://localhost/api/targets/99999/history?from=2024-01-01T00:00:00.000Z&to=2026-12-31T23:59:59.999Z",
),
const response = await fetch(
`${baseUrl}/api/targets/99999/history?from=2024-01-01T00:00:00.000Z&to=2026-12-31T23:59:59.999Z`,
);
const body = (await response.json()) as Record<string, unknown>;
@@ -255,7 +232,7 @@ describe("API 路由", () => {
test("history 缺少 from/to 参数返回 400", async () => {
const targets = store.getTargets();
const response = fetchHandler(new Request(`http://localhost/api/targets/${targets[0]!.id}/history`));
const response = await fetch(`${baseUrl}/api/targets/${targets[0]!.id}/history`);
const body = (await response.json()) as Record<string, unknown>;
expect(response.status).toBe(400);
@@ -264,7 +241,7 @@ describe("API 路由", () => {
test("trend 缺少 from/to 参数返回 400", async () => {
const targets = store.getTargets();
const response = fetchHandler(new Request(`http://localhost/api/targets/${targets[0]!.id}/trend`));
const response = await fetch(`${baseUrl}/api/targets/${targets[0]!.id}/trend`);
const body = (await response.json()) as Record<string, unknown>;
expect(response.status).toBe(400);
@@ -272,8 +249,8 @@ describe("API 路由", () => {
});
test("trend 无效 targetId 返回 400", async () => {
const response = fetchHandler(
new Request("http://localhost/api/targets/invalid/trend?from=2024-01-01T00:00:00Z&to=2024-01-02T00:00:00Z"),
const response = await fetch(
`${baseUrl}/api/targets/invalid/trend?from=2024-01-01T00:00:00Z&to=2024-01-02T00:00:00Z`,
);
const body = (await response.json()) as Record<string, unknown>;
@@ -281,44 +258,24 @@ describe("API 路由", () => {
expect(body["error"]).toBe("Invalid target ID");
});
test("未知 /api/* 返回 404", () => {
const response = fetchHandler(new Request("http://localhost/api/missing"));
test("未知 /api/* 返回 404", async () => {
const response = await fetch(`${baseUrl}/api/missing`);
expect(response.status).toBe(404);
});
test("HEAD 请求返回 headers 无 body", async () => {
const response = fetchHandler(new Request("http://localhost/api/summary", { method: "HEAD" }));
const body = await response.text();
expect(response.status).toBe(200);
expect(body).toBe("");
});
test("不支持的 method 返回 405", () => {
const response = fetchHandler(new Request("http://localhost/api/summary", { method: "POST" }));
expect(response.status).toBe(405);
expect(response.headers.get("allow")).toBe("GET, HEAD");
});
test("生产响应包含安全 headers", () => {
const prodHandler = createFetchHandler({ mode: "production", staticAssets, store });
const response = prodHandler(new Request("http://localhost/api/summary"));
expect(response.headers.get("x-content-type-options")).toBe("nosniff");
expect(response.headers.get("referrer-policy")).toBe("strict-origin-when-cross-origin");
});
test("静态资源和 SPA fallback 正常工作", () => {
const root = fetchHandler(new Request("http://localhost/"));
expect(root.status).toBe(200);
const fallback = fetchHandler(new Request("http://localhost/dashboard"));
expect(fallback.status).toBe(200);
const asset = fetchHandler(new Request("http://localhost/assets/app.js"));
expect(asset.status).toBe(200);
test("生产响应包含安全 headers", async () => {
const prodServer = startServer({
config: { host: "127.0.0.1", port: 0 },
mode: "production",
store,
});
try {
const response = await fetch(`http://127.0.0.1:${prodServer.port}/api/summary`);
expect(response.headers.get("x-content-type-options")).toBe("nosniff");
expect(response.headers.get("referrer-policy")).toBe("strict-origin-when-cross-origin");
} finally {
await prodServer.stop(true);
}
});
test("损坏的 failure JSON 返回 null 而不崩溃", async () => {
@@ -340,7 +297,7 @@ describe("API 路由", () => {
const from = "2025-06-01T00:00:00.000Z";
const to = "2025-06-01T23:59:59.999Z";
const response = fetchHandler(new Request(`http://localhost/api/targets/${t1Id}/history?from=${from}&to=${to}`));
const response = await fetch(`${baseUrl}/api/targets/${t1Id}/history?from=${from}&to=${to}`);
const body = (await response.json()) as HistoryResponse;
expect(response.status).toBe(200);