openvibers/openvibe.media Commits

Shorten webhook retry waits in tests without patching global setTimeout

OP OpenVibers committed 2 Oct 2026 · 593f0a710b · 2 files, +15 −6

Requested by finish-f1. Handed to the local harness by the cloud job pr-OpenVibe.Media-8; done by Claude opus.

Changes

ddiff --git a/server/webhooks.js b/server/webhooks.js
iindex dc0c380..6009194 100644
---- a/server/webhooks.js
++++ b/server/webhooks.js
@@@ -42,6 +42,11 @@ function stats() {
/** Test-only: forget every tally. */
function _resetStats() { tallies.clear(); }
++// Test-only: the base of the retry wait (null restores RETRY_BASE_MS), so a test never patches the
++// global setTimeout the database pool's own timers use.
++let retryBaseMs = RETRY_BASE_MS;
++function _setRetryBaseMs(ms) { retryBaseMs = ms == null ? RETRY_BASE_MS : ms; }
++
function sign(secret, rawBody) {
return 'sha256=' + crypto.createHmac('sha256', String(secret || '')).update(rawBody).digest('hex');
}
@@@ -99,7 +104,7 @@ async function sendWebhook(appOrId, event, data, { eventId = null } = {}) {
console.warn(`[Webhooks] ${event} → ${app.app_id} failed after ${MAX_ATTEMPTS} attempts: ${err.message}`);
return false;
}
-- await new Promise(r => setTimeout(r, RETRY_BASE_MS * attempt));
++ await new Promise(r => setTimeout(r, retryBaseMs * attempt));
}
}
return false;
@@@ -129,4 +134,4 @@ async function announce(appId, event, { change = null, payload }) {
return { data, eventId };
}
--module.exports = { sendWebhook, announce, sign, stats, _resetStats };
++module.exports = { sendWebhook, announce, sign, stats, _resetStats, _setRetryBaseMs };
ddiff --git a/test/me-explorer.test.js b/test/me-explorer.test.js
iindex b8d2105..2605089 100644
---- a/test/me-explorer.test.js
++++ b/test/me-explorer.test.js
@@@ -389,14 +389,18 @@ const http = require('http');
const hooks = require('../server/webhooks');
hooks._resetStats();
await db.run("UPDATE apps SET webhook_url = 'https://hooks-down.example.test/never' WHERE app_id = 'tools'");
-- const realFetch = global.fetch, realSetTimeout = global.setTimeout;
-- global.setTimeout = (fn) => realSetTimeout(fn, 0); // retries must not slow the suite
-- global.fetch = async (url) => ({ ok: String(url).includes('hooks.example.test'), status: String(url).includes('hooks.example.test') ? 200 : 500 });
++ const realFetch = global.fetch;
++ hooks._setRetryBaseMs(0); // retries must not slow the suite (never a global setTimeout patch: the pg pool's timers use it)
++ global.fetch = async (url, opts) => {
++ const u = String(url);
++ if (!u.includes('hooks.example.test') && !u.includes('hooks-down.example.test')) return realFetch(url, opts);
++ return { ok: u.includes('hooks.example.test'), status: u.includes('hooks.example.test') ? 200 : 500 };
++ };
try {
assert.strictEqual(await hooks.sendWebhook('live', 'vod.ready', { id: 1 }), true);
assert.strictEqual(await hooks.sendWebhook('tools', 'clip.ready', { id: 2 }), false);
} finally {
-- global.fetch = realFetch; global.setTimeout = realSetTimeout;
++ global.fetch = realFetch; hooks._setRetryBaseMs(null);
await db.run("UPDATE apps SET webhook_url = NULL, webhook_secret = NULL WHERE app_id = 'tools'");
}
const st = hooks.stats();