retry.test.js
1.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import { describe, it, expect } from 'vitest'
import {
DEFAULT_RETRY_POLICY,
isRetryableReason,
shouldRetry,
backoffDelayMs,
} from '../src/retry.js'
describe('isRetryableReason', () => {
it('rateLimit / transient / timeout = true', () => {
expect(isRetryableReason('rateLimit')).toBe(true)
expect(isRetryableReason('transient')).toBe(true)
expect(isRetryableReason('timeout')).toBe(true)
})
it('auth / badRequest / aborted / unknown = false', () => {
expect(isRetryableReason('auth')).toBe(false)
expect(isRetryableReason('badRequest')).toBe(false)
expect(isRetryableReason('aborted')).toBe(false)
expect(isRetryableReason('unknown')).toBe(false)
})
})
describe('shouldRetry', () => {
const p = { maxRetries: 2, baseDelayMs: 1, maxDelayMs: 10, factor: 2 }
it('never retries non-retryable', () => {
expect(shouldRetry(1, false, p)).toBe(false)
})
it('retries up to maxRetries then stops', () => {
expect(shouldRetry(1, true, p)).toBe(true)
expect(shouldRetry(2, true, p)).toBe(true)
expect(shouldRetry(3, true, p)).toBe(false)
})
it('uses DEFAULT_RETRY_POLICY when omitted', () => {
expect(shouldRetry(1, true)).toBe(true)
expect(shouldRetry(DEFAULT_RETRY_POLICY.maxRetries + 1, true)).toBe(false)
})
})
describe('backoffDelayMs', () => {
const p = { maxRetries: 5, baseDelayMs: 100, maxDelayMs: 1000, factor: 2 }
it('grows exponentially from attempt 1', () => {
expect(backoffDelayMs(1, p)).toBe(100)
expect(backoffDelayMs(2, p)).toBe(200)
expect(backoffDelayMs(3, p)).toBe(400)
})
it('caps at maxDelayMs', () => {
expect(backoffDelayMs(10, p)).toBe(1000)
})
it('prefers explicit retryAfterMs but still capped', () => {
expect(backoffDelayMs(1, p, 250)).toBe(250)
expect(backoffDelayMs(1, p, 99999)).toBe(1000)
})
})