/** * RetryPolicy Tests — Verify retry logic for different error types. */ import { describe, it, expect, beforeEach } from 'vitest'; import { DefaultRetryPolicy, type AgentError } from './retry-policy.js'; describe('DefaultRetryPolicy', () => { let retryPolicy: DefaultRetryPolicy; beforeEach(() => { retryPolicy = new DefaultRetryPolicy(); }); describe('configuration', () => { it('should have correct max attempts', () => { expect(retryPolicy.maxAttempts).toBe(3); }); it('should have exponential backoff delays', () => { expect(retryPolicy.backoffMs).toEqual([1000, 2000, 4000]); }); }); describe('shouldRetry', () => { it('should retry auth failures', () => { const error: AgentError = { type: 'auth_failure', message: 'Unauthorized', isTransient: true, requiresAccountSwitch: false, shouldPersistToDB: true }; expect(retryPolicy.shouldRetry(error, 1)).toBe(true); expect(retryPolicy.shouldRetry(error, 2)).toBe(true); expect(retryPolicy.shouldRetry(error, 3)).toBe(false); // At max attempts }); it('should not retry usage limit errors', () => { const error: AgentError = { type: 'usage_limit', message: 'Rate limit exceeded', isTransient: false, requiresAccountSwitch: true, shouldPersistToDB: true }; expect(retryPolicy.shouldRetry(error, 1)).toBe(false); expect(retryPolicy.shouldRetry(error, 2)).toBe(false); }); it('should retry missing signal errors', () => { const error: AgentError = { type: 'missing_signal', message: 'No signal.json found', isTransient: true, requiresAccountSwitch: false, shouldPersistToDB: false }; expect(retryPolicy.shouldRetry(error, 1)).toBe(true); expect(retryPolicy.shouldRetry(error, 2)).toBe(true); expect(retryPolicy.shouldRetry(error, 3)).toBe(false); // At max attempts }); it('should retry transient process crashes', () => { const error: AgentError = { type: 'process_crash', message: 'Process died', isTransient: true, requiresAccountSwitch: false, shouldPersistToDB: true }; expect(retryPolicy.shouldRetry(error, 1)).toBe(true); expect(retryPolicy.shouldRetry(error, 2)).toBe(true); }); it('should not retry non-transient process crashes', () => { const error: AgentError = { type: 'process_crash', message: 'Segmentation fault', isTransient: false, requiresAccountSwitch: false, shouldPersistToDB: true }; expect(retryPolicy.shouldRetry(error, 1)).toBe(false); expect(retryPolicy.shouldRetry(error, 2)).toBe(false); }); it('should retry timeouts', () => { const error: AgentError = { type: 'timeout', message: 'Process timed out', isTransient: true, requiresAccountSwitch: false, shouldPersistToDB: true }; expect(retryPolicy.shouldRetry(error, 1)).toBe(true); expect(retryPolicy.shouldRetry(error, 2)).toBe(true); expect(retryPolicy.shouldRetry(error, 3)).toBe(false); // At max attempts }); it('should not retry unknown errors', () => { const error: AgentError = { type: 'unknown', message: 'Something weird happened', isTransient: false, requiresAccountSwitch: false, shouldPersistToDB: true }; expect(retryPolicy.shouldRetry(error, 1)).toBe(false); expect(retryPolicy.shouldRetry(error, 2)).toBe(false); }); it('should not retry when at max attempts regardless of error type', () => { const error: AgentError = { type: 'auth_failure', message: 'Unauthorized', isTransient: true, requiresAccountSwitch: false, shouldPersistToDB: true }; expect(retryPolicy.shouldRetry(error, 3)).toBe(false); expect(retryPolicy.shouldRetry(error, 4)).toBe(false); }); }); describe('getRetryDelay', () => { it('should return correct delay for each attempt', () => { expect(retryPolicy.getRetryDelay(1)).toBe(1000); expect(retryPolicy.getRetryDelay(2)).toBe(2000); expect(retryPolicy.getRetryDelay(3)).toBe(4000); }); it('should cap delay at maximum for high attempts', () => { expect(retryPolicy.getRetryDelay(4)).toBe(4000); expect(retryPolicy.getRetryDelay(10)).toBe(4000); }); }); });