import { describe, it, expect, jest, beforeAll, afterAll } from '@jest/globals';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { MemoryTransport } from '../../utils/memory-transport.js';
import { compareProjects } from '../../../src/tools/compare-projects.js';
import { mcpDb } from '../../../src/db/supabase.js';
// Mock external dependencies
jest.mock('../../../src/db/supabase.js');
jest.mock('../../../src/services/ml-comparison-engine.js');
jest.mock('../../../src/services/benchmark-aggregator.js');
describe('Compare Projects Tool Integration', () => {
let server: McpServer;
let transport: MemoryTransport;
beforeAll(() => {
// Setup MCP server
server = new McpServer(
{
name: 'test-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
}
}
);
// Register the compare_projects tool
server.tool(
'compare_projects',
'Test compare projects tool',
{},
async (args: any) => {
const result = await compareProjects(args);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
);
// Create memory transport for testing
transport = new MemoryTransport();
});
afterAll(async () => {
await server.close();
});
describe('Tool Registration', () => {
it('should register compare_projects tool correctly', async () => {
await server.connect(transport);
const tools = await transport.callMethod('tools/list', {});
expect(tools.tools).toHaveLength(1);
expect(tools.tools[0].name).toBe('compare_projects');
});
});
describe('Tool Execution', () => {
const validInput = {
project_ids: ['proj-123', 'proj-456'],
comparison_metrics: ['roi', 'payback_period', 'npv'],
time_horizon: 60,
enable_ml_insights: true,
include_visualizations: false
};
const mockProjects = [
{
id: 'proj-123',
project_name: 'AI Project 1',
client_name: 'Client A',
industry: 'technology',
status: 'active'
},
{
id: 'proj-456',
project_name: 'AI Project 2',
client_name: 'Client B',
industry: 'healthcare',
status: 'active'
}
];
const mockProjections = [
{
project_id: 'proj-123',
financial_summary: {
expected_roi: 150,
payback_period_months: 12,
net_present_value: 500000,
total_investment: 200000
}
},
{
project_id: 'proj-456',
financial_summary: {
expected_roi: 200,
payback_period_months: 18,
net_present_value: 750000,
total_investment: 300000
}
}
];
beforeEach(() => {
// Mock database responses
(mcpDb as any).from = jest.fn(() => ({
select: jest.fn(() => ({
in: jest.fn(() => ({
data: mockProjects,
error: null
}))
}))
}));
(mcpDb as any).rpc = jest.fn()
.mockResolvedValue({
data: mockProjections,
error: null
});
});
it('should execute compare_projects successfully with valid input', async () => {
const result = await transport.callTool('compare_projects', validInput);
expect(result).toHaveProperty('content');
const content = JSON.parse(result.content[0].text);
expect(content).toHaveProperty('projects');
expect(content).toHaveProperty('rankings');
expect(content).toHaveProperty('insights');
expect(content).toHaveProperty('recommendations');
expect(content.projects).toHaveLength(2);
});
it('should handle minimum project requirement', async () => {
const singleProjectInput = {
...validInput,
project_ids: ['proj-123'] // Only one project
};
const result = await transport.callTool('compare_projects', singleProjectInput);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Validation Error');
});
it('should handle maximum project limit', async () => {
const tooManyProjects = {
...validInput,
project_ids: Array(11).fill('proj-123') // 11 projects (max is 10)
};
const result = await transport.callTool('compare_projects', tooManyProjects);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Validation Error');
});
it('should apply ML insights when enabled', async () => {
// Mock ML comparison engine
jest.mock('../../../src/services/ml-comparison-engine.js', () => ({
MLComparisonEngine: {
compareProjects: jest.fn().mockResolvedValue({
success_probability: { 'proj-123': 0.85, 'proj-456': 0.92 },
risk_factors: {},
synergies: []
})
}
}));
const result = await transport.callTool('compare_projects', validInput);
const content = JSON.parse(result.content[0].text);
expect(content.projects[0]).toHaveProperty('ml_insights');
});
it('should rank projects by different metrics', async () => {
const result = await transport.callTool('compare_projects', validInput);
const content = JSON.parse(result.content[0].text);
expect(content.rankings).toHaveProperty('by_roi');
expect(content.rankings).toHaveProperty('by_payback_period');
expect(content.rankings).toHaveProperty('by_npv');
// Verify rankings are arrays of project objects
expect(Array.isArray(content.rankings.by_roi)).toBe(true);
expect(content.rankings.by_roi[0]).toHaveProperty('project_id');
expect(content.rankings.by_roi[0]).toHaveProperty('value');
});
});
describe('Error Handling', () => {
it('should handle database connection errors', async () => {
(mcpDb as any).from = jest.fn(() => ({
select: jest.fn(() => ({
in: jest.fn(() => ({
data: null,
error: { message: 'Database connection failed' }
}))
}))
}));
const result = await transport.callTool('compare_projects', validInput);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Error');
});
it('should handle projects not found', async () => {
(mcpDb as any).from = jest.fn(() => ({
select: jest.fn(() => ({
in: jest.fn(() => ({
data: [],
error: null
}))
}))
}));
const result = await transport.callTool('compare_projects', validInput);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('No projects found');
});
});
});