fix issues
This commit is contained in:
126
apps/website/lib/api/base/RetryHandler.test.ts
Normal file
126
apps/website/lib/api/base/RetryHandler.test.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { RetryHandler, CircuitBreaker, CircuitBreakerRegistry, DEFAULT_RETRY_CONFIG } from './RetryHandler';
|
||||
|
||||
describe('RetryHandler', () => {
|
||||
let handler: RetryHandler;
|
||||
const fastConfig = {
|
||||
...DEFAULT_RETRY_CONFIG,
|
||||
baseDelay: 1,
|
||||
maxDelay: 1,
|
||||
backoffMultiplier: 1,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
handler = new RetryHandler(fastConfig);
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
it('should execute function successfully without retry', async () => {
|
||||
const fn = vi.fn().mockResolvedValue('success');
|
||||
const result = await handler.execute(fn);
|
||||
|
||||
expect(result).toBe('success');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should retry on failure and eventually succeed', async () => {
|
||||
const fn = vi.fn()
|
||||
.mockRejectedValueOnce(new Error('First attempt'))
|
||||
.mockResolvedValueOnce('success');
|
||||
|
||||
const result = await handler.execute(fn);
|
||||
|
||||
expect(result).toBe('success');
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should exhaust retries and throw final error', async () => {
|
||||
const fn = vi.fn().mockRejectedValue(new Error('Always fails'));
|
||||
|
||||
await expect(handler.execute(fn)).rejects.toThrow('Always fails');
|
||||
expect(fn).toHaveBeenCalledTimes(fastConfig.maxRetries + 1);
|
||||
});
|
||||
|
||||
it('should respect custom retry config', async () => {
|
||||
const customConfig = { ...fastConfig, maxRetries: 2 };
|
||||
const customHandler = new RetryHandler(customConfig);
|
||||
const fn = vi.fn().mockRejectedValue(new Error('Fail'));
|
||||
|
||||
await expect(customHandler.execute(fn)).rejects.toThrow('Fail');
|
||||
expect(fn).toHaveBeenCalledTimes(3); // 2 retries + 1 initial
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('CircuitBreaker', () => {
|
||||
let breaker: CircuitBreaker;
|
||||
|
||||
beforeEach(() => {
|
||||
breaker = new CircuitBreaker({ failureThreshold: 3, successThreshold: 1, timeout: 1000 });
|
||||
});
|
||||
|
||||
describe('canExecute', () => {
|
||||
it('should allow execution when closed', () => {
|
||||
expect(breaker.canExecute()).toBe(true);
|
||||
});
|
||||
|
||||
it('should prevent execution when open', () => {
|
||||
breaker.recordFailure();
|
||||
breaker.recordFailure();
|
||||
breaker.recordFailure();
|
||||
|
||||
expect(breaker.canExecute()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('recordSuccess', () => {
|
||||
it('should reset failure count on success', () => {
|
||||
breaker.recordFailure();
|
||||
breaker.recordFailure();
|
||||
breaker.recordSuccess();
|
||||
|
||||
// Should be closed again
|
||||
expect(breaker.canExecute()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('recordFailure', () => {
|
||||
it('should increment failure count', () => {
|
||||
breaker.recordFailure();
|
||||
expect(breaker.canExecute()).toBe(true);
|
||||
|
||||
breaker.recordFailure();
|
||||
expect(breaker.canExecute()).toBe(true);
|
||||
|
||||
breaker.recordFailure();
|
||||
expect(breaker.canExecute()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('CircuitBreakerRegistry', () => {
|
||||
it('should return singleton instance', () => {
|
||||
const registry1 = CircuitBreakerRegistry.getInstance();
|
||||
const registry2 = CircuitBreakerRegistry.getInstance();
|
||||
expect(registry1).toBe(registry2);
|
||||
});
|
||||
|
||||
it('should return same breaker for same path', () => {
|
||||
const registry = CircuitBreakerRegistry.getInstance();
|
||||
const breaker1 = registry.getBreaker('/api/test');
|
||||
const breaker2 = registry.getBreaker('/api/test');
|
||||
expect(breaker1).toBe(breaker2);
|
||||
});
|
||||
|
||||
it('should return different breakers for different paths', () => {
|
||||
const registry = CircuitBreakerRegistry.getInstance();
|
||||
const breaker1 = registry.getBreaker('/api/test1');
|
||||
const breaker2 = registry.getBreaker('/api/test2');
|
||||
expect(breaker1).not.toBe(breaker2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user