Skip to content

Commit da44b47

Browse files
amcaplanclaude
andcommitted
Add test verifying transient network errors ARE retried
Complements the "does not retry certificate errors" test by explicitly verifying that transient errors DO trigger retry logic and eventually succeed. Tests 5 key transient error types from the PR: - ETIMEDOUT (timeout) - ECONNRESET (connection reset) - socket hang up - premature close - getaddrinfo ENOTFOUND (DNS failure) Each error is tested to ensure: 1. The error triggers a retry (mockRequestFn called 2x) 2. The request eventually succeeds after retry This confirms the distinction: transient errors retry, permanent errors don't. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 25e6449 commit da44b47

File tree

1 file changed

+48
-0
lines changed

1 file changed

+48
-0
lines changed

packages/cli-kit/src/private/node/api.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,54 @@ describe('retryAwareRequest', () => {
372372
expect(mockRequestFn).toHaveBeenCalledTimes(1)
373373
}
374374
})
375+
376+
test('retries transient network errors and eventually succeeds', async () => {
377+
const transientErrors = [
378+
{error: 'ETIMEDOUT', description: 'timeout'},
379+
{error: 'ECONNRESET', description: 'connection reset'},
380+
{error: 'socket hang up', description: 'socket hang up'},
381+
{error: 'premature close', description: 'premature close'},
382+
{error: 'getaddrinfo ENOTFOUND', description: 'DNS failure'},
383+
]
384+
385+
for (const {error: transientError, description} of transientErrors) {
386+
const mockRequestFn = vi
387+
.fn()
388+
.mockImplementationOnce(() => {
389+
throw new Error(transientError)
390+
})
391+
.mockImplementationOnce(() => {
392+
return Promise.resolve({
393+
status: 200,
394+
data: {success: true},
395+
headers: new Headers(),
396+
})
397+
})
398+
399+
const mockScheduleDelayFn = vi.fn((fn, _delay) => fn())
400+
401+
const result = retryAwareRequest(
402+
{
403+
request: mockRequestFn,
404+
url: 'https://example.com/graphql.json',
405+
useNetworkLevelRetry: true,
406+
maxRetryTimeMs: 2000,
407+
},
408+
undefined,
409+
{defaultDelayMs: 10, scheduleDelay: mockScheduleDelayFn},
410+
)
411+
412+
await vi.runAllTimersAsync()
413+
414+
await expect(result).resolves.toEqual({
415+
headers: expect.anything(),
416+
status: 200,
417+
data: {success: true},
418+
})
419+
expect(mockRequestFn, `${description} should trigger retry`).toHaveBeenCalledTimes(2)
420+
// scheduleDelay might not always be called depending on timing, so we just verify the retry happened
421+
}
422+
})
375423
})
376424

377425
describe('isTransientNetworkError', () => {

0 commit comments

Comments
 (0)