import { describe, it, expect, vi, beforeEach } from 'vitest'; import { networkInterfaces } from 'os'; import { getRecentTrafficLogs, getTrafficLogStats, exportTrafficLogs, getParityCounters, getCircuitBreakerStatus, getProxyMetrics, getRecentRequests, replayRequest, getModelCapabilities, generateIdeConfig, } from '../../../ipc/proxy-advanced/handler'; import { TrafficLogsRepo } from '../../../ipc/database/proxyMetricsHandler'; import { proxyAdvancedRegistry } from '../../../ipc/proxy-advanced/service-registry'; vi.mock('os', async (importOriginal) => { const actual = await importOriginal(); const mockedNetworkInterfaces = vi.fn().mockReturnValue({}); return { ...actual, default: { ...actual, networkInterfaces: mockedNetworkInterfaces }, networkInterfaces: mockedNetworkInterfaces, }; }); vi.mock('../../../utils/logger', () => ({ logger: { info: vi.fn(), error: vi.fn(), debug: vi.fn(), warn: vi.fn(), }, })); vi.mock('../../../ipc/database/proxyMetricsHandler', () => ({ TrafficLogsRepo: { getRecent: vi.fn().mockReturnValue([]), getStats: vi.fn().mockReturnValue({ byDirection: {}, byEndpoint: {}, total: 0 }), getAllForExport: vi.fn().mockReturnValue([]), }, })); vi.mock('../../../ipc/database/cloudHandler', () => ({ CloudAccountRepo: { getAccounts: vi.fn().mockResolvedValue([]), }, })); vi.mock('../../../ipc/proxy-advanced/service-registry', () => { const proxyAdvancedRegistry = { tokenManager: null, proxyMetrics: null, proxyReplay: null, proxyIdeConfig: null, }; function getServiceOrThrow(key: string) { const service = (proxyAdvancedRegistry as Record)[key]; if (!service) { throw new Error(`ProxyAdvanced service '${key}' is not registered`); } return service; } function getServiceOptional(key: string) { return (proxyAdvancedRegistry as Record)[key]; } return { proxyAdvancedRegistry, getServiceOrThrow, getServiceOptional, }; }); describe('Proxy Advanced Handler', () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(networkInterfaces).mockReset().mockReturnValue({}); }); describe('getRecentTrafficLogs', () => { it('should return empty logs when no records', async () => { const result = await getRecentTrafficLogs(); expect(result.success).toBe(true); expect(result.data).toEqual([]); }); it('should return parsed logs with default limit', async () => { vi.mocked(TrafficLogsRepo.getRecent).mockReturnValueOnce([ { timestamp: Date.now(), direction: 'outbound', requestId: 'req-1', endpoint: '/v1/chat', method: 'POST', status: 200, headers: { 'content-type': 'application/json' }, body: { message: 'hello' }, durationMs: 120, }, ]); const result = await getRecentTrafficLogs(); expect(result.success).toBe(true); expect(result.data).toHaveLength(1); expect(result.data?.[0].direction).toBe('outbound'); expect(result.data?.[0].endpoint).toBe('/v1/chat'); }); it('should filter by direction when provided', async () => { await getRecentTrafficLogs(10, 'inbound'); expect(TrafficLogsRepo.getRecent).toHaveBeenCalledWith(10, 'inbound'); }); it('should handle errors gracefully', async () => { vi.mocked(TrafficLogsRepo.getRecent).mockImplementationOnce(() => { throw new Error('DB error'); }); const result = await getRecentTrafficLogs(); expect(result.success).toBe(false); expect(result.error).toBe('DB error'); }); }); describe('getTrafficLogStats', () => { it('should return stats from repo', async () => { vi.mocked(TrafficLogsRepo.getStats).mockReturnValueOnce({ byDirection: { outbound: 5, inbound: 3 }, byEndpoint: { '/v1/chat': 8 }, total: 8, }); const result = await getTrafficLogStats(); expect(result.success).toBe(true); expect(result.data?.total).toBe(8); expect(result.data?.byDirection.outbound).toBe(5); }); it('should handle errors gracefully', async () => { vi.mocked(TrafficLogsRepo.getStats).mockImplementationOnce(() => { throw new Error('stats error'); }); const result = await getTrafficLogStats(); expect(result.success).toBe(false); expect(result.error).toBe('stats error'); }); }); describe('exportTrafficLogs', () => { it('should export as JSON', async () => { vi.mocked(TrafficLogsRepo.getAllForExport).mockReturnValueOnce([ { timestamp: 1000, direction: 'outbound', requestId: 'r1', endpoint: '/chat' }, ]); const result = await exportTrafficLogs('json'); expect(result.success).toBe(true); const parsed = JSON.parse(result.data ?? '[]'); expect(parsed).toHaveLength(1); }); it('should export as CSV', async () => { vi.mocked(TrafficLogsRepo.getAllForExport).mockReturnValueOnce([ { timestamp: 1000, direction: 'outbound', requestId: 'r1', endpoint: '/chat', method: 'POST', status: 200, durationMs: 50, }, ]); const result = await exportTrafficLogs('csv'); expect(result.success).toBe(true); expect(result.data).toContain( 'timestamp,direction,requestId,endpoint,method,status,durationMs', ); expect(result.data).toContain('outbound'); }); it('should handle errors gracefully', async () => { vi.mocked(TrafficLogsRepo.getAllForExport).mockImplementationOnce(() => { throw new Error('export error'); }); const result = await exportTrafficLogs('json'); expect(result.success).toBe(false); expect(result.error).toBe('export error'); }); }); describe('getParityCounters', () => { it('should return empty counters when tokenManager is null', async () => { const result = await getParityCounters(); expect(result.success).toBe(true); expect(result.data?.totalRequests).toBe(0); }); it('should return counters from tokenManager', async () => { vi.mocked(proxyAdvancedRegistry).tokenManager = { getParityCounters: vi.fn().mockReturnValue({ totalRequests: 10, matchedRequests: 8, parityViolations: 2, lastUpdated: new Date().toISOString(), }), } as any; const result = await getParityCounters(); expect(result.success).toBe(true); expect(result.data?.totalRequests).toBe(10); vi.mocked(proxyAdvancedRegistry).tokenManager = null; }); }); describe('getCircuitBreakerStatus', () => { it('should return empty status when tokenManager is null', async () => { const result = await getCircuitBreakerStatus(); expect(result.success).toBe(true); expect(result.data?.states).toEqual({}); }); it('should return mapped status from tokenManager', async () => { vi.mocked(proxyAdvancedRegistry).tokenManager = { getCircuitBreakerStatus: vi.fn().mockReturnValue({ acc1: { state: 'open', failureCount: 3, lastFailureTime: Date.now() }, }), } as any; const result = await getCircuitBreakerStatus(); expect(result.success).toBe(true); expect(result.data?.states.acc1.state).toBe('open'); expect(result.data?.states.acc1.failures).toBe(3); vi.mocked(proxyAdvancedRegistry).tokenManager = null; }); }); describe('getProxyMetrics', () => { it('should return zero metrics when proxyMetrics is null', async () => { const result = await getProxyMetrics(); expect(result.success).toBe(true); expect(result.data?.totalRequests).toBe(0); }); it('should return metrics from proxyMetrics service', async () => { vi.mocked(proxyAdvancedRegistry).proxyMetrics = { getMetrics: vi.fn().mockReturnValue({ totalRequests: 100, activeConnections: 5, avgLatency: 120, errorRate: 0.02, requestsPerMinute: 10, cacheHitRate: 0.8, }), } as any; const result = await getProxyMetrics(); expect(result.success).toBe(true); expect(result.data?.totalRequests).toBe(100); expect(result.data?.avgLatency).toBe(120); vi.mocked(proxyAdvancedRegistry).proxyMetrics = null; }); }); describe('getRecentRequests', () => { it('should return empty array when proxyReplay is null', async () => { const result = await getRecentRequests(); expect(result.success).toBe(true); expect(result.data).toEqual([]); }); it('should return recent requests from proxyReplay', async () => { vi.mocked(proxyAdvancedRegistry).proxyReplay = { getRecentRequests: vi.fn().mockReturnValue([{ id: 'req-1' }]), } as any; const result = await getRecentRequests(); expect(result.success).toBe(true); expect(result.data).toHaveLength(1); vi.mocked(proxyAdvancedRegistry).proxyReplay = null; }); }); describe('replayRequest', () => { it('should return error when proxyReplay is null', async () => { const result = await replayRequest('req-1'); expect(result.success).toBe(false); expect(result.error).toBe('Proxy advanced service not initialized'); }); it('should replay request through proxyReplay', async () => { vi.mocked(proxyAdvancedRegistry).proxyReplay = { replayRequest: vi.fn().mockResolvedValue({ original: { id: 'req-1' }, newResponse: { status: 200 }, }), } as any; const result = await replayRequest('req-1'); expect(result.success).toBe(true); expect(result.data?.original).toEqual({ id: 'req-1' }); vi.mocked(proxyAdvancedRegistry).proxyReplay = null; }); }); describe('getModelCapabilities', () => { it('should return empty array when no accounts have quota', async () => { const { CloudAccountRepo } = await import('../../../ipc/database/cloudHandler'); vi.mocked(CloudAccountRepo.getAccounts).mockResolvedValueOnce([]); const result = await getModelCapabilities(); expect(result.success).toBe(true); expect(result.data).toEqual([]); }); it('should aggregate unique models across accounts', async () => { const { CloudAccountRepo } = await import('../../../ipc/database/cloudHandler'); vi.mocked(CloudAccountRepo.getAccounts).mockResolvedValueOnce([ { id: 'acc-1', provider: 'google', email: 'a@example.com', token: {} as any, quota: { models: { 'gemini-pro': { percentage: 80, resetTime: new Date().toISOString(), display_name: 'Gemini Pro', supports_images: true, supports_thinking: false, recommended: true, max_tokens: 1000000, max_output_tokens: 8192, }, }, }, created_at: Date.now(), last_used: Date.now(), }, { id: 'acc-2', provider: 'google', email: 'b@example.com', token: {} as any, quota: { models: { 'gemini-pro': { percentage: 60, resetTime: new Date().toISOString(), display_name: 'Gemini Pro Duplicate', supports_images: true, supports_thinking: false, recommended: true, max_tokens: 1000000, max_output_tokens: 8192, }, 'gemini-flash': { percentage: 90, resetTime: new Date().toISOString(), display_name: 'Gemini Flash', supports_images: true, supports_thinking: true, thinking_budget: 24000, recommended: false, max_tokens: 500000, max_output_tokens: 4096, }, }, }, created_at: Date.now(), last_used: Date.now(), }, ]); const result = await getModelCapabilities(); expect(result.success).toBe(true); expect(result.data).toHaveLength(2); const pro = result.data!.find((m) => m.id === 'gemini-pro'); expect(pro).toBeDefined(); expect(pro!.displayName).toBe('Gemini Pro'); expect(pro!.capabilities.vision).toBe(true); expect(pro!.capabilities.thinking).toBe(false); expect(pro!.capabilities.streaming).toBe(true); expect(pro!.capabilities.jsonMode).toBe(true); expect(pro!.capabilities.recommended).toBe(true); expect(pro!.limits.maxTokens).toBe(1000000); expect(pro!.limits.maxOutputTokens).toBe(8192); const flash = result.data!.find((m) => m.id === 'gemini-flash'); expect(flash).toBeDefined(); expect(flash!.displayName).toBe('Gemini Flash'); expect(flash!.capabilities.thinking).toBe(true); expect(flash!.limits.thinkingBudget).toBe(24000); }); it('should handle accounts without quota gracefully', async () => { const { CloudAccountRepo } = await import('../../../ipc/database/cloudHandler'); vi.mocked(CloudAccountRepo.getAccounts).mockResolvedValueOnce([ { id: 'acc-1', provider: 'google', email: 'a@example.com', token: {} as any, created_at: Date.now(), last_used: Date.now(), }, ]); const result = await getModelCapabilities(); expect(result.success).toBe(true); expect(result.data).toEqual([]); }); it('should handle errors from CloudAccountRepo', async () => { const { CloudAccountRepo } = await import('../../../ipc/database/cloudHandler'); vi.mocked(CloudAccountRepo.getAccounts).mockRejectedValueOnce(new Error('DB failure')); const result = await getModelCapabilities(); expect(result.success).toBe(false); expect(result.error).toBe('DB failure'); }); }); describe('generateIdeConfig', () => { it('uses loopback when network enumeration fails', async () => { vi.mocked(networkInterfaces).mockImplementationOnce(() => { throw new Error('network enumeration denied'); }); const result = await generateIdeConfig('vscode'); expect(networkInterfaces).toHaveBeenCalledOnce(); expect(result.success, result.error).toBe(true); expect(result.data?.content).toMatchObject({ 'openai.url': 'http://127.0.0.1:8045/v1' }); }); it('uses the discovered LAN address when enumeration succeeds', async () => { vi.mocked(networkInterfaces).mockReturnValueOnce({ eth0: [ { address: '192.0.2.10', netmask: '255.255.255.0', family: 'IPv4', mac: '00:11:22:33:44:55', internal: false, cidr: '192.0.2.10/24', }, ], }); const result = await generateIdeConfig('vscode'); expect(networkInterfaces).toHaveBeenCalledOnce(); expect(result.success, result.error).toBe(true); expect(result.data?.content).toMatchObject({ 'openai.url': 'http://192.0.2.10:8045/v1' }); }); it('should generate fallback config when proxyIdeConfig is null', async () => { const result = await generateIdeConfig('vscode'); expect(result.success, result.error).toBe(true); expect(result.data?.name).toBe('VS Code'); }); it('should return error for unsupported IDE', async () => { vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = {} as any; const result = await generateIdeConfig('unknown-ide'); expect(result.success).toBe(false); expect(result.error).toContain('Unsupported IDE'); vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = null; }); it('should generate config for VS Code', async () => { vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = { generateConfigForCurrentServer: vi.fn().mockReturnValue({ ide: 'vscode', format: 'json', content: { 'openai.url': 'http://localhost:8045/v1', 'openai.apiKey': 'key' }, }), } as any; const result = await generateIdeConfig('vscode'); expect(result.success).toBe(true); expect(result.data?.name).toBe('VS Code'); expect(result.data?.proxySetting).toBe('openai.url'); vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = null; }); it('should generate config for Cursor', async () => { vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = { generateConfigForCurrentServer: vi.fn().mockReturnValue({ ide: 'cursor', format: 'json', content: { OPENAI_BASE_URL: 'http://localhost:8045/v1', OPENAI_API_KEY: 'key' }, }), } as any; const result = await generateIdeConfig('cursor'); expect(result.success).toBe(true); expect(result.data?.name).toBe('Cursor'); expect(result.data?.proxySetting).toBe('OPENAI_BASE_URL'); vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = null; }); }); });