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); });