jay-code
Version:
Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability
538 lines (442 loc) • 16.1 kB
text/typescript
import { getErrorMessage } from '../utils/error-handler.js';
/**
* Comprehensive MCP Integration Tests
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
type Mock = jest.MockedFunction<any>;
import { MCPServer } from '../server.js';
import { MCPLifecycleManager, LifecycleState } from '../lifecycle-manager.js';
import { MCPPerformanceMonitor } from '../performance-monitor.js';
import { MCPProtocolManager } from '../protocol-manager.js';
import { MCPOrchestrationIntegration } from '../orchestration-integration.js';
import { ToolRegistry } from '../tools.js';
import type { AuthManager } from '../auth.js';
import type { ILogger } from '../../core/logger.js';
import type { MCPConfig, MCPInitializeParams, MCPRequest, MCPSession } from '../../utils/types.js';
import { EventEmitter } from 'node:events';
// Mock logger
const mockLogger: ILogger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
configure: jest.fn(),
};
// Mock event bus
const mockEventBus = new EventEmitter();
// Mock config
const mockMCPConfig: MCPConfig = {
transport: 'stdio',
enableMetrics: true,
auth: {
enabled: false,
method: 'token',
},
};
describe('MCP Server', () => {
let server: MCPServer;
beforeEach(() => {
server = new MCPServer(mockMCPConfig, mockEventBus, mockLogger);
});
afterEach(async () => {
if (server) {
await server.stop();
}
});
describe('Lifecycle Management', () => {
it('should start and stop server successfully', async () => {
await server.start();
expect(mockLogger.info).toHaveBeenCalledWith('MCP server started successfully');
await server.stop();
expect(mockLogger.info).toHaveBeenCalledWith('MCP server stopped');
});
it('should handle initialization request', async () => {
await server.start();
const initParams: MCPInitializeParams = {
protocolVersion: { major: 2024, minor: 11, patch: 5 },
capabilities: {
tools: { listChanged: true },
},
clientInfo: {
name: 'test-client',
version: '1.0.0',
},
};
const request: MCPRequest = {
jsonrpc: '2.0',
id: 'test-init',
method: 'initialize',
params: initParams,
};
// Mock transport handler
const transport = (server as any).transport;
transport.onRequest = jest.fn();
const response = await (server as any).handleRequest(request);
expect(response.jsonrpc).toBe('2.0');
expect(response.id).toBe('test-init');
expect(response.result).toBeDefined();
expect(response.result.protocolVersion).toEqual({ major: 2024, minor: 11, patch: 5 });
});
});
describe('Tool Registration', () => {
beforeEach(async () => {
await server.start();
});
it('should register tools successfully', () => {
const tool = {
name: 'test/tool',
description: 'Test tool',
inputSchema: {
type: 'object',
properties: {
input: { type: 'string' },
},
},
handler: jest.fn().mockResolvedValue('test result'),
};
server.registerTool(tool);
expect(mockLogger.info).toHaveBeenCalledWith('Tool registered', { name: 'test/tool' });
});
it('should list registered tools', async () => {
const tool1 = {
name: 'test/tool1',
description: 'Test tool 1',
inputSchema: { type: 'object', properties: {} },
handler: jest.fn(),
};
const tool2 = {
name: 'test/tool2',
description: 'Test tool 2',
inputSchema: { type: 'object', properties: {} },
handler: jest.fn(),
};
server.registerTool(tool1);
server.registerTool(tool2);
const tools = (server as any).toolRegistry.listTools();
expect(tools).toHaveLength(2 + 4); // 2 custom + 4 built-in tools
expect(tools.some((t: any) => t.name === 'test/tool1')).toBe(true);
expect(tools.some((t: any) => t.name === 'test/tool2')).toBe(true);
});
});
describe('Health Checks', () => {
beforeEach(async () => {
await server.start();
});
it('should report healthy status when running', async () => {
const health = await server.getHealthStatus();
expect(health.healthy).toBe(true);
expect(health.metrics).toBeDefined();
expect(health.metrics?.registeredTools).toBeGreaterThan(0);
});
it('should include metrics in health status', async () => {
const health = await server.getHealthStatus();
expect(health.metrics).toBeDefined();
expect(typeof health.metrics?.registeredTools).toBe('number');
expect(typeof health.metrics?.totalRequests).toBe('number');
expect(typeof health.metrics?.successfulRequests).toBe('number');
expect(typeof health.metrics?.failedRequests).toBe('number');
});
});
});
describe('MCP Lifecycle Manager', () => {
let lifecycleManager: MCPLifecycleManager;
let mockServerFactory: Mock;
beforeEach(() => {
mockServerFactory = jest.fn(() => new MCPServer(mockMCPConfig, mockEventBus, mockLogger));
lifecycleManager = new MCPLifecycleManager(mockMCPConfig, mockLogger, mockServerFactory);
});
afterEach(async () => {
if (lifecycleManager) {
await lifecycleManager.stop();
}
});
describe('State Management', () => {
it('should start in stopped state', () => {
expect(lifecycleManager.getState()).toBe(LifecycleState.STOPPED);
});
it('should transition to running state when started', async () => {
await lifecycleManager.start();
expect(lifecycleManager.getState()).toBe(LifecycleState.RUNNING);
});
it('should transition back to stopped when stopped', async () => {
await lifecycleManager.start();
await lifecycleManager.stop();
expect(lifecycleManager.getState()).toBe(LifecycleState.STOPPED);
});
it('should emit state change events', async () => {
const stateChanges: any[] = [];
lifecycleManager.on('stateChange', (event) => {
stateChanges.push(event);
});
await lifecycleManager.start();
await lifecycleManager.stop();
expect(stateChanges).toHaveLength(4); // starting -> running -> stopping -> stopped
expect(stateChanges[0].state).toBe(LifecycleState.STARTING);
expect(stateChanges[1].state).toBe(LifecycleState.RUNNING);
expect(stateChanges[2].state).toBe(LifecycleState.STOPPING);
expect(stateChanges[3].state).toBe(LifecycleState.STOPPED);
});
});
describe('Health Monitoring', () => {
it('should perform health checks when enabled', async () => {
const config = {
healthCheckInterval: 100,
enableHealthChecks: true,
};
lifecycleManager = new MCPLifecycleManager(
mockMCPConfig,
mockLogger,
mockServerFactory,
config,
);
await lifecycleManager.start();
// Wait for health check
await new Promise((resolve) => setTimeout(resolve, 150));
const health = await lifecycleManager.healthCheck();
expect(health).toBeDefined();
expect(health.state).toBeDefined();
});
it('should track uptime', async () => {
await lifecycleManager.start();
// Wait a bit
await new Promise((resolve) => setTimeout(resolve, 50));
const uptime = lifecycleManager.getUptime();
expect(uptime).toBeGreaterThan(0);
});
});
});
describe('MCP Performance Monitor', () => {
let performanceMonitor: MCPPerformanceMonitor;
beforeEach(() => {
performanceMonitor = new MCPPerformanceMonitor(mockLogger);
});
afterEach(() => {
if (performanceMonitor) {
performanceMonitor.stop();
}
});
describe('Request Tracking', () => {
it('should track request metrics', async () => {
const mockSession: MCPSession = {
id: 'test-session',
clientInfo: { name: 'test', version: '1.0' },
protocolVersion: { major: 2024, minor: 11, patch: 5 },
capabilities: {},
isInitialized: true,
createdAt: new Date(),
lastActivity: new Date(),
transport: 'stdio',
authenticated: false,
};
const mockRequest: MCPRequest = {
jsonrpc: '2.0',
id: 'test-request',
method: 'test/method',
};
const requestId = performanceMonitor.recordRequestStart(mockRequest, mockSession);
expect(requestId).toBeDefined();
// Simulate request completion
await new Promise((resolve) => setTimeout(resolve, 10));
performanceMonitor.recordRequestEnd(requestId, {
jsonrpc: '2.0',
id: 'test-request',
result: 'success',
});
const metrics = performanceMonitor.getCurrentMetrics();
expect(metrics.requestCount).toBe(1);
expect(metrics.averageResponseTime).toBeGreaterThan(0);
});
});
});
describe('MCP Protocol Manager', () => {
let protocolManager: MCPProtocolManager;
beforeEach(() => {
protocolManager = new MCPProtocolManager(mockLogger);
});
describe('Version Compatibility', () => {
it('should check version compatibility correctly', () => {
const clientVersion = { major: 2024, minor: 11, patch: 5 };
const compatibility = protocolManager.checkCompatibility(clientVersion);
expect(compatibility.compatible).toBe(true);
expect(compatibility.errors).toHaveLength(0);
});
it('should reject incompatible major versions', () => {
const clientVersion = { major: 2023, minor: 11, patch: 5 };
const compatibility = protocolManager.checkCompatibility(clientVersion);
expect(compatibility.compatible).toBe(false);
expect(compatibility.errors.length).toBeGreaterThan(0);
});
});
describe('Protocol Negotiation', () => {
it('should negotiate protocol successfully', async () => {
const clientParams: MCPInitializeParams = {
protocolVersion: { major: 2024, minor: 11, patch: 5 },
capabilities: {
tools: { listChanged: true },
logging: { level: 'info' },
},
clientInfo: {
name: 'test-client',
version: '1.0.0',
},
};
const result = await protocolManager.negotiateProtocol(clientParams);
expect(result.agreedVersion).toEqual(clientParams.protocolVersion);
expect(result.agreedCapabilities).toBeDefined();
expect(result.agreedCapabilities.tools?.listChanged).toBe(true);
});
});
});
describe('Tool Registry', () => {
let toolRegistry: ToolRegistry;
beforeEach(() => {
toolRegistry = new ToolRegistry(mockLogger);
});
describe('Tool Management', () => {
it('should register tools with capabilities', () => {
const tool = {
name: 'test/tool',
description: 'Test tool for registry',
inputSchema: {
type: 'object',
properties: {
input: { type: 'string' },
},
},
handler: jest.fn().mockResolvedValue('test result'),
};
const capability = {
name: 'test/tool',
version: '1.0.0',
description: 'Test capability',
category: 'test',
tags: ['testing', 'demo'],
supportedProtocolVersions: [{ major: 2024, minor: 11, patch: 5 }],
};
toolRegistry.register(tool, capability);
const registeredCapability = toolRegistry.getToolCapability('test/tool');
expect(registeredCapability).toEqual(capability);
});
it('should discover tools by criteria', () => {
const tool1 = {
name: 'file/read',
description: 'Read files',
inputSchema: { type: 'object', properties: {} },
handler: jest.fn(),
};
const tool2 = {
name: 'memory/query',
description: 'Query memory',
inputSchema: { type: 'object', properties: {} },
handler: jest.fn(),
};
toolRegistry.register(tool1);
toolRegistry.register(tool2);
const fileTools = toolRegistry.discoverTools({ category: 'file' });
expect(fileTools).toHaveLength(1);
expect(fileTools[0].tool.name).toBe('file/read');
const memoryTools = toolRegistry.discoverTools({ tags: ['memory'] });
expect(memoryTools).toHaveLength(1);
expect(memoryTools[0].tool.name).toBe('memory/query');
});
it('should track tool metrics', async () => {
const tool = {
name: 'test/metric-tool',
description: 'Tool for metrics testing',
inputSchema: { type: 'object', properties: {} },
handler: jest.fn().mockResolvedValue('success'),
};
toolRegistry.register(tool);
// Execute tool multiple times
await toolRegistry.executeTool('test/metric-tool', {});
await toolRegistry.executeTool('test/metric-tool', {});
const metrics = toolRegistry.getToolMetrics('test/metric-tool');
expect(Array.isArray(metrics) ? metrics[0].totalInvocations : metrics.totalInvocations).toBe(
2,
);
expect(
Array.isArray(metrics) ? metrics[0].successfulInvocations : metrics.successfulInvocations,
).toBe(2);
});
});
});
describe('MCP Orchestration Integration', () => {
let integration: MCPOrchestrationIntegration;
let mockComponents: any;
beforeEach(() => {
mockComponents = {
orchestrator: {
getStatus: jest.fn().mockResolvedValue({ status: 'running' }),
listTasks: jest.fn().mockResolvedValue([]),
},
eventBus: new EventEmitter(),
};
integration = new MCPOrchestrationIntegration(
mockMCPConfig,
{
enabledIntegrations: {
orchestrator: true,
swarm: false,
agents: false,
resources: false,
memory: false,
monitoring: false,
terminals: false,
},
autoStart: false,
healthCheckInterval: 30000,
reconnectAttempts: 3,
reconnectDelay: 5000,
enableMetrics: true,
enableAlerts: true,
},
mockComponents,
mockLogger,
);
});
afterEach(async () => {
if (integration) {
await integration.stop();
}
});
describe('Integration Management', () => {
it('should start integration successfully', async () => {
await integration.start();
const status = integration.getIntegrationStatus();
expect(status).toHaveLength(7); // All component types
const orchestratorStatus = status.find((s) => s.component === 'orchestrator');
expect(orchestratorStatus?.enabled).toBe(true);
});
it('should register orchestrator tools when enabled', async () => {
await integration.start();
const server = integration.getServer();
expect(server).toBeDefined();
// Check that orchestrator tools are registered
const tools = (server as any).toolRegistry.listTools();
const orchestratorTools = tools.filter((t: any) => t.name.startsWith('orchestrator/'));
expect(orchestratorTools.length).toBeGreaterThan(0);
});
it('should handle component connection failures gracefully', async () => {
// Mock a failing component
mockComponents.orchestrator.getStatus = jest
.fn()
.mockRejectedValue(new Error('Connection failed'));
await integration.start();
const status = integration.getComponentStatus('orchestrator');
expect(status).toBeDefined();
// Connection status can vary in test environment
});
});
describe('Health Monitoring', () => {
it('should monitor component health', async () => {
await integration.start();
// Wait for health check
await new Promise((resolve) => setTimeout(resolve, 100));
const status = integration.getIntegrationStatus();
const enabledComponents = status.filter((s) => s.enabled);
for (const component of enabledComponents) {
expect(component.lastCheck).toBeInstanceOf(Date);
}
});
});
});