@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.
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/mercury-plugin.js +125 -0
- package/dist/mercury-plugin.js.map +1 -0
- package/dist/mercury.js +241 -419
- package/dist/mercury.js.map +1 -1
- package/package.json +17 -17
- package/src/index.js +3 -2
- package/src/mercury-plugin.ts +79 -0
- package/src/mercury.js +198 -404
- package/test/unit/spec/mercury-events.js +2 -5
- package/test/unit/spec/mercury-plugin.js +280 -0
- package/test/unit/spec/mercury.js +233 -282
|
@@ -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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
// Abort any pending backoff calls first
|
|
110
|
+
if (mercury.backoffCall) {
|
|
111
|
+
mercury.backoffCall.abort();
|
|
112
|
+
mercury.backoffCall = undefined;
|
|
113
113
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
mercury.
|
|
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.
|
|
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.
|
|
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.
|
|
840
|
+
mercury.backoffCall = undefined;
|
|
823
841
|
|
|
824
|
-
const promise = mercury._attemptConnection(
|
|
825
|
-
|
|
826
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
|
1107
|
-
mercury.
|
|
1114
|
+
// Simulate an existing switchover in progress
|
|
1115
|
+
mercury._shutdownSwitchoverBackoffCall = {abort: sinon.stub()};
|
|
1108
1116
|
|
|
1109
|
-
mercury._handleImminentShutdown(
|
|
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(
|
|
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.
|
|
1125
|
-
assert.
|
|
1126
|
-
assert.
|
|
1127
|
-
assert.
|
|
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(
|
|
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.
|
|
1139
|
-
assert.
|
|
1140
|
-
assert.
|
|
1141
|
-
assert.
|
|
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(
|
|
1155
|
+
mercury._handleImminentShutdown();
|
|
1152
1156
|
|
|
1153
|
-
// When an exception happens synchronously, the
|
|
1154
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
1239
|
+
const result = mercury._onmessage(event);
|
|
1243
1240
|
|
|
1244
1241
|
assert.instanceOf(result, Promise);
|
|
1245
|
-
|
|
1242
|
+
await result;
|
|
1243
|
+
assert.calledOnceWithExactly(mercury._emit, 'event', event.data);
|
|
1244
|
+
assert.notCalled(mercury._applyOverrides);
|
|
1246
1245
|
});
|
|
1247
1246
|
|
|
1248
|
-
it('should
|
|
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(
|
|
1258
|
+
const result = mercury._onmessage(event);
|
|
1260
1259
|
|
|
1261
1260
|
assert.instanceOf(result, Promise);
|
|
1262
|
-
|
|
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(
|
|
1277
|
+
const result = mercury._onmessage(event);
|
|
1277
1278
|
|
|
1278
1279
|
assert.instanceOf(result, Promise);
|
|
1279
|
-
|
|
1280
|
-
assert.
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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,
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
1543
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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(
|
|
1585
|
+
mercury._onclose({code, reason: 'transient'}, sourceSocket);
|
|
1634
1586
|
|
|
1635
|
-
assert.calledOnceWithExactly(mercury._reconnect, resolvedUrl
|
|
1636
|
-
assert.neverCalledWith(mercury._reconnect, proxiedUrl
|
|
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(
|
|
1593
|
+
mercury._onclose({code: 1000, reason: 'done (forced)'}, sourceSocket);
|
|
1642
1594
|
|
|
1643
|
-
assert.calledOnceWithExactly(mercury._reconnect, resolvedUrl
|
|
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(
|
|
1604
|
+
mercury._onclose({code: 1006, reason: 'transient'}, oldSocket);
|
|
1653
1605
|
|
|
1654
1606
|
assert.notCalled(mercury._reconnect);
|
|
1655
1607
|
});
|
|
1656
|
-
});
|
|
1657
1608
|
|
|
1658
|
-
|
|
1659
|
-
|
|
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
|
-
|
|
1612
|
+
mercury._onclose({code: 1006, reason: 'transient'}, sourceSocket);
|
|
1664
1613
|
|
|
1665
|
-
assert.
|
|
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.
|
|
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(
|
|
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.
|
|
1701
|
-
assert.
|
|
1702
|
-
assert.
|
|
1703
|
-
assert.
|
|
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
|
|
1710
|
-
//
|
|
1711
|
-
//
|
|
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(
|
|
1661
|
+
mercury._handleImminentShutdown();
|
|
1720
1662
|
|
|
1721
|
-
|
|
1722
|
-
assert.
|
|
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,
|
|
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(
|
|
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.
|
|
1691
|
+
connectWithBackoffStub.callsFake(() => Promise.reject(testError));
|
|
1752
1692
|
|
|
1753
|
-
mercury._handleImminentShutdown(
|
|
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] ===
|
|
1760
|
-
call.args[1]
|
|
1761
|
-
call.args[
|
|
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.
|
|
1708
|
+
connectWithBackoffStub.callsFake(() => Promise.reject(new Error('Failed')));
|
|
1770
1709
|
|
|
1771
|
-
mercury._handleImminentShutdown(
|
|
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
|
|
1858
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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',
|
|
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',
|
|
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',
|
|
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(
|
|
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',
|
|
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.
|
|
1904
|
+
mercury._shutdownSwitchoverBackoffCall = undefined;
|
|
1961
1905
|
|
|
1962
|
-
const result = mercury._attemptConnection('ws://test.com',
|
|
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(
|
|
1923
|
+
mercury._handleImminentShutdown();
|
|
1982
1924
|
|
|
1983
1925
|
assert.calledOnce(connectWithBackoffStub);
|
|
1984
1926
|
const callArgs = connectWithBackoffStub.firstCall.args;
|
|
1985
|
-
|
|
1986
|
-
assert.isObject(callArgs[
|
|
1987
|
-
assert.isTrue(callArgs[
|
|
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,
|
|
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,
|
|
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.
|
|
2010
|
-
assert.
|
|
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
|
|
2017
|
-
|
|
1957
|
+
it('should set _shutdownSwitchoverBackoffCall during attempt and clear it after completion', () => {
|
|
1958
|
+
let backoffCallDuringAttempt;
|
|
2018
1959
|
|
|
2019
|
-
mercury.
|
|
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,
|
|
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
|
-
|
|
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.
|
|
1989
|
+
mercury._shutdownSwitchoverBackoffCall = {abort: abortStub};
|
|
2045
1990
|
});
|
|
2046
1991
|
|
|
2047
|
-
it('should abort shutdown switchover backoff call on disconnect',
|
|
2048
|
-
|
|
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
|
-
|
|
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',
|
|
2054
|
-
mercury.
|
|
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
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
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
|
});
|