Skip to main content
Glama
CastPlanUltimateAutomationServer.test.ts19.2 kB
/** * CastPlan Ultimate Automation Server Integration Tests * * Comprehensive test suite for the main server class */ import { CastPlanUltimateAutomationServer } from '../index.js'; import { ServiceMockFactory, ServerMockFactory, ConfigMockFactory } from './helpers/MockFactories.js'; import { TestDataFactory, PerformanceTestUtils, TestAssertions, TestEnvironment } from './helpers/TestUtils.js'; // Mock external modules jest.mock('@modelcontextprotocol/sdk/server/index.js'); jest.mock('@modelcontextprotocol/sdk/server/stdio.js'); jest.mock('winston'); describe('CastPlanUltimateAutomationServer', () => { let server: CastPlanUltimateAutomationServer; let mockMCPServer: any; let mockTransport: any; beforeEach(() => { TestEnvironment.setupTestEnv(); mockMCPServer = ServerMockFactory.createMCPServerMock(); mockTransport = ServerMockFactory.createTransportMock(); // Mock the Server constructor (require('@modelcontextprotocol/sdk/server/index.js').Server as jest.Mock) .mockImplementation(() => mockMCPServer); // Mock the transport (require('@modelcontextprotocol/sdk/server/stdio.js').StdioServerTransport as jest.Mock) .mockImplementation(() => mockTransport); }); afterEach(() => { TestEnvironment.restoreEnv(); jest.clearAllMocks(); }); describe('Server Initialization', () => { test('should initialize server with default configuration', () => { expect(() => { server = new CastPlanUltimateAutomationServer(); }).not.toThrow(); expect(mockMCPServer.setRequestHandler).toHaveBeenCalledTimes(4); // 4 MCP handlers }); test('should initialize server with custom environment', () => { process.env.CASTPLAN_PROJECT_ROOT = '/custom/project'; process.env.CASTPLAN_ENABLE_AI = 'true'; process.env.CASTPLAN_AI_PROVIDER = 'anthropic'; process.env.CASTPLAN_LOG_LEVEL = 'debug'; expect(() => { server = new CastPlanUltimateAutomationServer(); }).not.toThrow(); }); test('should handle disabled services', () => { process.env.CASTPLAN_ENABLE_BMAD = 'false'; process.env.CASTPLAN_ENABLE_DOCS = 'false'; process.env.CASTPLAN_ENABLE_HOOKS = 'false'; process.env.CASTPLAN_ENABLE_ENHANCED = 'false'; expect(() => { server = new CastPlanUltimateAutomationServer(); }).not.toThrow(); }); test('should setup request handlers correctly', () => { server = new CastPlanUltimateAutomationServer(); // Verify all required handlers are set expect(mockMCPServer.setRequestHandler).toHaveBeenCalledWith( expect.objectContaining({ method: 'resources/list' }), expect.any(Function) ); expect(mockMCPServer.setRequestHandler).toHaveBeenCalledWith( expect.objectContaining({ method: 'resources/read' }), expect.any(Function) ); expect(mockMCPServer.setRequestHandler).toHaveBeenCalledWith( expect.objectContaining({ method: 'tools/list' }), expect.any(Function) ); expect(mockMCPServer.setRequestHandler).toHaveBeenCalledWith( expect.objectContaining({ method: 'tools/call' }), expect.any(Function) ); }); }); describe('Service Management', () => { beforeEach(() => { server = new CastPlanUltimateAutomationServer(); }); test('should initialize all services when enabled', () => { process.env.CASTPLAN_ENABLE_BMAD = 'true'; process.env.CASTPLAN_ENABLE_DOCS = 'true'; process.env.CASTPLAN_ENABLE_HOOKS = 'true'; process.env.CASTPLAN_ENABLE_ENHANCED = 'true'; server = new CastPlanUltimateAutomationServer(); // Services should be initialized (verified through constructor calls) expect(() => server).not.toThrow(); }); test('should skip disabled services', () => { process.env.CASTPLAN_ENABLE_BMAD = 'false'; server = new CastPlanUltimateAutomationServer(); // Should not throw even with disabled services expect(() => server).not.toThrow(); }); test('should handle AI service configuration', () => { process.env.CASTPLAN_ENABLE_AI = 'true'; process.env.CASTPLAN_AI_PROVIDER = 'openai'; process.env.CASTPLAN_AI_API_KEY = 'test-key'; server = new CastPlanUltimateAutomationServer(); expect(() => server).not.toThrow(); }); }); describe('MCP Request Handling', () => { beforeEach(() => { server = new CastPlanUltimateAutomationServer(); }); test('should handle resources/list request', async () => { const listResourcesHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/list')?.[1]; expect(listResourcesHandler).toBeDefined(); const result = await listResourcesHandler({}); expect(result).toBeDefined(); expect(result.resources).toBeDefined(); expect(Array.isArray(result.resources)).toBe(true); }); test('should handle resources/read request', async () => { const readResourceHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/read')?.[1]; expect(readResourceHandler).toBeDefined(); const result = await readResourceHandler({ params: { uri: 'castplan://status' } }); expect(result).toBeDefined(); expect(result.contents).toBeDefined(); expect(Array.isArray(result.contents)).toBe(true); }); test('should handle tools/list request', async () => { const listToolsHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'tools/list')?.[1]; expect(listToolsHandler).toBeDefined(); const result = await listToolsHandler({}); expect(result).toBeDefined(); expect(result.tools).toBeDefined(); expect(Array.isArray(result.tools)).toBe(true); }); test('should handle tools/call request', async () => { const callToolHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'tools/call')?.[1]; expect(callToolHandler).toBeDefined(); // This will throw because the tool doesn't exist, but handler should be set up await expect(callToolHandler({ params: { name: 'non-existent-tool', arguments: {} } })).rejects.toThrow(); }); }); describe('Resource Management', () => { beforeEach(() => { server = new CastPlanUltimateAutomationServer(); }); test('should provide system status resource', async () => { const readResourceHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/read')?.[1]; const result = await readResourceHandler({ params: { uri: 'castplan://status' } }); expect(result.contents[0].text).toBeDefined(); const status = JSON.parse(result.contents[0].text); expect(status.health).toBeDefined(); expect(status.health.status).toBeDefined(); expect(status.health.uptime).toBeDefined(); expect(status.health.version).toBe('2.0.0'); }); test('should handle invalid resource URIs', async () => { const readResourceHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/read')?.[1]; await expect(readResourceHandler({ params: { uri: 'castplan://invalid-resource' } })).rejects.toThrow(); }); test('should provide service-specific resources when enabled', async () => { process.env.CASTPLAN_ENABLE_BMAD = 'true'; server = new CastPlanUltimateAutomationServer(); const listResourcesHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/list')?.[1]; const result = await listResourcesHandler({}); const bmadResources = result.resources.filter((r: any) => r.uri.startsWith('castplan://') && ['tasks', 'agents', 'assignments'].some(path => r.uri.includes(path)) ); expect(bmadResources.length).toBeGreaterThan(0); }); }); describe('Tool Management', () => { beforeEach(() => { server = new CastPlanUltimateAutomationServer(); }); test('should register tools for enabled services', async () => { process.env.CASTPLAN_ENABLE_BMAD = 'true'; process.env.CASTPLAN_ENABLE_DOCS = 'true'; server = new CastPlanUltimateAutomationServer(); const listToolsHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'tools/list')?.[1]; const result = await listToolsHandler({}); expect(result.tools.length).toBeGreaterThan(0); // Should have BMAD tools const bmadTools = result.tools.filter((t: any) => t.name.startsWith('bmad_')); expect(bmadTools.length).toBeGreaterThan(0); // Should have documentation tools const docTools = result.tools.filter((t: any) => t.name.startsWith('docs_')); expect(docTools.length).toBeGreaterThan(0); }); test('should not register tools for disabled services', async () => { process.env.CASTPLAN_ENABLE_BMAD = 'false'; server = new CastPlanUltimateAutomationServer(); const listToolsHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'tools/list')?.[1]; const result = await listToolsHandler({}); // Should not have BMAD tools const bmadTools = result.tools.filter((t: any) => t.name.startsWith('bmad_')); expect(bmadTools.length).toBe(0); }); test('should validate tool definitions', async () => { const listToolsHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'tools/list')?.[1]; const result = await listToolsHandler({}); result.tools.forEach((tool: any) => { expect(tool.name).toBeDefined(); expect(tool.description).toBeDefined(); expect(tool.inputSchema).toBeDefined(); expect(tool.inputSchema.type).toBe('object'); expect(tool.inputSchema.properties).toBeDefined(); }); }); }); describe('Server Lifecycle', () => { test('should start server successfully', async () => { server = new CastPlanUltimateAutomationServer(); await expect(server.start()).resolves.not.toThrow(); expect(mockMCPServer.connect).toHaveBeenCalledWith(mockTransport); }); test('should handle startup errors gracefully', async () => { mockMCPServer.connect.mockRejectedValueOnce(new Error('Connection failed')); server = new CastPlanUltimateAutomationServer(); await expect(server.start()).rejects.toThrow('Connection failed'); }); test('should handle transport errors', async () => { mockTransport = { ...mockTransport, start: jest.fn().mockRejectedValue(new Error('Transport error')) }; server = new CastPlanUltimateAutomationServer(); // Connection might still succeed even if transport has issues await expect(server.start()).resolves.not.toThrow(); }); }); describe('Configuration Management', () => { test('should load configuration from environment variables', () => { process.env.CASTPLAN_PROJECT_ROOT = '/custom/root'; process.env.CASTPLAN_DATABASE_PATH = '/custom/db.sqlite'; process.env.CASTPLAN_TIMEZONE = 'UTC'; process.env.CASTPLAN_LOCALE = 'en-US'; process.env.CASTPLAN_LOG_LEVEL = 'debug'; server = new CastPlanUltimateAutomationServer(); // Configuration should be loaded without errors expect(() => server).not.toThrow(); }); test('should use default values when environment variables are not set', () => { // Clear environment variables delete process.env.CASTPLAN_PROJECT_ROOT; delete process.env.CASTPLAN_TIMEZONE; delete process.env.CASTPLAN_LOCALE; server = new CastPlanUltimateAutomationServer(); // Should not throw with default configuration expect(() => server).not.toThrow(); }); test('should handle invalid configuration values', () => { process.env.CASTPLAN_MAX_CONCURRENT = 'invalid-number'; server = new CastPlanUltimateAutomationServer(); // Should handle invalid values gracefully expect(() => server).not.toThrow(); }); }); describe('Error Handling', () => { beforeEach(() => { server = new CastPlanUltimateAutomationServer(); }); test('should handle resource read errors', async () => { const readResourceHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/read')?.[1]; await expect(readResourceHandler({ params: { uri: 'castplan://non-existent' } })).rejects.toThrow(); }); test('should handle tool call errors', async () => { const callToolHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'tools/call')?.[1]; await expect(callToolHandler({ params: { name: 'non-existent-tool', arguments: {} } })).rejects.toThrow(); }); test('should handle malformed requests', async () => { const callToolHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'tools/call')?.[1]; await expect(callToolHandler({ params: null })).rejects.toThrow(); }); }); describe('Performance Tests', () => { beforeEach(() => { server = new CastPlanUltimateAutomationServer(); }); test('should handle rapid resource requests', async () => { const readResourceHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/read')?.[1]; const requests = Array.from({ length: 100 }, () => () => readResourceHandler({ params: { uri: 'castplan://status' } }) ); const { duration } = await PerformanceTestUtils.measureExecutionTime(async () => { await Promise.all(requests.map(req => req())); }); TestAssertions.assertExecutionTime(duration, 2000, '100 concurrent resource requests'); }); test('should handle concurrent tool list requests', async () => { const listToolsHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'tools/list')?.[1]; const requests = Array.from({ length: 50 }, () => () => listToolsHandler({}) ); const { duration } = await PerformanceTestUtils.measureExecutionTime(async () => { await Promise.all(requests.map(req => req())); }); TestAssertions.assertExecutionTime(duration, 1000, '50 concurrent tool list requests'); }); test('should handle memory efficiently', async () => { const listResourcesHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/list')?.[1]; const { memoryDelta } = await PerformanceTestUtils.measureMemoryUsage(async () => { for (let i = 0; i < 1000; i++) { await listResourcesHandler({}); } }); TestAssertions.assertMemoryUsage(memoryDelta, 50 * 1024 * 1024, '1000 resource list requests'); }); }); describe('Integration Tests', () => { test('should handle complete server workflow', async () => { process.env.CASTPLAN_ENABLE_BMAD = 'true'; process.env.CASTPLAN_ENABLE_DOCS = 'true'; server = new CastPlanUltimateAutomationServer(); // Start server await server.start(); // List resources const listResourcesHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/list')?.[1]; const resources = await listResourcesHandler({}); expect(resources.resources.length).toBeGreaterThan(0); // List tools const listToolsHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'tools/list')?.[1]; const tools = await listToolsHandler({}); expect(tools.tools.length).toBeGreaterThan(0); // Read status resource const readResourceHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/read')?.[1]; const status = await readResourceHandler({ params: { uri: 'castplan://status' } }); expect(status.contents[0].text).toBeDefined(); }); test('should handle service interactions', async () => { process.env.CASTPLAN_ENABLE_ENHANCED = 'true'; server = new CastPlanUltimateAutomationServer(); const listToolsHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'tools/list')?.[1]; const tools = await listToolsHandler({}); // Should have enhanced tools const enhancedTools = tools.tools.filter((t: any) => ['initialize_documentation_system', 'track_document_work'].includes(t.name) ); expect(enhancedTools.length).toBeGreaterThan(0); }); }); describe('Health and Monitoring', () => { test('should provide comprehensive health status', async () => { process.env.CASTPLAN_ENABLE_BMAD = 'true'; process.env.CASTPLAN_ENABLE_DOCS = 'true'; process.env.CASTPLAN_ENABLE_HOOKS = 'true'; process.env.CASTPLAN_ENABLE_ENHANCED = 'true'; server = new CastPlanUltimateAutomationServer(); const readResourceHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/read')?.[1]; const result = await readResourceHandler({ params: { uri: 'castplan://status' } }); const status = JSON.parse(result.contents[0].text); expect(status.health.status).toBeDefined(); expect(['healthy', 'degraded', 'error']).toContain(status.health.status); expect(status.health.uptime).toBeGreaterThanOrEqual(0); expect(status.health.version).toBe('2.0.0'); expect(status.bmad).toBeDefined(); expect(status.documentation).toBeDefined(); expect(status.hooks).toBeDefined(); expect(status.enhanced).toBeDefined(); }); test('should detect service health issues', async () => { // Test with no services enabled process.env.CASTPLAN_ENABLE_BMAD = 'false'; process.env.CASTPLAN_ENABLE_DOCS = 'false'; process.env.CASTPLAN_ENABLE_HOOKS = 'false'; process.env.CASTPLAN_ENABLE_ENHANCED = 'false'; server = new CastPlanUltimateAutomationServer(); const readResourceHandler = mockMCPServer.setRequestHandler.mock.calls .find(call => call[0].method === 'resources/read')?.[1]; const result = await readResourceHandler({ params: { uri: 'castplan://status' } }); const status = JSON.parse(result.contents[0].text); // Should detect no services as error condition expect(status.health.status).toBe('error'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ghostseller/CastPlan_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server