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