UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

395 lines (337 loc) 12.8 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { DashboardServer } from '../server.js'; import { Server } from 'http'; import { io as ioClient, Socket } from 'socket.io-client'; import request from 'supertest'; // Mock the modules we depend on vi.mock('../../utils/performance-monitor.js', () => ({ PerformanceMonitor: { getInstance: vi.fn(() => ({ getSystemMetrics: vi.fn().mockResolvedValue({ cpu: 50, memory: 60 }), getToolMetrics: vi.fn().mockResolvedValue([]), getQualityMetrics: vi.fn().mockResolvedValue({ testCoverage: 80 }), getPerformanceAlerts: vi.fn().mockResolvedValue([]) })) } })); vi.mock('../../utils/security-manager.js', () => ({ SecurityManager: { getInstance: vi.fn(() => ({ getSecurityStatus: vi.fn().mockResolvedValue({ level: 'medium' }), getSecurityEvents: vi.fn().mockResolvedValue([]), generateSecurityMetrics: vi.fn().mockResolvedValue({ totalEvents: 0 }) })) } })); vi.mock('../../utils/error-handler.js', () => ({ ErrorHandler: vi.fn().mockImplementation(() => ({ getErrorTimeline: vi.fn().mockResolvedValue([]), analyzeErrorPatterns: vi.fn().mockResolvedValue({ patterns: [] }) })) })); vi.mock('../../modules/agile-management/agile-manager.js', () => ({ AgileManager: vi.fn().mockImplementation(() => ({ getAllSprints: vi.fn().mockResolvedValue([]), getAllStories: vi.fn().mockResolvedValue([]), getAllEpics: vi.fn().mockResolvedValue([]) })) })); // Mock the SQLite manager to prevent database initialization issues vi.mock('../../storage/sqlite-manager.js', () => ({ getSQLiteManager: vi.fn(() => ({ query: vi.fn().mockResolvedValue({ success: true, data: [] }), get: vi.fn().mockResolvedValue({ success: true, data: null }), run: vi.fn().mockResolvedValue({ success: true, data: { changes: 1 } }), initialize: vi.fn().mockResolvedValue(true), close: vi.fn().mockResolvedValue(true) })), ensureDatabaseReady: vi.fn().mockResolvedValue({ query: vi.fn().mockResolvedValue({ success: true, data: [] }), get: vi.fn().mockResolvedValue({ success: true, data: null }), run: vi.fn().mockResolvedValue({ success: true, data: { changes: 1 } }), initialize: vi.fn().mockResolvedValue(true), close: vi.fn().mockResolvedValue(true), getConnectionInfo: vi.fn().mockReturnValue({ isInitialized: true, dbPath: ':memory:' }) }) })); describe('DashboardServer', () => { let dashboardServer: DashboardServer; let httpServer: Server; let clientSocket: Socket; let testPort: number; let mockPerformanceMonitor: any; let mockSecurityManager: any; let mockErrorHandler: any; let mockAgileManager: any; // Generate a unique port for each test run const getRandomPort = () => Math.floor(Math.random() * 10000) + 40000; beforeEach(async () => { // Generate a unique port for this test testPort = getRandomPort(); // Create mocks for dependencies mockPerformanceMonitor = { getSystemMetrics: vi.fn().mockResolvedValue({ cpu: 50, memory: 60 }), getToolMetrics: vi.fn().mockResolvedValue([]), getQualityMetrics: vi.fn().mockResolvedValue({ testCoverage: 80 }), getPerformanceAlerts: vi.fn().mockResolvedValue([]) }; mockSecurityManager = { getSecurityStatus: vi.fn().mockResolvedValue({ level: 'medium' }), getSecurityEvents: vi.fn().mockResolvedValue([]), generateSecurityMetrics: vi.fn().mockResolvedValue({ totalEvents: 0 }) }; mockErrorHandler = { getErrorTimeline: vi.fn().mockResolvedValue([]), analyzeErrorPatterns: vi.fn().mockResolvedValue({ patterns: [] }) }; mockAgileManager = { getAllSprints: vi.fn().mockResolvedValue([]), getAllStories: vi.fn().mockResolvedValue([]), getAllEpics: vi.fn().mockResolvedValue([]) }; const config = { enabled: true, port: testPort, host: 'localhost', autoOpen: false, features: { performance: true, security: true, agile: true, errors: true }, realTimeUpdates: true, exportEnabled: true }; dashboardServer = new DashboardServer( config, mockPerformanceMonitor, mockSecurityManager, mockErrorHandler, mockAgileManager ); await dashboardServer.start(); httpServer = dashboardServer['server']; // Access private server property for testing // Give the server time to start await new Promise(resolve => setTimeout(resolve, 100)); }); afterEach(async () => { if (clientSocket && clientSocket.connected) { clientSocket.disconnect(); clientSocket = null; } if (dashboardServer) { await dashboardServer.stop(); // Add a small delay to ensure port is released await new Promise(resolve => setTimeout(resolve, 100)); } }); describe('Server Lifecycle', () => { it('should start the server on specified port', async () => { const response = await request(`http://localhost:${testPort}`).get('/'); expect(response.status).toBe(200); expect(response.type).toBe('text/html'); }); it('should serve static files', async () => { const response = await request(`http://localhost:${testPort}`).get('/css/dashboard.css'); expect(response.status).toBe(200); expect(response.type).toBe('text/css'); }); it('should handle health check endpoint', async () => { const response = await request(`http://localhost:${testPort}`).get('/api/health'); expect(response.status).toBe(200); expect(response.body).toEqual({ status: 'healthy', timestamp: expect.any(String), features: expect.any(Object), version: expect.any(String), database: expect.any(Object) }); }); it('should stop gracefully', async () => { // Create a separate instance for this test const stopTestPort = getRandomPort(); const stopTestServer = new DashboardServer( { enabled: true, port: stopTestPort, host: 'localhost', autoOpen: false, features: { performance: true, security: true, agile: true, errors: true }, realTimeUpdates: false, exportEnabled: true }, mockPerformanceMonitor, mockSecurityManager, mockErrorHandler, mockAgileManager ); await stopTestServer.start(); await stopTestServer.stop(); // Try to connect - should fail try { await request(`http://localhost:${stopTestPort}`).get('/'); expect.fail('Server should be stopped'); } catch (error) { // Expected - server is stopped expect(error).toBeDefined(); } }); }); describe('WebSocket Communication', () => { beforeEach((done) => { clientSocket = ioClient(`http://localhost:${testPort}`, { transports: ['websocket'], autoConnect: true }); clientSocket.on('connect', done); }); it('should accept websocket connections', () => { // The beforeEach already establishes the connection via done callback // So if we reach this test, the connection was successful expect(clientSocket).toBeDefined(); expect(clientSocket.io).toBeDefined(); }); it('should send initial data on connection', (done) => { clientSocket.on('initial-data', (data: any) => { expect(data).toHaveProperty('metrics'); expect(data).toHaveProperty('security'); expect(data).toHaveProperty('errors'); expect(data).toHaveProperty('agile'); done(); }); }); it('should broadcast dashboard updates', (done) => { clientSocket.on('dashboard-update', (data: any) => { expect(data).toHaveProperty('metrics'); expect(data).toHaveProperty('timestamp'); done(); }); // Trigger an update by waiting for the interval setTimeout(() => { // Update should have been broadcast by now }, 2000); }); it('should handle client disconnection', (done) => { clientSocket.on('disconnect', () => { expect(clientSocket.connected).toBe(false); done(); }); clientSocket.disconnect(); }); it('should handle request-data event', (done) => { clientSocket.emit('request-data', { type: 'metrics' }); clientSocket.on('data-response', (data: any) => { expect(data.type).toBe('metrics'); expect(data.data).toBeDefined(); done(); }); }); }); describe('API Integration', () => { it('should serve metrics API', async () => { const response = await request(`http://localhost:${testPort}`) .get('/api/metrics/overview'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data).toHaveProperty('cpu'); }); it('should serve security API', async () => { const response = await request(`http://localhost:${testPort}`) .get('/api/security/status'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data).toHaveProperty('level'); }); it('should serve agile API', async () => { const response = await request(`http://localhost:${testPort}`) .get('/api/agile/sprints'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); // The API returns data object with sprints array expect(response.body.data).toBeDefined(); expect(response.body.data.sprints).toBeInstanceOf(Array); }); it('should serve errors API', async () => { // Add a small delay to ensure server is ready await new Promise(resolve => setTimeout(resolve, 100)); const response = await request(`http://localhost:${testPort}`) .get('/api/errors/timeline') .timeout(5000); // Add explicit timeout expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data).toHaveProperty('timeline'); }, 10000); // Increase test timeout }); describe('Error Handling', () => { it('should handle 404 for unknown routes', async () => { const response = await request(`http://localhost:${testPort}`) .get('/unknown/route'); expect(response.status).toBe(404); }); it('should handle API errors gracefully', async () => { // Force an error by mocking a failure mockPerformanceMonitor.getSystemMetrics.mockRejectedValueOnce(new Error('Test error')); const response = await request(`http://localhost:${testPort}`) .get('/api/metrics/overview'); expect(response.status).toBe(500); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Failed to fetch system metrics'); }); }); describe('Data Collection', () => { it('should collect data periodically', async () => { const initialDataSpy = vi.fn(); // Connect a client to capture updates const testClient = ioClient(`http://localhost:${testPort}`, { transports: ['websocket'] }); testClient.on('performance_update', initialDataSpy); // Wait for at least one update cycle (6 seconds to be safe) await new Promise(resolve => setTimeout(resolve, 6000)); expect(initialDataSpy).toHaveBeenCalled(); testClient.disconnect(); }, 15000); // Set timeout to 15 seconds }); describe('Configuration', () => { it('should use custom configuration when provided', async () => { const customPort = getRandomPort(); const customConfig = { enabled: true, port: customPort, host: 'localhost', autoOpen: false, features: { performance: true, security: true, agile: true, errors: true }, realTimeUpdates: true, exportEnabled: true, updateInterval: 5000, maxDataPoints: 200 }; const customDashboard = new DashboardServer( customConfig, mockPerformanceMonitor, mockSecurityManager, mockErrorHandler, mockAgileManager ); await customDashboard.start(); // Verify server started with custom config const response = await request(`http://localhost:${customPort}`).get('/api/health'); expect(response.status).toBe(200); await customDashboard.stop(); }); }); });