retry.test.js 1.8 KB
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)
  })
})