@webex/internal-plugin-mercury 3.9.0 → 3.10.0-multi-llms.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.
- package/dist/mercury.js +571 -164
- package/dist/mercury.js.map +1 -1
- package/dist/socket/socket-base.js +15 -0
- package/dist/socket/socket-base.js.map +1 -1
- package/package.json +18 -18
- package/src/mercury.js +589 -137
- package/src/socket/socket-base.js +13 -0
- package/test/unit/spec/mercury-events.js +20 -2
- package/test/unit/spec/mercury.js +737 -23
- package/test/unit/spec/socket.js +6 -6
|
@@ -75,6 +75,8 @@ describe('plugin-mercury', () => {
|
|
|
75
75
|
webex.internal.services = {
|
|
76
76
|
convertUrlToPriorityHostUrl: sinon.stub().returns(Promise.resolve('ws://example-2.com')),
|
|
77
77
|
markFailedUrl: sinon.stub().returns(Promise.resolve()),
|
|
78
|
+
switchActiveClusterIds: sinon.stub(),
|
|
79
|
+
invalidateCache: sinon.stub(),
|
|
78
80
|
};
|
|
79
81
|
webex.internal.metrics.submitClientMetrics = sinon.stub();
|
|
80
82
|
webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus = sinon.stub();
|
|
@@ -97,9 +99,32 @@ describe('plugin-mercury', () => {
|
|
|
97
99
|
});
|
|
98
100
|
|
|
99
101
|
mercury = webex.internal.mercury;
|
|
102
|
+
mercury.defaultSessionId = 'mercury-default-session';
|
|
100
103
|
});
|
|
101
104
|
|
|
102
|
-
afterEach(() => {
|
|
105
|
+
afterEach(async () => {
|
|
106
|
+
// Clean up Mercury connections and internal state
|
|
107
|
+
if (mercury) {
|
|
108
|
+
try {
|
|
109
|
+
await mercury.disconnectAll();
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// Ignore cleanup errors
|
|
112
|
+
}
|
|
113
|
+
// Clear any remaining connection promises
|
|
114
|
+
if (mercury._connectPromises) {
|
|
115
|
+
mercury._connectPromises.clear();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Ensure mock socket is properly closed
|
|
120
|
+
if (mockWebSocket && typeof mockWebSocket.close === 'function') {
|
|
121
|
+
try {
|
|
122
|
+
mockWebSocket.close();
|
|
123
|
+
} catch (e) {
|
|
124
|
+
// Ignore cleanup errors
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
103
128
|
if (socketOpenStub) {
|
|
104
129
|
socketOpenStub.restore();
|
|
105
130
|
}
|
|
@@ -107,6 +132,9 @@ describe('plugin-mercury', () => {
|
|
|
107
132
|
if (Socket.getWebSocketConstructor.restore) {
|
|
108
133
|
Socket.getWebSocketConstructor.restore();
|
|
109
134
|
}
|
|
135
|
+
|
|
136
|
+
// Small delay to ensure all async operations complete
|
|
137
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
110
138
|
});
|
|
111
139
|
|
|
112
140
|
describe('#listen()', () => {
|
|
@@ -162,7 +190,6 @@ describe('plugin-mercury', () => {
|
|
|
162
190
|
},
|
|
163
191
|
},
|
|
164
192
|
};
|
|
165
|
-
|
|
166
193
|
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
167
194
|
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
168
195
|
mockWebSocket.open();
|
|
@@ -191,6 +218,67 @@ describe('plugin-mercury', () => {
|
|
|
191
218
|
sinon.restore();
|
|
192
219
|
});
|
|
193
220
|
});
|
|
221
|
+
it('Mercury emit event:ActiveClusterStatusEvent, call services switchActiveClusterIds', () => {
|
|
222
|
+
const promise = mercury.connect();
|
|
223
|
+
const activeClusterEventEnvelope = {
|
|
224
|
+
data: {
|
|
225
|
+
activeClusters: {
|
|
226
|
+
wdm: 'wdm-cluster-id.com',
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
mockWebSocket.open();
|
|
231
|
+
|
|
232
|
+
return promise.then(() => {
|
|
233
|
+
mercury._emit('event:ActiveClusterStatusEvent', activeClusterEventEnvelope);
|
|
234
|
+
assert.calledOnceWithExactly(
|
|
235
|
+
webex.internal.services.switchActiveClusterIds,
|
|
236
|
+
activeClusterEventEnvelope.data.activeClusters
|
|
237
|
+
);
|
|
238
|
+
sinon.restore();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
it('Mercury emit event:ActiveClusterStatusEvent with no data, not call services switchActiveClusterIds', () => {
|
|
242
|
+
webex.internal.feature.updateFeature = sinon.stub();
|
|
243
|
+
const promise = mercury.connect();
|
|
244
|
+
const envelope = {};
|
|
245
|
+
|
|
246
|
+
return promise.then(() => {
|
|
247
|
+
mercury._emit('event:ActiveClusterStatusEvent', envelope);
|
|
248
|
+
assert.notCalled(webex.internal.services.switchActiveClusterIds);
|
|
249
|
+
sinon.restore();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
it('Mercury emit event:u2c.cache-invalidation, call services invalidateCache', () => {
|
|
253
|
+
const promise = mercury.connect();
|
|
254
|
+
const u2cInvalidateEventEnvelope = {
|
|
255
|
+
data: {
|
|
256
|
+
timestamp: '1759289614',
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
mockWebSocket.open();
|
|
261
|
+
|
|
262
|
+
return promise.then(() => {
|
|
263
|
+
mercury._emit('event:u2c.cache-invalidation', u2cInvalidateEventEnvelope);
|
|
264
|
+
assert.calledOnceWithExactly(
|
|
265
|
+
webex.internal.services.invalidateCache,
|
|
266
|
+
u2cInvalidateEventEnvelope.data.timestamp
|
|
267
|
+
);
|
|
268
|
+
sinon.restore();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
it('Mercury emit event:u2c.cache-invalidation with no data, not call services switchActiveClusterIds', () => {
|
|
272
|
+
webex.internal.feature.updateFeature = sinon.stub();
|
|
273
|
+
const promise = mercury.connect();
|
|
274
|
+
const envelope = {};
|
|
275
|
+
|
|
276
|
+
return promise.then(() => {
|
|
277
|
+
mercury._emit('event:u2c.cache-invalidation', envelope);
|
|
278
|
+
assert.notCalled(webex.internal.services.invalidateCache);
|
|
279
|
+
sinon.restore();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
194
282
|
|
|
195
283
|
describe('when `maxRetries` is set', () => {
|
|
196
284
|
const check = () => {
|
|
@@ -436,9 +524,13 @@ describe('plugin-mercury', () => {
|
|
|
436
524
|
|
|
437
525
|
// skipping due to apparent bug with lolex in all browsers but Chrome.
|
|
438
526
|
skipInBrowser(it)('does not continue attempting to connect', () => {
|
|
439
|
-
mercury.connect();
|
|
527
|
+
const promise = mercury.connect();
|
|
528
|
+
|
|
529
|
+
// Wait for the connection to be established before proceeding
|
|
530
|
+
mockWebSocket.open();
|
|
440
531
|
|
|
441
|
-
return
|
|
532
|
+
return promise.then(() =>
|
|
533
|
+
promiseTick(2)
|
|
442
534
|
.then(() => {
|
|
443
535
|
clock.tick(6 * webex.internal.mercury.config.backoffTimeReset);
|
|
444
536
|
|
|
@@ -446,7 +538,8 @@ describe('plugin-mercury', () => {
|
|
|
446
538
|
})
|
|
447
539
|
.then(() => {
|
|
448
540
|
assert.calledOnce(Socket.prototype.open);
|
|
449
|
-
|
|
541
|
+
})
|
|
542
|
+
);
|
|
450
543
|
});
|
|
451
544
|
});
|
|
452
545
|
|
|
@@ -521,11 +614,11 @@ describe('plugin-mercury', () => {
|
|
|
521
614
|
});
|
|
522
615
|
|
|
523
616
|
describe('#logout()', () => {
|
|
524
|
-
it('calls
|
|
617
|
+
it('calls disconnectAll and logs', () => {
|
|
525
618
|
sinon.stub(mercury.logger, 'info');
|
|
526
|
-
sinon.stub(mercury, '
|
|
619
|
+
sinon.stub(mercury, 'disconnectAll');
|
|
527
620
|
mercury.logout();
|
|
528
|
-
assert.called(mercury.
|
|
621
|
+
assert.called(mercury.disconnectAll);
|
|
529
622
|
assert.calledTwice(mercury.logger.info);
|
|
530
623
|
|
|
531
624
|
assert.calledWith(mercury.logger.info.getCall(0), 'Mercury: logout() called');
|
|
@@ -537,24 +630,24 @@ describe('plugin-mercury', () => {
|
|
|
537
630
|
});
|
|
538
631
|
|
|
539
632
|
it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send code 3050 for logout', () => {
|
|
540
|
-
sinon.stub(mercury, '
|
|
633
|
+
sinon.stub(mercury, 'disconnectAll');
|
|
541
634
|
mercury.config.beforeLogoutOptionsCloseReason = 'done (permanent)';
|
|
542
635
|
mercury.logout();
|
|
543
|
-
assert.calledWith(mercury.
|
|
636
|
+
assert.calledWith(mercury.disconnectAll, {code: 3050, reason: 'done (permanent)'});
|
|
544
637
|
});
|
|
545
638
|
|
|
546
639
|
it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send code 3050 for logout if the reason is different than standard', () => {
|
|
547
|
-
sinon.stub(mercury, '
|
|
640
|
+
sinon.stub(mercury, 'disconnectAll');
|
|
548
641
|
mercury.config.beforeLogoutOptionsCloseReason = 'test';
|
|
549
642
|
mercury.logout();
|
|
550
|
-
assert.calledWith(mercury.
|
|
643
|
+
assert.calledWith(mercury.disconnectAll, {code: 3050, reason: 'test'});
|
|
551
644
|
});
|
|
552
645
|
|
|
553
646
|
it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send undefined for logout if the reason is same as standard', () => {
|
|
554
|
-
sinon.stub(mercury, '
|
|
647
|
+
sinon.stub(mercury, 'disconnectAll');
|
|
555
648
|
mercury.config.beforeLogoutOptionsCloseReason = 'done (forced)';
|
|
556
649
|
mercury.logout();
|
|
557
|
-
assert.calledWith(mercury.
|
|
650
|
+
assert.calledWith(mercury.disconnectAll, undefined);
|
|
558
651
|
});
|
|
559
652
|
});
|
|
560
653
|
|
|
@@ -660,12 +753,12 @@ describe('plugin-mercury', () => {
|
|
|
660
753
|
return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
|
|
661
754
|
// By this time backoffCall and mercury socket should be defined by the
|
|
662
755
|
// 'connect' call
|
|
663
|
-
assert.isDefined(mercury.
|
|
756
|
+
assert.isDefined(mercury.backoffCalls.get('mercury-default-session'), 'Mercury backoffCall is not defined');
|
|
664
757
|
assert.isDefined(mercury.socket, 'Mercury socket is not defined');
|
|
665
758
|
// Calling disconnect will abort the backoffCall, close the socket, and
|
|
666
759
|
// reject the connect
|
|
667
760
|
mercury.disconnect();
|
|
668
|
-
assert.isUndefined(mercury.
|
|
761
|
+
assert.isUndefined(mercury.backoffCalls.get('mercury-default-session'), 'Mercury backoffCall is still defined');
|
|
669
762
|
// The socket will never be unset (which seems bad)
|
|
670
763
|
assert.isDefined(mercury.socket, 'Mercury socket is not defined');
|
|
671
764
|
|
|
@@ -683,15 +776,15 @@ describe('plugin-mercury', () => {
|
|
|
683
776
|
|
|
684
777
|
let reason;
|
|
685
778
|
|
|
686
|
-
mercury.
|
|
687
|
-
mercury._attemptConnection('ws://example.com', (_reason) => {
|
|
779
|
+
mercury.backoffCalls.clear();
|
|
780
|
+
mercury._attemptConnection('ws://example.com', 'mercury-default-session',(_reason) => {
|
|
688
781
|
reason = _reason;
|
|
689
782
|
});
|
|
690
783
|
|
|
691
784
|
return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
|
|
692
785
|
assert.equal(
|
|
693
786
|
reason.message,
|
|
694
|
-
|
|
787
|
+
`Mercury: prevent socket open when backoffCall no longer defined for ${mercury.defaultSessionId}`
|
|
695
788
|
);
|
|
696
789
|
});
|
|
697
790
|
});
|
|
@@ -713,7 +806,7 @@ describe('plugin-mercury', () => {
|
|
|
713
806
|
return assert.isRejected(promise).then((error) => {
|
|
714
807
|
const lastError = mercury.getLastError();
|
|
715
808
|
|
|
716
|
-
assert.equal(error.message,
|
|
809
|
+
assert.equal(error.message, `Mercury Connection Aborted for ${mercury.defaultSessionId}`);
|
|
717
810
|
assert.isDefined(lastError);
|
|
718
811
|
assert.equal(lastError, realError);
|
|
719
812
|
});
|
|
@@ -807,7 +900,7 @@ describe('plugin-mercury', () => {
|
|
|
807
900
|
},
|
|
808
901
|
};
|
|
809
902
|
assert.isUndefined(mercury.mercuryTimeOffset);
|
|
810
|
-
mercury._setTimeOffset(event);
|
|
903
|
+
mercury._setTimeOffset('mercury-default-session', event);
|
|
811
904
|
assert.isDefined(mercury.mercuryTimeOffset);
|
|
812
905
|
assert.isTrue(mercury.mercuryTimeOffset > 0);
|
|
813
906
|
});
|
|
@@ -817,7 +910,7 @@ describe('plugin-mercury', () => {
|
|
|
817
910
|
wsWriteTimestamp: Date.now() + 60000,
|
|
818
911
|
},
|
|
819
912
|
};
|
|
820
|
-
mercury._setTimeOffset(event);
|
|
913
|
+
mercury._setTimeOffset('mercury-default-session', event);
|
|
821
914
|
assert.isTrue(mercury.mercuryTimeOffset < 0);
|
|
822
915
|
});
|
|
823
916
|
it('handles invalid wsWriteTimestamp', () => {
|
|
@@ -828,7 +921,7 @@ describe('plugin-mercury', () => {
|
|
|
828
921
|
wsWriteTimestamp: invalidTimestamp,
|
|
829
922
|
},
|
|
830
923
|
};
|
|
831
|
-
mercury._setTimeOffset(event);
|
|
924
|
+
mercury._setTimeOffset('mercury-default-session', event);
|
|
832
925
|
assert.isUndefined(mercury.mercuryTimeOffset);
|
|
833
926
|
});
|
|
834
927
|
});
|
|
@@ -922,5 +1015,626 @@ describe('plugin-mercury', () => {
|
|
|
922
1015
|
});
|
|
923
1016
|
});
|
|
924
1017
|
});
|
|
1018
|
+
|
|
1019
|
+
describe('shutdown protocol', () => {
|
|
1020
|
+
describe('#_handleImminentShutdown()', () => {
|
|
1021
|
+
let connectWithBackoffStub;
|
|
1022
|
+
|
|
1023
|
+
beforeEach(() => {
|
|
1024
|
+
mercury.connected = true;
|
|
1025
|
+
mercury.socket = {
|
|
1026
|
+
url: 'ws://old-socket.com',
|
|
1027
|
+
removeAllListeners: sinon.stub(),
|
|
1028
|
+
};
|
|
1029
|
+
connectWithBackoffStub = sinon.stub(mercury, '_connectWithBackoff');
|
|
1030
|
+
connectWithBackoffStub.returns(Promise.resolve());
|
|
1031
|
+
sinon.stub(mercury, '_emit');
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
afterEach(() => {
|
|
1035
|
+
connectWithBackoffStub.restore();
|
|
1036
|
+
mercury._emit.restore();
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
it('should be idempotent - no-op if already in progress', () => {
|
|
1040
|
+
mercury._shutdownSwitchoverInProgress = true;
|
|
1041
|
+
|
|
1042
|
+
mercury._handleImminentShutdown();
|
|
1043
|
+
|
|
1044
|
+
assert.notCalled(connectWithBackoffStub);
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
it('should set switchover flags when called', () => {
|
|
1048
|
+
mercury._handleImminentShutdown();
|
|
1049
|
+
|
|
1050
|
+
assert.isTrue(mercury._shutdownSwitchoverInProgress);
|
|
1051
|
+
assert.isDefined(mercury._shutdownSwitchoverId);
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
it('should call _connectWithBackoff with correct parameters', (done) => {
|
|
1055
|
+
mercury._handleImminentShutdown();
|
|
1056
|
+
|
|
1057
|
+
process.nextTick(() => {
|
|
1058
|
+
assert.calledOnce(connectWithBackoffStub);
|
|
1059
|
+
const callArgs = connectWithBackoffStub.firstCall.args;
|
|
1060
|
+
assert.isUndefined(callArgs[0]); // webSocketUrl
|
|
1061
|
+
assert.isObject(callArgs[1]); // context
|
|
1062
|
+
assert.isTrue(callArgs[1].isShutdownSwitchover);
|
|
1063
|
+
done();
|
|
1064
|
+
});
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
it('should handle exceptions during switchover', () => {
|
|
1068
|
+
connectWithBackoffStub.restore();
|
|
1069
|
+
sinon.stub(mercury, '_connectWithBackoff').throws(new Error('Connection failed'));
|
|
1070
|
+
|
|
1071
|
+
mercury._handleImminentShutdown();
|
|
1072
|
+
|
|
1073
|
+
assert.isFalse(mercury._shutdownSwitchoverInProgress);
|
|
1074
|
+
});
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
describe('#_onmessage() with shutdown message', () => {
|
|
1078
|
+
beforeEach(() => {
|
|
1079
|
+
sinon.stub(mercury, '_handleImminentShutdown');
|
|
1080
|
+
sinon.stub(mercury, '_emit');
|
|
1081
|
+
sinon.stub(mercury, '_setTimeOffset');
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
afterEach(() => {
|
|
1085
|
+
mercury._handleImminentShutdown.restore();
|
|
1086
|
+
mercury._emit.restore();
|
|
1087
|
+
mercury._setTimeOffset.restore();
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
it('should trigger _handleImminentShutdown on shutdown message', () => {
|
|
1091
|
+
const shutdownEvent = {
|
|
1092
|
+
data: {
|
|
1093
|
+
type: 'shutdown',
|
|
1094
|
+
},
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
const result = mercury._onmessage(shutdownEvent);
|
|
1098
|
+
|
|
1099
|
+
assert.calledOnce(mercury._handleImminentShutdown);
|
|
1100
|
+
assert.calledWith(mercury._emit, 'event:mercury_shutdown_imminent', shutdownEvent.data);
|
|
1101
|
+
assert.instanceOf(result, Promise);
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
it('should handle shutdown message without additional data gracefully', () => {
|
|
1105
|
+
const shutdownEvent = {
|
|
1106
|
+
data: {
|
|
1107
|
+
type: 'shutdown',
|
|
1108
|
+
},
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
mercury._onmessage(shutdownEvent);
|
|
1112
|
+
|
|
1113
|
+
assert.calledOnce(mercury._handleImminentShutdown);
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
it('should not trigger shutdown handling for non-shutdown messages', () => {
|
|
1117
|
+
const regularEvent = {
|
|
1118
|
+
data: {
|
|
1119
|
+
type: 'regular',
|
|
1120
|
+
data: {
|
|
1121
|
+
eventType: 'conversation.activity',
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
mercury._onmessage(regularEvent);
|
|
1127
|
+
|
|
1128
|
+
assert.notCalled(mercury._handleImminentShutdown);
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
describe('#_onclose() with code 4001 (shutdown replacement)', () => {
|
|
1133
|
+
let mockSocket, anotherSocket;
|
|
1134
|
+
|
|
1135
|
+
beforeEach(() => {
|
|
1136
|
+
mockSocket = {
|
|
1137
|
+
url: 'ws://active-socket.com',
|
|
1138
|
+
removeAllListeners: sinon.stub(),
|
|
1139
|
+
};
|
|
1140
|
+
anotherSocket = {
|
|
1141
|
+
url: 'ws://old-socket.com',
|
|
1142
|
+
removeAllListeners: sinon.stub(),
|
|
1143
|
+
};
|
|
1144
|
+
mercury.socket = mockSocket;
|
|
1145
|
+
mercury.connected = true;
|
|
1146
|
+
sinon.stub(mercury, '_emit');
|
|
1147
|
+
sinon.stub(mercury, '_reconnect');
|
|
1148
|
+
sinon.stub(mercury, 'unset');
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
afterEach(() => {
|
|
1152
|
+
mercury._emit.restore();
|
|
1153
|
+
mercury._reconnect.restore();
|
|
1154
|
+
mercury.unset.restore();
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
it('should handle active socket close with 4001 - permanent failure', () => {
|
|
1158
|
+
const closeEvent = {
|
|
1159
|
+
code: 4001,
|
|
1160
|
+
reason: 'replaced during shutdown',
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
mercury._onclose(closeEvent, mockSocket);
|
|
1164
|
+
|
|
1165
|
+
assert.calledWith(mercury._emit, 'offline.permanent', closeEvent);
|
|
1166
|
+
assert.notCalled(mercury._reconnect); // No reconnect for 4001 on active socket
|
|
1167
|
+
assert.isFalse(mercury.connected);
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
it('should handle non-active socket close with 4001 - no reconnect needed', () => {
|
|
1171
|
+
const closeEvent = {
|
|
1172
|
+
code: 4001,
|
|
1173
|
+
reason: 'replaced during shutdown',
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
mercury._onclose(closeEvent, anotherSocket);
|
|
1177
|
+
|
|
1178
|
+
assert.calledWith(mercury._emit, 'offline.replaced', closeEvent);
|
|
1179
|
+
assert.notCalled(mercury._reconnect);
|
|
1180
|
+
assert.isTrue(mercury.connected); // Should remain connected
|
|
1181
|
+
assert.notCalled(mercury.unset);
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
it('should distinguish between active and non-active socket closes', () => {
|
|
1185
|
+
const closeEvent = {
|
|
1186
|
+
code: 4001,
|
|
1187
|
+
reason: 'replaced during shutdown',
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
// Test non-active socket
|
|
1191
|
+
mercury._onclose(closeEvent, anotherSocket);
|
|
1192
|
+
assert.calledWith(mercury._emit, 'offline.replaced', closeEvent);
|
|
1193
|
+
|
|
1194
|
+
// Reset the spy call history
|
|
1195
|
+
mercury._emit.resetHistory();
|
|
1196
|
+
|
|
1197
|
+
// Test active socket
|
|
1198
|
+
mercury._onclose(closeEvent, mockSocket);
|
|
1199
|
+
assert.calledWith(mercury._emit, 'offline.permanent', closeEvent);
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
it('should handle missing sourceSocket parameter (treats as non-active)', () => {
|
|
1203
|
+
const closeEvent = {
|
|
1204
|
+
code: 4001,
|
|
1205
|
+
reason: 'replaced during shutdown',
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
mercury._onclose(closeEvent); // No sourceSocket parameter
|
|
1209
|
+
|
|
1210
|
+
// With simplified logic, undefined !== this.socket, so isActiveSocket = false
|
|
1211
|
+
assert.calledWith(mercury._emit, 'offline.replaced', closeEvent);
|
|
1212
|
+
assert.notCalled(mercury._reconnect);
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
it('should clean up event listeners from non-active socket when it closes', () => {
|
|
1216
|
+
const closeEvent = {
|
|
1217
|
+
code: 4001,
|
|
1218
|
+
reason: 'replaced during shutdown',
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
// Close non-active socket (not the active one)
|
|
1222
|
+
mercury._onclose(closeEvent, anotherSocket);
|
|
1223
|
+
|
|
1224
|
+
// Verify listeners were removed from the old socket
|
|
1225
|
+
// The _onclose method checks if sourceSocket !== this.socket (non-active)
|
|
1226
|
+
// and then calls removeAllListeners in the else branch
|
|
1227
|
+
assert.calledOnce(anotherSocket.removeAllListeners);
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
it('should not clean up listeners from active socket listeners until close handler runs', () => {
|
|
1231
|
+
const closeEvent = {
|
|
1232
|
+
code: 4001,
|
|
1233
|
+
reason: 'replaced during shutdown',
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
// Close active socket
|
|
1237
|
+
mercury._onclose(closeEvent, mockSocket);
|
|
1238
|
+
|
|
1239
|
+
// Verify listeners were removed from active socket
|
|
1240
|
+
assert.calledOnce(mockSocket.removeAllListeners);
|
|
1241
|
+
});
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
describe('shutdown switchover with retry logic', () => {
|
|
1245
|
+
let connectWithBackoffStub;
|
|
1246
|
+
|
|
1247
|
+
beforeEach(() => {
|
|
1248
|
+
mercury.connected = true;
|
|
1249
|
+
mercury.socket = {
|
|
1250
|
+
url: 'ws://old-socket.com',
|
|
1251
|
+
removeAllListeners: sinon.stub(),
|
|
1252
|
+
};
|
|
1253
|
+
connectWithBackoffStub = sinon.stub(mercury, '_connectWithBackoff');
|
|
1254
|
+
sinon.stub(mercury, '_emit');
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
afterEach(() => {
|
|
1258
|
+
connectWithBackoffStub.restore();
|
|
1259
|
+
mercury._emit.restore();
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
it('should call _connectWithBackoff with shutdown switchover context', (done) => {
|
|
1263
|
+
connectWithBackoffStub.returns(Promise.resolve());
|
|
1264
|
+
|
|
1265
|
+
mercury._handleImminentShutdown();
|
|
1266
|
+
|
|
1267
|
+
// Give it a tick for the async call to happen
|
|
1268
|
+
process.nextTick(() => {
|
|
1269
|
+
assert.calledOnce(connectWithBackoffStub);
|
|
1270
|
+
const callArgs = connectWithBackoffStub.firstCall.args;
|
|
1271
|
+
|
|
1272
|
+
assert.isUndefined(callArgs[0]); // webSocketUrl is undefined
|
|
1273
|
+
assert.isObject(callArgs[1]); // context object
|
|
1274
|
+
assert.isTrue(callArgs[1].isShutdownSwitchover);
|
|
1275
|
+
assert.isObject(callArgs[1].attemptOptions);
|
|
1276
|
+
assert.isTrue(callArgs[1].attemptOptions.isShutdownSwitchover);
|
|
1277
|
+
done();
|
|
1278
|
+
});
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
it('should set _shutdownSwitchoverInProgress flag during switchover', () => {
|
|
1282
|
+
connectWithBackoffStub.returns(new Promise(() => {})); // Never resolves
|
|
1283
|
+
|
|
1284
|
+
mercury._handleImminentShutdown();
|
|
1285
|
+
|
|
1286
|
+
assert.isTrue(mercury._shutdownSwitchoverInProgress);
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
it('should emit success event when switchover completes', async () => {
|
|
1290
|
+
// We need to actually call the onSuccess callback to trigger the event
|
|
1291
|
+
connectWithBackoffStub.callsFake((url, context) => {
|
|
1292
|
+
// Simulate successful connection by calling onSuccess
|
|
1293
|
+
if (context && context.attemptOptions && context.attemptOptions.onSuccess) {
|
|
1294
|
+
const mockSocket = {url: 'ws://new-socket.com'};
|
|
1295
|
+
context.attemptOptions.onSuccess(mockSocket, 'ws://new-socket.com');
|
|
1296
|
+
}
|
|
1297
|
+
return Promise.resolve();
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
mercury._handleImminentShutdown();
|
|
1301
|
+
|
|
1302
|
+
// Wait for async operations
|
|
1303
|
+
await promiseTick(50);
|
|
1304
|
+
|
|
1305
|
+
const emitCalls = mercury._emit.getCalls();
|
|
1306
|
+
const hasCompleteEvent = emitCalls.some(
|
|
1307
|
+
(call) => call.args[0] === 'event:mercury_shutdown_switchover_complete'
|
|
1308
|
+
);
|
|
1309
|
+
|
|
1310
|
+
assert.isTrue(hasCompleteEvent, 'Should emit switchover complete event');
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
it('should emit failure event when switchover exhausts retries', async () => {
|
|
1314
|
+
const testError = new Error('Connection failed');
|
|
1315
|
+
|
|
1316
|
+
connectWithBackoffStub.returns(Promise.reject(testError));
|
|
1317
|
+
|
|
1318
|
+
mercury._handleImminentShutdown();
|
|
1319
|
+
await promiseTick(50);
|
|
1320
|
+
|
|
1321
|
+
// Check if failure event was emitted
|
|
1322
|
+
const emitCalls = mercury._emit.getCalls();
|
|
1323
|
+
const hasFailureEvent = emitCalls.some(
|
|
1324
|
+
(call) =>
|
|
1325
|
+
call.args[0] === 'event:mercury_shutdown_switchover_failed' &&
|
|
1326
|
+
call.args[1] &&
|
|
1327
|
+
call.args[1].reason === testError
|
|
1328
|
+
);
|
|
1329
|
+
|
|
1330
|
+
assert.isTrue(hasFailureEvent, 'Should emit switchover failed event');
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
it('should allow old socket to be closed by server after switchover failure', async () => {
|
|
1334
|
+
connectWithBackoffStub.returns(Promise.reject(new Error('Failed')));
|
|
1335
|
+
|
|
1336
|
+
mercury._handleImminentShutdown();
|
|
1337
|
+
await promiseTick(50);
|
|
1338
|
+
|
|
1339
|
+
// Old socket should not be closed immediately - server will close it
|
|
1340
|
+
assert.equal(mercury.socket.removeAllListeners.callCount, 0);
|
|
1341
|
+
});
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
describe('#_prepareAndOpenSocket()', () => {
|
|
1345
|
+
let mockSocket, prepareUrlStub, getUserTokenStub;
|
|
1346
|
+
|
|
1347
|
+
beforeEach(() => {
|
|
1348
|
+
mockSocket = {
|
|
1349
|
+
open: sinon.stub().returns(Promise.resolve()),
|
|
1350
|
+
};
|
|
1351
|
+
prepareUrlStub = sinon
|
|
1352
|
+
.stub(mercury, '_prepareUrl')
|
|
1353
|
+
.returns(Promise.resolve('ws://example.com'));
|
|
1354
|
+
getUserTokenStub = webex.credentials.getUserToken;
|
|
1355
|
+
getUserTokenStub.returns(
|
|
1356
|
+
Promise.resolve({
|
|
1357
|
+
toString: () => 'mock-token',
|
|
1358
|
+
})
|
|
1359
|
+
);
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
afterEach(() => {
|
|
1363
|
+
prepareUrlStub.restore();
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
it('should prepare URL and get user token', async () => {
|
|
1367
|
+
await mercury._prepareAndOpenSocket(mockSocket, 'ws://test.com', false);
|
|
1368
|
+
|
|
1369
|
+
assert.calledOnce(prepareUrlStub);
|
|
1370
|
+
assert.calledWith(prepareUrlStub, 'ws://test.com');
|
|
1371
|
+
assert.calledOnce(getUserTokenStub);
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
it('should open socket with correct options for normal connection', async () => {
|
|
1375
|
+
await mercury._prepareAndOpenSocket(mockSocket, undefined, false);
|
|
1376
|
+
|
|
1377
|
+
assert.calledOnce(mockSocket.open);
|
|
1378
|
+
const callArgs = mockSocket.open.firstCall.args;
|
|
1379
|
+
|
|
1380
|
+
assert.equal(callArgs[0], 'ws://example.com');
|
|
1381
|
+
assert.isObject(callArgs[1]);
|
|
1382
|
+
assert.equal(callArgs[1].token, 'mock-token');
|
|
1383
|
+
assert.isDefined(callArgs[1].forceCloseDelay);
|
|
1384
|
+
assert.isDefined(callArgs[1].pingInterval);
|
|
1385
|
+
assert.isDefined(callArgs[1].pongTimeout);
|
|
1386
|
+
});
|
|
1387
|
+
|
|
1388
|
+
it('should log with correct prefix for normal connection', async () => {
|
|
1389
|
+
await mercury._prepareAndOpenSocket(mockSocket, undefined, false);
|
|
1390
|
+
|
|
1391
|
+
// The method should complete successfully - we're testing it runs without error
|
|
1392
|
+
// Actual log message verification is complex due to existing stubs in parent scope
|
|
1393
|
+
assert.calledOnce(mockSocket.open);
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
it('should log with shutdown prefix for shutdown connection', async () => {
|
|
1397
|
+
await mercury._prepareAndOpenSocket(mockSocket, undefined, true);
|
|
1398
|
+
|
|
1399
|
+
// The method should complete successfully with shutdown flag
|
|
1400
|
+
assert.calledOnce(mockSocket.open);
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
it('should merge custom mercury options when provided', async () => {
|
|
1404
|
+
webex.config.defaultMercuryOptions = {
|
|
1405
|
+
customOption: 'test-value',
|
|
1406
|
+
pingInterval: 99999,
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
await mercury._prepareAndOpenSocket(mockSocket, undefined, false);
|
|
1410
|
+
|
|
1411
|
+
const callArgs = mockSocket.open.firstCall.args;
|
|
1412
|
+
|
|
1413
|
+
assert.equal(callArgs[1].customOption, 'test-value');
|
|
1414
|
+
assert.equal(callArgs[1].pingInterval, 99999); // Custom value overrides default
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
it('should return the webSocketUrl after opening', async () => {
|
|
1418
|
+
const result = await mercury._prepareAndOpenSocket(mockSocket, undefined, false);
|
|
1419
|
+
|
|
1420
|
+
assert.equal(result, 'ws://example.com');
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
it('should handle errors during socket open', async () => {
|
|
1424
|
+
mockSocket.open.returns(Promise.reject(new Error('Open failed')));
|
|
1425
|
+
|
|
1426
|
+
try {
|
|
1427
|
+
await mercury._prepareAndOpenSocket(mockSocket, undefined, false);
|
|
1428
|
+
assert.fail('Should have thrown an error');
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
assert.equal(err.message, 'Open failed');
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
});
|
|
1434
|
+
|
|
1435
|
+
describe('#_attemptConnection() with shutdown switchover', () => {
|
|
1436
|
+
let mockSocket, prepareAndOpenSocketStub, callback;
|
|
1437
|
+
|
|
1438
|
+
beforeEach(() => {
|
|
1439
|
+
mockSocket = {
|
|
1440
|
+
url: 'ws://test.com',
|
|
1441
|
+
};
|
|
1442
|
+
prepareAndOpenSocketStub = sinon
|
|
1443
|
+
.stub(mercury, '_prepareAndOpenSocket')
|
|
1444
|
+
.returns(Promise.resolve('ws://new-socket.com'));
|
|
1445
|
+
callback = sinon.stub();
|
|
1446
|
+
mercury._shutdownSwitchoverBackoffCall = {}; // Mock backoff call
|
|
1447
|
+
mercury.socket = mockSocket;
|
|
1448
|
+
mercury.connected = true;
|
|
1449
|
+
sinon.stub(mercury, '_emit');
|
|
1450
|
+
sinon.stub(mercury, '_attachSocketEventListeners');
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
afterEach(() => {
|
|
1454
|
+
prepareAndOpenSocketStub.restore();
|
|
1455
|
+
mercury._emit.restore();
|
|
1456
|
+
mercury._attachSocketEventListeners.restore();
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
it('should not set socket reference before opening for shutdown switchover', async () => {
|
|
1460
|
+
const originalSocket = mercury.socket;
|
|
1461
|
+
|
|
1462
|
+
await mercury._attemptConnection('ws://test.com', callback, {
|
|
1463
|
+
isShutdownSwitchover: true,
|
|
1464
|
+
onSuccess: (newSocket, url) => {
|
|
1465
|
+
// During onSuccess, verify original socket is still set
|
|
1466
|
+
// (socket swap happens inside onSuccess callback in _handleImminentShutdown)
|
|
1467
|
+
assert.equal(mercury.socket, originalSocket);
|
|
1468
|
+
},
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
// After onSuccess, socket should still be original since we only swap in _handleImminentShutdown
|
|
1472
|
+
assert.equal(mercury.socket, originalSocket);
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
it('should call onSuccess callback with new socket and URL for shutdown', async () => {
|
|
1476
|
+
const onSuccessStub = sinon.stub();
|
|
1477
|
+
|
|
1478
|
+
await mercury._attemptConnection('ws://test.com', callback, {
|
|
1479
|
+
isShutdownSwitchover: true,
|
|
1480
|
+
onSuccess: onSuccessStub,
|
|
1481
|
+
});
|
|
1482
|
+
|
|
1483
|
+
assert.calledOnce(onSuccessStub);
|
|
1484
|
+
assert.equal(onSuccessStub.firstCall.args[1], 'ws://new-socket.com');
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
it('should emit shutdown switchover complete event', async () => {
|
|
1488
|
+
const oldSocket = mercury.socket;
|
|
1489
|
+
|
|
1490
|
+
await mercury._attemptConnection('ws://test.com', callback, {
|
|
1491
|
+
isShutdownSwitchover: true,
|
|
1492
|
+
onSuccess: (newSocket, url) => {
|
|
1493
|
+
// Simulate the onSuccess callback behavior
|
|
1494
|
+
mercury.socket = newSocket;
|
|
1495
|
+
mercury.connected = true;
|
|
1496
|
+
mercury._emit('event:mercury_shutdown_switchover_complete', {url});
|
|
1497
|
+
},
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
assert.calledWith(
|
|
1501
|
+
mercury._emit,
|
|
1502
|
+
'event:mercury_shutdown_switchover_complete',
|
|
1503
|
+
sinon.match.has('url', 'ws://new-socket.com')
|
|
1504
|
+
);
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
it('should use simpler error handling for shutdown switchover failures', async () => {
|
|
1508
|
+
prepareAndOpenSocketStub.returns(Promise.reject(new Error('Connection failed')));
|
|
1509
|
+
|
|
1510
|
+
try {
|
|
1511
|
+
await mercury._attemptConnection('ws://test.com', callback, {
|
|
1512
|
+
isShutdownSwitchover: true,
|
|
1513
|
+
});
|
|
1514
|
+
} catch (err) {
|
|
1515
|
+
// Error should be caught and passed to callback
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// Should call callback with error for retry
|
|
1519
|
+
assert.calledOnce(callback);
|
|
1520
|
+
assert.instanceOf(callback.firstCall.args[0], Error);
|
|
1521
|
+
});
|
|
1522
|
+
|
|
1523
|
+
it('should check _shutdownSwitchoverBackoffCall for shutdown connections', () => {
|
|
1524
|
+
mercury._shutdownSwitchoverBackoffCall = undefined;
|
|
1525
|
+
|
|
1526
|
+
const result = mercury._attemptConnection('ws://test.com', callback, {
|
|
1527
|
+
isShutdownSwitchover: true,
|
|
1528
|
+
});
|
|
1529
|
+
|
|
1530
|
+
return result.catch((err) => {
|
|
1531
|
+
assert.instanceOf(err, Error);
|
|
1532
|
+
assert.match(err.message, /switchover backoff call/);
|
|
1533
|
+
});
|
|
1534
|
+
});
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
describe('#_connectWithBackoff() with shutdown switchover', () => {
|
|
1538
|
+
// Note: These tests verify the parameterization logic without running real backoff timers
|
|
1539
|
+
// to avoid test hangs. The backoff mechanism itself is tested in other test suites.
|
|
1540
|
+
|
|
1541
|
+
it('should use shutdown-specific parameters when called', () => {
|
|
1542
|
+
// Stub _connectWithBackoff to prevent real execution
|
|
1543
|
+
const connectWithBackoffStub = sinon
|
|
1544
|
+
.stub(mercury, '_connectWithBackoff')
|
|
1545
|
+
.returns(Promise.resolve());
|
|
1546
|
+
|
|
1547
|
+
mercury._handleImminentShutdown();
|
|
1548
|
+
|
|
1549
|
+
// Verify it was called with shutdown context
|
|
1550
|
+
assert.calledOnce(connectWithBackoffStub);
|
|
1551
|
+
const callArgs = connectWithBackoffStub.firstCall.args;
|
|
1552
|
+
assert.isObject(callArgs[1]); // context
|
|
1553
|
+
assert.isTrue(callArgs[1].isShutdownSwitchover);
|
|
1554
|
+
|
|
1555
|
+
connectWithBackoffStub.restore();
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
it('should pass shutdown switchover options to _attemptConnection', () => {
|
|
1559
|
+
// Stub _attemptConnection to verify it receives correct options
|
|
1560
|
+
const attemptStub = sinon.stub(mercury, '_attemptConnection');
|
|
1561
|
+
attemptStub.callsFake((url, callback) => {
|
|
1562
|
+
// Immediately succeed
|
|
1563
|
+
callback();
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
// Call _connectWithBackoff with shutdown context
|
|
1567
|
+
const context = {
|
|
1568
|
+
isShutdownSwitchover: true,
|
|
1569
|
+
attemptOptions: {
|
|
1570
|
+
isShutdownSwitchover: true,
|
|
1571
|
+
onSuccess: () => {},
|
|
1572
|
+
},
|
|
1573
|
+
};
|
|
1574
|
+
|
|
1575
|
+
// Start the backoff
|
|
1576
|
+
const promise = mercury._connectWithBackoff(undefined, context);
|
|
1577
|
+
|
|
1578
|
+
// Check that _attemptConnection was called with shutdown options
|
|
1579
|
+
return promise.then(() => {
|
|
1580
|
+
assert.calledOnce(attemptStub);
|
|
1581
|
+
const callArgs = attemptStub.firstCall.args;
|
|
1582
|
+
assert.isObject(callArgs[2]); // options parameter
|
|
1583
|
+
assert.isTrue(callArgs[2].isShutdownSwitchover);
|
|
1584
|
+
|
|
1585
|
+
attemptStub.restore();
|
|
1586
|
+
});
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
it('should set and clear state flags appropriately', () => {
|
|
1590
|
+
// Stub to prevent actual connection
|
|
1591
|
+
sinon.stub(mercury, '_attemptConnection').callsFake((url, callback) => callback());
|
|
1592
|
+
|
|
1593
|
+
mercury._shutdownSwitchoverInProgress = true;
|
|
1594
|
+
|
|
1595
|
+
const promise = mercury._connectWithBackoff(undefined, {
|
|
1596
|
+
isShutdownSwitchover: true,
|
|
1597
|
+
attemptOptions: {isShutdownSwitchover: true, onSuccess: () => {}},
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
return promise.then(() => {
|
|
1601
|
+
// Should be cleared after completion
|
|
1602
|
+
assert.isFalse(mercury._shutdownSwitchoverInProgress);
|
|
1603
|
+
mercury._attemptConnection.restore();
|
|
1604
|
+
});
|
|
1605
|
+
});
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
describe('#disconnect() with shutdown switchover in progress', () => {
|
|
1609
|
+
let abortStub;
|
|
1610
|
+
|
|
1611
|
+
beforeEach(() => {
|
|
1612
|
+
mercury.socket = {
|
|
1613
|
+
close: sinon.stub().returns(Promise.resolve()),
|
|
1614
|
+
removeAllListeners: sinon.stub(),
|
|
1615
|
+
};
|
|
1616
|
+
abortStub = sinon.stub();
|
|
1617
|
+
mercury._shutdownSwitchoverBackoffCall = {
|
|
1618
|
+
abort: abortStub,
|
|
1619
|
+
};
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1622
|
+
it('should abort shutdown switchover backoff call on disconnect', async () => {
|
|
1623
|
+
await mercury.disconnect();
|
|
1624
|
+
|
|
1625
|
+
assert.calledOnce(abortStub);
|
|
1626
|
+
});
|
|
1627
|
+
|
|
1628
|
+
it('should handle disconnect when no switchover is in progress', async () => {
|
|
1629
|
+
mercury._shutdownSwitchoverBackoffCall = undefined;
|
|
1630
|
+
|
|
1631
|
+
// Should not throw
|
|
1632
|
+
await mercury.disconnect();
|
|
1633
|
+
|
|
1634
|
+
// Should still close the socket
|
|
1635
|
+
assert.calledOnce(mercury.socket.close);
|
|
1636
|
+
});
|
|
1637
|
+
});
|
|
1638
|
+
});
|
|
925
1639
|
});
|
|
926
1640
|
});
|