feat: 完善全栈打包质量门禁
在业务开发前补齐 lint、format、verify 与生产运行时契约,确保开发联调和 executable 打包链路可重复验证。
This commit is contained in:
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";
|
||||
import { createFetchHandler, type StaticAssets } from "../../src/server/app";
|
||||
|
||||
const staticAssets: StaticAssets = {
|
||||
indexHtml: new Blob(["<!doctype html><title>Gateway Checker Demo</title><div id=\"root\"></div>"], {
|
||||
indexHtml: new Blob(['<!doctype html><title>Gateway Checker Demo</title><div id="root"></div>'], {
|
||||
type: "text/html",
|
||||
}),
|
||||
files: {
|
||||
@@ -12,6 +12,7 @@ const staticAssets: StaticAssets = {
|
||||
|
||||
describe("Bun fullstack runtime", () => {
|
||||
const fetchHandler = createFetchHandler({ mode: "test", staticAssets });
|
||||
const productionFetchHandler = createFetchHandler({ mode: "production", staticAssets });
|
||||
|
||||
test("/api/demo 返回 JSON demo 响应", async () => {
|
||||
const response = await fetchHandler(new Request("http://localhost/api/demo"));
|
||||
@@ -32,12 +33,53 @@ describe("Bun fullstack runtime", () => {
|
||||
expect(body.service).toBe("gateway-checker");
|
||||
});
|
||||
|
||||
test("HEAD 请求运行时端点返回 headers 但无 body", async () => {
|
||||
const response = await fetchHandler(new Request("http://localhost/api/demo", { 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("HEAD 请求健康检查端点返回 headers 但无 body", async () => {
|
||||
const response = await fetchHandler(new Request("http://localhost/health", { 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("运行时端点拒绝不支持的 method", async () => {
|
||||
const response = await fetchHandler(new Request("http://localhost/api/demo", { method: "POST" }));
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status).toBe(405);
|
||||
expect(response.headers.get("allow")).toBe("GET, HEAD");
|
||||
expect(response.headers.get("content-type")).toContain("application/json");
|
||||
expect(body.status).toBe(405);
|
||||
expect(body.error).toBe("Method not allowed");
|
||||
});
|
||||
|
||||
test("健康检查端点拒绝不支持的 method", async () => {
|
||||
const response = await fetchHandler(new Request("http://localhost/health", { method: "POST" }));
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status).toBe(405);
|
||||
expect(response.headers.get("allow")).toBe("GET, HEAD");
|
||||
expect(response.headers.get("content-type")).toContain("application/json");
|
||||
expect(body.status).toBe(405);
|
||||
expect(body.error).toBe("Method not allowed");
|
||||
});
|
||||
|
||||
test("未知 /api/* 路由返回 JSON 404", async () => {
|
||||
const response = await fetchHandler(new Request("http://localhost/api/missing"));
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.headers.get("content-type")).toContain("application/json");
|
||||
expect(body.error).toBe("API route not found");
|
||||
expect(body.status).toBe(404);
|
||||
});
|
||||
|
||||
@@ -47,6 +89,7 @@ describe("Bun fullstack runtime", () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("content-type")).toContain("text/html");
|
||||
expect(response.headers.get("cache-control")).toBe("no-cache");
|
||||
expect(body).toContain("Gateway Checker Demo");
|
||||
});
|
||||
|
||||
@@ -56,9 +99,18 @@ describe("Bun fullstack runtime", () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("content-type")).toContain("text/javascript");
|
||||
expect(response.headers.get("cache-control")).toBe("public, max-age=31536000, immutable");
|
||||
expect(body).toContain("demo");
|
||||
});
|
||||
|
||||
test("未知静态资源返回 404 且不 fallback 到入口 HTML", async () => {
|
||||
const response = await fetchHandler(new Request("http://localhost/assets/missing.js"));
|
||||
const body = await response.text();
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(body).not.toContain("Gateway Checker Demo");
|
||||
});
|
||||
|
||||
test("前端路由 fallback 到入口 HTML", async () => {
|
||||
const response = await fetchHandler(new Request("http://localhost/dashboard"));
|
||||
const body = await response.text();
|
||||
@@ -66,4 +118,15 @@ describe("Bun fullstack runtime", () => {
|
||||
expect(response.status).toBe(200);
|
||||
expect(body).toContain("Gateway Checker Demo");
|
||||
});
|
||||
|
||||
test("生产响应包含低风险安全 headers", async () => {
|
||||
const json = await productionFetchHandler(new Request("http://localhost/api/demo"));
|
||||
const html = await productionFetchHandler(new Request("http://localhost/"));
|
||||
const asset = await productionFetchHandler(new Request("http://localhost/assets/app.js"));
|
||||
|
||||
for (const response of [json, html, asset]) {
|
||||
expect(response.headers.get("x-content-type-options")).toBe("nosniff");
|
||||
expect(response.headers.get("referrer-policy")).toBe("strict-origin-when-cross-origin");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,10 @@ describe("runtime config", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("环境变量可以覆盖默认端口", () => {
|
||||
expect(readRuntimeConfig([], { PORT: "4100" })).toEqual({ host: "127.0.0.1", port: 4100 });
|
||||
});
|
||||
|
||||
test("支持 inline CLI 参数", () => {
|
||||
expect(readRuntimeConfig(["--host=localhost", "--port=4002"], {})).toEqual({
|
||||
host: "localhost",
|
||||
@@ -22,5 +26,13 @@ describe("runtime config", () => {
|
||||
|
||||
test("拒绝无效端口", () => {
|
||||
expect(() => readRuntimeConfig(["--port", "invalid"], {})).toThrow("无效端口");
|
||||
expect(() => readRuntimeConfig(["--port", "3000.5"], {})).toThrow("无效端口");
|
||||
expect(() => readRuntimeConfig(["--port", "-1"], {})).toThrow("无效端口");
|
||||
expect(() => readRuntimeConfig(["--port", "65536"], {})).toThrow("无效端口");
|
||||
});
|
||||
|
||||
test("接受端口边界值", () => {
|
||||
expect(readRuntimeConfig(["--port", "0"], {})).toEqual({ host: "127.0.0.1", port: 0 });
|
||||
expect(readRuntimeConfig(["--port", "65535"], {})).toEqual({ host: "127.0.0.1", port: 65535 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user