159 lines
5.3 KiB
JavaScript
159 lines
5.3 KiB
JavaScript
/**
|
||
* 集成测试:Cloudflare Worker 主入口 handle fetch(ARCH-LB-WORKER / SAF-LB-PASSTHRU)。
|
||
* 验证:路由分发、/healthz 聚合、未白名单 404、method 拦截、配置异常 fail-closed、鉴权头透传。
|
||
* 运行:cd cloudflare-worker && node --test
|
||
*/
|
||
import test from "node:test";
|
||
import assert from "node:assert/strict";
|
||
import { handleFetch } from "../src/index.js";
|
||
|
||
function makeEnv(overrides = {}) {
|
||
return {
|
||
BACKEND_ORIGINS_JSON: JSON.stringify(["https://a.example", "https://b.example"]),
|
||
LB_REQUEST_TIMEOUT_MS: "5000",
|
||
LB_MAX_ATTEMPTS: "2",
|
||
...overrides,
|
||
};
|
||
}
|
||
|
||
/** 安装/卸载 globalThis.fetch mock。 */
|
||
function stubFetch(impl) {
|
||
const orig = globalThis.fetch;
|
||
globalThis.fetch = impl;
|
||
return () => {
|
||
globalThis.fetch = orig;
|
||
};
|
||
}
|
||
|
||
test("Worker GET /healthz aggregates backend probes without touching forwarded routes", async () => {
|
||
const restore = stubFetch(async (url) => new Response("ok", { status: 200 }));
|
||
try {
|
||
const res = await handleFetch(new Request("https://lb.example/healthz", { method: "GET" }), makeEnv());
|
||
assert.equal(res.status, 200);
|
||
const body = await res.json();
|
||
assert.equal(body.status, "ok");
|
||
assert.equal(body.available, 2);
|
||
assert.equal(body.total, 2);
|
||
assert.ok(Array.isArray(body.backends));
|
||
} finally {
|
||
restore();
|
||
}
|
||
});
|
||
|
||
test("Worker: unknown route => 404 (C-LB-SINGLE-REG-URL)", async () => {
|
||
const res = await handleFetch(
|
||
new Request("https://lb.example/unknown", { method: "POST", body: "{}" }),
|
||
makeEnv(),
|
||
);
|
||
assert.equal(res.status, 404);
|
||
assert.match(await res.text(), /route not forwarded/);
|
||
});
|
||
|
||
test("Worker: wrong method on safelist route => 405", async () => {
|
||
const res = await handleFetch(
|
||
new Request("https://lb.example/webhook/tg", { method: "GET" }),
|
||
makeEnv(),
|
||
);
|
||
assert.equal(res.status, 405);
|
||
});
|
||
|
||
test("Worker: business config PUT is forwarded with the session bearer and empty 204 response", async () => {
|
||
const restore = stubFetch(async (url, init) => {
|
||
assert.match(url, /^https:\/\/(a|b)\.example\/api\/business-config$/);
|
||
assert.equal(init.method, "PUT");
|
||
assert.equal(init.headers.get("authorization"), "Bearer opaque-session");
|
||
assert.equal(await new Response(init.body).text(), "{\"llm_enabled\":false}");
|
||
return new Response(null, { status: 204 });
|
||
});
|
||
try {
|
||
const res = await handleFetch(
|
||
new Request("https://lb.example/api/business-config", {
|
||
method: "PUT",
|
||
headers: { authorization: "Bearer opaque-session", "content-type": "application/json" },
|
||
body: "{\"llm_enabled\":false}",
|
||
}),
|
||
makeEnv(),
|
||
);
|
||
assert.equal(res.status, 204);
|
||
} finally {
|
||
restore();
|
||
}
|
||
});
|
||
|
||
test("Worker: public setup status is forwarded without an authorization header", async () => {
|
||
const restore = stubFetch(async (url, init) => {
|
||
assert.match(url, /^https:\/\/(a|b)\.example\/api\/status$/);
|
||
assert.equal(init.method, "GET");
|
||
assert.equal(new Headers(init.headers).has("authorization"), false);
|
||
return new Response(JSON.stringify({
|
||
ready: false,
|
||
mode: "configuration-setup",
|
||
missing: ["REDIS_URL"],
|
||
}), { status: 200, headers: { "content-type": "application/json" } });
|
||
});
|
||
try {
|
||
const res = await handleFetch(new Request("https://lb.example/api/status"), makeEnv());
|
||
assert.equal(res.status, 200);
|
||
assert.deepEqual(await res.json(), {
|
||
ready: false,
|
||
mode: "configuration-setup",
|
||
missing: ["REDIS_URL"],
|
||
});
|
||
} finally {
|
||
restore();
|
||
}
|
||
});
|
||
|
||
test("Worker: misconfigured origins => 503 fail-closed (no env leak)", async () => {
|
||
const res = await handleFetch(
|
||
new Request("https://lb.example/reconcile", { method: "POST", body: "{}" }),
|
||
makeEnv({ BACKEND_ORIGINS_JSON: "not-json" }),
|
||
);
|
||
assert.equal(res.status, 503);
|
||
assert.equal(await res.text(), "misconfigured backends");
|
||
});
|
||
|
||
test("Worker: valid env, GET /ready forwarded to backend with auth header intact", async () => {
|
||
const restore = stubFetch(async (url, init) => {
|
||
assert.match(url, /^https:\/\/(a|b)\.example\/ready$/);
|
||
return new Response("ready", { status: 200, headers: { "content-type": "text/plain" } });
|
||
});
|
||
try {
|
||
const res = await handleFetch(
|
||
new Request("https://lb.example/ready", {
|
||
method: "GET",
|
||
headers: { authorization: "Bearer lb-test" },
|
||
}),
|
||
makeEnv(),
|
||
);
|
||
assert.equal(res.status, 200);
|
||
} finally {
|
||
restore();
|
||
}
|
||
});
|
||
|
||
test("Worker: POST /api/push/register and /api/push/disable are forwarded", async () => {
|
||
const seen = [];
|
||
const restore = stubFetch(async (url, init) => {
|
||
seen.push([url, init.method]);
|
||
return new Response("{\"error\":\"invalid_request\",\"request_id\":\"x\"}", {
|
||
status: 400,
|
||
headers: { "content-type": "application/json" },
|
||
});
|
||
});
|
||
try {
|
||
for (const path of ["/api/push/register", "/api/push/disable"]) {
|
||
const res = await handleFetch(
|
||
new Request(`https://lb.example${path}`, { method: "POST", body: "{}" }),
|
||
makeEnv(),
|
||
);
|
||
assert.equal(res.status, 400);
|
||
assert.deepEqual(await res.json(), { error: "invalid_request", request_id: "x" });
|
||
}
|
||
assert.equal(seen.length, 2);
|
||
assert.ok(seen.every(([url, method]) => method === "POST"));
|
||
} finally {
|
||
restore();
|
||
}
|
||
});
|