babylon-mcp/src/mcp/server.test.ts
Michael Mainguy a3e027ef02 Initial commit: Babylon MCP server
- MCP server infrastructure with Express and SSE transport
- Repository management for BabylonJS repos (Documentation, Babylon.js, havok)
- Comprehensive test suite with 100% coverage (87 tests passing)
- All code meets standards (files <100 lines, functions <20 lines)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 14:42:47 -06:00

249 lines
6.9 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { BabylonMCPServer } from './server.js';
import { MCP_SERVER_CONFIG } from './config.js';
vi.mock('express', () => ({
default: vi.fn(() => ({
listen: vi.fn((_port: number, callback: () => void) => {
callback();
return {
close: vi.fn((cb: () => void) => cb()),
};
}),
})),
}));
vi.mock('@modelcontextprotocol/sdk/server/mcp.js', () => {
const MockMcpServer = vi.fn(function () {
return {
close: vi.fn().mockResolvedValue(undefined),
};
});
return { McpServer: MockMcpServer };
});
vi.mock('./handlers.js', () => ({
setupHandlers: vi.fn(),
}));
vi.mock('./routes.js', () => ({
setupRoutes: vi.fn(),
}));
vi.mock('./repository-manager.js', () => ({
RepositoryManager: vi.fn(function () {
return {
initializeAllRepositories: vi.fn().mockResolvedValue(undefined),
getRepositoryPath: vi.fn((name: string) => `/mock/path/${name}`),
};
}),
}));
describe('BabylonMCPServer', () => {
let server: BabylonMCPServer;
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
vi.clearAllMocks();
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
});
afterEach(() => {
consoleLogSpy.mockRestore();
});
describe('Constructor', () => {
it('should create Express app', () => {
server = new BabylonMCPServer();
expect(express).toHaveBeenCalled();
});
it('should create McpServer with correct config', () => {
server = new BabylonMCPServer();
expect(McpServer).toHaveBeenCalledWith(
{
name: MCP_SERVER_CONFIG.name,
version: MCP_SERVER_CONFIG.version,
},
{
capabilities: {
tools: {},
prompts: {},
resources: {},
},
instructions: MCP_SERVER_CONFIG.instructions,
}
);
});
it('should setup MCP handlers', async () => {
const { setupHandlers } = await import('./handlers.js');
server = new BabylonMCPServer();
expect(setupHandlers).toHaveBeenCalledWith(expect.any(Object));
});
});
describe('start()', () => {
beforeEach(() => {
server = new BabylonMCPServer();
});
it('should setup routes with app and server', async () => {
const { setupRoutes } = await import('./routes.js');
await server.start();
expect(setupRoutes).toHaveBeenCalledWith(expect.any(Object), expect.any(Object));
});
it('should start HTTP server on default port 4000', async () => {
const mockApp = (express as unknown as ReturnType<typeof vi.fn>).mock.results[0]!
.value;
await server.start();
expect(mockApp.listen).toHaveBeenCalledWith(4000, expect.any(Function));
});
it('should start HTTP server on custom port', async () => {
const mockApp = (express as unknown as ReturnType<typeof vi.fn>).mock.results[0]!
.value;
await server.start(8080);
expect(mockApp.listen).toHaveBeenCalledWith(8080, expect.any(Function));
});
it('should log server information after starting', async () => {
await server.start(4000);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining(MCP_SERVER_CONFIG.name)
);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining(MCP_SERVER_CONFIG.version)
);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining('http://localhost:4000')
);
});
});
describe('shutdown()', () => {
beforeEach(() => {
server = new BabylonMCPServer();
});
it('should log shutdown message', async () => {
await server.shutdown();
expect(consoleLogSpy).toHaveBeenCalledWith(
'Shutting down Babylon MCP Server...'
);
expect(consoleLogSpy).toHaveBeenCalledWith('Server shutdown complete');
});
it('should close MCP server', async () => {
const mockMcpServer = (McpServer as unknown as ReturnType<typeof vi.fn>).mock
.results[0]!.value;
await server.shutdown();
expect(mockMcpServer.close).toHaveBeenCalled();
});
it('should close HTTP server if running', async () => {
await server.start();
const mockApp = (express as unknown as ReturnType<typeof vi.fn>).mock.results[0]!
.value;
const mockHttpServer = mockApp.listen.mock.results[0]!.value;
await server.shutdown();
expect(mockHttpServer.close).toHaveBeenCalled();
});
it('should handle shutdown when HTTP server not started', async () => {
await expect(server.shutdown()).resolves.not.toThrow();
});
});
describe('Error Handling', () => {
let originalProcessOn: typeof process.on;
let processListeners: Record<string, ((...args: unknown[]) => void)[]>;
beforeEach(() => {
processListeners = {};
originalProcessOn = process.on;
process.on = vi.fn((event: string, callback: (...args: unknown[]) => void) => {
if (!processListeners[event]) processListeners[event] = [];
processListeners[event].push(callback);
return process;
}) as typeof process.on;
});
afterEach(() => {
process.on = originalProcessOn;
});
it('should setup SIGINT handler', () => {
server = new BabylonMCPServer();
expect(process.on).toHaveBeenCalledWith('SIGINT', expect.any(Function));
});
it('should setup SIGTERM handler', () => {
server = new BabylonMCPServer();
expect(process.on).toHaveBeenCalledWith('SIGTERM', expect.any(Function));
});
it('should shutdown on SIGINT', async () => {
server = new BabylonMCPServer();
const shutdownSpy = vi.spyOn(server, 'shutdown').mockResolvedValue();
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
return undefined as never;
});
const sigintHandlers = processListeners['SIGINT'];
expect(sigintHandlers).toBeDefined();
expect(sigintHandlers!.length).toBeGreaterThan(0);
await sigintHandlers![0]!();
expect(shutdownSpy).toHaveBeenCalled();
expect(exitSpy).toHaveBeenCalledWith(0);
shutdownSpy.mockRestore();
exitSpy.mockRestore();
});
it('should shutdown on SIGTERM', async () => {
server = new BabylonMCPServer();
const shutdownSpy = vi.spyOn(server, 'shutdown').mockResolvedValue();
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
return undefined as never;
});
const sigtermHandlers = processListeners['SIGTERM'];
expect(sigtermHandlers).toBeDefined();
expect(sigtermHandlers!.length).toBeGreaterThan(0);
await sigtermHandlers![0]!();
expect(shutdownSpy).toHaveBeenCalled();
expect(exitSpy).toHaveBeenCalledWith(0);
shutdownSpy.mockRestore();
exitSpy.mockRestore();
});
});
});