98 lines
3.0 KiB
JavaScript
98 lines
3.0 KiB
JavaScript
/**
|
||
* 单元测试:健康聚合(MOD-HEALTH-AGG / C-NO-DB / C-REDIS-ONLY-STATE)。
|
||
* 运行:cd cloudflare-worker && node --test
|
||
*/
|
||
import test from "node:test";
|
||
import assert from "node:assert/strict";
|
||
import { aggregateHealth, healthResponse } from "../src/health.js";
|
||
|
||
function mockCtx(plan, opts = {}) {
|
||
const calls = [];
|
||
const cache = opts.cache ?? new Map();
|
||
let time = 1_000_000;
|
||
const fetch = async (url) => {
|
||
calls.push(url);
|
||
const step = plan[url] ?? { status: 500 };
|
||
if (step.error) throw step.error;
|
||
return new Response(step.body ?? "ok", { status: step.status });
|
||
};
|
||
return {
|
||
fetch,
|
||
timeoutMs: 1000,
|
||
ttlMs: opts.ttlMs ?? 30_000,
|
||
now: () => time,
|
||
advance: (ms) => {
|
||
time += ms;
|
||
},
|
||
setTimeout: () => 0,
|
||
clearTimeout: () => {},
|
||
cache,
|
||
calls,
|
||
};
|
||
}
|
||
|
||
test("aggregateHealth: all up => 200 ok summary", async () => {
|
||
const ctx = mockCtx({
|
||
"https://a.example/healthz": { status: 200 },
|
||
"https://b.example/healthz": { status: 200 },
|
||
});
|
||
const res = await aggregateHealth(["https://a.example", "https://b.example"], ctx);
|
||
assert.equal(res.available, 2);
|
||
assert.equal(res.total, 2);
|
||
const resp = healthResponse(res);
|
||
assert.equal(resp.status, 200);
|
||
const body = await resp.json();
|
||
assert.equal(body.status, "ok");
|
||
assert.equal(body.available, 2);
|
||
assert.equal(body.total, 2);
|
||
});
|
||
|
||
test("aggregateHealth: partial down => still reports status (partial ok)", async () => {
|
||
const ctx = mockCtx({
|
||
"https://a.example/healthz": { status: 200 },
|
||
"https://b.example/healthz": { error: new Error("conn refused") },
|
||
});
|
||
const res = await aggregateHealth(["https://a.example", "https://b.example"], ctx);
|
||
assert.equal(res.available, 1);
|
||
assert.equal(res.total, 2);
|
||
const resp = healthResponse(res);
|
||
assert.equal(resp.status, 200, "at least one up => 200 ok");
|
||
const body = await resp.json();
|
||
assert.equal(body.status, "ok");
|
||
assert.equal(body.available, 1);
|
||
});
|
||
|
||
test("aggregateHealth: all down => 503", async () => {
|
||
const ctx = mockCtx({
|
||
"https://a.example/healthz": { status: 503 },
|
||
"https://b.example/healthz": { error: new Error("conn refused") },
|
||
});
|
||
const res = await aggregateHealth(["https://a.example", "https://b.example"], ctx);
|
||
assert.equal(res.available, 0);
|
||
const resp = healthResponse(res);
|
||
assert.equal(resp.status, 503);
|
||
const body = await resp.json();
|
||
assert.equal(body.status, "down");
|
||
});
|
||
|
||
test("aggregateHealth: respects cache TTL to avoid probe storm", async () => {
|
||
const ctx = mockCtx(
|
||
{
|
||
"https://a.example/healthz": { status: 200 },
|
||
},
|
||
{ ttlMs: 10_000 },
|
||
);
|
||
await aggregateHealth(["https://a.example"], ctx);
|
||
assert.equal(ctx.calls.length, 1);
|
||
|
||
// 未过 TTL:命中缓存,不再发起 fetch。
|
||
ctx.advance(5_000);
|
||
await aggregateHealth(["https://a.example"], ctx);
|
||
assert.equal(ctx.calls.length, 1);
|
||
|
||
// 超过 TTL:发起新 fetch。
|
||
ctx.advance(6_000);
|
||
await aggregateHealth(["https://a.example"], ctx);
|
||
assert.equal(ctx.calls.length, 2);
|
||
});
|