| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- /**
- * 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');
- });
- });
|