fix(api): retry transient session reads safely
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { fetchWithTransientRetry } from './src/api/core.ts';
|
||||
|
||||
async function withMockFetch(mock, run) {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = mock;
|
||||
try {
|
||||
await run();
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
}
|
||||
|
||||
test('idempotent requests retry transient network failures', async () => {
|
||||
let calls = 0;
|
||||
await withMockFetch(
|
||||
async () => {
|
||||
calls += 1;
|
||||
if (calls < 3) throw new TypeError('fetch failed');
|
||||
return new Response('{}', { status: 200 });
|
||||
},
|
||||
async () => {
|
||||
const response = await fetchWithTransientRetry('/api/sessions', undefined, 1_000, [0, 0]);
|
||||
assert.equal(response.status, 200);
|
||||
},
|
||||
);
|
||||
assert.equal(calls, 3);
|
||||
});
|
||||
|
||||
test('idempotent requests retry transient gateway responses', async () => {
|
||||
let calls = 0;
|
||||
await withMockFetch(
|
||||
async () => {
|
||||
calls += 1;
|
||||
return calls === 1
|
||||
? new Response('temporary', { status: 503 })
|
||||
: new Response('{}', { status: 200 });
|
||||
},
|
||||
async () => {
|
||||
const response = await fetchWithTransientRetry('/api/sessions', undefined, 1_000, [0]);
|
||||
assert.equal(response.status, 200);
|
||||
},
|
||||
);
|
||||
assert.equal(calls, 2);
|
||||
});
|
||||
|
||||
test('non-idempotent requests are never retried', async () => {
|
||||
let calls = 0;
|
||||
await withMockFetch(
|
||||
async () => {
|
||||
calls += 1;
|
||||
throw new TypeError('fetch failed');
|
||||
},
|
||||
async () => {
|
||||
await assert.rejects(
|
||||
fetchWithTransientRetry(
|
||||
'/api/agent/start',
|
||||
{ method: 'POST', body: '{}' },
|
||||
1_000,
|
||||
[0, 0],
|
||||
),
|
||||
/fetch failed/,
|
||||
);
|
||||
},
|
||||
);
|
||||
assert.equal(calls, 1);
|
||||
});
|
||||
|
||||
test('aborted requests are never retried', async () => {
|
||||
let calls = 0;
|
||||
await withMockFetch(
|
||||
async () => {
|
||||
calls += 1;
|
||||
throw new DOMException('Aborted', 'AbortError');
|
||||
},
|
||||
async () => {
|
||||
await assert.rejects(
|
||||
fetchWithTransientRetry('/api/sessions', undefined, 1_000, [0, 0]),
|
||||
(error) => error instanceof DOMException && error.name === 'AbortError',
|
||||
);
|
||||
},
|
||||
);
|
||||
assert.equal(calls, 1);
|
||||
});
|
||||
Reference in New Issue
Block a user