/** * Integration test for WebSocket session updates * Tests the complete flow of real-time WebSocket communication for hedging sessions */ import { describe, it, expect, beforeAll, afterAll, beforeEach } from '@jest/globals'; import WebSocket from 'ws'; import { HedgingManager } from '../../src/core/HedgingManager'; import { WebSocketManager } from '../../src/services/WebSocketManager'; import { SessionUpdateMessage, SessionSubscription } from '../../src/types/hedging'; describe('WebSocket Session Updates Integration', () => { let hedgingManager: HedgingManager; let webSocketManager: WebSocketManager; let testSessionId: string; let wsServer: any; let wsPort: number = 8080; beforeAll(async () => { // Initialize hedging manager hedgingManager = new HedgingManager({ accounts: './config/accounts.json', hedging: './config/hedging-config.json', marketData: './config/market-data-config.json' }); // Initialize WebSocket manager webSocketManager = new WebSocketManager({ port: wsPort, heartbeatInterval: 30000, reconnectInterval: 5000, maxReconnectAttempts: 5, connectionTimeout: 10000 }); await hedgingManager.initialize(); await webSocketManager.initialize(); }); afterAll(async () => { if (hedgingManager) { await hedgingManager.shutdown(); } if (webSocketManager) { await webSocketManager.shutdown(); } if (wsServer) { wsServer.close(); } }); beforeEach(async () => { // Create a test session for each test const sessionRequest = { name: 'WebSocket Test Session', accountIds: ['account-1', 'account-2'], volumeTarget: 10000, strategy: { symbol: 'ETH/USD', volumeDistribution: 'equal' as const, priceRange: { min: 0.001, max: 0.01 }, timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } }, riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 }, orderTypes: { primary: 'limit' as const, fallback: 'market' as const } } }; try { const session = await hedgingManager.createSession(sessionRequest); testSessionId = session.id; } catch (error) { testSessionId = 'mock-session-id'; } }); describe('WebSocket Connection', () => { it('should establish WebSocket connection', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`); ws.on('open', () => { expect(ws.readyState).toBe(WebSocket.OPEN); ws.close(); done(); }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); }); it('should authenticate WebSocket connection', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); ws.on('open', () => { // Send authentication message const authMessage = { type: 'authenticate', token: 'test-api-key' }; ws.send(JSON.stringify(authMessage)); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { expect(message.success).toBe(true); ws.close(); done(); } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); }); it('should reject unauthenticated connections', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`); ws.on('open', () => { // Don't send authentication setTimeout(() => { expect(ws.readyState).toBe(WebSocket.CLOSED); done(); }, 1000); }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); }); }); describe('Session Subscription', () => { it('should subscribe to session updates', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); ws.on('open', () => { // Authenticate first ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { // Subscribe to session updates const subscription: SessionSubscription = { type: 'subscribe', sessionId: testSessionId, channels: ['status', 'orders', 'risk', 'metrics'] }; ws.send(JSON.stringify(subscription)); } else if (message.type === 'subscribed') { expect(message.success).toBe(true); expect(message.channels).toEqual(['status', 'orders', 'risk', 'metrics']); ws.close(); done(); } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); }); it('should validate subscription channels', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); ws.on('open', () => { ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { // Subscribe with invalid channel const invalidSubscription = { type: 'subscribe', sessionId: testSessionId, channels: ['invalid_channel'] }; ws.send(JSON.stringify(invalidSubscription)); } else if (message.type === 'error') { expect(message.error.code).toBe('INVALID_CHANNEL'); ws.close(); done(); } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); }); }); describe('Real-time Updates', () => { it('should receive status updates', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); let updateReceived = false; ws.on('open', () => { ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { // Subscribe to status updates ws.send(JSON.stringify({ type: 'subscribe', sessionId: testSessionId, channels: ['status'] })); } else if (message.type === 'session_update' && message.channel === 'status') { const updateMessage: SessionUpdateMessage = message; expect(updateMessage.sessionId).toBe(testSessionId); expect(updateMessage.channel).toBe('status'); expect(updateMessage.data.status).toBeDefined(); expect(updateMessage.timestamp).toBeDefined(); updateReceived = true; ws.close(); done(); } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); // Timeout if no update received setTimeout(() => { if (!updateReceived) { ws.close(); done(); } }, 5000); }); it('should receive order updates', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); let updateReceived = false; ws.on('open', () => { ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { // Subscribe to order updates ws.send(JSON.stringify({ type: 'subscribe', sessionId: testSessionId, channels: ['orders'] })); } else if (message.type === 'session_update' && message.channel === 'orders') { const updateMessage: SessionUpdateMessage = message; expect(updateMessage.sessionId).toBe(testSessionId); expect(updateMessage.channel).toBe('orders'); expect(updateMessage.data.orders).toBeDefined(); expect(Array.isArray(updateMessage.data.orders)).toBe(true); updateReceived = true; ws.close(); done(); } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); // Timeout if no update received setTimeout(() => { if (!updateReceived) { ws.close(); done(); } }, 5000); }); it('should receive risk updates', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); let updateReceived = false; ws.on('open', () => { ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { // Subscribe to risk updates ws.send(JSON.stringify({ type: 'subscribe', sessionId: testSessionId, channels: ['risk'] })); } else if (message.type === 'session_update' && message.channel === 'risk') { const updateMessage: SessionUpdateMessage = message; expect(updateMessage.sessionId).toBe(testSessionId); expect(updateMessage.channel).toBe('risk'); expect(updateMessage.data.riskBreaches).toBeDefined(); expect(Array.isArray(updateMessage.data.riskBreaches)).toBe(true); updateReceived = true; ws.close(); done(); } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); // Timeout if no update received setTimeout(() => { if (!updateReceived) { ws.close(); done(); } }, 5000); }); it('should receive metrics updates', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); let updateReceived = false; ws.on('open', () => { ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { // Subscribe to metrics updates ws.send(JSON.stringify({ type: 'subscribe', sessionId: testSessionId, channels: ['metrics'] })); } else if (message.type === 'session_update' && message.channel === 'metrics') { const updateMessage: SessionUpdateMessage = message; expect(updateMessage.sessionId).toBe(testSessionId); expect(updateMessage.channel).toBe('metrics'); expect(updateMessage.data.metrics).toBeDefined(); expect(updateMessage.data.metrics.volume).toBeDefined(); expect(updateMessage.data.metrics.orders).toBeDefined(); expect(updateMessage.data.metrics.performance).toBeDefined(); expect(updateMessage.data.metrics.risk).toBeDefined(); updateReceived = true; ws.close(); done(); } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); // Timeout if no update received setTimeout(() => { if (!updateReceived) { ws.close(); done(); } }, 5000); }); }); describe('Connection Management', () => { it('should handle connection heartbeat', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); let heartbeatReceived = false; ws.on('open', () => { ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { // Subscribe to updates ws.send(JSON.stringify({ type: 'subscribe', sessionId: testSessionId, channels: ['status'] })); } else if (message.type === 'heartbeat') { expect(message.timestamp).toBeDefined(); heartbeatReceived = true; ws.close(); done(); } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); // Timeout if no heartbeat received setTimeout(() => { if (!heartbeatReceived) { ws.close(); done(); } }, 10000); }); it('should handle connection reconnection', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); ws.on('open', () => { ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { // Subscribe to updates ws.send(JSON.stringify({ type: 'subscribe', sessionId: testSessionId, channels: ['status'] })); // Simulate connection drop and reconnect setTimeout(() => { ws.close(); // Reconnect const ws2 = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); ws2.on('open', () => { ws2.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws2.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { ws2.close(); done(); } }); ws2.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); }, 1000); } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); }); it('should handle multiple concurrent connections', (done) => { const connections: WebSocket[] = []; let connectedCount = 0; // Create multiple connections for (let i = 0; i < 5; i++) { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); ws.on('open', () => { ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { connectedCount++; if (connectedCount === 5) { // Close all connections connections.forEach(conn => conn.close()); done(); } } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); connections.push(ws); } }); }); describe('Error Handling', () => { it('should handle invalid session ID', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/invalid-session-id`, { headers: { 'Authorization': 'Bearer test-api-key' } }); ws.on('open', () => { ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'error') { expect(message.error.code).toBe('SESSION_NOT_FOUND'); ws.close(); done(); } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); }); it('should handle malformed messages', (done) => { const ws = new WebSocket(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, { headers: { 'Authorization': 'Bearer test-api-key' } }); ws.on('open', () => { ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' })); }); ws.on('message', (data) => { const message = JSON.parse(data.toString()); if (message.type === 'authenticated') { // Send malformed message ws.send('invalid json'); } else if (message.type === 'error') { expect(message.error.code).toBe('INVALID_MESSAGE_FORMAT'); ws.close(); done(); } }); ws.on('error', (error) => { // This test should fail initially since WebSocket server doesn't exist yet expect(error.message).toContain('ECONNREFUSED'); done(); }); }); }); });