Files
OmniRoute/tests/unit/proxy-probe-target.test.ts
Dizzle f4772500bc fix(api): share one probe-target resolution between both proxy health checks (#10657)
Merged — locally validated (15/15 focused tests, typecheck:core clean, file-size/changelog gates green) after resolving base-drift against #10654 (both landed today, same file — combined import block, no logical conflict). Thanks!
2026-08-20 10:32:38 -03:00

162 lines
5.9 KiB
TypeScript

/**
* Guards for the shared probe-target resolution (#8411).
*
* The scheduler sweep and the "Test All" endpoint used to carry their own copy of the probe
* target and batch size. They now share src/lib/proxyHealth/probeTarget.ts, so these tests pin
* two things: that the defaults still match what both call sites hardcoded before, and that no
* environment value can produce a batch size that would hang the sweep loop.
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
DEFAULT_PROBE_CONCURRENCY,
DEFAULT_PROBE_STAGGER_MS,
DEFAULT_PROBE_TARGET,
MAX_PROBE_CONCURRENCY,
MAX_PROBE_STAGGER_MS,
resolveProbeConcurrency,
resolveProbeStaggerMs,
resolveProbeTarget,
staggerDelayMs,
waitForProbeSlot,
} from "../../src/lib/proxyHealth/probeTarget.ts";
// ─── the loop-safety invariant ─────────────────────────
test("no environment value can yield a batch size below 1", () => {
// `for (i = 0; i < n; i += concurrency)` never advances at 0 and walks backwards below it,
// so this is the one property that turns a bad config into a hung sweep rather than a slow one.
const hostile = [
"0",
"-1",
"-9999",
"",
" ",
"abc",
"NaN",
"Infinity",
"-Infinity",
"1e-9",
"0.4",
"null",
undefined,
];
for (const raw of hostile) {
const resolved = resolveProbeConcurrency({ PROXY_HEALTH_TEST_CONCURRENCY: raw });
assert.ok(
Number.isInteger(resolved) && resolved >= 1,
`concurrency ${JSON.stringify(raw)} resolved to ${resolved}`
);
}
});
// ─── defaults: what both call sites hardcoded before ───
test("the defaults reproduce the previous hardcoded values", () => {
assert.equal(resolveProbeTarget({}), DEFAULT_PROBE_TARGET);
assert.equal(DEFAULT_PROBE_TARGET, "https://httpbin.org/ip");
assert.equal(resolveProbeConcurrency({}), DEFAULT_PROBE_CONCURRENCY);
assert.equal(DEFAULT_PROBE_CONCURRENCY, 10);
assert.equal(resolveProbeStaggerMs({}), DEFAULT_PROBE_STAGGER_MS);
});
// ─── target ────────────────────────────────────────────
test("an operator-supplied target wins", () => {
assert.equal(
resolveProbeTarget({ PROXY_HEALTH_TEST_URL: "https://probe.internal/ping" }),
"https://probe.internal/ping"
);
});
test("an empty target falls back, a whitespace one is left untouched", () => {
assert.equal(resolveProbeTarget({ PROXY_HEALTH_TEST_URL: "" }), DEFAULT_PROBE_TARGET);
// Whitespace is truthy, so it reached the probe before this refactor. Trimming it here would
// silently repair a broken deployment — a behavior change this change is not entitled to make.
assert.equal(resolveProbeTarget({ PROXY_HEALTH_TEST_URL: " " }), " ");
});
// ─── concurrency ───────────────────────────────────────
test("concurrency honours a sane value and is capped", () => {
assert.equal(resolveProbeConcurrency({ PROXY_HEALTH_TEST_CONCURRENCY: "3" }), 3);
assert.equal(
resolveProbeConcurrency({ PROXY_HEALTH_TEST_CONCURRENCY: "10000" }),
MAX_PROBE_CONCURRENCY
);
});
test("an unparseable concurrency falls back instead of yielding NaN", () => {
assert.equal(
resolveProbeConcurrency({ PROXY_HEALTH_TEST_CONCURRENCY: "abc" }),
DEFAULT_PROBE_CONCURRENCY
);
});
// ─── stagger ───────────────────────────────────────────
test("the stagger step is clamped to its bounds", () => {
assert.equal(resolveProbeStaggerMs({ PROXY_HEALTH_TEST_STAGGER_MS: "250" }), 250);
assert.equal(resolveProbeStaggerMs({ PROXY_HEALTH_TEST_STAGGER_MS: "0" }), 0);
assert.equal(resolveProbeStaggerMs({ PROXY_HEALTH_TEST_STAGGER_MS: "-50" }), 0);
assert.equal(
resolveProbeStaggerMs({ PROXY_HEALTH_TEST_STAGGER_MS: "999999" }),
MAX_PROBE_STAGGER_MS
);
});
test("an unparseable stagger falls back instead of yielding NaN", () => {
const resolved = resolveProbeStaggerMs({ PROXY_HEALTH_TEST_STAGGER_MS: "later" });
assert.equal(resolved, DEFAULT_PROBE_STAGGER_MS);
assert.ok(Number.isFinite(resolved));
});
// ─── delay computation ─────────────────────────────────
test("the head of a batch is never delayed", () => {
assert.equal(staggerDelayMs(0, 100), 0);
});
test("delays grow strictly with the position in the batch", () => {
const step = 100;
for (let i = 1; i < 10; i++) {
assert.equal(staggerDelayMs(i, step), i * step);
assert.ok(staggerDelayMs(i, step) > staggerDelayMs(i - 1, step));
}
});
test("a zero or negative step inserts no wait at all", () => {
for (const step of [0, -1]) {
for (let i = 0; i < 5; i++) {
assert.equal(staggerDelayMs(i, step), 0);
}
}
});
test("a negative index cannot produce a negative wait", () => {
assert.equal(staggerDelayMs(-1, 100), 0);
});
// ─── the wait itself ───────────────────────────────────
test("the head of a batch is not held back at all", async () => {
const before = Date.now();
await waitForProbeSlot(0, 100);
// No timer is armed for slot 0, so this resolves on the microtask queue. The assertion is
// deliberately loose: it proves no ~100ms timer fired, not a precise scheduler timing.
assert.ok(Date.now() - before < 50, "slot 0 must not wait");
});
test("a later slot is actually held back", async () => {
const before = Date.now();
await waitForProbeSlot(2, 30);
assert.ok(Date.now() - before >= 55, "slot 2 must wait about two steps");
});
test("a disabled step holds nobody back", async () => {
const before = Date.now();
await Promise.all([waitForProbeSlot(5, 0), waitForProbeSlot(9, 0)]);
assert.ok(Date.now() - before < 50, "a zero step must insert no wait");
});