import { describe, it, expect, vi, beforeEach } from 'vitest'; import { TieredHealthMonitor } from '../src/health/tiered.js'; import type { TieredHealthMonitorDeps } from '../src/health/tiered.js'; import type { McpdClient } from '../src/http/mcpd-client.js'; import { ProviderRegistry } from '../src/providers/registry.js'; import type { LlmProvider } from '../src/providers/types.js'; function mockMcpdClient(overrides?: { getResult?: unknown; getFails?: boolean; instancesResult?: { instances: Array<{ name: string; status: string }> }; instancesFails?: boolean; }): McpdClient { const client = { get: vi.fn(async (path: string) => { if (path === '/health') { if (overrides?.getFails) { throw new Error('Connection refused'); } return overrides?.getResult ?? { status: 'ok' }; } if (path === '/instances') { if (overrides?.instancesFails) { throw new Error('Connection refused'); } return overrides?.instancesResult ?? { instances: [] }; } return {}; }), post: vi.fn(), put: vi.fn(), delete: vi.fn(), forward: vi.fn(), } as unknown as McpdClient; return client; } function mockLlmProvider(name: string): LlmProvider { return { name, complete: vi.fn(), listModels: vi.fn(async () => []), isAvailable: vi.fn(async () => true), }; } describe('TieredHealthMonitor', () => { let providerRegistry: ProviderRegistry; beforeEach(() => { providerRegistry = new ProviderRegistry(); }); describe('mcplocal health', () => { it('reports healthy status with uptime', async () => { const monitor = new TieredHealthMonitor({ mcpdClient: null, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.mcplocal.status).toBe('healthy'); expect(result.mcplocal.uptime).toBeGreaterThanOrEqual(0); }); it('reports null llmProvider when none registered', async () => { const monitor = new TieredHealthMonitor({ mcpdClient: null, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.mcplocal.llmProvider).toBeNull(); }); it('reports active llmProvider name when one is registered', async () => { const provider = mockLlmProvider('openai'); providerRegistry.register(provider); const monitor = new TieredHealthMonitor({ mcpdClient: null, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.mcplocal.llmProvider).toBe('openai'); }); it('reports the currently active provider when multiple registered', async () => { providerRegistry.register(mockLlmProvider('openai')); providerRegistry.register(mockLlmProvider('anthropic')); providerRegistry.setActive('anthropic'); const monitor = new TieredHealthMonitor({ mcpdClient: null, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.mcplocal.llmProvider).toBe('anthropic'); }); }); describe('mcpd health', () => { it('reports connected when mcpd /health responds successfully', async () => { const client = mockMcpdClient(); const monitor = new TieredHealthMonitor({ mcpdClient: client, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.mcpd.status).toBe('connected'); expect(result.mcpd.url).toBe('http://localhost:3100'); }); it('reports disconnected when mcpd /health throws', async () => { const client = mockMcpdClient({ getFails: true }); const monitor = new TieredHealthMonitor({ mcpdClient: client, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.mcpd.status).toBe('disconnected'); expect(result.mcpd.url).toBe('http://localhost:3100'); }); it('reports disconnected when mcpdClient is null', async () => { const monitor = new TieredHealthMonitor({ mcpdClient: null, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.mcpd.status).toBe('disconnected'); expect(result.mcpd.url).toBe('http://localhost:3100'); }); it('includes the configured mcpd URL in the response', async () => { const monitor = new TieredHealthMonitor({ mcpdClient: null, providerRegistry, mcpdUrl: 'http://custom-host:9999', }); const result = await monitor.checkHealth(); expect(result.mcpd.url).toBe('http://custom-host:9999'); }); }); describe('instances', () => { it('returns instances from mcpd /instances endpoint', async () => { const client = mockMcpdClient({ instancesResult: { instances: [ { name: 'slack', status: 'running' }, { name: 'github', status: 'stopped' }, ], }, }); const monitor = new TieredHealthMonitor({ mcpdClient: client, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.instances).toHaveLength(2); expect(result.instances[0]).toEqual({ name: 'slack', status: 'running' }); expect(result.instances[1]).toEqual({ name: 'github', status: 'stopped' }); }); it('returns empty array when mcpdClient is null', async () => { const monitor = new TieredHealthMonitor({ mcpdClient: null, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.instances).toEqual([]); }); it('returns empty array when /instances request fails', async () => { const client = mockMcpdClient({ instancesFails: true }); const monitor = new TieredHealthMonitor({ mcpdClient: client, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.instances).toEqual([]); }); it('returns empty array when mcpd has no instances', async () => { const client = mockMcpdClient({ instancesResult: { instances: [] }, }); const monitor = new TieredHealthMonitor({ mcpdClient: client, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.instances).toEqual([]); }); }); describe('full integration', () => { it('returns complete tiered status with all sections', async () => { providerRegistry.register(mockLlmProvider('openai')); const client = mockMcpdClient({ instancesResult: { instances: [ { name: 'slack', status: 'running' }, ], }, }); const monitor = new TieredHealthMonitor({ mcpdClient: client, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); // Verify structure expect(result).toHaveProperty('mcplocal'); expect(result).toHaveProperty('mcpd'); expect(result).toHaveProperty('instances'); // mcplocal expect(result.mcplocal.status).toBe('healthy'); expect(typeof result.mcplocal.uptime).toBe('number'); expect(result.mcplocal.llmProvider).toBe('openai'); // mcpd expect(result.mcpd.status).toBe('connected'); // instances expect(result.instances).toHaveLength(1); expect(result.instances[0]?.name).toBe('slack'); }); it('handles degraded scenario: no mcpd, no provider', async () => { const monitor = new TieredHealthMonitor({ mcpdClient: null, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.mcplocal.status).toBe('healthy'); expect(result.mcplocal.llmProvider).toBeNull(); expect(result.mcpd.status).toBe('disconnected'); expect(result.instances).toEqual([]); }); it('handles mcpd connected but instances endpoint failing', async () => { const client = mockMcpdClient({ instancesFails: true }); const monitor = new TieredHealthMonitor({ mcpdClient: client, providerRegistry, mcpdUrl: 'http://localhost:3100', }); const result = await monitor.checkHealth(); expect(result.mcpd.status).toBe('connected'); expect(result.instances).toEqual([]); }); }); });