| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- /**
- * ProviderRegistry 单元测试
- * 测试供应商注册、查找、轮转、熔断集成
- */
- import { describe, it, expect, beforeEach, vi } from 'vitest';
- import { ProviderRegistry, ProviderNode } from '@/common/provider-registry';
- import { CircuitBreaker } from '@/common/circuit-breaker';
- interface MockProvider {
- readonly name: string;
- readonly vendor: string;
- }
- const createMockProvider = (name: string, vendor: string = 'mock'): MockProvider => ({
- name,
- vendor,
- });
- describe('ProviderRegistry', () => {
- let registry: ProviderRegistry<MockProvider>;
- beforeEach(() => {
- registry = new ProviderRegistry<MockProvider>();
- });
- describe('注册与注销', () => {
- it('注册一个 provider', () => {
- const provider = createMockProvider('provider-a');
- registry.register(provider);
- expect(registry.get('provider-a')).toBeDefined();
- expect(registry.get('provider-a')?.provider.name).toBe('provider-a');
- });
- it('注册多个 provider', () => {
- registry.register(createMockProvider('a'));
- registry.register(createMockProvider('b'));
- registry.register(createMockProvider('c'));
- expect(registry.size).toBe(3);
- });
- it('同名 provider 覆盖', () => {
- registry.register(createMockProvider('same'));
- registry.register(createMockProvider('same'));
- expect(registry.size).toBe(1);
- });
- it('注销已存在的 provider', () => {
- registry.register(createMockProvider('to-remove'));
- expect(registry.get('to-remove')).toBeDefined();
- const result = registry.unregister('to-remove');
- expect(result).toBe(true);
- expect(registry.get('to-remove')).toBeUndefined();
- });
- it('注销不存在的 provider 返回 false', () => {
- const result = registry.unregister('nonexistent');
- expect(result).toBe(false);
- });
- });
- describe('查找', () => {
- beforeEach(() => {
- registry.register(createMockProvider('provider-a', 'vendor-x'));
- registry.register(createMockProvider('provider-b', 'vendor-x'));
- registry.register(createMockProvider('provider-c', 'vendor-y'));
- });
- it('按名称查找', () => {
- const node = registry.get('provider-a');
- expect(node?.provider.name).toBe('provider-a');
- });
- it('按供应商查找', () => {
- const node = registry.getByVendor('vendor-x');
- expect(node?.provider.name).toBe('provider-a');
- });
- it('获取所有节点', () => {
- const all = registry.list();
- expect(all.length).toBe(3);
- });
- it('过滤器筛选', () => {
- const enabledOnly = registry.list(n => n.enabled);
- expect(enabledOnly.length).toBe(3);
- });
- });
- describe('状态查询', () => {
- beforeEach(() => {
- registry.register(createMockProvider('enabled-provider'));
- registry.register(createMockProvider('disabled-provider'), { enabled: false });
- });
- it('listEnabled 返回已启用节点', () => {
- const enabled = registry.listEnabled();
- expect(enabled.length).toBe(1);
- expect(enabled[0].provider.name).toBe('enabled-provider');
- });
- it('listAvailable 返回可用节点(启用+未熔断+未耗尽)', () => {
- const available = registry.listAvailable();
- expect(available.length).toBe(1);
- expect(available[0].provider.name).toBe('enabled-provider');
- });
- it('listBroken 返回已熔断节点', async () => {
- const broken = registry.listBroken();
- expect(broken.length).toBe(0);
- // 触发熔断
- const node = registry.get('enabled-provider');
- for (let i = 0; i < 3; i++) {
- await node?.breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
- }
- // 确保状态更新
- await new Promise(resolve => setTimeout(resolve, 50));
- const brokenAfter = registry.listBroken();
- expect(brokenAfter.length).toBe(1);
- });
- it('listExhausted 返回额度耗尽节点', () => {
- registry.markExhausted('enabled-provider', 'quota exceeded', 0);
- const exhausted = registry.listExhausted();
- expect(exhausted.length).toBe(1);
- expect(exhausted[0].provider.name).toBe('enabled-provider');
- });
- });
- describe('额度耗尽管理', () => {
- beforeEach(() => {
- registry.register(createMockProvider('provider'));
- });
- it('markExhausted 标记供应商额度耗尽', () => {
- registry.markExhausted('provider', 'Daily quota exceeded', 0);
- const node = registry.get('provider');
- expect(node?.exhausted).toBe(true);
- expect(node?.exhaustedReason).toBe('Daily quota exceeded');
- });
- it('markExhausted 带 TTL 自动恢复', async () => {
- registry.markExhausted('provider', 'temp', 100);
- const node = registry.get('provider');
- expect(node?.exhausted).toBe(true);
- await new Promise(resolve => setTimeout(resolve, 150));
- expect(node?.exhausted).toBe(false);
- });
- it('clearExhausted 清除耗尽标记', () => {
- registry.markExhausted('provider', 'quota', 0);
- expect(registry.get('provider')?.exhausted).toBe(true);
- const result = registry.clearExhausted('provider');
- expect(result).toBe(true);
- expect(registry.get('provider')?.exhausted).toBe(false);
- });
- it('clearAllExhausted 清除所有耗尽标记', () => {
- registry.register(createMockProvider('p1'));
- registry.register(createMockProvider('p2'));
- registry.markExhausted('p1', 'q1', 0);
- registry.markExhausted('p2', 'q2', 0);
- registry.clearAllExhausted();
- expect(registry.listExhausted().length).toBe(0);
- });
- });
- describe('轮转调度', () => {
- beforeEach(() => {
- registry.register(createMockProvider('p1'));
- registry.register(createMockProvider('p2'));
- registry.register(createMockProvider('p3'));
- });
- it('getNextAvailable 返回下一个可用 provider', () => {
- const next = registry.getNextAvailable('p1');
- expect(next?.provider.name).toBe('p2');
- });
- it('轮转到末尾后从头开始', () => {
- const next = registry.getNextAvailable('p3');
- expect(next?.provider.name).toBe('p1');
- });
- it('当前 provider 不可用时返回第一个可用', () => {
- // 禁用 p1
- const p1Node = registry.get('p1');
- p1Node && (p1Node.enabled = false);
- const next = registry.getNextAvailable('p1');
- expect(next?.provider.name).toBe('p2');
- });
- it('所有 provider 都不可用时返回 undefined', () => {
- registry.clearAllExhausted();
- registry.get('p1')!.enabled = false;
- registry.get('p2')!.enabled = false;
- registry.get('p3')!.enabled = false;
- const next = registry.getNextAvailable('p1');
- expect(next).toBeUndefined();
- });
- it('getPrimary 返回第一个可用 provider', () => {
- const primary = registry.getPrimary();
- expect(primary?.provider.name).toBe('p1');
- });
- });
- describe('熔断器集成', () => {
- beforeEach(() => {
- registry.register(createMockProvider('provider'), {
- breakerConfig: { failureThreshold: 3, cooldownMs: 1000 }
- });
- });
- it('Provider 节点包含熔断器', () => {
- const node = registry.get('provider');
- expect(node?.breaker).toBeInstanceOf(CircuitBreaker);
- });
- it('熔断后 provider 不再可用', async () => {
- const node = registry.get('provider');
- // 触发熔断
- for (let i = 0; i < 3; i++) {
- await node?.breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
- }
- expect(node?.breaker.isOpen()).toBe(true);
- expect(registry.listAvailable().length).toBe(0);
- });
- it('resetAllBreakers 重置所有熔断器', async () => {
- const node = registry.get('provider');
- for (let i = 0; i < 3; i++) {
- await node?.breaker.call(() => Promise.reject(new Error('fail'))).catch(() => {});
- }
- registry.resetAllBreakers();
- expect(node?.breaker.isOpen()).toBe(false);
- expect(registry.listAvailable().length).toBe(1);
- });
- });
- describe('健康检查', () => {
- beforeEach(() => {
- registry.register(createMockProvider('healthy'));
- registry.register(createMockProvider('unhealthy'));
- });
- it('healthCheckAll 检查所有 provider', async () => {
- const results = await registry.healthCheckAll(async (p) => {
- return p.name === 'healthy';
- });
- expect(results.get('healthy')).toBe(true);
- expect(results.get('unhealthy')).toBe(false);
- });
- it('健康检查失败触发熔断', async () => {
- const node = registry.get('healthy')!;
- await registry.healthCheckAll(async (p) => {
- if (p.name === 'healthy') {
- throw new Error('health check failed');
- }
- return true;
- });
- // 健康检查失败应该触发熔断
- expect(node.breaker.isOpen()).toBe(false); // 注意:healthCheckAll 内部不会自动熔断
- });
- });
- describe('状态摘要', () => {
- beforeEach(() => {
- registry.register(createMockProvider('p1', 'vendor1'));
- registry.register(createMockProvider('p2', 'vendor2'));
- });
- it('getStatus 返回完整状态', () => {
- const status = registry.getStatus();
- expect(status.length).toBe(2);
- expect(status[0]).toHaveProperty('name');
- expect(status[0]).toHaveProperty('vendor');
- expect(status[0]).toHaveProperty('enabled');
- expect(status[0]).toHaveProperty('exhausted');
- expect(status[0]).toHaveProperty('state');
- });
- });
- });
|