circuit-breaker.test.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /**
  2. * CircuitBreaker 熔断器单元测试
  3. * 测试状态转换: CLOSED → OPEN → HALF_OPEN → CLOSED
  4. */
  5. import { describe, it, expect, beforeEach, vi } from 'vitest';
  6. import { CircuitBreaker, CircuitBreakerOpenError, CircuitState } from '@/common/circuit-breaker';
  7. describe('CircuitBreaker', () => {
  8. let breaker: CircuitBreaker;
  9. beforeEach(() => {
  10. breaker = new CircuitBreaker({
  11. name: 'test-provider',
  12. failureThreshold: 3,
  13. cooldownMs: 1000, // 1秒冷却,方便测试
  14. successThreshold: 2,
  15. });
  16. });
  17. describe('初始状态', () => {
  18. it('初始状态为 CLOSED', () => {
  19. expect(breaker.getState()).toBe('CLOSED');
  20. expect(breaker.isOpen()).toBe(false);
  21. });
  22. });
  23. describe('CLOSED → OPEN 状态转换', () => {
  24. it('连续失败 3 次后进入 OPEN 状态', async () => {
  25. for (let i = 0; i < 3; i++) {
  26. await expect(breaker.call(() => Promise.reject(new Error('fail')))).rejects.toThrow('fail');
  27. }
  28. expect(breaker.getState()).toBe('OPEN');
  29. expect(breaker.isOpen()).toBe(true);
  30. });
  31. it('OPEN 状态拒绝所有请求', async () => {
  32. // 先触发熔断
  33. for (let i = 0; i < 3; i++) {
  34. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  35. }
  36. expect(breaker.getState()).toBe('OPEN');
  37. // 再次调用应抛出 CircuitBreakerOpenError
  38. await expect(breaker.call(() => Promise.resolve('success'))).rejects.toThrow(CircuitBreakerOpenError);
  39. });
  40. it('OPEN 状态下调用抛出包含剩余冷却时间', async () => {
  41. for (let i = 0; i < 3; i++) {
  42. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  43. }
  44. try {
  45. await breaker.call(() => Promise.resolve('success'));
  46. } catch (e) {
  47. expect(e).toBeInstanceOf(CircuitBreakerOpenError);
  48. expect((e as CircuitBreakerOpenError).remainingMs).toBeGreaterThan(0);
  49. expect((e as CircuitBreakerOpenError).breakerName).toBe('test-provider');
  50. }
  51. });
  52. it('失败计数在成功后重置', async () => {
  53. // 失败 2 次(未达到阈值)
  54. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  55. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  56. expect(breaker.getState()).toBe('CLOSED');
  57. // 成功后,失败计数应重置
  58. await breaker.call(() => Promise.resolve('success'));
  59. expect(breaker.getState()).toBe('CLOSED');
  60. // 再失败 3 次才会熔断
  61. for (let i = 0; i < 3; i++) {
  62. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  63. }
  64. expect(breaker.getState()).toBe('OPEN');
  65. });
  66. });
  67. describe('OPEN → HALF_OPEN 状态转换', () => {
  68. it('冷却期过后自动进入 HALF_OPEN', async () => {
  69. // 触发熔断
  70. for (let i = 0; i < 3; i++) {
  71. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  72. }
  73. expect(breaker.getState()).toBe('OPEN');
  74. // 等待冷却期
  75. await new Promise((resolve) => setTimeout(resolve, 1100));
  76. // 冷却期过后应自动进入 HALF_OPEN
  77. expect(breaker.getState()).toBe('HALF_OPEN');
  78. });
  79. it('HALF_OPEN 状态下可以接受请求', async () => {
  80. // 触发熔断
  81. for (let i = 0; i < 3; i++) {
  82. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  83. }
  84. await new Promise((resolve) => setTimeout(resolve, 1100));
  85. expect(breaker.getState()).toBe('HALF_OPEN');
  86. // HALF_OPEN 状态下调用应成功
  87. const result = await breaker.call(() => Promise.resolve('success'));
  88. expect(result).toBe('success');
  89. });
  90. });
  91. describe('HALF_OPEN → CLOSED 状态转换', () => {
  92. it('HALF_OPEN 连续成功 2 次后恢复 CLOSED', async () => {
  93. // 触发熔断
  94. for (let i = 0; i < 3; i++) {
  95. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  96. }
  97. await new Promise((resolve) => setTimeout(resolve, 1100));
  98. expect(breaker.getState()).toBe('HALF_OPEN');
  99. // 第一次成功
  100. await breaker.call(() => Promise.resolve('success1'));
  101. expect(breaker.getState()).toBe('HALF_OPEN');
  102. // 第二次成功
  103. await breaker.call(() => Promise.resolve('success2'));
  104. expect(breaker.getState()).toBe('CLOSED');
  105. });
  106. it('HALF_OPEN 失败 1 次立即回到 OPEN', async () => {
  107. // 触发熔断
  108. for (let i = 0; i < 3; i++) {
  109. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  110. }
  111. await new Promise((resolve) => setTimeout(resolve, 1100));
  112. expect(breaker.getState()).toBe('HALF_OPEN');
  113. // 失败
  114. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  115. expect(breaker.getState()).toBe('OPEN');
  116. });
  117. it('HALF_OPEN 失败后重新开始冷却计时', async () => {
  118. // 触发熔断
  119. for (let i = 0; i < 3; i++) {
  120. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  121. }
  122. await new Promise((resolve) => setTimeout(resolve, 1100));
  123. // 进入 HALF_OPEN 后失败
  124. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  125. expect(breaker.getState()).toBe('OPEN');
  126. // 此时 getState() 不应立即进入 HALF_OPEN(需要等待新的冷却期)
  127. expect(breaker.getState()).toBe('OPEN');
  128. });
  129. });
  130. describe('reset() 方法', () => {
  131. it('reset() 恢复到 CLOSED 状态', async () => {
  132. // 触发熔断
  133. for (let i = 0; i < 3; i++) {
  134. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  135. }
  136. expect(breaker.getState()).toBe('OPEN');
  137. // 重置
  138. breaker.reset();
  139. expect(breaker.getState()).toBe('CLOSED');
  140. expect(breaker.isOpen()).toBe(false);
  141. });
  142. it('reset() 清除所有计数', async () => {
  143. // 触发熔断
  144. for (let i = 0; i < 3; i++) {
  145. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  146. }
  147. breaker.reset();
  148. // 重置后应该可以正常调用
  149. const result = await breaker.call(() => Promise.resolve('success'));
  150. expect(result).toBe('success');
  151. expect(breaker.getState()).toBe('CLOSED');
  152. });
  153. });
  154. describe('isOpen() 方法', () => {
  155. it('CLOSED 状态返回 false', () => {
  156. expect(breaker.isOpen()).toBe(false);
  157. });
  158. it('OPEN 状态返回 true', async () => {
  159. for (let i = 0; i < 3; i++) {
  160. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  161. }
  162. expect(breaker.isOpen()).toBe(true);
  163. });
  164. it('HALF_OPEN 状态返回 false', async () => {
  165. for (let i = 0; i < 3; i++) {
  166. await breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  167. }
  168. await new Promise((resolve) => setTimeout(resolve, 1100));
  169. expect(breaker.getState()).toBe('HALF_OPEN');
  170. expect(breaker.isOpen()).toBe(false);
  171. });
  172. });
  173. describe('自定义配置', () => {
  174. it('使用自定义 failureThreshold', async () => {
  175. const customBreaker = new CircuitBreaker({
  176. name: 'custom',
  177. failureThreshold: 5,
  178. cooldownMs: 1000,
  179. });
  180. for (let i = 0; i < 5; i++) {
  181. await customBreaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  182. }
  183. expect(customBreaker.getState()).toBe('OPEN');
  184. });
  185. it('使用自定义 cooldownMs', async () => {
  186. const customBreaker = new CircuitBreaker({
  187. name: 'custom-cooldown',
  188. failureThreshold: 3,
  189. cooldownMs: 500, // 500ms
  190. });
  191. for (let i = 0; i < 3; i++) {
  192. await customBreaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
  193. }
  194. expect(customBreaker.getState()).toBe('OPEN');
  195. // 等待 500ms
  196. await new Promise((resolve) => setTimeout(resolve, 600));
  197. expect(customBreaker.getState()).toBe('HALF_OPEN');
  198. });
  199. });
  200. describe('错误传播', () => {
  201. it('原始错误被正确传播', async () => {
  202. const customError = new Error('Custom error message');
  203. await expect(breaker.call(() => Promise.reject(customError))).rejects.toThrow('Custom error message');
  204. });
  205. it('同步错误也被正确处理', async () => {
  206. await expect(breaker.call(() => { throw new Error('sync error'); })).rejects.toThrow('sync error');
  207. });
  208. });
  209. });
  210. describe('CircuitBreakerOpenError', () => {
  211. it('包含正确的错误信息', () => {
  212. const error = new CircuitBreakerOpenError('test-provider', 5000);
  213. expect(error.message).toContain('test-provider');
  214. expect(error.message).toContain('5s');
  215. expect(error.breakerName).toBe('test-provider');
  216. expect(error.remainingMs).toBe(5000);
  217. });
  218. it('继承自 Error', () => {
  219. const error = new CircuitBreakerOpenError('test', 1000);
  220. expect(error).toBeInstanceOf(Error);
  221. expect(error.name).toBe('CircuitBreakerOpenError');
  222. });
  223. });