test_websocket_updates.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. "use strict";
  2. /**
  3. * Integration test for WebSocket session updates
  4. * Tests the complete flow of real-time WebSocket communication for hedging sessions
  5. */
  6. var __importDefault = (this && this.__importDefault) || function (mod) {
  7. return (mod && mod.__esModule) ? mod : { "default": mod };
  8. };
  9. Object.defineProperty(exports, "__esModule", { value: true });
  10. const globals_1 = require("@jest/globals");
  11. const ws_1 = __importDefault(require("ws"));
  12. const HedgingManager_1 = require("../../src/core/HedgingManager");
  13. const WebSocketManager_1 = require("../../src/services/WebSocketManager");
  14. (0, globals_1.describe)('WebSocket Session Updates Integration', () => {
  15. let hedgingManager;
  16. let webSocketManager;
  17. let testSessionId;
  18. let wsServer;
  19. let wsPort = 8080;
  20. (0, globals_1.beforeAll)(async () => {
  21. // Initialize hedging manager
  22. hedgingManager = new HedgingManager_1.HedgingManager({
  23. accounts: './config/accounts.json',
  24. hedging: './config/hedging-config.json',
  25. marketData: './config/market-data-config.json'
  26. });
  27. // Initialize WebSocket manager
  28. webSocketManager = new WebSocketManager_1.WebSocketManager({
  29. port: wsPort,
  30. heartbeatInterval: 30000,
  31. reconnectInterval: 5000,
  32. maxReconnectAttempts: 5,
  33. connectionTimeout: 10000
  34. });
  35. await hedgingManager.initialize();
  36. await webSocketManager.initialize();
  37. });
  38. (0, globals_1.afterAll)(async () => {
  39. if (hedgingManager) {
  40. await hedgingManager.shutdown();
  41. }
  42. if (webSocketManager) {
  43. await webSocketManager.shutdown();
  44. }
  45. if (wsServer) {
  46. wsServer.close();
  47. }
  48. });
  49. (0, globals_1.beforeEach)(async () => {
  50. // Create a test session for each test
  51. const sessionRequest = {
  52. name: 'WebSocket Test Session',
  53. accountIds: ['account-1', 'account-2'],
  54. volumeTarget: 10000,
  55. strategy: {
  56. symbol: 'ETH/USD',
  57. volumeDistribution: 'equal',
  58. priceRange: { min: 0.001, max: 0.01 },
  59. timing: { minInterval: 30, maxInterval: 120, orderSize: { min: 100, max: 500 } },
  60. riskLimits: { maxPositionSize: 0.1, stopLossThreshold: 0.05, maxSlippage: 0.02 },
  61. orderTypes: { primary: 'limit', fallback: 'market' }
  62. }
  63. };
  64. try {
  65. const session = await hedgingManager.createSession(sessionRequest);
  66. testSessionId = session.id;
  67. }
  68. catch (error) {
  69. testSessionId = 'mock-session-id';
  70. }
  71. });
  72. (0, globals_1.describe)('WebSocket Connection', () => {
  73. (0, globals_1.it)('should establish WebSocket connection', (done) => {
  74. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`);
  75. ws.on('open', () => {
  76. (0, globals_1.expect)(ws.readyState).toBe(ws_1.default.OPEN);
  77. ws.close();
  78. done();
  79. });
  80. ws.on('error', (error) => {
  81. // This test should fail initially since WebSocket server doesn't exist yet
  82. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  83. done();
  84. });
  85. });
  86. (0, globals_1.it)('should authenticate WebSocket connection', (done) => {
  87. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  88. headers: {
  89. 'Authorization': 'Bearer test-api-key'
  90. }
  91. });
  92. ws.on('open', () => {
  93. // Send authentication message
  94. const authMessage = {
  95. type: 'authenticate',
  96. token: 'test-api-key'
  97. };
  98. ws.send(JSON.stringify(authMessage));
  99. });
  100. ws.on('message', (data) => {
  101. const message = JSON.parse(data.toString());
  102. if (message.type === 'authenticated') {
  103. (0, globals_1.expect)(message.success).toBe(true);
  104. ws.close();
  105. done();
  106. }
  107. });
  108. ws.on('error', (error) => {
  109. // This test should fail initially since WebSocket server doesn't exist yet
  110. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  111. done();
  112. });
  113. });
  114. (0, globals_1.it)('should reject unauthenticated connections', (done) => {
  115. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`);
  116. ws.on('open', () => {
  117. // Don't send authentication
  118. setTimeout(() => {
  119. (0, globals_1.expect)(ws.readyState).toBe(ws_1.default.CLOSED);
  120. done();
  121. }, 1000);
  122. });
  123. ws.on('error', (error) => {
  124. // This test should fail initially since WebSocket server doesn't exist yet
  125. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  126. done();
  127. });
  128. });
  129. });
  130. (0, globals_1.describe)('Session Subscription', () => {
  131. (0, globals_1.it)('should subscribe to session updates', (done) => {
  132. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  133. headers: {
  134. 'Authorization': 'Bearer test-api-key'
  135. }
  136. });
  137. ws.on('open', () => {
  138. // Authenticate first
  139. ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  140. });
  141. ws.on('message', (data) => {
  142. const message = JSON.parse(data.toString());
  143. if (message.type === 'authenticated') {
  144. // Subscribe to session updates
  145. const subscription = {
  146. type: 'subscribe',
  147. sessionId: testSessionId,
  148. channels: ['status', 'orders', 'risk', 'metrics']
  149. };
  150. ws.send(JSON.stringify(subscription));
  151. }
  152. else if (message.type === 'subscribed') {
  153. (0, globals_1.expect)(message.success).toBe(true);
  154. (0, globals_1.expect)(message.channels).toEqual(['status', 'orders', 'risk', 'metrics']);
  155. ws.close();
  156. done();
  157. }
  158. });
  159. ws.on('error', (error) => {
  160. // This test should fail initially since WebSocket server doesn't exist yet
  161. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  162. done();
  163. });
  164. });
  165. (0, globals_1.it)('should validate subscription channels', (done) => {
  166. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  167. headers: {
  168. 'Authorization': 'Bearer test-api-key'
  169. }
  170. });
  171. ws.on('open', () => {
  172. ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  173. });
  174. ws.on('message', (data) => {
  175. const message = JSON.parse(data.toString());
  176. if (message.type === 'authenticated') {
  177. // Subscribe with invalid channel
  178. const invalidSubscription = {
  179. type: 'subscribe',
  180. sessionId: testSessionId,
  181. channels: ['invalid_channel']
  182. };
  183. ws.send(JSON.stringify(invalidSubscription));
  184. }
  185. else if (message.type === 'error') {
  186. (0, globals_1.expect)(message.error.code).toBe('INVALID_CHANNEL');
  187. ws.close();
  188. done();
  189. }
  190. });
  191. ws.on('error', (error) => {
  192. // This test should fail initially since WebSocket server doesn't exist yet
  193. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  194. done();
  195. });
  196. });
  197. });
  198. (0, globals_1.describe)('Real-time Updates', () => {
  199. (0, globals_1.it)('should receive status updates', (done) => {
  200. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  201. headers: {
  202. 'Authorization': 'Bearer test-api-key'
  203. }
  204. });
  205. let updateReceived = false;
  206. ws.on('open', () => {
  207. ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  208. });
  209. ws.on('message', (data) => {
  210. const message = JSON.parse(data.toString());
  211. if (message.type === 'authenticated') {
  212. // Subscribe to status updates
  213. ws.send(JSON.stringify({
  214. type: 'subscribe',
  215. sessionId: testSessionId,
  216. channels: ['status']
  217. }));
  218. }
  219. else if (message.type === 'session_update' && message.channel === 'status') {
  220. const updateMessage = message;
  221. (0, globals_1.expect)(updateMessage.sessionId).toBe(testSessionId);
  222. (0, globals_1.expect)(updateMessage.channel).toBe('status');
  223. (0, globals_1.expect)(updateMessage.data.status).toBeDefined();
  224. (0, globals_1.expect)(updateMessage.timestamp).toBeDefined();
  225. updateReceived = true;
  226. ws.close();
  227. done();
  228. }
  229. });
  230. ws.on('error', (error) => {
  231. // This test should fail initially since WebSocket server doesn't exist yet
  232. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  233. done();
  234. });
  235. // Timeout if no update received
  236. setTimeout(() => {
  237. if (!updateReceived) {
  238. ws.close();
  239. done();
  240. }
  241. }, 5000);
  242. });
  243. (0, globals_1.it)('should receive order updates', (done) => {
  244. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  245. headers: {
  246. 'Authorization': 'Bearer test-api-key'
  247. }
  248. });
  249. let updateReceived = false;
  250. ws.on('open', () => {
  251. ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  252. });
  253. ws.on('message', (data) => {
  254. const message = JSON.parse(data.toString());
  255. if (message.type === 'authenticated') {
  256. // Subscribe to order updates
  257. ws.send(JSON.stringify({
  258. type: 'subscribe',
  259. sessionId: testSessionId,
  260. channels: ['orders']
  261. }));
  262. }
  263. else if (message.type === 'session_update' && message.channel === 'orders') {
  264. const updateMessage = message;
  265. (0, globals_1.expect)(updateMessage.sessionId).toBe(testSessionId);
  266. (0, globals_1.expect)(updateMessage.channel).toBe('orders');
  267. (0, globals_1.expect)(updateMessage.data.orders).toBeDefined();
  268. (0, globals_1.expect)(Array.isArray(updateMessage.data.orders)).toBe(true);
  269. updateReceived = true;
  270. ws.close();
  271. done();
  272. }
  273. });
  274. ws.on('error', (error) => {
  275. // This test should fail initially since WebSocket server doesn't exist yet
  276. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  277. done();
  278. });
  279. // Timeout if no update received
  280. setTimeout(() => {
  281. if (!updateReceived) {
  282. ws.close();
  283. done();
  284. }
  285. }, 5000);
  286. });
  287. (0, globals_1.it)('should receive risk updates', (done) => {
  288. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  289. headers: {
  290. 'Authorization': 'Bearer test-api-key'
  291. }
  292. });
  293. let updateReceived = false;
  294. ws.on('open', () => {
  295. ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  296. });
  297. ws.on('message', (data) => {
  298. const message = JSON.parse(data.toString());
  299. if (message.type === 'authenticated') {
  300. // Subscribe to risk updates
  301. ws.send(JSON.stringify({
  302. type: 'subscribe',
  303. sessionId: testSessionId,
  304. channels: ['risk']
  305. }));
  306. }
  307. else if (message.type === 'session_update' && message.channel === 'risk') {
  308. const updateMessage = message;
  309. (0, globals_1.expect)(updateMessage.sessionId).toBe(testSessionId);
  310. (0, globals_1.expect)(updateMessage.channel).toBe('risk');
  311. (0, globals_1.expect)(updateMessage.data.riskBreaches).toBeDefined();
  312. (0, globals_1.expect)(Array.isArray(updateMessage.data.riskBreaches)).toBe(true);
  313. updateReceived = true;
  314. ws.close();
  315. done();
  316. }
  317. });
  318. ws.on('error', (error) => {
  319. // This test should fail initially since WebSocket server doesn't exist yet
  320. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  321. done();
  322. });
  323. // Timeout if no update received
  324. setTimeout(() => {
  325. if (!updateReceived) {
  326. ws.close();
  327. done();
  328. }
  329. }, 5000);
  330. });
  331. (0, globals_1.it)('should receive metrics updates', (done) => {
  332. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  333. headers: {
  334. 'Authorization': 'Bearer test-api-key'
  335. }
  336. });
  337. let updateReceived = false;
  338. ws.on('open', () => {
  339. ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  340. });
  341. ws.on('message', (data) => {
  342. const message = JSON.parse(data.toString());
  343. if (message.type === 'authenticated') {
  344. // Subscribe to metrics updates
  345. ws.send(JSON.stringify({
  346. type: 'subscribe',
  347. sessionId: testSessionId,
  348. channels: ['metrics']
  349. }));
  350. }
  351. else if (message.type === 'session_update' && message.channel === 'metrics') {
  352. const updateMessage = message;
  353. (0, globals_1.expect)(updateMessage.sessionId).toBe(testSessionId);
  354. (0, globals_1.expect)(updateMessage.channel).toBe('metrics');
  355. (0, globals_1.expect)(updateMessage.data.metrics).toBeDefined();
  356. (0, globals_1.expect)(updateMessage.data.metrics.volume).toBeDefined();
  357. (0, globals_1.expect)(updateMessage.data.metrics.orders).toBeDefined();
  358. (0, globals_1.expect)(updateMessage.data.metrics.performance).toBeDefined();
  359. (0, globals_1.expect)(updateMessage.data.metrics.risk).toBeDefined();
  360. updateReceived = true;
  361. ws.close();
  362. done();
  363. }
  364. });
  365. ws.on('error', (error) => {
  366. // This test should fail initially since WebSocket server doesn't exist yet
  367. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  368. done();
  369. });
  370. // Timeout if no update received
  371. setTimeout(() => {
  372. if (!updateReceived) {
  373. ws.close();
  374. done();
  375. }
  376. }, 5000);
  377. });
  378. });
  379. (0, globals_1.describe)('Connection Management', () => {
  380. (0, globals_1.it)('should handle connection heartbeat', (done) => {
  381. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  382. headers: {
  383. 'Authorization': 'Bearer test-api-key'
  384. }
  385. });
  386. let heartbeatReceived = false;
  387. ws.on('open', () => {
  388. ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  389. });
  390. ws.on('message', (data) => {
  391. const message = JSON.parse(data.toString());
  392. if (message.type === 'authenticated') {
  393. // Subscribe to updates
  394. ws.send(JSON.stringify({
  395. type: 'subscribe',
  396. sessionId: testSessionId,
  397. channels: ['status']
  398. }));
  399. }
  400. else if (message.type === 'heartbeat') {
  401. (0, globals_1.expect)(message.timestamp).toBeDefined();
  402. heartbeatReceived = true;
  403. ws.close();
  404. done();
  405. }
  406. });
  407. ws.on('error', (error) => {
  408. // This test should fail initially since WebSocket server doesn't exist yet
  409. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  410. done();
  411. });
  412. // Timeout if no heartbeat received
  413. setTimeout(() => {
  414. if (!heartbeatReceived) {
  415. ws.close();
  416. done();
  417. }
  418. }, 10000);
  419. });
  420. (0, globals_1.it)('should handle connection reconnection', (done) => {
  421. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  422. headers: {
  423. 'Authorization': 'Bearer test-api-key'
  424. }
  425. });
  426. ws.on('open', () => {
  427. ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  428. });
  429. ws.on('message', (data) => {
  430. const message = JSON.parse(data.toString());
  431. if (message.type === 'authenticated') {
  432. // Subscribe to updates
  433. ws.send(JSON.stringify({
  434. type: 'subscribe',
  435. sessionId: testSessionId,
  436. channels: ['status']
  437. }));
  438. // Simulate connection drop and reconnect
  439. setTimeout(() => {
  440. ws.close();
  441. // Reconnect
  442. const ws2 = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  443. headers: {
  444. 'Authorization': 'Bearer test-api-key'
  445. }
  446. });
  447. ws2.on('open', () => {
  448. ws2.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  449. });
  450. ws2.on('message', (data) => {
  451. const message = JSON.parse(data.toString());
  452. if (message.type === 'authenticated') {
  453. ws2.close();
  454. done();
  455. }
  456. });
  457. ws2.on('error', (error) => {
  458. // This test should fail initially since WebSocket server doesn't exist yet
  459. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  460. done();
  461. });
  462. }, 1000);
  463. }
  464. });
  465. ws.on('error', (error) => {
  466. // This test should fail initially since WebSocket server doesn't exist yet
  467. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  468. done();
  469. });
  470. });
  471. (0, globals_1.it)('should handle multiple concurrent connections', (done) => {
  472. const connections = [];
  473. let connectedCount = 0;
  474. // Create multiple connections
  475. for (let i = 0; i < 5; i++) {
  476. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  477. headers: {
  478. 'Authorization': 'Bearer test-api-key'
  479. }
  480. });
  481. ws.on('open', () => {
  482. ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  483. });
  484. ws.on('message', (data) => {
  485. const message = JSON.parse(data.toString());
  486. if (message.type === 'authenticated') {
  487. connectedCount++;
  488. if (connectedCount === 5) {
  489. // Close all connections
  490. connections.forEach(conn => conn.close());
  491. done();
  492. }
  493. }
  494. });
  495. ws.on('error', (error) => {
  496. // This test should fail initially since WebSocket server doesn't exist yet
  497. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  498. done();
  499. });
  500. connections.push(ws);
  501. }
  502. });
  503. });
  504. (0, globals_1.describe)('Error Handling', () => {
  505. (0, globals_1.it)('should handle invalid session ID', (done) => {
  506. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/invalid-session-id`, {
  507. headers: {
  508. 'Authorization': 'Bearer test-api-key'
  509. }
  510. });
  511. ws.on('open', () => {
  512. ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  513. });
  514. ws.on('message', (data) => {
  515. const message = JSON.parse(data.toString());
  516. if (message.type === 'error') {
  517. (0, globals_1.expect)(message.error.code).toBe('SESSION_NOT_FOUND');
  518. ws.close();
  519. done();
  520. }
  521. });
  522. ws.on('error', (error) => {
  523. // This test should fail initially since WebSocket server doesn't exist yet
  524. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  525. done();
  526. });
  527. });
  528. (0, globals_1.it)('should handle malformed messages', (done) => {
  529. const ws = new ws_1.default(`ws://localhost:${wsPort}/ws/sessions/${testSessionId}`, {
  530. headers: {
  531. 'Authorization': 'Bearer test-api-key'
  532. }
  533. });
  534. ws.on('open', () => {
  535. ws.send(JSON.stringify({ type: 'authenticate', token: 'test-api-key' }));
  536. });
  537. ws.on('message', (data) => {
  538. const message = JSON.parse(data.toString());
  539. if (message.type === 'authenticated') {
  540. // Send malformed message
  541. ws.send('invalid json');
  542. }
  543. else if (message.type === 'error') {
  544. (0, globals_1.expect)(message.error.code).toBe('INVALID_MESSAGE_FORMAT');
  545. ws.close();
  546. done();
  547. }
  548. });
  549. ws.on('error', (error) => {
  550. // This test should fail initially since WebSocket server doesn't exist yet
  551. (0, globals_1.expect)(error.message).toContain('ECONNREFUSED');
  552. done();
  553. });
  554. });
  555. });
  556. });
  557. //# sourceMappingURL=test_websocket_updates.js.map