@webex/internal-plugin-mercury 2.59.1 → 2.59.3-next.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.
@@ -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
+ });