/** * CircuitBreaker 熔断器单元测试 * 测试状态转换: CLOSED → OPEN → HALF_OPEN → CLOSED */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { CircuitBreaker, CircuitBreakerOpenError, CircuitState } from '@/common/circuit-breaker'; describe('CircuitBreaker', () => { let breaker: CircuitBreaker; beforeEach(() => { breaker = new CircuitBreaker({ name: 'test-provider', failureThreshold: 3, cooldownMs: 1000, // 1秒冷却,方便测试 successThreshold: 2, }); }); describe('初始状态', () => { it('初始状态为 CLOSED', () => { expect(breaker.getState()).toBe('CLOSED'); expect(breaker.isOpen()).toBe(false); }); }); describe('CLOSED → OPEN 状态转换', () => { it('连续失败 3 次后进入 OPEN 状态', async () => { for (let i = 0; i < 3; i++) { await expect(breaker.call(() => Promise.reject(new Error('fail')))).rejects.toThrow('fail'); } expect(breaker.getState()).toBe('OPEN'); expect(breaker.isOpen()).toBe(true); }); it('OPEN 状态拒绝所有请求', async () => { // 先触发熔断 for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } expect(breaker.getState()).toBe('OPEN'); // 再次调用应抛出 CircuitBreakerOpenError await expect(breaker.call(() => Promise.resolve('success'))).rejects.toThrow(CircuitBreakerOpenError); }); it('OPEN 状态下调用抛出包含剩余冷却时间', async () => { for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } try { await breaker.call(() => Promise.resolve('success')); } catch (e) { expect(e).toBeInstanceOf(CircuitBreakerOpenError); expect((e as CircuitBreakerOpenError).remainingMs).toBeGreaterThan(0); expect((e as CircuitBreakerOpenError).breakerName).toBe('test-provider'); } }); it('失败计数在成功后重置', async () => { // 失败 2 次(未达到阈值) await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); expect(breaker.getState()).toBe('CLOSED'); // 成功后,失败计数应重置 await breaker.call(() => Promise.resolve('success')); expect(breaker.getState()).toBe('CLOSED'); // 再失败 3 次才会熔断 for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } expect(breaker.getState()).toBe('OPEN'); }); }); describe('OPEN → HALF_OPEN 状态转换', () => { it('冷却期过后自动进入 HALF_OPEN', async () => { // 触发熔断 for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } expect(breaker.getState()).toBe('OPEN'); // 等待冷却期 await new Promise((resolve) => setTimeout(resolve, 1100)); // 冷却期过后应自动进入 HALF_OPEN expect(breaker.getState()).toBe('HALF_OPEN'); }); it('HALF_OPEN 状态下可以接受请求', async () => { // 触发熔断 for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } await new Promise((resolve) => setTimeout(resolve, 1100)); expect(breaker.getState()).toBe('HALF_OPEN'); // HALF_OPEN 状态下调用应成功 const result = await breaker.call(() => Promise.resolve('success')); expect(result).toBe('success'); }); }); describe('HALF_OPEN → CLOSED 状态转换', () => { it('HALF_OPEN 连续成功 2 次后恢复 CLOSED', async () => { // 触发熔断 for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } await new Promise((resolve) => setTimeout(resolve, 1100)); expect(breaker.getState()).toBe('HALF_OPEN'); // 第一次成功 await breaker.call(() => Promise.resolve('success1')); expect(breaker.getState()).toBe('HALF_OPEN'); // 第二次成功 await breaker.call(() => Promise.resolve('success2')); expect(breaker.getState()).toBe('CLOSED'); }); it('HALF_OPEN 失败 1 次立即回到 OPEN', async () => { // 触发熔断 for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } await new Promise((resolve) => setTimeout(resolve, 1100)); expect(breaker.getState()).toBe('HALF_OPEN'); // 失败 await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); expect(breaker.getState()).toBe('OPEN'); }); it('HALF_OPEN 失败后重新开始冷却计时', async () => { // 触发熔断 for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } await new Promise((resolve) => setTimeout(resolve, 1100)); // 进入 HALF_OPEN 后失败 await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); expect(breaker.getState()).toBe('OPEN'); // 此时 getState() 不应立即进入 HALF_OPEN(需要等待新的冷却期) expect(breaker.getState()).toBe('OPEN'); }); }); describe('reset() 方法', () => { it('reset() 恢复到 CLOSED 状态', async () => { // 触发熔断 for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } expect(breaker.getState()).toBe('OPEN'); // 重置 breaker.reset(); expect(breaker.getState()).toBe('CLOSED'); expect(breaker.isOpen()).toBe(false); }); it('reset() 清除所有计数', async () => { // 触发熔断 for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } breaker.reset(); // 重置后应该可以正常调用 const result = await breaker.call(() => Promise.resolve('success')); expect(result).toBe('success'); expect(breaker.getState()).toBe('CLOSED'); }); }); describe('isOpen() 方法', () => { it('CLOSED 状态返回 false', () => { expect(breaker.isOpen()).toBe(false); }); it('OPEN 状态返回 true', async () => { for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } expect(breaker.isOpen()).toBe(true); }); it('HALF_OPEN 状态返回 false', async () => { for (let i = 0; i < 3; i++) { await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } await new Promise((resolve) => setTimeout(resolve, 1100)); expect(breaker.getState()).toBe('HALF_OPEN'); expect(breaker.isOpen()).toBe(false); }); }); describe('自定义配置', () => { it('使用自定义 failureThreshold', async () => { const customBreaker = new CircuitBreaker({ name: 'custom', failureThreshold: 5, cooldownMs: 1000, }); for (let i = 0; i < 5; i++) { await customBreaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } expect(customBreaker.getState()).toBe('OPEN'); }); it('使用自定义 cooldownMs', async () => { const customBreaker = new CircuitBreaker({ name: 'custom-cooldown', failureThreshold: 3, cooldownMs: 500, // 500ms }); for (let i = 0; i < 3; i++) { await customBreaker.call(() => Promise.reject(new Error('fail'))).catch(() => {}); } expect(customBreaker.getState()).toBe('OPEN'); // 等待 500ms await new Promise((resolve) => setTimeout(resolve, 600)); expect(customBreaker.getState()).toBe('HALF_OPEN'); }); }); describe('错误传播', () => { it('原始错误被正确传播', async () => { const customError = new Error('Custom error message'); await expect(breaker.call(() => Promise.reject(customError))).rejects.toThrow('Custom error message'); }); it('同步错误也被正确处理', async () => { await expect(breaker.call(() => { throw new Error('sync error'); })).rejects.toThrow('sync error'); }); }); }); describe('CircuitBreakerOpenError', () => { it('包含正确的错误信息', () => { const error = new CircuitBreakerOpenError('test-provider', 5000); expect(error.message).toContain('test-provider'); expect(error.message).toContain('5s'); expect(error.breakerName).toBe('test-provider'); expect(error.remainingMs).toBe(5000); }); it('继承自 Error', () => { const error = new CircuitBreakerOpenError('test', 1000); expect(error).toBeInstanceOf(Error); expect(error.name).toBe('CircuitBreakerOpenError'); }); });