/**
* NCPOrchestrator tests - Core functionality testing
*/
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { NCPOrchestrator } from '../src/orchestrator/ncp-orchestrator.js';
describe('NCPOrchestrator - Basic Tests', () => {
let orchestrator: NCPOrchestrator;
beforeEach(() => {
jest.clearAllMocks();
orchestrator = new NCPOrchestrator('test');
});
describe('instantiation', () => {
it('should create orchestrator with profile name', () => {
expect(orchestrator).toBeDefined();
});
it('should create orchestrator with default profile', () => {
const defaultOrchestrator = new NCPOrchestrator();
expect(defaultOrchestrator).toBeDefined();
});
});
describe('basic api calls', () => {
it('should have find method', () => {
expect(typeof orchestrator.find).toBe('function');
});
it('should have run method', () => {
expect(typeof orchestrator.run).toBe('function');
});
it('should handle find with empty query', async () => {
await orchestrator.initialize();
const result = await orchestrator.find('');
expect(Array.isArray(result)).toBe(true);
});
it('should handle find with query', async () => {
await orchestrator.initialize();
const result = await orchestrator.find('test');
expect(Array.isArray(result)).toBe(true);
});
it('should handle run with non-existent tool', async () => {
await orchestrator.initialize();
const result = await orchestrator.run('nonexistent:tool', {});
expect(result).toHaveProperty('success');
expect(result.success).toBe(false);
});
});
describe('initialization', () => {
it('should initialize without throwing', async () => {
await expect(orchestrator.initialize()).resolves.not.toThrow();
});
it('should be able to initialize multiple times', async () => {
await orchestrator.initialize();
await expect(orchestrator.initialize()).resolves.not.toThrow();
});
});
describe('cleanup', () => {
it('should cleanup without throwing', async () => {
await expect(orchestrator.cleanup()).resolves.not.toThrow();
});
it('should cleanup after initialization', async () => {
await orchestrator.initialize();
await expect(orchestrator.cleanup()).resolves.not.toThrow();
});
});
describe('error scenarios', () => {
it('should handle run method with invalid tool format', async () => {
await orchestrator.initialize();
// Tool name without MCP prefix
const result = await orchestrator.run('invalidtool', {});
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
it('should handle find with very long query', async () => {
await orchestrator.initialize();
const longQuery = 'a'.repeat(1000);
const result = await orchestrator.find(longQuery);
expect(Array.isArray(result)).toBe(true);
});
it('should handle find with special characters', async () => {
await orchestrator.initialize();
const result = await orchestrator.find('!@#$%^&*()');
expect(Array.isArray(result)).toBe(true);
});
});
describe('advanced find scenarios', () => {
it('should return all tools when query is empty', async () => {
await orchestrator.initialize();
const result = await orchestrator.find('');
expect(Array.isArray(result)).toBe(true);
});
it('should respect limit parameter', async () => {
await orchestrator.initialize();
const result = await orchestrator.find('', 3);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeLessThanOrEqual(3);
});
it('should handle detailed flag for schema information', async () => {
await orchestrator.initialize();
const result = await orchestrator.find('test', 5, true);
expect(Array.isArray(result)).toBe(true);
});
it('should handle vector search fallback', async () => {
await orchestrator.initialize();
const result = await orchestrator.find('complex search query that uses vector search');
expect(Array.isArray(result)).toBe(true);
});
it('should handle tool name extraction', async () => {
await orchestrator.initialize();
const result = await orchestrator.find('', 10);
expect(Array.isArray(result)).toBe(true);
});
});
describe('run method advanced scenarios', () => {
it('should handle tool execution with parameters', async () => {
await orchestrator.initialize();
const result = await orchestrator.run('test:tool', { param1: 'value1' });
expect(result).toHaveProperty('success');
expect(typeof result.success).toBe('boolean');
});
it('should validate required parameters before execution', async () => {
// This test validates that the parameter validation method exists and works
const testOrchestrator = new (orchestrator.constructor as any)('test');
// Test the validation method with a mock schema
const mockSchema = {
type: 'object',
properties: {
required_param: { type: 'string', description: 'Required parameter' },
optional_param: { type: 'string', description: 'Optional parameter' }
},
required: ['required_param']
};
// Mock getToolSchema to return our test schema
jest.spyOn(testOrchestrator, 'getToolSchema' as any).mockReturnValue(mockSchema);
// Test validation with missing required parameter
const validationError = (testOrchestrator as any).validateToolParameters('test-mcp', 'test_tool', {});
expect(validationError).toContain('Missing required parameters: required_param');
// Test validation with valid parameters
const validationSuccess = (testOrchestrator as any).validateToolParameters('test-mcp', 'test_tool', { required_param: 'value' });
expect(validationSuccess).toBeNull();
// Test validation with null parameters
const validationNull = (testOrchestrator as any).validateToolParameters('test-mcp', 'test_tool', null);
expect(validationNull).toContain('Missing required parameters: required_param');
});
it('should handle MCP name resolution from tool name', async () => {
await orchestrator.initialize();
// Test tool-to-MCP mapping logic
const result = await orchestrator.run('filesystem:read', { path: '/test' });
expect(result).toHaveProperty('success');
});
it('should handle connection establishment', async () => {
await orchestrator.initialize();
// This should test connection logic paths
const result = await orchestrator.run('memory:store', { key: 'test', value: 'data' });
expect(result).toHaveProperty('success');
});
});
describe('MCP connection and execution scenarios', () => {
it('should execute tool with valid MCP connection', async () => {
await orchestrator.initialize();
// This should trigger the connection logic
const result = await orchestrator.run('memory:store', { key: 'test', value: 'data' });
// Should attempt connection even if it fails in test environment
expect(result).toHaveProperty('success');
expect(typeof result.success).toBe('boolean');
});
it('should handle MCP not configured error', async () => {
await orchestrator.initialize();
// This should hit the "MCP not configured" path
const result = await orchestrator.run('nonexistent:tool', {});
expect(result.success).toBe(false);
expect(result.error).toContain('not found');
});
it('should handle connection errors and mark MCP unhealthy', async () => {
await orchestrator.initialize();
// This should trigger connection attempt and failure
const result = await orchestrator.run('failing:test', {});
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
it('should handle multiple initialization calls', async () => {
await orchestrator.initialize();
await orchestrator.initialize();
await orchestrator.initialize();
// Should not crash with multiple inits
expect(orchestrator).toBeDefined();
});
it('should handle cleanup with connections', async () => {
await orchestrator.initialize();
// Attempt to create some state that needs cleanup
await orchestrator.find('test');
await orchestrator.run('test:tool', {});
await orchestrator.cleanup();
expect(orchestrator).toBeDefined();
});
});
describe('resource management', () => {
it('should get all resources from MCPs', async () => {
await orchestrator.initialize();
// Mock the getAllResources method behavior to avoid integration complexity
const mockGetAllResources = jest.spyOn(orchestrator, 'getAllResources').mockResolvedValue([
{ uri: 'file:///tmp/test.txt', name: 'Test File', mimeType: 'text/plain' },
{ uri: 'memory://cache/item1', name: 'Cache Item', mimeType: 'application/json' }
]);
const resources = await orchestrator.getAllResources();
expect(Array.isArray(resources)).toBe(true);
expect(resources).toHaveLength(2);
expect(resources[0].uri).toBe('file:///tmp/test.txt');
expect(mockGetAllResources).toHaveBeenCalled();
});
it('should handle resource retrieval errors gracefully', async () => {
await orchestrator.initialize();
// Mock getAllResources to simulate error handling
const mockGetAllResourcesError = jest.spyOn(orchestrator, 'getAllResources').mockResolvedValue([]);
// This should handle resource retrieval errors gracefully
const resources = await orchestrator.getAllResources();
expect(Array.isArray(resources)).toBe(true);
expect(resources.length).toBe(0); // Should be empty due to errors
expect(mockGetAllResourcesError).toHaveBeenCalled();
});
});
describe('schema operations', () => {
it('should retrieve tool schema from definitions when no connection exists', async () => {
await orchestrator.initialize();
// This should trigger getToolSchema method and find schemas
const result = await orchestrator.find('test:tool', 5, true);
expect(Array.isArray(result)).toBe(true);
// The detailed flag should trigger schema retrieval
});
});
describe('advanced MCP operations', () => {
beforeEach(async () => {
await orchestrator.initialize();
});
it('should get prompts from MCP servers', async () => {
// This should trigger getPromptsFromMCP method
try {
await (orchestrator as any).getPromptsFromMCP('test-prompts');
} catch (error) {
// Expected to fail in test environment, but should exercise the code path
expect(error).toBeDefined();
}
});
it('should handle MCP server connection timeouts for prompts', async () => {
// Test prompt connection timeout handling
try {
await (orchestrator as any).getPromptsFromMCP('memory');
} catch (error) {
// Should handle connection timeouts gracefully
expect(error).toBeDefined();
}
});
it('should get resources from MCP servers with error handling', async () => {
// This should trigger getResourcesFromMCP method and error paths
try {
await (orchestrator as any).getResourcesFromMCP('filesystem');
} catch (error) {
// Expected to fail but should test the resource retrieval path
expect(error).toBeDefined();
}
});
it('should handle resource connection errors gracefully', async () => {
// Test resource connection error handling
try {
await (orchestrator as any).getResourcesFromMCP('nonexistent-mcp');
} catch (error) {
expect(error).toBeDefined();
}
});
it('should handle cache save failures', async () => {
// Test cache save error handling
try {
await (orchestrator as any).saveToolsCache();
} catch (error) {
// Should handle cache save errors gracefully or succeed
expect(true).toBe(true);
}
});
it('should test tool schema retrieval with connections', async () => {
// Test getToolSchema with existing connections
const result = (orchestrator as any).getToolSchema('memory', 'memory:store');
expect(result === undefined || typeof result === 'object').toBe(true);
});
it('should test tool schema retrieval without connections', async () => {
// Test getToolSchema without connections
const result = (orchestrator as any).getToolSchema('nonexistent', 'test:tool');
expect(result).toBeUndefined();
});
it('should handle MCP server environment variable configuration', async () => {
const newOrchestrator = new NCPOrchestrator('env-test');
await newOrchestrator.initialize();
// Should handle custom environment variables
expect(newOrchestrator).toBeDefined();
});
it('should test MCP health monitoring integration', async () => {
// Test health monitor integration with MCP operations
const result = await orchestrator.run('nonexistent:tool', {});
expect(result.success).toBe(false);
// Should mark MCP as unhealthy
expect(result.error).toBeDefined();
});
it('should handle quick probe timeouts', async () => {
// Test QUICK_PROBE_TIMEOUT handling in resource/prompt probes
try {
await (orchestrator as any).getResourcesFromMCP('slow-mcp');
} catch (error) {
// Should timeout quickly for probe operations
expect(error).toBeDefined();
}
});
it('should test connection pool management', async () => {
// Test connection reuse and pool management
await orchestrator.run('memory:store', { key: 'test1', value: 'data1' });
await orchestrator.run('memory:store', { key: 'test2', value: 'data2' });
// Should reuse connections efficiently
expect(true).toBe(true); // Tests connection management paths
});
it('should handle wrapper script creation errors', async () => {
const errorOrchestrator = new NCPOrchestrator('wrapper-error');
await errorOrchestrator.initialize();
const result = await errorOrchestrator.run('error-mcp:test', {});
expect(result.success).toBe(false);
});
});
describe('connection lifecycle and cleanup', () => {
beforeEach(async () => {
await orchestrator.initialize();
});
it('should cleanup idle connections', async () => {
// Test cleanupIdleConnections method
try {
await (orchestrator as any).cleanupIdleConnections();
} catch (error) {
// Should handle cleanup gracefully
}
expect(true).toBe(true);
});
it('should disconnect specific MCP', async () => {
// Test disconnectMCP method
try {
await (orchestrator as any).disconnectMCP('lifecycle-test');
} catch (error) {
// Should handle disconnect gracefully
}
expect(true).toBe(true);
});
it('should handle disconnect errors gracefully', async () => {
// Test error handling in disconnectMCP
try {
await (orchestrator as any).disconnectMCP('nonexistent-connection');
} catch (error) {
// Should handle nonexistent connections gracefully
}
expect(true).toBe(true);
});
it('should manage connection idle timeouts', async () => {
// Test idle time calculation and connection management
const mockConnection = {
client: { close: jest.fn() },
transport: {},
tools: [],
lastUsed: Date.now() - 100000, // Old timestamp to trigger cleanup
connectTime: 1000,
executionCount: 1
};
// Simulate idle connection
(orchestrator as any).connections.set('idle-test', mockConnection);
try {
await (orchestrator as any).cleanupIdleConnections();
} catch (error) {
// Should handle cleanup process
}
expect(true).toBe(true);
});
});
describe('discovery engine integration', () => {
it('should handle discovery engine indexing during initialization', async () => {
await orchestrator.initialize();
// Verify discovery engine stats are accessible
const discoveryStats = (orchestrator as any).discovery.getStats();
expect(discoveryStats).toBeDefined();
// Test tool discovery functionality
const tools = await orchestrator.find('test', 5);
expect(Array.isArray(tools)).toBe(true);
});
});
});