test(ql3): bound provider convergence retries

This commit is contained in:
whyour
2026-08-30 01:22:55 +08:00
parent be7a3fea41
commit 97333da34c
2 changed files with 44 additions and 10 deletions
@@ -1105,11 +1105,24 @@ request.on('timeout',()=>request.destroy(Object.assign(new Error('timeout'),{cod
return evidence; return evidence;
} }
async function retryProviderEvidence(read, pause = undefined) { async function retryProviderEvidence(read, options = {}) {
assert.equal(typeof read, 'function'); assert.equal(typeof read, 'function');
assert.ok(pause === undefined || typeof pause === 'function'); assert.equal(typeof options, 'object');
assert.ok(options !== null && !Array.isArray(options));
const pause =
options.pause ??
((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
const now = options.now ?? Date.now;
const intervalMs = options.intervalMs ?? 1_000;
const timeoutMs = options.timeoutMs ?? 3 * 60_000;
const maxAttempts = options.maxAttempts ?? 16;
assert.equal(typeof pause, 'function');
assert.equal(typeof now, 'function');
assert.ok(Number.isSafeInteger(intervalMs) && intervalMs >= 0);
assert.ok(Number.isSafeInteger(timeoutMs) && timeoutMs > 0);
assert.ok(Number.isSafeInteger(maxAttempts) && maxAttempts > 0);
const startedAt = now();
let lastError; let lastError;
const maxAttempts = 8;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try { try {
return await read(); return await read();
@@ -1119,13 +1132,12 @@ async function retryProviderEvidence(read, pause = undefined) {
!/\b(?:ECONNREFUSED|ECONNRESET|ETIMEDOUT|TIMEOUT)\b/.test( !/\b(?:ECONNREFUSED|ECONNRESET|ETIMEDOUT|TIMEOUT)\b/.test(
error instanceof Error ? error.message : String(error), error instanceof Error ? error.message : String(error),
) || ) ||
attempt === maxAttempts attempt === maxAttempts ||
now() - startedAt >= timeoutMs
) { ) {
throw error; throw error;
} }
await ( await pause(intervalMs);
pause ?? (() => new Promise((resolve) => setTimeout(resolve, 1_000)))
)();
} }
} }
throw lastError; throw lastError;
@@ -377,7 +377,7 @@ test('retries only bounded transient provider evidence failures', async () => {
if (attempts < 3) throw new Error('{"code":"ECONNREFUSED"}'); if (attempts < 3) throw new Error('{"code":"ECONNREFUSED"}');
return { requestCount: 1 }; return { requestCount: 1 };
}, },
async () => {}, { pause: async () => {} },
); );
assert.deepEqual(evidence, { requestCount: 1 }); assert.deepEqual(evidence, { requestCount: 1 });
assert.equal(attempts, 3); assert.equal(attempts, 3);
@@ -386,7 +386,7 @@ test('retries only bounded transient provider evidence failures', async () => {
async () => { async () => {
throw new Error('invalid evidence schema'); throw new Error('invalid evidence schema');
}, },
async () => {}, { pause: async () => {} },
), ),
/invalid evidence schema/, /invalid evidence schema/,
); );
@@ -398,11 +398,33 @@ test('retries only bounded transient provider evidence failures', async () => {
attempts += 1; attempts += 1;
throw new Error('{"code":"ECONNREFUSED"}'); throw new Error('{"code":"ECONNREFUSED"}');
}, },
async () => {}, { maxAttempts: 8, pause: async () => {} },
), ),
/ECONNREFUSED/, /ECONNREFUSED/,
); );
assert.equal(attempts, 8); assert.equal(attempts, 8);
attempts = 0;
let elapsedMs = 0;
await assert.rejects(
retryProviderEvidence(
async () => {
attempts += 1;
throw new Error('{"code":"ETIMEDOUT"}');
},
{
intervalMs: 1_000,
maxAttempts: 100,
now: () => elapsedMs,
pause: async (delayMs) => {
elapsedMs += delayMs;
},
timeoutMs: 3_000,
},
),
/ETIMEDOUT/,
);
assert.equal(attempts, 4);
}); });
test('provider fixture logs only generation and authorization decision', () => { test('provider fixture logs only generation and authorization decision', () => {