mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-18 12:52:25 +03:00
Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit. Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them. - ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on #13243) - `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK - complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline - 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs - `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for #13278 and #13276, annotated and landed on #13243 ⚠️ base-red inherited: #12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch.
65 lines
2.8 KiB
TypeScript
65 lines
2.8 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { deliverWebhook } = await import("../../src/lib/webhookDispatcher.ts");
|
|
|
|
// Regression for the dangling abort-timer leak: deliverWebhook arms a 10s
|
|
// setTimeout(() => controller.abort()) before each fetch. The pre-fix code only
|
|
// called clearTimeout on the success path, so a non-timeout fetch rejection
|
|
// (ECONNREFUSED, DNS failure, etc.) skipped clearTimeout, leaking a live 10s timer
|
|
// + AbortController per failed delivery. The fix clears the timer in a `finally`.
|
|
//
|
|
// #12569: deliverWebhook now DNS-resolves and pins the connection before dispatch, so a
|
|
// `globalThis.fetch` stub alone no longer intercepts the outbound call (the pinned fetch talks
|
|
// to undici directly). Inject a fake `lookup` (no real DNS) and `fetchImpl` (the documented
|
|
// escape hatch — see `WebhookDeliveryOptions`) instead, so this test stays deterministic and
|
|
// network-free while still exercising the exact "fetch rejects" path it targets.
|
|
test("deliverWebhook clears the abort timer even when fetch rejects", async () => {
|
|
const realSetTimeout = globalThis.setTimeout;
|
|
const realClearTimeout = globalThis.clearTimeout;
|
|
|
|
const abortTimerIds = new Set<unknown>();
|
|
const clearedIds = new Set<unknown>();
|
|
|
|
// Track the 10s abort timer ids; delegate to the real timer so ids stay valid.
|
|
globalThis.setTimeout = ((fn: any, delay?: number, ...args: any[]) => {
|
|
const id = realSetTimeout(fn, delay as any, ...args);
|
|
if (delay === 10_000) abortTimerIds.add(id);
|
|
return id;
|
|
}) as typeof setTimeout;
|
|
globalThis.clearTimeout = ((id: any) => {
|
|
clearedIds.add(id);
|
|
return realClearTimeout(id);
|
|
}) as typeof clearTimeout;
|
|
|
|
try {
|
|
const res = await deliverWebhook(
|
|
"https://example.com/webhook",
|
|
{ event: "test.event" as any, timestamp: new Date().toISOString(), data: {} },
|
|
null,
|
|
0, // maxRetries=0 → single attempt, no exponential-backoff timers
|
|
{
|
|
lookup: async () => [{ address: "203.0.113.5", family: 4 }],
|
|
// Non-timeout network failure — the exact path that previously skipped clearTimeout.
|
|
fetchImpl: async () => {
|
|
throw new Error("ECONNREFUSED");
|
|
},
|
|
}
|
|
);
|
|
|
|
assert.equal(res.success, false, "delivery should fail when fetch rejects");
|
|
assert.ok(abortTimerIds.size >= 1, "an abort timer should have been armed");
|
|
// The regression guard: every armed abort timer must have been cleared,
|
|
// even though the fetch rejected.
|
|
for (const id of abortTimerIds) {
|
|
assert.ok(
|
|
clearedIds.has(id),
|
|
"abort timer must be cleared in finally even when fetch rejects (no dangling 10s timer)"
|
|
);
|
|
}
|
|
} finally {
|
|
globalThis.setTimeout = realSetTimeout;
|
|
globalThis.clearTimeout = realClearTimeout;
|
|
}
|
|
});
|