Files
mcpctl/src/mcplocal/tests/tiered-health.test.ts

305 lines
8.8 KiB
TypeScript
Raw Normal View History

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([]);
});
});
});