@webex/internal-plugin-mercury 3.12.0-auth-prejoin-fetch.1 → 3.12.0-llmrefactor.10

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,21 +100,39 @@ describe('plugin-mercury', () => {
100
100
  });
101
101
 
102
102
  mercury = webex.internal.mercury;
103
- mercury.defaultSessionId = 'mercury-default-session';
104
103
  });
105
104
 
106
105
  afterEach(async () => {
107
- // Clean up Mercury connections and internal state
106
+ // Clean up Mercury connections and internal state synchronously
107
+ // to avoid issues with fake timers
108
108
  if (mercury) {
109
- try {
110
- await mercury.disconnectAll();
111
- } catch (e) {
112
- // Ignore cleanup errors
109
+ // Abort any pending backoff calls first
110
+ if (mercury.backoffCall) {
111
+ mercury.backoffCall.abort();
112
+ mercury.backoffCall = undefined;
113
113
  }
114
- // Clear any remaining connection promises
115
- if (mercury._connectPromises) {
116
- mercury._connectPromises.clear();
114
+ if (mercury._shutdownSwitchoverBackoffCall) {
115
+ mercury._shutdownSwitchoverBackoffCall.abort();
116
+ mercury._shutdownSwitchoverBackoffCall = undefined;
117
+ }
118
+
119
+ // Close socket directly without waiting for offline event
120
+ if (mercury.socket) {
121
+ if (typeof mercury.socket.removeAllListeners === 'function') {
122
+ mercury.socket.removeAllListeners();
123
+ }
124
+ try {
125
+ mercury.socket.close();
126
+ } catch (e) {
127
+ // Ignore cleanup errors
128
+ }
129
+ mercury.socket = undefined;
117
130
  }
131
+
132
+ // Clear any remaining connection promise
133
+ mercury._connectPromise = null;
134
+ mercury.connected = false;
135
+ mercury.connecting = false;
118
136
  }
119
137
 
120
138
  // Ensure mock socket is properly closed
@@ -650,44 +668,6 @@ describe('plugin-mercury', () => {
650
668
  });
651
669
  });
652
670
 
653
- describe('#logout()', () => {
654
- it('calls disconnectAll and logs', () => {
655
- sinon.stub(mercury.logger, 'info');
656
- sinon.stub(mercury, 'disconnectAll');
657
- mercury.logout();
658
- assert.called(mercury.disconnectAll);
659
- assert.calledTwice(mercury.logger.info);
660
-
661
- assert.calledWith(mercury.logger.info.getCall(0), 'Mercury: logout() called');
662
- assert.isTrue(
663
- mercury.logger.info
664
- .getCall(1)
665
- .args[0].startsWith('Mercury: debug_mercury_logging stack: ')
666
- );
667
- });
668
-
669
- it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send code 3050 for logout', () => {
670
- sinon.stub(mercury, 'disconnectAll');
671
- mercury.config.beforeLogoutOptionsCloseReason = 'done (permanent)';
672
- mercury.logout();
673
- assert.calledWith(mercury.disconnectAll, {code: 3050, reason: 'done (permanent)'});
674
- });
675
-
676
- it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send code 3050 for logout if the reason is different than standard', () => {
677
- sinon.stub(mercury, 'disconnectAll');
678
- mercury.config.beforeLogoutOptionsCloseReason = 'test';
679
- mercury.logout();
680
- assert.calledWith(mercury.disconnectAll, {code: 3050, reason: 'test'});
681
- });
682
-
683
- it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send undefined for logout if the reason is same as standard', () => {
684
- sinon.stub(mercury, 'disconnectAll');
685
- mercury.config.beforeLogoutOptionsCloseReason = 'done (forced)';
686
- mercury.logout();
687
- assert.calledWith(mercury.disconnectAll, undefined);
688
- });
689
- });
690
-
691
671
  describe('#disconnect()', () => {
692
672
  it('disconnects the WebSocket', () =>
693
673
  mercury
@@ -710,6 +690,47 @@ describe('plugin-mercury', () => {
710
690
  assert.isUndefined(mercury.mockWebSocket, 'Mercury does not have a mockWebSocket');
711
691
  }));
712
692
 
693
+ it('emits disconnected event after offline when socket exists', () => {
694
+ const offlineSpy = sinon.spy();
695
+ const disconnectedSpy = sinon.spy();
696
+ let offlineCalledFirst = false;
697
+
698
+ return mercury.connect().then(() => {
699
+ mercury.on('offline', () => {
700
+ offlineSpy();
701
+ // At this point, disconnected should not have been called yet
702
+ offlineCalledFirst = !disconnectedSpy.called;
703
+ });
704
+ mercury.on('disconnected', disconnectedSpy);
705
+
706
+ const promise = mercury.disconnect();
707
+
708
+ mockWebSocket.emit('close', {
709
+ code: 1000,
710
+ reason: 'Done',
711
+ });
712
+
713
+ return promise.then(() => {
714
+ assert.calledOnce(offlineSpy);
715
+ assert.calledOnce(disconnectedSpy);
716
+ assert.isTrue(offlineCalledFirst, 'offline was emitted before disconnected');
717
+ });
718
+ });
719
+ });
720
+
721
+ it('emits disconnected event when no socket exists', () => {
722
+ const disconnectedSpy = sinon.spy();
723
+
724
+ mercury.on('disconnected', disconnectedSpy);
725
+
726
+ // Ensure there's no socket
727
+ assert.isUndefined(mercury.socket, 'Mercury should not have a socket');
728
+
729
+ return mercury.disconnect().then(() => {
730
+ assert.calledOnce(disconnectedSpy);
731
+ });
732
+ });
733
+
713
734
  it('disconnects the WebSocket with code 3050', () =>
714
735
  mercury
715
736
  .connect()
@@ -791,17 +812,14 @@ describe('plugin-mercury', () => {
791
812
  // By this time backoffCall and mercury socket should be defined by the
792
813
  // 'connect' call
793
814
  assert.isDefined(
794
- mercury.backoffCalls.get('mercury-default-session'),
815
+ mercury.backoffCall,
795
816
  'Mercury backoffCall is not defined'
796
817
  );
797
818
  assert.isDefined(mercury.socket, 'Mercury socket is not defined');
798
819
  // Calling disconnect will abort the backoffCall, close the socket, and
799
820
  // reject the connect
800
821
  mercury.disconnect();
801
- assert.isUndefined(
802
- mercury.backoffCalls.get('mercury-default-session'),
803
- 'Mercury backoffCall is still defined'
804
- );
822
+ assert.notExists(mercury.backoffCall, 'Mercury backoffCall is still defined');
805
823
  // The socket will never be unset (which seems bad)
806
824
  assert.isDefined(mercury.socket, 'Mercury socket is not defined');
807
825
 
@@ -819,20 +837,16 @@ describe('plugin-mercury', () => {
819
837
 
820
838
  let reason;
821
839
 
822
- mercury.backoffCalls.clear();
840
+ mercury.backoffCall = undefined;
823
841
 
824
- const promise = mercury._attemptConnection(
825
- 'ws://example.com',
826
- 'mercury-default-session',
827
- (_reason) => {
828
- reason = _reason;
829
- }
830
- );
842
+ const promise = mercury._attemptConnection('ws://example.com', (_reason) => {
843
+ reason = _reason;
844
+ });
831
845
 
832
846
  return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
833
847
  assert.equal(
834
848
  reason.message,
835
- `Mercury: prevent socket open when backoffCall no longer defined for ${mercury.defaultSessionId}`
849
+ 'Mercury: prevent socket open when backoffCall no longer defined'
836
850
  );
837
851
 
838
852
  // Ensure the promise was actually rejected (short-circuited)
@@ -857,10 +871,7 @@ describe('plugin-mercury', () => {
857
871
  return assert.isRejected(promise).then((error) => {
858
872
  const lastError = mercury.getLastError();
859
873
 
860
- assert.equal(
861
- error.message,
862
- `Mercury Connection Aborted for ${mercury.defaultSessionId}`
863
- );
874
+ assert.equal(error.message, 'Mercury Connection Aborted');
864
875
  assert.isDefined(lastError);
865
876
  assert.equal(lastError, realError);
866
877
  });
@@ -954,7 +965,7 @@ describe('plugin-mercury', () => {
954
965
  },
955
966
  };
956
967
  assert.isUndefined(mercury.mercuryTimeOffset);
957
- mercury._setTimeOffset('mercury-default-session', event);
968
+ mercury._setTimeOffset(event);
958
969
  assert.isDefined(mercury.mercuryTimeOffset);
959
970
  assert.isTrue(mercury.mercuryTimeOffset > 0);
960
971
  });
@@ -964,7 +975,7 @@ describe('plugin-mercury', () => {
964
975
  wsWriteTimestamp: Date.now() + 60000,
965
976
  },
966
977
  };
967
- mercury._setTimeOffset('mercury-default-session', event);
978
+ mercury._setTimeOffset(event);
968
979
  assert.isTrue(mercury.mercuryTimeOffset < 0);
969
980
  });
970
981
  it('handles invalid wsWriteTimestamp', () => {
@@ -975,7 +986,7 @@ describe('plugin-mercury', () => {
975
986
  wsWriteTimestamp: invalidTimestamp,
976
987
  },
977
988
  };
978
- mercury._setTimeOffset('mercury-default-session', event);
989
+ mercury._setTimeOffset(event);
979
990
  assert.isUndefined(mercury.mercuryTimeOffset);
980
991
  });
981
992
  });
@@ -1082,15 +1093,13 @@ describe('plugin-mercury', () => {
1082
1093
  describe('shutdown protocol', () => {
1083
1094
  describe('#_handleImminentShutdown()', () => {
1084
1095
  let connectWithBackoffStub;
1085
- const sessionId = 'mercury-default-session';
1086
1096
 
1087
1097
  beforeEach(() => {
1088
1098
  mercury.connected = true;
1089
- mercury.sockets.set(sessionId, {
1099
+ mercury.socket = {
1090
1100
  url: 'ws://old-socket.com',
1091
1101
  removeAllListeners: sinon.stub(),
1092
- });
1093
- mercury.socket = mercury.sockets.get(sessionId);
1102
+ };
1094
1103
  connectWithBackoffStub = sinon.stub(mercury, '_connectWithBackoff');
1095
1104
  connectWithBackoffStub.returns(Promise.resolve());
1096
1105
  sinon.stub(mercury, '_emit');
@@ -1099,47 +1108,42 @@ describe('plugin-mercury', () => {
1099
1108
  afterEach(() => {
1100
1109
  connectWithBackoffStub.restore();
1101
1110
  mercury._emit.restore();
1102
- mercury.sockets.clear();
1103
1111
  });
1104
1112
 
1105
1113
  it('should be idempotent - no-op if already in progress', () => {
1106
- // Simulate an existing switchover in progress by seeding the backoff map
1107
- mercury._shutdownSwitchoverBackoffCalls.set(sessionId, {placeholder: true});
1114
+ // Simulate an existing switchover in progress
1115
+ mercury._shutdownSwitchoverBackoffCall = {abort: sinon.stub()};
1108
1116
 
1109
- mercury._handleImminentShutdown(sessionId);
1117
+ mercury._handleImminentShutdown();
1110
1118
 
1111
1119
  assert.notCalled(connectWithBackoffStub);
1112
1120
  });
1113
1121
 
1114
1122
  it('should set switchover flags when called', () => {
1115
- mercury._handleImminentShutdown(sessionId);
1123
+ mercury._handleImminentShutdown();
1116
1124
 
1117
- // With _connectWithBackoff stubbed, the backoff map entry may not be created here.
1118
- // Assert that switchover initiation state was set and a shutdown switchover connect was requested.
1119
1125
  assert.isDefined(mercury._shutdownSwitchoverId);
1120
1126
 
1121
1127
  assert.calledOnce(connectWithBackoffStub);
1122
1128
  const callArgs = connectWithBackoffStub.firstCall.args;
1123
1129
  assert.isUndefined(callArgs[0]); // webSocketUrl
1124
- assert.equal(callArgs[1], sessionId); // sessionId
1125
- assert.isObject(callArgs[2]); // context
1126
- assert.isTrue(callArgs[2].isShutdownSwitchover);
1127
- assert.isObject(callArgs[2].attemptOptions);
1128
- assert.isTrue(callArgs[2].attemptOptions.isShutdownSwitchover);
1130
+ assert.isObject(callArgs[1]); // context
1131
+ assert.isTrue(callArgs[1].isShutdownSwitchover);
1132
+ assert.isObject(callArgs[1].attemptOptions);
1133
+ assert.isTrue(callArgs[1].attemptOptions.isShutdownSwitchover);
1129
1134
  });
1130
1135
 
1131
1136
  it('should call _connectWithBackoff with correct parameters', (done) => {
1132
- mercury._handleImminentShutdown(sessionId);
1137
+ mercury._handleImminentShutdown();
1133
1138
 
1134
1139
  process.nextTick(() => {
1135
1140
  assert.calledOnce(connectWithBackoffStub);
1136
1141
  const callArgs = connectWithBackoffStub.firstCall.args;
1137
1142
  assert.isUndefined(callArgs[0]); // webSocketUrl
1138
- assert.equal(callArgs[1], sessionId); // sessionId
1139
- assert.isObject(callArgs[2]); // context
1140
- assert.isTrue(callArgs[2].isShutdownSwitchover);
1141
- assert.isObject(callArgs[2].attemptOptions);
1142
- assert.isTrue(callArgs[2].attemptOptions.isShutdownSwitchover);
1143
+ assert.isObject(callArgs[1]); // context
1144
+ assert.isTrue(callArgs[1].isShutdownSwitchover);
1145
+ assert.isObject(callArgs[1].attemptOptions);
1146
+ assert.isTrue(callArgs[1].attemptOptions.isShutdownSwitchover);
1143
1147
  done();
1144
1148
  });
1145
1149
  });
@@ -1148,12 +1152,10 @@ describe('plugin-mercury', () => {
1148
1152
  connectWithBackoffStub.restore();
1149
1153
  sinon.stub(mercury, '_connectWithBackoff').throws(new Error('Connection failed'));
1150
1154
 
1151
- mercury._handleImminentShutdown(sessionId);
1155
+ mercury._handleImminentShutdown();
1152
1156
 
1153
- // When an exception happens synchronously, the placeholder entry
1154
- // should be removed from the map.
1155
- const switchoverCall = mercury._shutdownSwitchoverBackoffCalls.get(sessionId);
1156
- assert.isUndefined(switchoverCall);
1157
+ // When an exception happens synchronously, the backoff call should be cleared
1158
+ assert.notExists(mercury._shutdownSwitchoverBackoffCall);
1157
1159
  mercury._connectWithBackoff.restore();
1158
1160
  });
1159
1161
  });
@@ -1178,15 +1180,10 @@ describe('plugin-mercury', () => {
1178
1180
  },
1179
1181
  };
1180
1182
 
1181
- const result = mercury._onmessage(mercury.defaultSessionId, shutdownEvent);
1183
+ const result = mercury._onmessage(shutdownEvent);
1182
1184
 
1183
1185
  assert.calledOnce(mercury._handleImminentShutdown);
1184
- assert.calledWith(
1185
- mercury._emit,
1186
- mercury.defaultSessionId,
1187
- 'event:mercury_shutdown_imminent',
1188
- shutdownEvent.data
1189
- );
1186
+ assert.calledWith(mercury._emit, 'event:mercury_shutdown_imminent', shutdownEvent.data);
1190
1187
  assert.instanceOf(result, Promise);
1191
1188
  });
1192
1189
 
@@ -1197,7 +1194,7 @@ describe('plugin-mercury', () => {
1197
1194
  },
1198
1195
  };
1199
1196
 
1200
- mercury._onmessage(mercury.defaultSessionId, shutdownEvent);
1197
+ mercury._onmessage(shutdownEvent);
1201
1198
 
1202
1199
  assert.calledOnce(mercury._handleImminentShutdown);
1203
1200
  });
@@ -1212,7 +1209,7 @@ describe('plugin-mercury', () => {
1212
1209
  },
1213
1210
  };
1214
1211
 
1215
- mercury._onmessage(mercury.defaultSessionId, regularEvent);
1212
+ mercury._onmessage(regularEvent);
1216
1213
 
1217
1214
  assert.notCalled(mercury._handleImminentShutdown);
1218
1215
  });
@@ -1231,7 +1228,7 @@ describe('plugin-mercury', () => {
1231
1228
  mercury._applyOverrides.restore();
1232
1229
  });
1233
1230
 
1234
- it('should not throw when envelope.data is undefined', () => {
1231
+ it('should emit generic event and return early when envelope.data (nested) is undefined', async () => {
1235
1232
  const event = {
1236
1233
  data: {
1237
1234
  type: 'someType',
@@ -1239,13 +1236,15 @@ describe('plugin-mercury', () => {
1239
1236
  },
1240
1237
  };
1241
1238
 
1242
- const result = mercury._onmessage(mercury.defaultSessionId, event);
1239
+ const result = mercury._onmessage(event);
1243
1240
 
1244
1241
  assert.instanceOf(result, Promise);
1245
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'event', event.data);
1242
+ await result;
1243
+ assert.calledOnceWithExactly(mercury._emit, 'event', event.data);
1244
+ assert.notCalled(mercury._applyOverrides);
1246
1245
  });
1247
1246
 
1248
- it('should not throw when data.eventType is undefined', () => {
1247
+ it('should emit generic event and return early when data.eventType is undefined', async () => {
1249
1248
  const event = {
1250
1249
  data: {
1251
1250
  type: 'someType',
@@ -1256,13 +1255,15 @@ describe('plugin-mercury', () => {
1256
1255
  },
1257
1256
  };
1258
1257
 
1259
- const result = mercury._onmessage(mercury.defaultSessionId, event);
1258
+ const result = mercury._onmessage(event);
1260
1259
 
1261
1260
  assert.instanceOf(result, Promise);
1262
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'event', event.data);
1261
+ await result;
1262
+ assert.calledOnceWithExactly(mercury._emit, 'event', event.data);
1263
+ assert.notCalled(mercury._applyOverrides);
1263
1264
  });
1264
1265
 
1265
- it('should emit generic event for messages without eventType (e.g. subscription responses)', () => {
1266
+ it('should emit generic event for messages without eventType (e.g. subscription responses)', async () => {
1266
1267
  const event = {
1267
1268
  data: {
1268
1269
  id: 'msg-123',
@@ -1273,11 +1274,12 @@ describe('plugin-mercury', () => {
1273
1274
  },
1274
1275
  };
1275
1276
 
1276
- const result = mercury._onmessage(mercury.defaultSessionId, event);
1277
+ const result = mercury._onmessage(event);
1277
1278
 
1278
1279
  assert.instanceOf(result, Promise);
1279
- assert.calledOnce(mercury._emit);
1280
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'event', event.data);
1280
+ await result;
1281
+ assert.calledOnceWithExactly(mercury._emit, 'event', event.data);
1282
+ assert.notCalled(mercury._applyOverrides);
1281
1283
  });
1282
1284
 
1283
1285
  it('should still process messages with a valid eventType', async () => {
@@ -1289,22 +1291,12 @@ describe('plugin-mercury', () => {
1289
1291
  },
1290
1292
  };
1291
1293
 
1292
- await mercury._onmessage(mercury.defaultSessionId, event);
1294
+ await mercury._onmessage(event);
1293
1295
 
1294
1296
  // Normal flow emits namespace-specific events after processing handlers.
1295
1297
  // The early-return guard only emits 'event', so asserting these proves the normal path was taken.
1296
- assert.calledWith(
1297
- mercury._emit,
1298
- mercury.defaultSessionId,
1299
- 'event:conversation',
1300
- event.data
1301
- );
1302
- assert.calledWith(
1303
- mercury._emit,
1304
- mercury.defaultSessionId,
1305
- 'event:conversation.activity',
1306
- event.data
1307
- );
1298
+ assert.calledWith(mercury._emit, 'event:conversation', event.data);
1299
+ assert.calledWith(mercury._emit, 'event:conversation.activity', event.data);
1308
1300
  });
1309
1301
  });
1310
1302
 
@@ -1347,12 +1339,13 @@ describe('plugin-mercury', () => {
1347
1339
  removeAllListeners: sinon.stub(),
1348
1340
  };
1349
1341
  mercury.socket = mockSocket;
1350
- mercury.sockets.set(mercury.defaultSessionId, mockSocket);
1351
- mercury.sessionWebSocketUrls.set(mercury.defaultSessionId, mockSocket.url);
1342
+ mercury.resolvedWebSocketUrl = mockSocket.url;
1352
1343
  mercury.connected = true;
1353
1344
  sinon.stub(mercury, '_emit');
1354
1345
  sinon.stub(mercury, '_reconnect');
1355
1346
  sinon.stub(mercury, 'unset');
1347
+ // Reset metrics stub before each test
1348
+ webex.internal.metrics.submitClientMetrics.resetHistory();
1356
1349
  });
1357
1350
 
1358
1351
  afterEach(() => {
@@ -1367,7 +1360,7 @@ describe('plugin-mercury', () => {
1367
1360
  reason: 'Replaced',
1368
1361
  };
1369
1362
 
1370
- mercury._onclose(mercury.defaultSessionId, closeEvent, mockSocket);
1363
+ mercury._onclose(closeEvent, mockSocket);
1371
1364
 
1372
1365
  assert.calledOnceWithExactly(
1373
1366
  webex.internal.metrics.submitClientMetrics,
@@ -1379,7 +1372,6 @@ describe('plugin-mercury', () => {
1379
1372
  close_reason: 'Replaced',
1380
1373
  is_active_socket: true,
1381
1374
  message_type: 'replaced',
1382
- session_id: mercury.defaultSessionId,
1383
1375
  },
1384
1376
  tags: {
1385
1377
  action: 'no_action',
@@ -1387,7 +1379,7 @@ describe('plugin-mercury', () => {
1387
1379
  },
1388
1380
  }
1389
1381
  );
1390
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'offline.replaced', closeEvent);
1382
+ assert.calledWith(mercury._emit, 'offline.replaced', closeEvent);
1391
1383
  assert.notCalled(mercury._reconnect);
1392
1384
  });
1393
1385
 
@@ -1397,7 +1389,7 @@ describe('plugin-mercury', () => {
1397
1389
  reason: 'Unexpected close',
1398
1390
  };
1399
1391
 
1400
- mercury._onclose(mercury.defaultSessionId, closeEvent, mockSocket);
1392
+ mercury._onclose(closeEvent, mockSocket);
1401
1393
 
1402
1394
  assert.calledOnceWithExactly(
1403
1395
  webex.internal.metrics.submitClientMetrics,
@@ -1409,7 +1401,6 @@ describe('plugin-mercury', () => {
1409
1401
  close_reason: 'Unexpected close',
1410
1402
  is_active_socket: true,
1411
1403
  message_type: 'other',
1412
- session_id: mercury.defaultSessionId,
1413
1404
  },
1414
1405
  tags: {
1415
1406
  action: 'reconnect',
@@ -1417,12 +1408,8 @@ describe('plugin-mercury', () => {
1417
1408
  },
1418
1409
  }
1419
1410
  );
1420
- assert.calledWith(mercury._emit, mercury.defaultSessionId, 'offline.transient', closeEvent);
1421
- assert.calledWith(
1422
- mercury._reconnect,
1423
- mockSocket.url,
1424
- mercury.defaultSessionId
1425
- );
1411
+ assert.calledWith(mercury._emit, 'offline.transient', closeEvent);
1412
+ assert.calledWith(mercury._reconnect, mockSocket.url);
1426
1413
  });
1427
1414
 
1428
1415
  it('should not reconnect a non-active socket with an unknown 4000 reason', () => {
@@ -1431,7 +1418,7 @@ describe('plugin-mercury', () => {
1431
1418
  reason: 'Unexpected close',
1432
1419
  };
1433
1420
 
1434
- mercury._onclose(mercury.defaultSessionId, closeEvent, anotherSocket);
1421
+ mercury._onclose(closeEvent, anotherSocket);
1435
1422
 
1436
1423
  assert.calledOnceWithExactly(
1437
1424
  webex.internal.metrics.submitClientMetrics,
@@ -1443,7 +1430,6 @@ describe('plugin-mercury', () => {
1443
1430
  close_reason: 'Unexpected close',
1444
1431
  is_active_socket: false,
1445
1432
  message_type: 'other',
1446
- session_id: mercury.defaultSessionId,
1447
1433
  },
1448
1434
  tags: {
1449
1435
  action: 'ignore_non_active',
@@ -1451,7 +1437,6 @@ describe('plugin-mercury', () => {
1451
1437
  },
1452
1438
  }
1453
1439
  );
1454
- assert.notCalled(mercury._emit);
1455
1440
  assert.notCalled(mercury._reconnect);
1456
1441
  assert.isTrue(mercury.connected);
1457
1442
  });
@@ -1470,7 +1455,6 @@ describe('plugin-mercury', () => {
1470
1455
  removeAllListeners: sinon.stub(),
1471
1456
  };
1472
1457
  mercury.socket = mockSocket;
1473
- mercury.sockets.set(mercury.defaultSessionId, mockSocket);
1474
1458
  mercury.connected = true;
1475
1459
  sinon.stub(mercury, '_emit');
1476
1460
  sinon.stub(mercury, '_reconnect');
@@ -1489,14 +1473,9 @@ describe('plugin-mercury', () => {
1489
1473
  reason: 'replaced during shutdown',
1490
1474
  };
1491
1475
 
1492
- mercury._onclose(mercury.defaultSessionId, closeEvent, mockSocket);
1476
+ mercury._onclose(closeEvent, mockSocket);
1493
1477
 
1494
- assert.calledWith(
1495
- mercury._emit,
1496
- mercury.defaultSessionId,
1497
- 'offline.permanent',
1498
- closeEvent
1499
- );
1478
+ assert.calledWith(mercury._emit, 'offline.permanent', closeEvent);
1500
1479
  assert.notCalled(mercury._reconnect); // No reconnect for 4001 on active socket
1501
1480
  assert.isFalse(mercury.connected);
1502
1481
  });
@@ -1507,14 +1486,9 @@ describe('plugin-mercury', () => {
1507
1486
  reason: 'replaced during shutdown',
1508
1487
  };
1509
1488
 
1510
- mercury._onclose(mercury.defaultSessionId, closeEvent, anotherSocket);
1489
+ mercury._onclose(closeEvent, anotherSocket);
1511
1490
 
1512
- assert.calledWith(
1513
- mercury._emit,
1514
- mercury.defaultSessionId,
1515
- 'offline.replaced',
1516
- closeEvent
1517
- );
1491
+ assert.calledWith(mercury._emit, 'offline.replaced', closeEvent);
1518
1492
  assert.notCalled(mercury._reconnect);
1519
1493
  assert.isTrue(mercury.connected); // Should remain connected
1520
1494
  assert.notCalled(mercury.unset);
@@ -1527,26 +1501,15 @@ describe('plugin-mercury', () => {
1527
1501
  };
1528
1502
 
1529
1503
  // Test non-active socket
1530
- mercury._onclose(mercury.defaultSessionId, closeEvent, anotherSocket);
1531
- assert.calledWith(
1532
- mercury._emit,
1533
- mercury.defaultSessionId,
1534
- 'offline.replaced',
1535
- closeEvent
1536
- );
1504
+ mercury._onclose(closeEvent, anotherSocket);
1505
+ assert.calledWith(mercury._emit, 'offline.replaced', closeEvent);
1537
1506
 
1538
1507
  // Reset the spy call history
1539
1508
  mercury._emit.resetHistory();
1540
1509
 
1541
1510
  // Test active socket
1542
- mercury.sockets.set(mercury.defaultSessionId, mockSocket);
1543
- mercury._onclose(mercury.defaultSessionId, closeEvent, mockSocket);
1544
- assert.calledWith(
1545
- mercury._emit,
1546
- mercury.defaultSessionId,
1547
- 'offline.permanent',
1548
- closeEvent
1549
- );
1511
+ mercury._onclose(closeEvent, mockSocket);
1512
+ assert.calledWith(mercury._emit, 'offline.permanent', closeEvent);
1550
1513
  });
1551
1514
 
1552
1515
  it('should handle missing sourceSocket parameter (treats as non-active)', () => {
@@ -1555,15 +1518,10 @@ describe('plugin-mercury', () => {
1555
1518
  reason: 'replaced during shutdown',
1556
1519
  };
1557
1520
 
1558
- mercury._onclose(mercury.defaultSessionId, closeEvent); // No sourceSocket parameter
1521
+ mercury._onclose(closeEvent); // No sourceSocket parameter
1559
1522
 
1560
1523
  // With simplified logic, undefined !== this.socket, so isActiveSocket = false
1561
- assert.calledWith(
1562
- mercury._emit,
1563
- mercury.defaultSessionId,
1564
- 'offline.replaced',
1565
- closeEvent
1566
- );
1524
+ assert.calledWith(mercury._emit, 'offline.replaced', closeEvent);
1567
1525
  assert.notCalled(mercury._reconnect);
1568
1526
  });
1569
1527
 
@@ -1574,11 +1532,9 @@ describe('plugin-mercury', () => {
1574
1532
  };
1575
1533
 
1576
1534
  // Close non-active socket (not the active one)
1577
- mercury._onclose(mercury.defaultSessionId, closeEvent, anotherSocket);
1535
+ mercury._onclose(closeEvent, anotherSocket);
1578
1536
 
1579
1537
  // Verify listeners were removed from the old socket
1580
- // The _onclose method checks if sourceSocket !== this.socket (non-active)
1581
- // and then calls removeAllListeners in the else branch
1582
1538
  assert.calledOnce(anotherSocket.removeAllListeners);
1583
1539
  });
1584
1540
 
@@ -1589,7 +1545,7 @@ describe('plugin-mercury', () => {
1589
1545
  };
1590
1546
 
1591
1547
  // Close active socket
1592
- mercury._onclose(mercury.defaultSessionId, closeEvent, mockSocket);
1548
+ mercury._onclose(closeEvent, mockSocket);
1593
1549
 
1594
1550
  // Verify listeners were removed from active socket
1595
1551
  assert.calledOnce(mockSocket.removeAllListeners);
@@ -1597,7 +1553,6 @@ describe('plugin-mercury', () => {
1597
1553
  });
1598
1554
 
1599
1555
  describe('#_onclose() reconnect URL derivation', () => {
1600
- const sessionId = 'mercury-default-session';
1601
1556
  // The URL resolved and stored in _prepareAndOpenSocket (catalog-valid host).
1602
1557
  const resolvedUrl = 'wss://mercury-connection.wbx2.com/v1/apps/wx2/registrations';
1603
1558
  // The URL the native socket was actually opened with, after a lower
@@ -1612,8 +1567,7 @@ describe('plugin-mercury', () => {
1612
1567
  removeAllListeners: sinon.stub(),
1613
1568
  };
1614
1569
  mercury.socket = sourceSocket;
1615
- mercury.sockets.set(sessionId, sourceSocket);
1616
- mercury.sessionWebSocketUrls.set(sessionId, resolvedUrl);
1570
+ mercury.resolvedWebSocketUrl = resolvedUrl;
1617
1571
  mercury.connected = true;
1618
1572
  sinon.stub(mercury, '_emit');
1619
1573
  sinon.stub(mercury, '_reconnect');
@@ -1624,23 +1578,21 @@ describe('plugin-mercury', () => {
1624
1578
  mercury._emit.restore();
1625
1579
  mercury._reconnect.restore();
1626
1580
  mercury.unset.restore();
1627
- mercury.sockets.clear();
1628
- mercury.sessionWebSocketUrls.clear();
1629
1581
  });
1630
1582
 
1631
1583
  [1001, 1005, 1006, 1011].forEach((code) => {
1632
1584
  it(`reconnects an active socket using the stored resolved URL (not the proxied socket.url) for code ${code}`, () => {
1633
- mercury._onclose(sessionId, {code, reason: 'transient'}, sourceSocket);
1585
+ mercury._onclose({code, reason: 'transient'}, sourceSocket);
1634
1586
 
1635
- assert.calledOnceWithExactly(mercury._reconnect, resolvedUrl, sessionId);
1636
- assert.neverCalledWith(mercury._reconnect, proxiedUrl, sessionId);
1587
+ assert.calledOnceWithExactly(mercury._reconnect, resolvedUrl);
1588
+ assert.neverCalledWith(mercury._reconnect, proxiedUrl);
1637
1589
  });
1638
1590
  });
1639
1591
 
1640
1592
  it('reconnects using the stored resolved URL for a normal close (1000) with a reconnect reason', () => {
1641
- mercury._onclose(sessionId, {code: 1000, reason: 'done (forced)'}, sourceSocket);
1593
+ mercury._onclose({code: 1000, reason: 'done (forced)'}, sourceSocket);
1642
1594
 
1643
- assert.calledOnceWithExactly(mercury._reconnect, resolvedUrl, sessionId);
1595
+ assert.calledOnceWithExactly(mercury._reconnect, resolvedUrl);
1644
1596
  });
1645
1597
 
1646
1598
  it('does not reconnect when a non-active (old) socket closes', () => {
@@ -1649,34 +1601,29 @@ describe('plugin-mercury', () => {
1649
1601
  removeAllListeners: sinon.stub(),
1650
1602
  };
1651
1603
 
1652
- mercury._onclose(sessionId, {code: 1006, reason: 'transient'}, oldSocket);
1604
+ mercury._onclose({code: 1006, reason: 'transient'}, oldSocket);
1653
1605
 
1654
1606
  assert.notCalled(mercury._reconnect);
1655
1607
  });
1656
- });
1657
1608
 
1658
- describe('#disconnectAll()', () => {
1659
- it('clears sessionWebSocketUrls along with the other session state', async () => {
1660
- mercury.sessionWebSocketUrls.set('mercury-default-session', 'ws://example.com');
1661
- mercury.sessionWebSocketUrls.set('llm-session', 'ws://example-2.com');
1609
+ it('falls back to socket.url when resolvedWebSocketUrl is not set', () => {
1610
+ mercury.resolvedWebSocketUrl = undefined;
1662
1611
 
1663
- await mercury.disconnectAll();
1612
+ mercury._onclose({code: 1006, reason: 'transient'}, sourceSocket);
1664
1613
 
1665
- assert.equal(mercury.sessionWebSocketUrls.size, 0);
1614
+ assert.calledOnceWithExactly(mercury._reconnect, proxiedUrl);
1666
1615
  });
1667
1616
  });
1668
1617
 
1669
1618
  describe('shutdown switchover with retry logic', () => {
1670
1619
  let connectWithBackoffStub;
1671
- const sessionId = 'mercury-default-session';
1672
1620
 
1673
1621
  beforeEach(() => {
1674
1622
  mercury.connected = true;
1675
- mercury.sockets.set(sessionId, {
1623
+ mercury.socket = {
1676
1624
  url: 'ws://old-socket.com',
1677
1625
  removeAllListeners: sinon.stub(),
1678
- });
1679
- mercury.socket = mercury.sockets.get(sessionId);
1626
+ };
1680
1627
  connectWithBackoffStub = sinon.stub(mercury, '_connectWithBackoff');
1681
1628
  sinon.stub(mercury, '_emit');
1682
1629
  });
@@ -1684,46 +1631,41 @@ describe('plugin-mercury', () => {
1684
1631
  afterEach(() => {
1685
1632
  connectWithBackoffStub.restore();
1686
1633
  mercury._emit.restore();
1687
- mercury.sockets.clear();
1688
1634
  });
1689
1635
 
1690
1636
  it('should call _connectWithBackoff with shutdown switchover context', (done) => {
1691
1637
  connectWithBackoffStub.returns(Promise.resolve());
1692
1638
 
1693
- mercury._handleImminentShutdown(sessionId);
1639
+ mercury._handleImminentShutdown();
1694
1640
 
1695
1641
  process.nextTick(() => {
1696
1642
  assert.calledOnce(connectWithBackoffStub);
1697
1643
  const callArgs = connectWithBackoffStub.firstCall.args;
1698
1644
 
1699
1645
  assert.isUndefined(callArgs[0]); // webSocketUrl
1700
- assert.equal(callArgs[1], sessionId);
1701
- assert.isObject(callArgs[2]);
1702
- assert.isTrue(callArgs[2].isShutdownSwitchover);
1703
- assert.isObject(callArgs[2].attemptOptions);
1704
- assert.isTrue(callArgs[2].attemptOptions.isShutdownSwitchover);
1646
+ assert.isObject(callArgs[1]);
1647
+ assert.isTrue(callArgs[1].isShutdownSwitchover);
1648
+ assert.isObject(callArgs[1].attemptOptions);
1649
+ assert.isTrue(callArgs[1].attemptOptions.isShutdownSwitchover);
1705
1650
  done();
1706
1651
  });
1707
1652
  });
1708
1653
 
1709
- it('should set _shutdownSwitchoverInProgress flag during switchover', () => {
1710
- // With the new behavior, "in progress" is represented by the presence
1711
- // of an entry in _shutdownSwitchoverBackoffCalls.
1712
- // Since _connectWithBackoff is stubbed in this suite, simulate its side-effect
1713
- // of seeding the backoff-call map entry.
1654
+ it('should set _shutdownSwitchoverBackoffCall during switchover', () => {
1655
+ // Since _connectWithBackoff is stubbed, we can just check that
1656
+ // _handleImminentShutdown sets the flag
1714
1657
  connectWithBackoffStub.callsFake(() => {
1715
- mercury._shutdownSwitchoverBackoffCalls.set(sessionId, {placeholder: true});
1716
1658
  return new Promise(() => {}); // Never resolves
1717
1659
  });
1718
1660
 
1719
- mercury._handleImminentShutdown(sessionId);
1661
+ mercury._handleImminentShutdown();
1720
1662
 
1721
- const switchoverBackoffCall = mercury._shutdownSwitchoverBackoffCalls.get(sessionId);
1722
- assert.isOk(switchoverBackoffCall);
1663
+ // The switchover id is set when switchover starts
1664
+ assert.isDefined(mercury._shutdownSwitchoverId);
1723
1665
  });
1724
1666
 
1725
1667
  it('should emit success event when switchover completes', async () => {
1726
- connectWithBackoffStub.callsFake((url, sid, context) => {
1668
+ connectWithBackoffStub.callsFake((url, context) => {
1727
1669
  if (context && context.attemptOptions && context.attemptOptions.onSuccess) {
1728
1670
  const mockSocket = {url: 'ws://new-socket.com'};
1729
1671
  context.attemptOptions.onSuccess(mockSocket, 'ws://new-socket.com');
@@ -1731,15 +1673,13 @@ describe('plugin-mercury', () => {
1731
1673
  return Promise.resolve();
1732
1674
  });
1733
1675
 
1734
- mercury._handleImminentShutdown(sessionId);
1676
+ mercury._handleImminentShutdown();
1735
1677
 
1736
1678
  await promiseTick(50);
1737
1679
 
1738
1680
  const emitCalls = mercury._emit.getCalls();
1739
1681
  const hasCompleteEvent = emitCalls.some(
1740
- (call) =>
1741
- call.args[0] === sessionId &&
1742
- call.args[1] === 'event:mercury_shutdown_switchover_complete'
1682
+ (call) => call.args[0] === 'event:mercury_shutdown_switchover_complete'
1743
1683
  );
1744
1684
 
1745
1685
  assert.isTrue(hasCompleteEvent, 'Should emit switchover complete event');
@@ -1748,27 +1688,26 @@ describe('plugin-mercury', () => {
1748
1688
  it('should emit failure event when switchover exhausts retries', async () => {
1749
1689
  const testError = new Error('Connection failed');
1750
1690
 
1751
- connectWithBackoffStub.returns(Promise.reject(testError));
1691
+ connectWithBackoffStub.callsFake(() => Promise.reject(testError));
1752
1692
 
1753
- mercury._handleImminentShutdown(sessionId);
1693
+ mercury._handleImminentShutdown();
1754
1694
  await promiseTick(50);
1755
1695
 
1756
1696
  const emitCalls = mercury._emit.getCalls();
1757
1697
  const hasFailureEvent = emitCalls.some(
1758
1698
  (call) =>
1759
- call.args[0] === sessionId &&
1760
- call.args[1] === 'event:mercury_shutdown_switchover_failed' &&
1761
- call.args[2] &&
1762
- call.args[2].reason === testError
1699
+ call.args[0] === 'event:mercury_shutdown_switchover_failed' &&
1700
+ call.args[1] &&
1701
+ call.args[1].reason === testError
1763
1702
  );
1764
1703
 
1765
1704
  assert.isTrue(hasFailureEvent, 'Should emit switchover failed event');
1766
1705
  });
1767
1706
 
1768
1707
  it('should allow old socket to be closed by server after switchover failure', async () => {
1769
- connectWithBackoffStub.returns(Promise.reject(new Error('Failed')));
1708
+ connectWithBackoffStub.callsFake(() => Promise.reject(new Error('Failed')));
1770
1709
 
1771
- mercury._handleImminentShutdown(sessionId);
1710
+ mercury._handleImminentShutdown();
1772
1711
  await promiseTick(50);
1773
1712
 
1774
1713
  assert.equal(mercury.socket.removeAllListeners.callCount, 0);
@@ -1854,14 +1793,21 @@ describe('plugin-mercury', () => {
1854
1793
  assert.equal(result, 'ws://example.com');
1855
1794
  });
1856
1795
 
1857
- it('stores the resolved (pre-proxy) URL under the sessionId in sessionWebSocketUrls', async () => {
1858
- const sessionId = 'llm-session';
1859
-
1860
- await mercury._prepareAndOpenSocket(mockSocket, 'ws://original.com', sessionId);
1796
+ it('stores the resolved (pre-proxy) URL in resolvedWebSocketUrl', async () => {
1797
+ await mercury._prepareAndOpenSocket(mockSocket, 'ws://original.com', false);
1861
1798
 
1862
1799
  // The stored value is the URL resolved by _prepareUrl, not the raw
1863
1800
  // input, so _onclose can later reconnect from a catalog-valid host.
1864
- assert.equal(mercury.sessionWebSocketUrls.get(sessionId), 'ws://example.com');
1801
+ assert.equal(mercury.resolvedWebSocketUrl, 'ws://example.com');
1802
+ });
1803
+
1804
+ it('does not store resolvedWebSocketUrl for shutdown switchover connections', async () => {
1805
+ mercury.resolvedWebSocketUrl = 'ws://existing.com';
1806
+
1807
+ await mercury._prepareAndOpenSocket(mockSocket, 'ws://original.com', true);
1808
+
1809
+ // Shutdown switchover should not overwrite the stored URL
1810
+ assert.equal(mercury.resolvedWebSocketUrl, 'ws://existing.com');
1865
1811
  });
1866
1812
 
1867
1813
  it('should handle errors during socket open', async () => {
@@ -1879,14 +1825,13 @@ describe('plugin-mercury', () => {
1879
1825
 
1880
1826
  describe('#_attemptConnection() with shutdown switchover', () => {
1881
1827
  let prepareAndOpenSocketStub, callback;
1882
- const sessionId = 'mercury-default-session';
1883
1828
 
1884
1829
  beforeEach(() => {
1885
1830
  prepareAndOpenSocketStub = sinon
1886
1831
  .stub(mercury, '_prepareAndOpenSocket')
1887
1832
  .returns(Promise.resolve('ws://new-socket.com'));
1888
1833
  callback = sinon.stub();
1889
- mercury._shutdownSwitchoverBackoffCalls.set(sessionId, {abort: sinon.stub()});
1834
+ mercury._shutdownSwitchoverBackoffCall = {abort: sinon.stub()};
1890
1835
  mercury.socket = {url: 'ws://test.com'};
1891
1836
  mercury.connected = true;
1892
1837
  sinon.stub(mercury, '_emit');
@@ -1897,13 +1842,13 @@ describe('plugin-mercury', () => {
1897
1842
  prepareAndOpenSocketStub.restore();
1898
1843
  mercury._emit.restore();
1899
1844
  mercury._attachSocketEventListeners.restore();
1900
- mercury._shutdownSwitchoverBackoffCalls.clear();
1845
+ mercury._shutdownSwitchoverBackoffCall = undefined;
1901
1846
  });
1902
1847
 
1903
1848
  it('should not set socket reference before opening for shutdown switchover', async () => {
1904
1849
  const originalSocket = mercury.socket;
1905
1850
 
1906
- await mercury._attemptConnection('ws://test.com', sessionId, callback, {
1851
+ await mercury._attemptConnection('ws://test.com', callback, {
1907
1852
  isShutdownSwitchover: true,
1908
1853
  onSuccess: (newSocket, url) => {
1909
1854
  assert.equal(mercury.socket, originalSocket);
@@ -1916,7 +1861,7 @@ describe('plugin-mercury', () => {
1916
1861
  it('should call onSuccess callback with new socket and URL for shutdown', async () => {
1917
1862
  const onSuccessStub = sinon.stub();
1918
1863
 
1919
- await mercury._attemptConnection('ws://test.com', sessionId, callback, {
1864
+ await mercury._attemptConnection('ws://test.com', callback, {
1920
1865
  isShutdownSwitchover: true,
1921
1866
  onSuccess: onSuccessStub,
1922
1867
  });
@@ -1926,18 +1871,17 @@ describe('plugin-mercury', () => {
1926
1871
  });
1927
1872
 
1928
1873
  it('should emit shutdown switchover complete event', async () => {
1929
- await mercury._attemptConnection('ws://test.com', sessionId, callback, {
1874
+ await mercury._attemptConnection('ws://test.com', callback, {
1930
1875
  isShutdownSwitchover: true,
1931
1876
  onSuccess: (newSocket, url) => {
1932
1877
  mercury.socket = newSocket;
1933
1878
  mercury.connected = true;
1934
- mercury._emit(sessionId, 'event:mercury_shutdown_switchover_complete', {url});
1879
+ mercury._emit('event:mercury_shutdown_switchover_complete', {url});
1935
1880
  },
1936
1881
  });
1937
1882
 
1938
1883
  assert.calledWith(
1939
1884
  mercury._emit,
1940
- sessionId,
1941
1885
  'event:mercury_shutdown_switchover_complete',
1942
1886
  sinon.match.has('url', 'ws://new-socket.com')
1943
1887
  );
@@ -1947,7 +1891,7 @@ describe('plugin-mercury', () => {
1947
1891
  prepareAndOpenSocketStub.returns(Promise.reject(new Error('Connection failed')));
1948
1892
 
1949
1893
  await mercury
1950
- ._attemptConnection('ws://test.com', sessionId, callback, {
1894
+ ._attemptConnection('ws://test.com', callback, {
1951
1895
  isShutdownSwitchover: true,
1952
1896
  })
1953
1897
  .catch(() => {});
@@ -1957,9 +1901,9 @@ describe('plugin-mercury', () => {
1957
1901
  });
1958
1902
 
1959
1903
  it('should check _shutdownSwitchoverBackoffCall for shutdown connections', () => {
1960
- mercury._shutdownSwitchoverBackoffCalls.clear();
1904
+ mercury._shutdownSwitchoverBackoffCall = undefined;
1961
1905
 
1962
- const result = mercury._attemptConnection('ws://test.com', sessionId, callback, {
1906
+ const result = mercury._attemptConnection('ws://test.com', callback, {
1963
1907
  isShutdownSwitchover: true,
1964
1908
  });
1965
1909
 
@@ -1971,27 +1915,25 @@ describe('plugin-mercury', () => {
1971
1915
  });
1972
1916
 
1973
1917
  describe('#_connectWithBackoff() with shutdown switchover', () => {
1974
- const sessionId = 'mercury-default-session';
1975
-
1976
1918
  it('should use shutdown-specific parameters when called', () => {
1977
1919
  const connectWithBackoffStub = sinon
1978
1920
  .stub(mercury, '_connectWithBackoff')
1979
1921
  .returns(Promise.resolve());
1980
1922
 
1981
- mercury._handleImminentShutdown(sessionId);
1923
+ mercury._handleImminentShutdown();
1982
1924
 
1983
1925
  assert.calledOnce(connectWithBackoffStub);
1984
1926
  const callArgs = connectWithBackoffStub.firstCall.args;
1985
- assert.equal(callArgs[1], sessionId);
1986
- assert.isObject(callArgs[2]);
1987
- assert.isTrue(callArgs[2].isShutdownSwitchover);
1927
+ // First arg is the URL, second is the context object
1928
+ assert.isObject(callArgs[1]);
1929
+ assert.isTrue(callArgs[1].isShutdownSwitchover);
1988
1930
 
1989
1931
  connectWithBackoffStub.restore();
1990
1932
  });
1991
1933
 
1992
1934
  it('should pass shutdown switchover options to _attemptConnection', () => {
1993
1935
  const attemptStub = sinon.stub(mercury, '_attemptConnection');
1994
- attemptStub.callsFake((url, sid, cb) => cb());
1936
+ attemptStub.callsFake((url, cb) => cb());
1995
1937
 
1996
1938
  const context = {
1997
1939
  isShutdownSwitchover: true,
@@ -2001,30 +1943,39 @@ describe('plugin-mercury', () => {
2001
1943
  },
2002
1944
  };
2003
1945
 
2004
- const promise = mercury._connectWithBackoff(undefined, sessionId, context);
1946
+ const promise = mercury._connectWithBackoff(undefined, context);
2005
1947
 
2006
1948
  return promise.then(() => {
2007
1949
  assert.calledOnce(attemptStub);
2008
1950
  const callArgs = attemptStub.firstCall.args;
2009
- assert.equal(callArgs[1], sessionId);
2010
- assert.isObject(callArgs[3]);
2011
- assert.isTrue(callArgs[3].isShutdownSwitchover);
1951
+ assert.isObject(callArgs[2]);
1952
+ assert.isTrue(callArgs[2].isShutdownSwitchover);
2012
1953
  attemptStub.restore();
2013
1954
  });
2014
1955
  });
2015
1956
 
2016
- it('should set and clear state flags appropriately', () => {
2017
- sinon.stub(mercury, '_attemptConnection').callsFake((url, sid, cb) => cb());
1957
+ it('should set _shutdownSwitchoverBackoffCall during attempt and clear it after completion', () => {
1958
+ let backoffCallDuringAttempt;
2018
1959
 
2019
- mercury._shutdownSwitchoverBackoffCalls.set(sessionId, {placeholder: true});
1960
+ sinon.stub(mercury, '_attemptConnection').callsFake((url, cb) => {
1961
+ // Capture the backoff call state during the connection attempt
1962
+ backoffCallDuringAttempt = mercury._shutdownSwitchoverBackoffCall;
1963
+ cb();
1964
+ });
2020
1965
 
2021
- const promise = mercury._connectWithBackoff(undefined, sessionId, {
1966
+ const promise = mercury._connectWithBackoff(undefined, {
2022
1967
  isShutdownSwitchover: true,
2023
1968
  attemptOptions: {isShutdownSwitchover: true, onSuccess: () => {}},
2024
1969
  });
2025
1970
 
2026
1971
  return promise.then(() => {
2027
- assert.isUndefined(mercury._shutdownSwitchoverBackoffCalls.get(sessionId));
1972
+ // Verify backoff call was set during the operation
1973
+ assert.isDefined(backoffCallDuringAttempt, 'backoffCall should be set during attempt');
1974
+ // Verify backoff call is cleared after completion
1975
+ assert.notExists(
1976
+ mercury._shutdownSwitchoverBackoffCall,
1977
+ 'backoffCall should be cleared after completion'
1978
+ );
2028
1979
  mercury._attemptConnection.restore();
2029
1980
  });
2030
1981
  });
@@ -2032,30 +1983,30 @@ describe('plugin-mercury', () => {
2032
1983
 
2033
1984
  describe('#disconnect() with shutdown switchover in progress', () => {
2034
1985
  let abortStub;
2035
- const sessionId = 'mercury-default-session';
2036
1986
 
2037
1987
  beforeEach(() => {
2038
- mercury.sockets.clear();
2039
- mercury.sockets.set(sessionId, {
2040
- close: sinon.stub().returns(Promise.resolve()),
2041
- removeAllListeners: sinon.stub(),
2042
- });
2043
1988
  abortStub = sinon.stub();
2044
- mercury._shutdownSwitchoverBackoffCalls.set(sessionId, {abort: abortStub});
1989
+ mercury._shutdownSwitchoverBackoffCall = {abort: abortStub};
2045
1990
  });
2046
1991
 
2047
- it('should abort shutdown switchover backoff call on disconnect', async () => {
2048
- await mercury.disconnect(undefined, sessionId);
1992
+ it('should abort shutdown switchover backoff call on disconnect', () => {
1993
+ // Don't set up socket so disconnect resolves immediately
1994
+ mercury.socket = undefined;
2049
1995
 
2050
- assert.calledOnce(abortStub);
1996
+ return mercury.disconnect().then(() => {
1997
+ assert.calledOnce(abortStub);
1998
+ });
2051
1999
  });
2052
2000
 
2053
- it('should handle disconnect when no switchover is in progress', async () => {
2054
- mercury._shutdownSwitchoverBackoffCalls.clear();
2001
+ it('should handle disconnect when no switchover is in progress', () => {
2002
+ mercury._shutdownSwitchoverBackoffCall = undefined;
2003
+ // Don't set up socket so disconnect resolves immediately
2004
+ mercury.socket = undefined;
2055
2005
 
2056
- await mercury.disconnect(undefined, sessionId);
2057
-
2058
- assert.calledOnce(mercury.sockets.get(sessionId).close);
2006
+ return mercury.disconnect().then(() => {
2007
+ // If we get here, disconnect completed without error
2008
+ assert.notExists(mercury._shutdownSwitchoverBackoffCall);
2009
+ });
2059
2010
  });
2060
2011
  });
2061
2012
  });