@webex/internal-plugin-mercury 3.12.0-next.9 → 3.12.0-task-refactor.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -100,32 +100,9 @@ describe('plugin-mercury', () => {
100
100
  });
101
101
 
102
102
  mercury = webex.internal.mercury;
103
- mercury.defaultSessionId = 'mercury-default-session';
104
103
  });
105
104
 
106
- afterEach(async () => {
107
- // Clean up Mercury connections and internal state
108
- if (mercury) {
109
- try {
110
- await mercury.disconnectAll();
111
- } catch (e) {
112
- // Ignore cleanup errors
113
- }
114
- // Clear any remaining connection promises
115
- if (mercury._connectPromises) {
116
- mercury._connectPromises.clear();
117
- }
118
- }
119
-
120
- // Ensure mock socket is properly closed
121
- if (mockWebSocket && typeof mockWebSocket.close === 'function') {
122
- try {
123
- mockWebSocket.close();
124
- } catch (e) {
125
- // Ignore cleanup errors
126
- }
127
- }
128
-
105
+ afterEach(() => {
129
106
  if (socketOpenStub) {
130
107
  socketOpenStub.restore();
131
108
  }
@@ -133,9 +110,6 @@ describe('plugin-mercury', () => {
133
110
  if (Socket.getWebSocketConstructor.restore) {
134
111
  Socket.getWebSocketConstructor.restore();
135
112
  }
136
-
137
- // Small delay to ensure all async operations complete
138
- await new Promise(resolve => setTimeout(resolve, 10));
139
113
  });
140
114
 
141
115
  describe('#listen()', () => {
@@ -525,13 +499,9 @@ describe('plugin-mercury', () => {
525
499
 
526
500
  // skipping due to apparent bug with lolex in all browsers but Chrome.
527
501
  skipInBrowser(it)('does not continue attempting to connect', () => {
528
- const promise = mercury.connect();
502
+ mercury.connect();
529
503
 
530
- // Wait for the connection to be established before proceeding
531
- mockWebSocket.open();
532
-
533
- return promise.then(() =>
534
- promiseTick(2)
504
+ return promiseTick(2)
535
505
  .then(() => {
536
506
  clock.tick(6 * webex.internal.mercury.config.backoffTimeReset);
537
507
 
@@ -539,8 +509,7 @@ describe('plugin-mercury', () => {
539
509
  })
540
510
  .then(() => {
541
511
  assert.calledOnce(Socket.prototype.open);
542
- })
543
- );
512
+ });
544
513
  });
545
514
  });
546
515
 
@@ -615,11 +584,11 @@ describe('plugin-mercury', () => {
615
584
  });
616
585
 
617
586
  describe('#logout()', () => {
618
- it('calls disconnectAll and logs', () => {
587
+ it('calls disconnect and logs', () => {
619
588
  sinon.stub(mercury.logger, 'info');
620
- sinon.stub(mercury, 'disconnectAll');
589
+ sinon.stub(mercury, 'disconnect');
621
590
  mercury.logout();
622
- assert.called(mercury.disconnectAll);
591
+ assert.called(mercury.disconnect);
623
592
  assert.calledTwice(mercury.logger.info);
624
593
 
625
594
  assert.calledWith(mercury.logger.info.getCall(0), 'Mercury: logout() called');
@@ -631,24 +600,24 @@ describe('plugin-mercury', () => {
631
600
  });
632
601
 
633
602
  it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send code 3050 for logout', () => {
634
- sinon.stub(mercury, 'disconnectAll');
603
+ sinon.stub(mercury, 'disconnect');
635
604
  mercury.config.beforeLogoutOptionsCloseReason = 'done (permanent)';
636
605
  mercury.logout();
637
- assert.calledWith(mercury.disconnectAll, {code: 3050, reason: 'done (permanent)'});
606
+ assert.calledWith(mercury.disconnect, {code: 3050, reason: 'done (permanent)'});
638
607
  });
639
608
 
640
609
  it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send code 3050 for logout if the reason is different than standard', () => {
641
- sinon.stub(mercury, 'disconnectAll');
610
+ sinon.stub(mercury, 'disconnect');
642
611
  mercury.config.beforeLogoutOptionsCloseReason = 'test';
643
612
  mercury.logout();
644
- assert.calledWith(mercury.disconnectAll, {code: 3050, reason: 'test'});
613
+ assert.calledWith(mercury.disconnect, {code: 3050, reason: 'test'});
645
614
  });
646
615
 
647
616
  it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send undefined for logout if the reason is same as standard', () => {
648
- sinon.stub(mercury, 'disconnectAll');
617
+ sinon.stub(mercury, 'disconnect');
649
618
  mercury.config.beforeLogoutOptionsCloseReason = 'done (forced)';
650
619
  mercury.logout();
651
- assert.calledWith(mercury.disconnectAll, undefined);
620
+ assert.calledWith(mercury.disconnect, undefined);
652
621
  });
653
622
  });
654
623
 
@@ -754,12 +723,12 @@ describe('plugin-mercury', () => {
754
723
  return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
755
724
  // By this time backoffCall and mercury socket should be defined by the
756
725
  // 'connect' call
757
- assert.isDefined(mercury.backoffCalls.get('mercury-default-session'), 'Mercury backoffCall is not defined');
726
+ assert.isDefined(mercury.backoffCall, 'Mercury backoffCall is not defined');
758
727
  assert.isDefined(mercury.socket, 'Mercury socket is not defined');
759
728
  // Calling disconnect will abort the backoffCall, close the socket, and
760
729
  // reject the connect
761
730
  mercury.disconnect();
762
- assert.isUndefined(mercury.backoffCalls.get('mercury-default-session'), 'Mercury backoffCall is still defined');
731
+ assert.isUndefined(mercury.backoffCall, 'Mercury backoffCall is still defined');
763
732
  // The socket will never be unset (which seems bad)
764
733
  assert.isDefined(mercury.socket, 'Mercury socket is not defined');
765
734
 
@@ -777,24 +746,16 @@ describe('plugin-mercury', () => {
777
746
 
778
747
  let reason;
779
748
 
780
- mercury.backoffCalls.clear();
781
-
782
- const promise = mercury._attemptConnection(
783
- 'ws://example.com',
784
- 'mercury-default-session',
785
- (_reason) => {
786
- reason = _reason;
787
- }
788
- );
749
+ mercury.backoffCall = undefined;
750
+ mercury._attemptConnection('ws://example.com', (_reason) => {
751
+ reason = _reason;
752
+ });
789
753
 
790
754
  return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
791
755
  assert.equal(
792
756
  reason.message,
793
- `Mercury: prevent socket open when backoffCall no longer defined for ${mercury.defaultSessionId}`
757
+ 'Mercury: prevent socket open when backoffCall no longer defined'
794
758
  );
795
-
796
- // Ensure the promise was actually rejected (short-circuited)
797
- return assert.isRejected(promise);
798
759
  });
799
760
  });
800
761
 
@@ -815,7 +776,7 @@ describe('plugin-mercury', () => {
815
776
  return assert.isRejected(promise).then((error) => {
816
777
  const lastError = mercury.getLastError();
817
778
 
818
- assert.equal(error.message, `Mercury Connection Aborted for ${mercury.defaultSessionId}`);
779
+ assert.equal(error.message, 'Mercury Connection Aborted');
819
780
  assert.isDefined(lastError);
820
781
  assert.equal(lastError, realError);
821
782
  });
@@ -909,7 +870,7 @@ describe('plugin-mercury', () => {
909
870
  },
910
871
  };
911
872
  assert.isUndefined(mercury.mercuryTimeOffset);
912
- mercury._setTimeOffset('mercury-default-session', event);
873
+ mercury._setTimeOffset(event);
913
874
  assert.isDefined(mercury.mercuryTimeOffset);
914
875
  assert.isTrue(mercury.mercuryTimeOffset > 0);
915
876
  });
@@ -919,7 +880,7 @@ describe('plugin-mercury', () => {
919
880
  wsWriteTimestamp: Date.now() + 60000,
920
881
  },
921
882
  };
922
- mercury._setTimeOffset('mercury-default-session', event);
883
+ mercury._setTimeOffset(event);
923
884
  assert.isTrue(mercury.mercuryTimeOffset < 0);
924
885
  });
925
886
  it('handles invalid wsWriteTimestamp', () => {
@@ -930,7 +891,7 @@ describe('plugin-mercury', () => {
930
891
  wsWriteTimestamp: invalidTimestamp,
931
892
  },
932
893
  };
933
- mercury._setTimeOffset('mercury-default-session', event);
894
+ mercury._setTimeOffset(event);
934
895
  assert.isUndefined(mercury.mercuryTimeOffset);
935
896
  });
936
897
  });
@@ -1037,15 +998,13 @@ describe('plugin-mercury', () => {
1037
998
  describe('shutdown protocol', () => {
1038
999
  describe('#_handleImminentShutdown()', () => {
1039
1000
  let connectWithBackoffStub;
1040
- const sessionId = 'mercury-default-session';
1041
1001
 
1042
1002
  beforeEach(() => {
1043
1003
  mercury.connected = true;
1044
- mercury.sockets.set(sessionId, {
1004
+ mercury.socket = {
1045
1005
  url: 'ws://old-socket.com',
1046
1006
  removeAllListeners: sinon.stub(),
1047
- });
1048
- mercury.socket = mercury.sockets.get(sessionId);
1007
+ };
1049
1008
  connectWithBackoffStub = sinon.stub(mercury, '_connectWithBackoff');
1050
1009
  connectWithBackoffStub.returns(Promise.resolve());
1051
1010
  sinon.stub(mercury, '_emit');
@@ -1054,47 +1013,32 @@ describe('plugin-mercury', () => {
1054
1013
  afterEach(() => {
1055
1014
  connectWithBackoffStub.restore();
1056
1015
  mercury._emit.restore();
1057
- mercury.sockets.clear();
1058
1016
  });
1059
1017
 
1060
1018
  it('should be idempotent - no-op if already in progress', () => {
1061
- // Simulate an existing switchover in progress by seeding the backoff map
1062
- mercury._shutdownSwitchoverBackoffCalls.set(sessionId, {placeholder: true});
1019
+ mercury._shutdownSwitchoverInProgress = true;
1063
1020
 
1064
- mercury._handleImminentShutdown(sessionId);
1021
+ mercury._handleImminentShutdown();
1065
1022
 
1066
1023
  assert.notCalled(connectWithBackoffStub);
1067
1024
  });
1068
1025
 
1069
1026
  it('should set switchover flags when called', () => {
1070
- mercury._handleImminentShutdown(sessionId);
1027
+ mercury._handleImminentShutdown();
1071
1028
 
1072
- // With _connectWithBackoff stubbed, the backoff map entry may not be created here.
1073
- // Assert that switchover initiation state was set and a shutdown switchover connect was requested.
1029
+ assert.isTrue(mercury._shutdownSwitchoverInProgress);
1074
1030
  assert.isDefined(mercury._shutdownSwitchoverId);
1075
-
1076
- assert.calledOnce(connectWithBackoffStub);
1077
- const callArgs = connectWithBackoffStub.firstCall.args;
1078
- assert.isUndefined(callArgs[0]); // webSocketUrl
1079
- assert.equal(callArgs[1], sessionId); // sessionId
1080
- assert.isObject(callArgs[2]); // context
1081
- assert.isTrue(callArgs[2].isShutdownSwitchover);
1082
- assert.isObject(callArgs[2].attemptOptions);
1083
- assert.isTrue(callArgs[2].attemptOptions.isShutdownSwitchover);
1084
1031
  });
1085
1032
 
1086
1033
  it('should call _connectWithBackoff with correct parameters', (done) => {
1087
- mercury._handleImminentShutdown(sessionId);
1034
+ mercury._handleImminentShutdown();
1088
1035
 
1089
1036
  process.nextTick(() => {
1090
1037
  assert.calledOnce(connectWithBackoffStub);
1091
1038
  const callArgs = connectWithBackoffStub.firstCall.args;
1092
1039
  assert.isUndefined(callArgs[0]); // webSocketUrl
1093
- assert.equal(callArgs[1], sessionId); // sessionId
1094
- assert.isObject(callArgs[2]); // context
1095
- assert.isTrue(callArgs[2].isShutdownSwitchover);
1096
- assert.isObject(callArgs[2].attemptOptions);
1097
- assert.isTrue(callArgs[2].attemptOptions.isShutdownSwitchover);
1040
+ assert.isObject(callArgs[1]); // context
1041
+ assert.isTrue(callArgs[1].isShutdownSwitchover);
1098
1042
  done();
1099
1043
  });
1100
1044
  });
@@ -1103,17 +1047,12 @@ describe('plugin-mercury', () => {
1103
1047
  connectWithBackoffStub.restore();
1104
1048
  sinon.stub(mercury, '_connectWithBackoff').throws(new Error('Connection failed'));
1105
1049
 
1106
- mercury._handleImminentShutdown(sessionId);
1050
+ mercury._handleImminentShutdown();
1107
1051
 
1108
- // When an exception happens synchronously, the placeholder entry
1109
- // should be removed from the map.
1110
- const switchoverCall = mercury._shutdownSwitchoverBackoffCalls.get(sessionId);
1111
- assert.isUndefined(switchoverCall);
1112
- mercury._connectWithBackoff.restore();
1052
+ assert.isFalse(mercury._shutdownSwitchoverInProgress);
1113
1053
  });
1114
1054
  });
1115
1055
 
1116
-
1117
1056
  describe('#_onmessage() with shutdown message', () => {
1118
1057
  beforeEach(() => {
1119
1058
  sinon.stub(mercury, '_handleImminentShutdown');
@@ -1134,15 +1073,10 @@ describe('plugin-mercury', () => {
1134
1073
  },
1135
1074
  };
1136
1075
 
1137
- const result = mercury._onmessage(mercury.defaultSessionId, shutdownEvent);
1076
+ const result = mercury._onmessage(shutdownEvent);
1138
1077
 
1139
1078
  assert.calledOnce(mercury._handleImminentShutdown);
1140
- assert.calledWith(
1141
- mercury._emit,
1142
- mercury.defaultSessionId,
1143
- 'event:mercury_shutdown_imminent',
1144
- shutdownEvent.data
1145
- );
1079
+ assert.calledWith(mercury._emit, 'event:mercury_shutdown_imminent', shutdownEvent.data);
1146
1080
  assert.instanceOf(result, Promise);
1147
1081
  });
1148
1082
 
@@ -1153,7 +1087,7 @@ describe('plugin-mercury', () => {
1153
1087
  },
1154
1088
  };
1155
1089
 
1156
- mercury._onmessage(mercury.defaultSessionId, shutdownEvent);
1090
+ mercury._onmessage(shutdownEvent);
1157
1091
 
1158
1092
  assert.calledOnce(mercury._handleImminentShutdown);
1159
1093
  });
@@ -1168,118 +1102,12 @@ describe('plugin-mercury', () => {
1168
1102
  },
1169
1103
  };
1170
1104
 
1171
- mercury._onmessage(mercury.defaultSessionId, regularEvent);
1105
+ mercury._onmessage(regularEvent);
1172
1106
 
1173
1107
  assert.notCalled(mercury._handleImminentShutdown);
1174
1108
  });
1175
1109
  });
1176
1110
 
1177
- describe('#_onmessage() with missing data or eventType', () => {
1178
- beforeEach(() => {
1179
- sinon.stub(mercury, '_emit');
1180
- sinon.stub(mercury, '_setTimeOffset');
1181
- sinon.stub(mercury, '_applyOverrides');
1182
- });
1183
-
1184
- afterEach(() => {
1185
- mercury._emit.restore();
1186
- mercury._setTimeOffset.restore();
1187
- mercury._applyOverrides.restore();
1188
- });
1189
-
1190
- it('should not throw when envelope.data is undefined', () => {
1191
- const event = {
1192
- data: {
1193
- type: 'someType',
1194
- // no nested data property
1195
- },
1196
- };
1197
-
1198
- const result = mercury._onmessage(mercury.defaultSessionId, event);
1199
-
1200
- assert.instanceOf(result, Promise);
1201
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'event', event.data);
1202
- });
1203
-
1204
- it('should not throw when data.eventType is undefined', () => {
1205
- const event = {
1206
- data: {
1207
- type: 'someType',
1208
- data: {
1209
- // no eventType property
1210
- someField: 'value',
1211
- },
1212
- },
1213
- };
1214
-
1215
- const result = mercury._onmessage(mercury.defaultSessionId, event);
1216
-
1217
- assert.instanceOf(result, Promise);
1218
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'event', event.data);
1219
- });
1220
-
1221
- it('should emit generic event for messages without eventType (e.g. subscription responses)', () => {
1222
- const event = {
1223
- data: {
1224
- id: 'msg-123',
1225
- sequenceNumber: 5,
1226
- data: {
1227
- statusCode: 200,
1228
- },
1229
- },
1230
- };
1231
-
1232
- const result = mercury._onmessage(mercury.defaultSessionId, event);
1233
-
1234
- assert.instanceOf(result, Promise);
1235
- assert.calledOnce(mercury._emit);
1236
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'event', event.data);
1237
- });
1238
-
1239
- it('should still process messages with a valid eventType', async () => {
1240
- const event = {
1241
- data: {
1242
- data: {
1243
- eventType: 'conversation.activity',
1244
- },
1245
- },
1246
- };
1247
-
1248
- await mercury._onmessage(mercury.defaultSessionId, event);
1249
-
1250
- // Normal flow emits namespace-specific events after processing handlers.
1251
- // The early-return guard only emits 'event', so asserting these proves the normal path was taken.
1252
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'event:conversation', event.data);
1253
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'event:conversation.activity', event.data);
1254
- });
1255
- });
1256
-
1257
- describe('#_getEventHandlers()', () => {
1258
- it('should return an empty array when eventType is undefined', () => {
1259
- const result = mercury._getEventHandlers(undefined);
1260
-
1261
- assert.deepEqual(result, []);
1262
- });
1263
-
1264
- it('should return an empty array when eventType is null', () => {
1265
- const result = mercury._getEventHandlers(null);
1266
-
1267
- assert.deepEqual(result, []);
1268
- });
1269
-
1270
- it('should return an empty array when eventType is an empty string', () => {
1271
- const result = mercury._getEventHandlers('');
1272
-
1273
- assert.deepEqual(result, []);
1274
- });
1275
-
1276
- it('should return an empty array when namespace is not registered', () => {
1277
- const result = mercury._getEventHandlers('unknownNamespace.someEvent');
1278
-
1279
- assert.deepEqual(result, []);
1280
- });
1281
- });
1282
-
1283
1111
  describe('#_onclose() with code 4001 (shutdown replacement)', () => {
1284
1112
  let mockSocket, anotherSocket;
1285
1113
 
@@ -1293,7 +1121,6 @@ describe('plugin-mercury', () => {
1293
1121
  removeAllListeners: sinon.stub(),
1294
1122
  };
1295
1123
  mercury.socket = mockSocket;
1296
- mercury.sockets.set(mercury.defaultSessionId, mockSocket);
1297
1124
  mercury.connected = true;
1298
1125
  sinon.stub(mercury, '_emit');
1299
1126
  sinon.stub(mercury, '_reconnect');
@@ -1312,9 +1139,9 @@ describe('plugin-mercury', () => {
1312
1139
  reason: 'replaced during shutdown',
1313
1140
  };
1314
1141
 
1315
- mercury._onclose(mercury.defaultSessionId, closeEvent, mockSocket);
1142
+ mercury._onclose(closeEvent, mockSocket);
1316
1143
 
1317
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'offline.permanent', closeEvent);
1144
+ assert.calledWith(mercury._emit, 'offline.permanent', closeEvent);
1318
1145
  assert.notCalled(mercury._reconnect); // No reconnect for 4001 on active socket
1319
1146
  assert.isFalse(mercury.connected);
1320
1147
  });
@@ -1325,9 +1152,9 @@ describe('plugin-mercury', () => {
1325
1152
  reason: 'replaced during shutdown',
1326
1153
  };
1327
1154
 
1328
- mercury._onclose(mercury.defaultSessionId, closeEvent, anotherSocket);
1155
+ mercury._onclose(closeEvent, anotherSocket);
1329
1156
 
1330
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'offline.replaced', closeEvent);
1157
+ assert.calledWith(mercury._emit, 'offline.replaced', closeEvent);
1331
1158
  assert.notCalled(mercury._reconnect);
1332
1159
  assert.isTrue(mercury.connected); // Should remain connected
1333
1160
  assert.notCalled(mercury.unset);
@@ -1340,16 +1167,15 @@ describe('plugin-mercury', () => {
1340
1167
  };
1341
1168
 
1342
1169
  // Test non-active socket
1343
- mercury._onclose(mercury.defaultSessionId, closeEvent, anotherSocket);
1344
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'offline.replaced', closeEvent);
1170
+ mercury._onclose(closeEvent, anotherSocket);
1171
+ assert.calledWith(mercury._emit, 'offline.replaced', closeEvent);
1345
1172
 
1346
1173
  // Reset the spy call history
1347
1174
  mercury._emit.resetHistory();
1348
1175
 
1349
1176
  // Test active socket
1350
- mercury.sockets.set(mercury.defaultSessionId, mockSocket);
1351
- mercury._onclose(mercury.defaultSessionId, closeEvent, mockSocket);
1352
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'offline.permanent', closeEvent);
1177
+ mercury._onclose(closeEvent, mockSocket);
1178
+ assert.calledWith(mercury._emit, 'offline.permanent', closeEvent);
1353
1179
  });
1354
1180
 
1355
1181
  it('should handle missing sourceSocket parameter (treats as non-active)', () => {
@@ -1358,10 +1184,10 @@ describe('plugin-mercury', () => {
1358
1184
  reason: 'replaced during shutdown',
1359
1185
  };
1360
1186
 
1361
- mercury._onclose(mercury.defaultSessionId, closeEvent); // No sourceSocket parameter
1187
+ mercury._onclose(closeEvent); // No sourceSocket parameter
1362
1188
 
1363
1189
  // With simplified logic, undefined !== this.socket, so isActiveSocket = false
1364
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'offline.replaced', closeEvent);
1190
+ assert.calledWith(mercury._emit, 'offline.replaced', closeEvent);
1365
1191
  assert.notCalled(mercury._reconnect);
1366
1192
  });
1367
1193
 
@@ -1372,7 +1198,7 @@ describe('plugin-mercury', () => {
1372
1198
  };
1373
1199
 
1374
1200
  // Close non-active socket (not the active one)
1375
- mercury._onclose(mercury.defaultSessionId, closeEvent, anotherSocket);
1201
+ mercury._onclose(closeEvent, anotherSocket);
1376
1202
 
1377
1203
  // Verify listeners were removed from the old socket
1378
1204
  // The _onclose method checks if sourceSocket !== this.socket (non-active)
@@ -1387,7 +1213,7 @@ describe('plugin-mercury', () => {
1387
1213
  };
1388
1214
 
1389
1215
  // Close active socket
1390
- mercury._onclose(mercury.defaultSessionId, closeEvent, mockSocket);
1216
+ mercury._onclose(closeEvent, mockSocket);
1391
1217
 
1392
1218
  // Verify listeners were removed from active socket
1393
1219
  assert.calledOnce(mockSocket.removeAllListeners);
@@ -1396,15 +1222,13 @@ describe('plugin-mercury', () => {
1396
1222
 
1397
1223
  describe('shutdown switchover with retry logic', () => {
1398
1224
  let connectWithBackoffStub;
1399
- const sessionId = 'mercury-default-session';
1400
1225
 
1401
1226
  beforeEach(() => {
1402
1227
  mercury.connected = true;
1403
- mercury.sockets.set(sessionId, {
1228
+ mercury.socket = {
1404
1229
  url: 'ws://old-socket.com',
1405
1230
  removeAllListeners: sinon.stub(),
1406
- });
1407
- mercury.socket = mercury.sockets.get(sessionId);
1231
+ };
1408
1232
  connectWithBackoffStub = sinon.stub(mercury, '_connectWithBackoff');
1409
1233
  sinon.stub(mercury, '_emit');
1410
1234
  });
@@ -1412,46 +1236,39 @@ describe('plugin-mercury', () => {
1412
1236
  afterEach(() => {
1413
1237
  connectWithBackoffStub.restore();
1414
1238
  mercury._emit.restore();
1415
- mercury.sockets.clear();
1416
1239
  });
1417
1240
 
1418
1241
  it('should call _connectWithBackoff with shutdown switchover context', (done) => {
1419
1242
  connectWithBackoffStub.returns(Promise.resolve());
1420
1243
 
1421
- mercury._handleImminentShutdown(sessionId);
1244
+ mercury._handleImminentShutdown();
1422
1245
 
1246
+ // Give it a tick for the async call to happen
1423
1247
  process.nextTick(() => {
1424
1248
  assert.calledOnce(connectWithBackoffStub);
1425
1249
  const callArgs = connectWithBackoffStub.firstCall.args;
1426
1250
 
1427
- assert.isUndefined(callArgs[0]); // webSocketUrl
1428
- assert.equal(callArgs[1], sessionId);
1429
- assert.isObject(callArgs[2]);
1430
- assert.isTrue(callArgs[2].isShutdownSwitchover);
1431
- assert.isObject(callArgs[2].attemptOptions);
1432
- assert.isTrue(callArgs[2].attemptOptions.isShutdownSwitchover);
1251
+ assert.isUndefined(callArgs[0]); // webSocketUrl is undefined
1252
+ assert.isObject(callArgs[1]); // context object
1253
+ assert.isTrue(callArgs[1].isShutdownSwitchover);
1254
+ assert.isObject(callArgs[1].attemptOptions);
1255
+ assert.isTrue(callArgs[1].attemptOptions.isShutdownSwitchover);
1433
1256
  done();
1434
1257
  });
1435
1258
  });
1436
1259
 
1437
1260
  it('should set _shutdownSwitchoverInProgress flag during switchover', () => {
1438
- // With the new behavior, "in progress" is represented by the presence
1439
- // of an entry in _shutdownSwitchoverBackoffCalls.
1440
- // Since _connectWithBackoff is stubbed in this suite, simulate its side-effect
1441
- // of seeding the backoff-call map entry.
1442
- connectWithBackoffStub.callsFake(() => {
1443
- mercury._shutdownSwitchoverBackoffCalls.set(sessionId, {placeholder: true});
1444
- return new Promise(() => {}); // Never resolves
1445
- });
1261
+ connectWithBackoffStub.returns(new Promise(() => {})); // Never resolves
1446
1262
 
1447
- mercury._handleImminentShutdown(sessionId);
1263
+ mercury._handleImminentShutdown();
1448
1264
 
1449
- const switchoverBackoffCall = mercury._shutdownSwitchoverBackoffCalls.get(sessionId);
1450
- assert.isOk(switchoverBackoffCall);
1265
+ assert.isTrue(mercury._shutdownSwitchoverInProgress);
1451
1266
  });
1452
1267
 
1453
1268
  it('should emit success event when switchover completes', async () => {
1454
- connectWithBackoffStub.callsFake((url, sid, context) => {
1269
+ // We need to actually call the onSuccess callback to trigger the event
1270
+ connectWithBackoffStub.callsFake((url, context) => {
1271
+ // Simulate successful connection by calling onSuccess
1455
1272
  if (context && context.attemptOptions && context.attemptOptions.onSuccess) {
1456
1273
  const mockSocket = {url: 'ws://new-socket.com'};
1457
1274
  context.attemptOptions.onSuccess(mockSocket, 'ws://new-socket.com');
@@ -1459,15 +1276,14 @@ describe('plugin-mercury', () => {
1459
1276
  return Promise.resolve();
1460
1277
  });
1461
1278
 
1462
- mercury._handleImminentShutdown(sessionId);
1279
+ mercury._handleImminentShutdown();
1463
1280
 
1281
+ // Wait for async operations
1464
1282
  await promiseTick(50);
1465
1283
 
1466
1284
  const emitCalls = mercury._emit.getCalls();
1467
1285
  const hasCompleteEvent = emitCalls.some(
1468
- (call) =>
1469
- call.args[0] === sessionId &&
1470
- call.args[1] === 'event:mercury_shutdown_switchover_complete'
1286
+ (call) => call.args[0] === 'event:mercury_shutdown_switchover_complete'
1471
1287
  );
1472
1288
 
1473
1289
  assert.isTrue(hasCompleteEvent, 'Should emit switchover complete event');
@@ -1478,16 +1294,16 @@ describe('plugin-mercury', () => {
1478
1294
 
1479
1295
  connectWithBackoffStub.returns(Promise.reject(testError));
1480
1296
 
1481
- mercury._handleImminentShutdown(sessionId);
1297
+ mercury._handleImminentShutdown();
1482
1298
  await promiseTick(50);
1483
1299
 
1300
+ // Check if failure event was emitted
1484
1301
  const emitCalls = mercury._emit.getCalls();
1485
1302
  const hasFailureEvent = emitCalls.some(
1486
1303
  (call) =>
1487
- call.args[0] === sessionId &&
1488
- call.args[1] === 'event:mercury_shutdown_switchover_failed' &&
1489
- call.args[2] &&
1490
- call.args[2].reason === testError
1304
+ call.args[0] === 'event:mercury_shutdown_switchover_failed' &&
1305
+ call.args[1] &&
1306
+ call.args[1].reason === testError
1491
1307
  );
1492
1308
 
1493
1309
  assert.isTrue(hasFailureEvent, 'Should emit switchover failed event');
@@ -1496,9 +1312,10 @@ describe('plugin-mercury', () => {
1496
1312
  it('should allow old socket to be closed by server after switchover failure', async () => {
1497
1313
  connectWithBackoffStub.returns(Promise.reject(new Error('Failed')));
1498
1314
 
1499
- mercury._handleImminentShutdown(sessionId);
1315
+ mercury._handleImminentShutdown();
1500
1316
  await promiseTick(50);
1501
1317
 
1318
+ // Old socket should not be closed immediately - server will close it
1502
1319
  assert.equal(mercury.socket.removeAllListeners.callCount, 0);
1503
1320
  });
1504
1321
  });
@@ -1595,16 +1412,18 @@ describe('plugin-mercury', () => {
1595
1412
  });
1596
1413
 
1597
1414
  describe('#_attemptConnection() with shutdown switchover', () => {
1598
- let prepareAndOpenSocketStub, callback;
1599
- const sessionId = 'mercury-default-session';
1415
+ let mockSocket, prepareAndOpenSocketStub, callback;
1600
1416
 
1601
1417
  beforeEach(() => {
1418
+ mockSocket = {
1419
+ url: 'ws://test.com',
1420
+ };
1602
1421
  prepareAndOpenSocketStub = sinon
1603
1422
  .stub(mercury, '_prepareAndOpenSocket')
1604
1423
  .returns(Promise.resolve('ws://new-socket.com'));
1605
1424
  callback = sinon.stub();
1606
- mercury._shutdownSwitchoverBackoffCalls.set(sessionId, {abort: sinon.stub()});
1607
- mercury.socket = {url: 'ws://test.com'};
1425
+ mercury._shutdownSwitchoverBackoffCall = {}; // Mock backoff call
1426
+ mercury.socket = mockSocket;
1608
1427
  mercury.connected = true;
1609
1428
  sinon.stub(mercury, '_emit');
1610
1429
  sinon.stub(mercury, '_attachSocketEventListeners');
@@ -1614,26 +1433,28 @@ describe('plugin-mercury', () => {
1614
1433
  prepareAndOpenSocketStub.restore();
1615
1434
  mercury._emit.restore();
1616
1435
  mercury._attachSocketEventListeners.restore();
1617
- mercury._shutdownSwitchoverBackoffCalls.clear();
1618
1436
  });
1619
1437
 
1620
1438
  it('should not set socket reference before opening for shutdown switchover', async () => {
1621
1439
  const originalSocket = mercury.socket;
1622
1440
 
1623
- await mercury._attemptConnection('ws://test.com', sessionId, callback, {
1441
+ await mercury._attemptConnection('ws://test.com', callback, {
1624
1442
  isShutdownSwitchover: true,
1625
1443
  onSuccess: (newSocket, url) => {
1444
+ // During onSuccess, verify original socket is still set
1445
+ // (socket swap happens inside onSuccess callback in _handleImminentShutdown)
1626
1446
  assert.equal(mercury.socket, originalSocket);
1627
1447
  },
1628
1448
  });
1629
1449
 
1450
+ // After onSuccess, socket should still be original since we only swap in _handleImminentShutdown
1630
1451
  assert.equal(mercury.socket, originalSocket);
1631
1452
  });
1632
1453
 
1633
1454
  it('should call onSuccess callback with new socket and URL for shutdown', async () => {
1634
1455
  const onSuccessStub = sinon.stub();
1635
1456
 
1636
- await mercury._attemptConnection('ws://test.com', sessionId, callback, {
1457
+ await mercury._attemptConnection('ws://test.com', callback, {
1637
1458
  isShutdownSwitchover: true,
1638
1459
  onSuccess: onSuccessStub,
1639
1460
  });
@@ -1643,22 +1464,20 @@ describe('plugin-mercury', () => {
1643
1464
  });
1644
1465
 
1645
1466
  it('should emit shutdown switchover complete event', async () => {
1646
- await mercury._attemptConnection('ws://test.com', sessionId, callback, {
1467
+ const oldSocket = mercury.socket;
1468
+
1469
+ await mercury._attemptConnection('ws://test.com', callback, {
1647
1470
  isShutdownSwitchover: true,
1648
1471
  onSuccess: (newSocket, url) => {
1472
+ // Simulate the onSuccess callback behavior
1649
1473
  mercury.socket = newSocket;
1650
1474
  mercury.connected = true;
1651
- mercury._emit(
1652
- sessionId,
1653
- 'event:mercury_shutdown_switchover_complete',
1654
- {url}
1655
- );
1475
+ mercury._emit('event:mercury_shutdown_switchover_complete', {url});
1656
1476
  },
1657
1477
  });
1658
1478
 
1659
1479
  assert.calledWith(
1660
1480
  mercury._emit,
1661
- sessionId,
1662
1481
  'event:mercury_shutdown_switchover_complete',
1663
1482
  sinon.match.has('url', 'ws://new-socket.com')
1664
1483
  );
@@ -1667,25 +1486,25 @@ describe('plugin-mercury', () => {
1667
1486
  it('should use simpler error handling for shutdown switchover failures', async () => {
1668
1487
  prepareAndOpenSocketStub.returns(Promise.reject(new Error('Connection failed')));
1669
1488
 
1670
- await mercury
1671
- ._attemptConnection('ws://test.com', sessionId, callback, {
1489
+ try {
1490
+ await mercury._attemptConnection('ws://test.com', callback, {
1672
1491
  isShutdownSwitchover: true,
1673
- })
1674
- .catch(() => {});
1492
+ });
1493
+ } catch (err) {
1494
+ // Error should be caught and passed to callback
1495
+ }
1675
1496
 
1497
+ // Should call callback with error for retry
1676
1498
  assert.calledOnce(callback);
1677
1499
  assert.instanceOf(callback.firstCall.args[0], Error);
1678
1500
  });
1679
1501
 
1680
1502
  it('should check _shutdownSwitchoverBackoffCall for shutdown connections', () => {
1681
- mercury._shutdownSwitchoverBackoffCalls.clear();
1503
+ mercury._shutdownSwitchoverBackoffCall = undefined;
1682
1504
 
1683
- const result = mercury._attemptConnection(
1684
- 'ws://test.com',
1685
- sessionId,
1686
- callback,
1687
- {isShutdownSwitchover: true}
1688
- );
1505
+ const result = mercury._attemptConnection('ws://test.com', callback, {
1506
+ isShutdownSwitchover: true,
1507
+ });
1689
1508
 
1690
1509
  return result.catch((err) => {
1691
1510
  assert.instanceOf(err, Error);
@@ -1695,28 +1514,35 @@ describe('plugin-mercury', () => {
1695
1514
  });
1696
1515
 
1697
1516
  describe('#_connectWithBackoff() with shutdown switchover', () => {
1698
- const sessionId = 'mercury-default-session';
1517
+ // Note: These tests verify the parameterization logic without running real backoff timers
1518
+ // to avoid test hangs. The backoff mechanism itself is tested in other test suites.
1699
1519
 
1700
1520
  it('should use shutdown-specific parameters when called', () => {
1521
+ // Stub _connectWithBackoff to prevent real execution
1701
1522
  const connectWithBackoffStub = sinon
1702
1523
  .stub(mercury, '_connectWithBackoff')
1703
1524
  .returns(Promise.resolve());
1704
1525
 
1705
- mercury._handleImminentShutdown(sessionId);
1526
+ mercury._handleImminentShutdown();
1706
1527
 
1528
+ // Verify it was called with shutdown context
1707
1529
  assert.calledOnce(connectWithBackoffStub);
1708
1530
  const callArgs = connectWithBackoffStub.firstCall.args;
1709
- assert.equal(callArgs[1], sessionId);
1710
- assert.isObject(callArgs[2]);
1711
- assert.isTrue(callArgs[2].isShutdownSwitchover);
1531
+ assert.isObject(callArgs[1]); // context
1532
+ assert.isTrue(callArgs[1].isShutdownSwitchover);
1712
1533
 
1713
1534
  connectWithBackoffStub.restore();
1714
1535
  });
1715
1536
 
1716
1537
  it('should pass shutdown switchover options to _attemptConnection', () => {
1538
+ // Stub _attemptConnection to verify it receives correct options
1717
1539
  const attemptStub = sinon.stub(mercury, '_attemptConnection');
1718
- attemptStub.callsFake((url, sid, cb) => cb());
1540
+ attemptStub.callsFake((url, callback) => {
1541
+ // Immediately succeed
1542
+ callback();
1543
+ });
1719
1544
 
1545
+ // Call _connectWithBackoff with shutdown context
1720
1546
  const context = {
1721
1547
  isShutdownSwitchover: true,
1722
1548
  attemptOptions: {
@@ -1725,30 +1551,34 @@ describe('plugin-mercury', () => {
1725
1551
  },
1726
1552
  };
1727
1553
 
1728
- const promise = mercury._connectWithBackoff(undefined, sessionId, context);
1554
+ // Start the backoff
1555
+ const promise = mercury._connectWithBackoff(undefined, context);
1729
1556
 
1557
+ // Check that _attemptConnection was called with shutdown options
1730
1558
  return promise.then(() => {
1731
1559
  assert.calledOnce(attemptStub);
1732
1560
  const callArgs = attemptStub.firstCall.args;
1733
- assert.equal(callArgs[1], sessionId);
1734
- assert.isObject(callArgs[3]);
1735
- assert.isTrue(callArgs[3].isShutdownSwitchover);
1561
+ assert.isObject(callArgs[2]); // options parameter
1562
+ assert.isTrue(callArgs[2].isShutdownSwitchover);
1563
+
1736
1564
  attemptStub.restore();
1737
1565
  });
1738
1566
  });
1739
1567
 
1740
1568
  it('should set and clear state flags appropriately', () => {
1741
- sinon.stub(mercury, '_attemptConnection').callsFake((url, sid, cb) => cb());
1569
+ // Stub to prevent actual connection
1570
+ sinon.stub(mercury, '_attemptConnection').callsFake((url, callback) => callback());
1742
1571
 
1743
- mercury._shutdownSwitchoverBackoffCalls.set(sessionId, {placeholder: true});
1572
+ mercury._shutdownSwitchoverInProgress = true;
1744
1573
 
1745
- const promise = mercury._connectWithBackoff(undefined, sessionId, {
1574
+ const promise = mercury._connectWithBackoff(undefined, {
1746
1575
  isShutdownSwitchover: true,
1747
1576
  attemptOptions: {isShutdownSwitchover: true, onSuccess: () => {}},
1748
1577
  });
1749
1578
 
1750
1579
  return promise.then(() => {
1751
- assert.isUndefined(mercury._shutdownSwitchoverBackoffCalls.get(sessionId));
1580
+ // Should be cleared after completion
1581
+ assert.isFalse(mercury._shutdownSwitchoverInProgress);
1752
1582
  mercury._attemptConnection.restore();
1753
1583
  });
1754
1584
  });
@@ -1756,30 +1586,32 @@ describe('plugin-mercury', () => {
1756
1586
 
1757
1587
  describe('#disconnect() with shutdown switchover in progress', () => {
1758
1588
  let abortStub;
1759
- const sessionId = 'mercury-default-session';
1760
1589
 
1761
1590
  beforeEach(() => {
1762
- mercury.sockets.clear();
1763
- mercury.sockets.set(sessionId, {
1591
+ mercury.socket = {
1764
1592
  close: sinon.stub().returns(Promise.resolve()),
1765
1593
  removeAllListeners: sinon.stub(),
1766
- });
1594
+ };
1767
1595
  abortStub = sinon.stub();
1768
- mercury._shutdownSwitchoverBackoffCalls.set(sessionId, {abort: abortStub});
1596
+ mercury._shutdownSwitchoverBackoffCall = {
1597
+ abort: abortStub,
1598
+ };
1769
1599
  });
1770
1600
 
1771
1601
  it('should abort shutdown switchover backoff call on disconnect', async () => {
1772
- await mercury.disconnect(undefined, sessionId);
1602
+ await mercury.disconnect();
1773
1603
 
1774
1604
  assert.calledOnce(abortStub);
1775
1605
  });
1776
1606
 
1777
1607
  it('should handle disconnect when no switchover is in progress', async () => {
1778
- mercury._shutdownSwitchoverBackoffCalls.clear();
1608
+ mercury._shutdownSwitchoverBackoffCall = undefined;
1779
1609
 
1780
- await mercury.disconnect(undefined, sessionId);
1610
+ // Should not throw
1611
+ await mercury.disconnect();
1781
1612
 
1782
- assert.calledOnce(mercury.sockets.get(sessionId).close);
1613
+ // Should still close the socket
1614
+ assert.calledOnce(mercury.socket.close);
1783
1615
  });
1784
1616
  });
1785
1617
  });