@webex/internal-plugin-mercury 2.59.3-next.1 → 2.59.4
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/.eslintrc.js +6 -6
- package/README.md +64 -64
- package/babel.config.js +3 -3
- package/dist/config.js +18 -18
- package/dist/config.js.map +1 -1
- package/dist/errors.js +13 -13
- package/dist/errors.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mercury.js +1 -1
- package/dist/mercury.js.map +1 -1
- package/dist/socket/index.js.map +1 -1
- package/dist/socket/socket-base.js +76 -76
- package/dist/socket/socket-base.js.map +1 -1
- package/dist/socket/socket.js +2 -2
- package/dist/socket/socket.js.map +1 -1
- package/dist/socket/socket.shim.js +2 -2
- package/dist/socket/socket.shim.js.map +1 -1
- package/jest.config.js +3 -3
- package/package.json +23 -24
- package/process +1 -1
- package/src/config.js +34 -34
- package/src/errors.js +66 -66
- package/src/index.js +32 -32
- package/src/mercury.js +498 -498
- package/src/socket/index.js +5 -5
- package/src/socket/socket-base.js +481 -481
- package/src/socket/socket.js +13 -13
- package/src/socket/socket.shim.js +31 -31
- package/test/integration/spec/mercury.js +117 -117
- package/test/integration/spec/sharable-mercury.js +59 -59
- package/test/integration/spec/webex.js +43 -43
- package/test/unit/lib/promise-tick.js +19 -19
- package/test/unit/spec/mercury-events.js +473 -473
- package/test/unit/spec/mercury.js +709 -709
- package/test/unit/spec/socket.js +812 -812
|
@@ -1,473 +1,473 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {assert} from '@webex/test-helper-chai';
|
|
6
|
-
import Mercury, {config as mercuryConfig, Socket} from '@webex/internal-plugin-mercury';
|
|
7
|
-
import sinon from 'sinon';
|
|
8
|
-
import MockWebex from '@webex/test-helper-mock-webex';
|
|
9
|
-
import MockWebSocket from '@webex/test-helper-mock-web-socket';
|
|
10
|
-
import uuid from 'uuid';
|
|
11
|
-
import FakeTimers from '@sinonjs/fake-timers';
|
|
12
|
-
import {wrap} from 'lodash';
|
|
13
|
-
|
|
14
|
-
import promiseTick from '../lib/promise-tick';
|
|
15
|
-
|
|
16
|
-
describe('plugin-mercury', () => {
|
|
17
|
-
describe('Mercury', () => {
|
|
18
|
-
describe('Events', () => {
|
|
19
|
-
let clock, mercury, mockWebSocket, socketOpenStub, webex;
|
|
20
|
-
|
|
21
|
-
const fakeTestMessage = {
|
|
22
|
-
id: uuid.v4(),
|
|
23
|
-
data: {
|
|
24
|
-
eventType: 'fake.test',
|
|
25
|
-
},
|
|
26
|
-
timestamp: Date.now(),
|
|
27
|
-
trackingId: `suffix_${uuid.v4()}_${Date.now()}`,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const statusStartTypingMessage = {
|
|
31
|
-
id: uuid.v4(),
|
|
32
|
-
data: {
|
|
33
|
-
eventType: 'status.start_typing',
|
|
34
|
-
actor: {
|
|
35
|
-
id: 'actorId',
|
|
36
|
-
},
|
|
37
|
-
conversationId: uuid.v4(),
|
|
38
|
-
},
|
|
39
|
-
timestamp: Date.now(),
|
|
40
|
-
trackingId: `suffix_${uuid.v4()}_${Date.now()}`,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
clock = FakeTimers.install({now: Date.now()});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
afterEach(() => {
|
|
48
|
-
clock.uninstall();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
webex = new MockWebex({
|
|
53
|
-
children: {
|
|
54
|
-
mercury: Mercury,
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
webex.internal.metrics.submitClientMetrics = sinon.stub();
|
|
59
|
-
webex.trackingId = 'fakeTrackingId';
|
|
60
|
-
webex.config.mercury = mercuryConfig.mercury;
|
|
61
|
-
|
|
62
|
-
webex.logger = console;
|
|
63
|
-
|
|
64
|
-
mockWebSocket = new MockWebSocket('ws://example.com');
|
|
65
|
-
sinon.stub(Socket, 'getWebSocketConstructor').returns(() => mockWebSocket);
|
|
66
|
-
|
|
67
|
-
const origOpen = Socket.prototype.open;
|
|
68
|
-
|
|
69
|
-
socketOpenStub = sinon.stub(Socket.prototype, 'open').callsFake(function (...args) {
|
|
70
|
-
const promise = Reflect.apply(origOpen, this, args);
|
|
71
|
-
|
|
72
|
-
process.nextTick(() => mockWebSocket.open());
|
|
73
|
-
|
|
74
|
-
return promise;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
mercury = webex.internal.mercury;
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
afterEach(() => {
|
|
81
|
-
if (socketOpenStub) {
|
|
82
|
-
socketOpenStub.restore();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (Socket.getWebSocketConstructor.restore) {
|
|
86
|
-
Socket.getWebSocketConstructor.restore();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('when connected', () => {
|
|
91
|
-
it('emits the `online` event', () => {
|
|
92
|
-
const spy = sinon.spy();
|
|
93
|
-
|
|
94
|
-
mercury.on('online', spy);
|
|
95
|
-
const promise = mercury.connect();
|
|
96
|
-
|
|
97
|
-
mockWebSocket.open();
|
|
98
|
-
|
|
99
|
-
return promise.then(() => assert.called(spy));
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe('when disconnected', () => {
|
|
104
|
-
it('emits the `offline` event', () => {
|
|
105
|
-
const spy = sinon.spy();
|
|
106
|
-
|
|
107
|
-
mercury.on('offline', spy);
|
|
108
|
-
const promise = mercury.connect();
|
|
109
|
-
|
|
110
|
-
mockWebSocket.open();
|
|
111
|
-
|
|
112
|
-
return promise
|
|
113
|
-
.then(() => {
|
|
114
|
-
const promise = mercury.disconnect();
|
|
115
|
-
|
|
116
|
-
mockWebSocket.emit('close', {
|
|
117
|
-
code: 1000,
|
|
118
|
-
reason: 'Done',
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
return promise;
|
|
122
|
-
})
|
|
123
|
-
.then(() => assert.calledOnce(spy));
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe('when reconnected', () => {
|
|
127
|
-
it('emits the `online` event', () => {
|
|
128
|
-
const spy = sinon.spy();
|
|
129
|
-
|
|
130
|
-
mercury.on('online', spy);
|
|
131
|
-
|
|
132
|
-
const promise = mercury.connect();
|
|
133
|
-
|
|
134
|
-
mockWebSocket.open();
|
|
135
|
-
|
|
136
|
-
return promise
|
|
137
|
-
.then(() => assert.calledOnce(spy))
|
|
138
|
-
.then(() => mockWebSocket.emit('close', {code: 1000, reason: 'Idle'}))
|
|
139
|
-
.then(() => mercury.connect())
|
|
140
|
-
.then(() => assert.calledTwice(spy));
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
describe.skip('when `mercury.buffer_state` is received', () => {
|
|
146
|
-
// This test is here because the buffer states message may arrive before
|
|
147
|
-
// the mercury Promise resolves.
|
|
148
|
-
it('gets emitted', (done) => {
|
|
149
|
-
const spy = mockWebSocket.send;
|
|
150
|
-
|
|
151
|
-
assert.notCalled(spy);
|
|
152
|
-
const bufferStateSpy = sinon.spy();
|
|
153
|
-
const onlineSpy = sinon.spy();
|
|
154
|
-
|
|
155
|
-
mercury.on('event:mercury.buffer_state', bufferStateSpy);
|
|
156
|
-
mercury.on('online', onlineSpy);
|
|
157
|
-
|
|
158
|
-
Socket.getWebSocketConstructor.returns(() => {
|
|
159
|
-
process.nextTick(() => {
|
|
160
|
-
assert.isTrue(mercury.connecting, 'Mercury is still connecting');
|
|
161
|
-
assert.isFalse(mercury.connected, 'Mercury has not yet connected');
|
|
162
|
-
assert.notCalled(onlineSpy);
|
|
163
|
-
assert.lengthOf(spy.args, 0, 'The client has not yet sent the auth message');
|
|
164
|
-
// set websocket readystate to 1 to allow a successful send message
|
|
165
|
-
mockWebSocket.readyState = 1;
|
|
166
|
-
mockWebSocket.emit('open');
|
|
167
|
-
mockWebSocket.emit('message', {
|
|
168
|
-
data: JSON.stringify({
|
|
169
|
-
id: uuid.v4(),
|
|
170
|
-
data: {
|
|
171
|
-
eventType: 'mercury.buffer_state',
|
|
172
|
-
},
|
|
173
|
-
}),
|
|
174
|
-
});
|
|
175
|
-
// using lengthOf because notCalled doesn't allow the helpful
|
|
176
|
-
// string assertion
|
|
177
|
-
assert.lengthOf(spy.args, 0, 'The client has not acked the buffer_state message');
|
|
178
|
-
|
|
179
|
-
promiseTick(1)
|
|
180
|
-
.then(() => {
|
|
181
|
-
assert.calledOnce(bufferStateSpy);
|
|
182
|
-
|
|
183
|
-
return mercury.connect().then(done);
|
|
184
|
-
})
|
|
185
|
-
.catch(done);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
return mockWebSocket;
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// Delay send for a tick to ensure the buffer message comes before
|
|
192
|
-
// auth completes.
|
|
193
|
-
mockWebSocket.send = wrap(mockWebSocket.send, function (fn, ...args) {
|
|
194
|
-
process.nextTick(() => {
|
|
195
|
-
Reflect.apply(fn, this, args);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
mercury.connect();
|
|
199
|
-
assert.lengthOf(spy.args, 0);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
describe('when a CloseEvent is received', () => {
|
|
204
|
-
const events = [
|
|
205
|
-
{
|
|
206
|
-
code: 1000,
|
|
207
|
-
reason: 'idle',
|
|
208
|
-
action: 'reconnect',
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
code: 1000,
|
|
212
|
-
reason: 'done (forced)',
|
|
213
|
-
action: 'reconnect',
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
code: 1000,
|
|
217
|
-
reason: 'pong not received',
|
|
218
|
-
action: 'reconnect',
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
code: 1000,
|
|
222
|
-
reason: 'pong mismatch',
|
|
223
|
-
action: 'reconnect',
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
code: 1000,
|
|
227
|
-
action: 'close',
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
code: 1003,
|
|
231
|
-
action: 'close',
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
code: 1001,
|
|
235
|
-
action: 'reconnect',
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
code: 1005,
|
|
239
|
-
action: 'reconnect',
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
code: 1006,
|
|
243
|
-
action: 'reconnect',
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
code: 1011,
|
|
247
|
-
action: 'reconnect',
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
code: 4000,
|
|
251
|
-
action: 'replace',
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
action: 'close',
|
|
255
|
-
},
|
|
256
|
-
];
|
|
257
|
-
|
|
258
|
-
events.forEach((def) => {
|
|
259
|
-
const {action, reason, code} = def;
|
|
260
|
-
let description;
|
|
261
|
-
|
|
262
|
-
if (code && reason) {
|
|
263
|
-
description = `with code \`${code}\` and reason \`${reason}\``;
|
|
264
|
-
} else if (code) {
|
|
265
|
-
description = `with code \`${code}\``;
|
|
266
|
-
} else if (reason) {
|
|
267
|
-
description = `with reason \`${reason}\``;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
describe(`when an event ${description} is received`, () => {
|
|
271
|
-
it(`takes the ${action} action`, () => {
|
|
272
|
-
if (mercury._reconnect.restore) {
|
|
273
|
-
mercury._reconnect.restore();
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
sinon.spy(mercury, 'connect');
|
|
277
|
-
|
|
278
|
-
const offlineSpy = sinon.spy();
|
|
279
|
-
const permanentSpy = sinon.spy();
|
|
280
|
-
const transientSpy = sinon.spy();
|
|
281
|
-
const replacedSpy = sinon.spy();
|
|
282
|
-
|
|
283
|
-
mercury.on('offline', offlineSpy);
|
|
284
|
-
mercury.on('offline.permanent', permanentSpy);
|
|
285
|
-
mercury.on('offline.transient', transientSpy);
|
|
286
|
-
mercury.on('offline.replaced', replacedSpy);
|
|
287
|
-
|
|
288
|
-
const promise = mercury.connect();
|
|
289
|
-
|
|
290
|
-
mockWebSocket.open();
|
|
291
|
-
|
|
292
|
-
return promise
|
|
293
|
-
.then(() => {
|
|
294
|
-
// Make sure mercury.connect has a call count of zero
|
|
295
|
-
mercury.connect.resetHistory();
|
|
296
|
-
|
|
297
|
-
mockWebSocket.emit('close', {code, reason});
|
|
298
|
-
|
|
299
|
-
return promiseTick(1);
|
|
300
|
-
})
|
|
301
|
-
.then(() => {
|
|
302
|
-
assert.called(offlineSpy);
|
|
303
|
-
assert.calledWith(offlineSpy, {code, reason});
|
|
304
|
-
switch (action) {
|
|
305
|
-
case 'close':
|
|
306
|
-
assert.called(permanentSpy);
|
|
307
|
-
assert.notCalled(transientSpy);
|
|
308
|
-
assert.notCalled(replacedSpy);
|
|
309
|
-
break;
|
|
310
|
-
case 'reconnect':
|
|
311
|
-
assert.notCalled(permanentSpy);
|
|
312
|
-
assert.called(transientSpy);
|
|
313
|
-
assert.notCalled(replacedSpy);
|
|
314
|
-
break;
|
|
315
|
-
case 'replace':
|
|
316
|
-
assert.notCalled(permanentSpy);
|
|
317
|
-
assert.notCalled(transientSpy);
|
|
318
|
-
assert.called(replacedSpy);
|
|
319
|
-
break;
|
|
320
|
-
default:
|
|
321
|
-
assert(false, 'unreachable code reached');
|
|
322
|
-
}
|
|
323
|
-
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
324
|
-
if (action === 'reconnect') {
|
|
325
|
-
assert.called(mercury.connect);
|
|
326
|
-
assert.calledWith(mercury.connect, mockWebSocket.url);
|
|
327
|
-
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
328
|
-
|
|
329
|
-
// Block until reconnect completes so logs don't overlap
|
|
330
|
-
return mercury.connect();
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
assert.notCalled(mercury.connect);
|
|
334
|
-
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
335
|
-
|
|
336
|
-
return Promise.resolve();
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
describe('when a MessageEvent is received', () => {
|
|
344
|
-
it('processes the Event via any autowired event handlers', () => {
|
|
345
|
-
webex.fake = {
|
|
346
|
-
processTestEvent: sinon.spy(),
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
const promise = mercury.connect();
|
|
350
|
-
|
|
351
|
-
mockWebSocket.open();
|
|
352
|
-
|
|
353
|
-
return promise
|
|
354
|
-
.then(() => {
|
|
355
|
-
mockWebSocket.emit('message', {data: JSON.stringify(fakeTestMessage)});
|
|
356
|
-
|
|
357
|
-
return promiseTick(1);
|
|
358
|
-
})
|
|
359
|
-
.then(() => {
|
|
360
|
-
assert.called(webex.fake.processTestEvent);
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it('emits the Mercury envelope', () => {
|
|
365
|
-
const startSpy = sinon.spy();
|
|
366
|
-
const stopSpy = sinon.spy();
|
|
367
|
-
|
|
368
|
-
mercury.on('event:status.start_typing', startSpy);
|
|
369
|
-
mercury.on('event:status.stop_typing', stopSpy);
|
|
370
|
-
|
|
371
|
-
const promise = mercury.connect();
|
|
372
|
-
|
|
373
|
-
mockWebSocket.open();
|
|
374
|
-
|
|
375
|
-
return promise
|
|
376
|
-
.then(() => {
|
|
377
|
-
mockWebSocket.emit('message', {data: JSON.stringify(statusStartTypingMessage)});
|
|
378
|
-
|
|
379
|
-
return promiseTick(1);
|
|
380
|
-
})
|
|
381
|
-
.then(() => {
|
|
382
|
-
assert.calledOnce(startSpy);
|
|
383
|
-
assert.notCalled(stopSpy);
|
|
384
|
-
assert.calledWith(startSpy, statusStartTypingMessage);
|
|
385
|
-
});
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it("emits the Mercury envelope named by the Mercury event's eventType", () => {
|
|
389
|
-
const startSpy = sinon.spy();
|
|
390
|
-
const stopSpy = sinon.spy();
|
|
391
|
-
|
|
392
|
-
mercury.on('event:status.start_typing', startSpy);
|
|
393
|
-
mercury.on('event:status.stop_typing', stopSpy);
|
|
394
|
-
|
|
395
|
-
const promise = mercury.connect();
|
|
396
|
-
|
|
397
|
-
mockWebSocket.open();
|
|
398
|
-
|
|
399
|
-
return promise
|
|
400
|
-
.then(() => {
|
|
401
|
-
mockWebSocket.emit('message', {data: JSON.stringify(statusStartTypingMessage)});
|
|
402
|
-
|
|
403
|
-
return promiseTick(1);
|
|
404
|
-
})
|
|
405
|
-
.then(() => {
|
|
406
|
-
assert.calledOnce(startSpy);
|
|
407
|
-
assert.notCalled(stopSpy);
|
|
408
|
-
assert.calledWith(startSpy, statusStartTypingMessage);
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
describe('when a sequence number is skipped', () => {
|
|
414
|
-
it('emits an event', () => {
|
|
415
|
-
const spy = sinon.spy();
|
|
416
|
-
|
|
417
|
-
mercury.on('sequence-mismatch', spy);
|
|
418
|
-
const promise = mercury.connect();
|
|
419
|
-
|
|
420
|
-
mockWebSocket.open();
|
|
421
|
-
|
|
422
|
-
return promise.then(() => {
|
|
423
|
-
mockWebSocket.emit('message', {
|
|
424
|
-
data: JSON.stringify({
|
|
425
|
-
sequenceNumber: 2,
|
|
426
|
-
id: 'mockid',
|
|
427
|
-
data: {
|
|
428
|
-
eventType: 'mercury.buffer_state',
|
|
429
|
-
},
|
|
430
|
-
}),
|
|
431
|
-
});
|
|
432
|
-
mockWebSocket.emit('message', {
|
|
433
|
-
data: JSON.stringify({
|
|
434
|
-
sequenceNumber: 4,
|
|
435
|
-
id: 'mockid',
|
|
436
|
-
data: {
|
|
437
|
-
eventType: 'mercury.buffer_state',
|
|
438
|
-
},
|
|
439
|
-
}),
|
|
440
|
-
});
|
|
441
|
-
assert.called(spy);
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
});
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
/*
|
|
449
|
-
// On mercury:
|
|
450
|
-
online
|
|
451
|
-
offline
|
|
452
|
-
offline.transient
|
|
453
|
-
offline.permanent
|
|
454
|
-
offline.replaced
|
|
455
|
-
event
|
|
456
|
-
event:locus.participant_joined
|
|
457
|
-
mockWebSocket.connection-failed
|
|
458
|
-
mockWebSocket.sequence-mismatch
|
|
459
|
-
|
|
460
|
-
// On webex:
|
|
461
|
-
mercury.online
|
|
462
|
-
mercury.offline
|
|
463
|
-
mercury.offline.transient
|
|
464
|
-
mercury.offline.permanent
|
|
465
|
-
mercury.offline.replaced
|
|
466
|
-
mercury.event
|
|
467
|
-
mercury.event:locus.participant_joined
|
|
468
|
-
mercury.mockWebSocket.connection-failed
|
|
469
|
-
mercury.mockWebSocket.sequence-mismatch
|
|
470
|
-
|
|
471
|
-
// TODO go through all it(`emits...`) and make sure corresponding tests are here
|
|
472
|
-
*/
|
|
473
|
-
});
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {assert} from '@webex/test-helper-chai';
|
|
6
|
+
import Mercury, {config as mercuryConfig, Socket} from '@webex/internal-plugin-mercury';
|
|
7
|
+
import sinon from 'sinon';
|
|
8
|
+
import MockWebex from '@webex/test-helper-mock-webex';
|
|
9
|
+
import MockWebSocket from '@webex/test-helper-mock-web-socket';
|
|
10
|
+
import uuid from 'uuid';
|
|
11
|
+
import FakeTimers from '@sinonjs/fake-timers';
|
|
12
|
+
import {wrap} from 'lodash';
|
|
13
|
+
|
|
14
|
+
import promiseTick from '../lib/promise-tick';
|
|
15
|
+
|
|
16
|
+
describe('plugin-mercury', () => {
|
|
17
|
+
describe('Mercury', () => {
|
|
18
|
+
describe('Events', () => {
|
|
19
|
+
let clock, mercury, mockWebSocket, socketOpenStub, webex;
|
|
20
|
+
|
|
21
|
+
const fakeTestMessage = {
|
|
22
|
+
id: uuid.v4(),
|
|
23
|
+
data: {
|
|
24
|
+
eventType: 'fake.test',
|
|
25
|
+
},
|
|
26
|
+
timestamp: Date.now(),
|
|
27
|
+
trackingId: `suffix_${uuid.v4()}_${Date.now()}`,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const statusStartTypingMessage = {
|
|
31
|
+
id: uuid.v4(),
|
|
32
|
+
data: {
|
|
33
|
+
eventType: 'status.start_typing',
|
|
34
|
+
actor: {
|
|
35
|
+
id: 'actorId',
|
|
36
|
+
},
|
|
37
|
+
conversationId: uuid.v4(),
|
|
38
|
+
},
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
trackingId: `suffix_${uuid.v4()}_${Date.now()}`,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
clock = FakeTimers.install({now: Date.now()});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
clock.uninstall();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
webex = new MockWebex({
|
|
53
|
+
children: {
|
|
54
|
+
mercury: Mercury,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
webex.internal.metrics.submitClientMetrics = sinon.stub();
|
|
59
|
+
webex.trackingId = 'fakeTrackingId';
|
|
60
|
+
webex.config.mercury = mercuryConfig.mercury;
|
|
61
|
+
|
|
62
|
+
webex.logger = console;
|
|
63
|
+
|
|
64
|
+
mockWebSocket = new MockWebSocket('ws://example.com');
|
|
65
|
+
sinon.stub(Socket, 'getWebSocketConstructor').returns(() => mockWebSocket);
|
|
66
|
+
|
|
67
|
+
const origOpen = Socket.prototype.open;
|
|
68
|
+
|
|
69
|
+
socketOpenStub = sinon.stub(Socket.prototype, 'open').callsFake(function (...args) {
|
|
70
|
+
const promise = Reflect.apply(origOpen, this, args);
|
|
71
|
+
|
|
72
|
+
process.nextTick(() => mockWebSocket.open());
|
|
73
|
+
|
|
74
|
+
return promise;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
mercury = webex.internal.mercury;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
afterEach(() => {
|
|
81
|
+
if (socketOpenStub) {
|
|
82
|
+
socketOpenStub.restore();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (Socket.getWebSocketConstructor.restore) {
|
|
86
|
+
Socket.getWebSocketConstructor.restore();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('when connected', () => {
|
|
91
|
+
it('emits the `online` event', () => {
|
|
92
|
+
const spy = sinon.spy();
|
|
93
|
+
|
|
94
|
+
mercury.on('online', spy);
|
|
95
|
+
const promise = mercury.connect();
|
|
96
|
+
|
|
97
|
+
mockWebSocket.open();
|
|
98
|
+
|
|
99
|
+
return promise.then(() => assert.called(spy));
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('when disconnected', () => {
|
|
104
|
+
it('emits the `offline` event', () => {
|
|
105
|
+
const spy = sinon.spy();
|
|
106
|
+
|
|
107
|
+
mercury.on('offline', spy);
|
|
108
|
+
const promise = mercury.connect();
|
|
109
|
+
|
|
110
|
+
mockWebSocket.open();
|
|
111
|
+
|
|
112
|
+
return promise
|
|
113
|
+
.then(() => {
|
|
114
|
+
const promise = mercury.disconnect();
|
|
115
|
+
|
|
116
|
+
mockWebSocket.emit('close', {
|
|
117
|
+
code: 1000,
|
|
118
|
+
reason: 'Done',
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return promise;
|
|
122
|
+
})
|
|
123
|
+
.then(() => assert.calledOnce(spy));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('when reconnected', () => {
|
|
127
|
+
it('emits the `online` event', () => {
|
|
128
|
+
const spy = sinon.spy();
|
|
129
|
+
|
|
130
|
+
mercury.on('online', spy);
|
|
131
|
+
|
|
132
|
+
const promise = mercury.connect();
|
|
133
|
+
|
|
134
|
+
mockWebSocket.open();
|
|
135
|
+
|
|
136
|
+
return promise
|
|
137
|
+
.then(() => assert.calledOnce(spy))
|
|
138
|
+
.then(() => mockWebSocket.emit('close', {code: 1000, reason: 'Idle'}))
|
|
139
|
+
.then(() => mercury.connect())
|
|
140
|
+
.then(() => assert.calledTwice(spy));
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe.skip('when `mercury.buffer_state` is received', () => {
|
|
146
|
+
// This test is here because the buffer states message may arrive before
|
|
147
|
+
// the mercury Promise resolves.
|
|
148
|
+
it('gets emitted', (done) => {
|
|
149
|
+
const spy = mockWebSocket.send;
|
|
150
|
+
|
|
151
|
+
assert.notCalled(spy);
|
|
152
|
+
const bufferStateSpy = sinon.spy();
|
|
153
|
+
const onlineSpy = sinon.spy();
|
|
154
|
+
|
|
155
|
+
mercury.on('event:mercury.buffer_state', bufferStateSpy);
|
|
156
|
+
mercury.on('online', onlineSpy);
|
|
157
|
+
|
|
158
|
+
Socket.getWebSocketConstructor.returns(() => {
|
|
159
|
+
process.nextTick(() => {
|
|
160
|
+
assert.isTrue(mercury.connecting, 'Mercury is still connecting');
|
|
161
|
+
assert.isFalse(mercury.connected, 'Mercury has not yet connected');
|
|
162
|
+
assert.notCalled(onlineSpy);
|
|
163
|
+
assert.lengthOf(spy.args, 0, 'The client has not yet sent the auth message');
|
|
164
|
+
// set websocket readystate to 1 to allow a successful send message
|
|
165
|
+
mockWebSocket.readyState = 1;
|
|
166
|
+
mockWebSocket.emit('open');
|
|
167
|
+
mockWebSocket.emit('message', {
|
|
168
|
+
data: JSON.stringify({
|
|
169
|
+
id: uuid.v4(),
|
|
170
|
+
data: {
|
|
171
|
+
eventType: 'mercury.buffer_state',
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
});
|
|
175
|
+
// using lengthOf because notCalled doesn't allow the helpful
|
|
176
|
+
// string assertion
|
|
177
|
+
assert.lengthOf(spy.args, 0, 'The client has not acked the buffer_state message');
|
|
178
|
+
|
|
179
|
+
promiseTick(1)
|
|
180
|
+
.then(() => {
|
|
181
|
+
assert.calledOnce(bufferStateSpy);
|
|
182
|
+
|
|
183
|
+
return mercury.connect().then(done);
|
|
184
|
+
})
|
|
185
|
+
.catch(done);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return mockWebSocket;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Delay send for a tick to ensure the buffer message comes before
|
|
192
|
+
// auth completes.
|
|
193
|
+
mockWebSocket.send = wrap(mockWebSocket.send, function (fn, ...args) {
|
|
194
|
+
process.nextTick(() => {
|
|
195
|
+
Reflect.apply(fn, this, args);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
mercury.connect();
|
|
199
|
+
assert.lengthOf(spy.args, 0);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('when a CloseEvent is received', () => {
|
|
204
|
+
const events = [
|
|
205
|
+
{
|
|
206
|
+
code: 1000,
|
|
207
|
+
reason: 'idle',
|
|
208
|
+
action: 'reconnect',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
code: 1000,
|
|
212
|
+
reason: 'done (forced)',
|
|
213
|
+
action: 'reconnect',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
code: 1000,
|
|
217
|
+
reason: 'pong not received',
|
|
218
|
+
action: 'reconnect',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
code: 1000,
|
|
222
|
+
reason: 'pong mismatch',
|
|
223
|
+
action: 'reconnect',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
code: 1000,
|
|
227
|
+
action: 'close',
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
code: 1003,
|
|
231
|
+
action: 'close',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
code: 1001,
|
|
235
|
+
action: 'reconnect',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
code: 1005,
|
|
239
|
+
action: 'reconnect',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
code: 1006,
|
|
243
|
+
action: 'reconnect',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
code: 1011,
|
|
247
|
+
action: 'reconnect',
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
code: 4000,
|
|
251
|
+
action: 'replace',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
action: 'close',
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
events.forEach((def) => {
|
|
259
|
+
const {action, reason, code} = def;
|
|
260
|
+
let description;
|
|
261
|
+
|
|
262
|
+
if (code && reason) {
|
|
263
|
+
description = `with code \`${code}\` and reason \`${reason}\``;
|
|
264
|
+
} else if (code) {
|
|
265
|
+
description = `with code \`${code}\``;
|
|
266
|
+
} else if (reason) {
|
|
267
|
+
description = `with reason \`${reason}\``;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
describe(`when an event ${description} is received`, () => {
|
|
271
|
+
it(`takes the ${action} action`, () => {
|
|
272
|
+
if (mercury._reconnect.restore) {
|
|
273
|
+
mercury._reconnect.restore();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
sinon.spy(mercury, 'connect');
|
|
277
|
+
|
|
278
|
+
const offlineSpy = sinon.spy();
|
|
279
|
+
const permanentSpy = sinon.spy();
|
|
280
|
+
const transientSpy = sinon.spy();
|
|
281
|
+
const replacedSpy = sinon.spy();
|
|
282
|
+
|
|
283
|
+
mercury.on('offline', offlineSpy);
|
|
284
|
+
mercury.on('offline.permanent', permanentSpy);
|
|
285
|
+
mercury.on('offline.transient', transientSpy);
|
|
286
|
+
mercury.on('offline.replaced', replacedSpy);
|
|
287
|
+
|
|
288
|
+
const promise = mercury.connect();
|
|
289
|
+
|
|
290
|
+
mockWebSocket.open();
|
|
291
|
+
|
|
292
|
+
return promise
|
|
293
|
+
.then(() => {
|
|
294
|
+
// Make sure mercury.connect has a call count of zero
|
|
295
|
+
mercury.connect.resetHistory();
|
|
296
|
+
|
|
297
|
+
mockWebSocket.emit('close', {code, reason});
|
|
298
|
+
|
|
299
|
+
return promiseTick(1);
|
|
300
|
+
})
|
|
301
|
+
.then(() => {
|
|
302
|
+
assert.called(offlineSpy);
|
|
303
|
+
assert.calledWith(offlineSpy, {code, reason});
|
|
304
|
+
switch (action) {
|
|
305
|
+
case 'close':
|
|
306
|
+
assert.called(permanentSpy);
|
|
307
|
+
assert.notCalled(transientSpy);
|
|
308
|
+
assert.notCalled(replacedSpy);
|
|
309
|
+
break;
|
|
310
|
+
case 'reconnect':
|
|
311
|
+
assert.notCalled(permanentSpy);
|
|
312
|
+
assert.called(transientSpy);
|
|
313
|
+
assert.notCalled(replacedSpy);
|
|
314
|
+
break;
|
|
315
|
+
case 'replace':
|
|
316
|
+
assert.notCalled(permanentSpy);
|
|
317
|
+
assert.notCalled(transientSpy);
|
|
318
|
+
assert.called(replacedSpy);
|
|
319
|
+
break;
|
|
320
|
+
default:
|
|
321
|
+
assert(false, 'unreachable code reached');
|
|
322
|
+
}
|
|
323
|
+
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
324
|
+
if (action === 'reconnect') {
|
|
325
|
+
assert.called(mercury.connect);
|
|
326
|
+
assert.calledWith(mercury.connect, mockWebSocket.url);
|
|
327
|
+
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
328
|
+
|
|
329
|
+
// Block until reconnect completes so logs don't overlap
|
|
330
|
+
return mercury.connect();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
assert.notCalled(mercury.connect);
|
|
334
|
+
assert.isFalse(mercury.connecting, 'Mercury is not connecting');
|
|
335
|
+
|
|
336
|
+
return Promise.resolve();
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
describe('when a MessageEvent is received', () => {
|
|
344
|
+
it('processes the Event via any autowired event handlers', () => {
|
|
345
|
+
webex.fake = {
|
|
346
|
+
processTestEvent: sinon.spy(),
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const promise = mercury.connect();
|
|
350
|
+
|
|
351
|
+
mockWebSocket.open();
|
|
352
|
+
|
|
353
|
+
return promise
|
|
354
|
+
.then(() => {
|
|
355
|
+
mockWebSocket.emit('message', {data: JSON.stringify(fakeTestMessage)});
|
|
356
|
+
|
|
357
|
+
return promiseTick(1);
|
|
358
|
+
})
|
|
359
|
+
.then(() => {
|
|
360
|
+
assert.called(webex.fake.processTestEvent);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('emits the Mercury envelope', () => {
|
|
365
|
+
const startSpy = sinon.spy();
|
|
366
|
+
const stopSpy = sinon.spy();
|
|
367
|
+
|
|
368
|
+
mercury.on('event:status.start_typing', startSpy);
|
|
369
|
+
mercury.on('event:status.stop_typing', stopSpy);
|
|
370
|
+
|
|
371
|
+
const promise = mercury.connect();
|
|
372
|
+
|
|
373
|
+
mockWebSocket.open();
|
|
374
|
+
|
|
375
|
+
return promise
|
|
376
|
+
.then(() => {
|
|
377
|
+
mockWebSocket.emit('message', {data: JSON.stringify(statusStartTypingMessage)});
|
|
378
|
+
|
|
379
|
+
return promiseTick(1);
|
|
380
|
+
})
|
|
381
|
+
.then(() => {
|
|
382
|
+
assert.calledOnce(startSpy);
|
|
383
|
+
assert.notCalled(stopSpy);
|
|
384
|
+
assert.calledWith(startSpy, statusStartTypingMessage);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("emits the Mercury envelope named by the Mercury event's eventType", () => {
|
|
389
|
+
const startSpy = sinon.spy();
|
|
390
|
+
const stopSpy = sinon.spy();
|
|
391
|
+
|
|
392
|
+
mercury.on('event:status.start_typing', startSpy);
|
|
393
|
+
mercury.on('event:status.stop_typing', stopSpy);
|
|
394
|
+
|
|
395
|
+
const promise = mercury.connect();
|
|
396
|
+
|
|
397
|
+
mockWebSocket.open();
|
|
398
|
+
|
|
399
|
+
return promise
|
|
400
|
+
.then(() => {
|
|
401
|
+
mockWebSocket.emit('message', {data: JSON.stringify(statusStartTypingMessage)});
|
|
402
|
+
|
|
403
|
+
return promiseTick(1);
|
|
404
|
+
})
|
|
405
|
+
.then(() => {
|
|
406
|
+
assert.calledOnce(startSpy);
|
|
407
|
+
assert.notCalled(stopSpy);
|
|
408
|
+
assert.calledWith(startSpy, statusStartTypingMessage);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe('when a sequence number is skipped', () => {
|
|
414
|
+
it('emits an event', () => {
|
|
415
|
+
const spy = sinon.spy();
|
|
416
|
+
|
|
417
|
+
mercury.on('sequence-mismatch', spy);
|
|
418
|
+
const promise = mercury.connect();
|
|
419
|
+
|
|
420
|
+
mockWebSocket.open();
|
|
421
|
+
|
|
422
|
+
return promise.then(() => {
|
|
423
|
+
mockWebSocket.emit('message', {
|
|
424
|
+
data: JSON.stringify({
|
|
425
|
+
sequenceNumber: 2,
|
|
426
|
+
id: 'mockid',
|
|
427
|
+
data: {
|
|
428
|
+
eventType: 'mercury.buffer_state',
|
|
429
|
+
},
|
|
430
|
+
}),
|
|
431
|
+
});
|
|
432
|
+
mockWebSocket.emit('message', {
|
|
433
|
+
data: JSON.stringify({
|
|
434
|
+
sequenceNumber: 4,
|
|
435
|
+
id: 'mockid',
|
|
436
|
+
data: {
|
|
437
|
+
eventType: 'mercury.buffer_state',
|
|
438
|
+
},
|
|
439
|
+
}),
|
|
440
|
+
});
|
|
441
|
+
assert.called(spy);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
/*
|
|
449
|
+
// On mercury:
|
|
450
|
+
online
|
|
451
|
+
offline
|
|
452
|
+
offline.transient
|
|
453
|
+
offline.permanent
|
|
454
|
+
offline.replaced
|
|
455
|
+
event
|
|
456
|
+
event:locus.participant_joined
|
|
457
|
+
mockWebSocket.connection-failed
|
|
458
|
+
mockWebSocket.sequence-mismatch
|
|
459
|
+
|
|
460
|
+
// On webex:
|
|
461
|
+
mercury.online
|
|
462
|
+
mercury.offline
|
|
463
|
+
mercury.offline.transient
|
|
464
|
+
mercury.offline.permanent
|
|
465
|
+
mercury.offline.replaced
|
|
466
|
+
mercury.event
|
|
467
|
+
mercury.event:locus.participant_joined
|
|
468
|
+
mercury.mockWebSocket.connection-failed
|
|
469
|
+
mercury.mockWebSocket.sequence-mismatch
|
|
470
|
+
|
|
471
|
+
// TODO go through all it(`emits...`) and make sure corresponding tests are here
|
|
472
|
+
*/
|
|
473
|
+
});
|