123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634 |
- /**
- * 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();
- });
- });
- });
- });
|