@webex/internal-plugin-board 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 Mercury, {Socket} from '@webex/internal-plugin-mercury';
6
- import {assert} from '@webex/test-helper-chai';
7
- import MockWebex from '@webex/test-helper-mock-webex';
8
- import MockWebSocket from '@webex/test-helper-mock-web-socket';
9
- import sinon from 'sinon';
10
- import Board, {config, RealtimeChannel} from '@webex/internal-plugin-board';
11
- import uuid from 'uuid';
12
-
13
- function delay(timeout) {
14
- return new Promise((resolve) => {
15
- setTimeout(resolve, timeout);
16
- });
17
- }
18
-
19
- describe('plugin-board', () => {
20
- describe('realtime', () => {
21
- let webex;
22
- let mockRealtimeChannel;
23
- let registrationRes;
24
- let socketOpenStub;
25
- const encryptedData = 'encryptedData';
26
- const fakeURL = 'fakeURL';
27
- const mockWebSocket = new MockWebSocket();
28
- const channel = {
29
- channelId: '1234-channel-id',
30
- defaultEncryptionKeyUrl: fakeURL,
31
- };
32
-
33
- beforeEach(() => {
34
- webex = new MockWebex({
35
- children: {
36
- board: Board,
37
- mercury: Mercury,
38
- },
39
- config: {
40
- board: config.board,
41
- },
42
- });
43
- Object.assign(webex.internal, {
44
- encryption: {
45
- encryptText: sinon.stub().returns(Promise.resolve(encryptedData)),
46
- },
47
- metrics: {
48
- submitClientMetrics: sinon.stub(),
49
- },
50
- });
51
-
52
- registrationRes = {
53
- id: '14d6abda-16de-4e02-bf7c-6d2a0e77ec38',
54
- url: 'https://mercury-api-a.wbx2.com/v1/apps/wx2/registrations/14d6abda-16de-4e02-bf7c-6d2a0e77ec38',
55
- bindings: ['board.0609e520.a21a.11e6.912e.e9562ab65926'],
56
- webSocketUrl:
57
- 'wss://mercury-connection-a.wbx2.com/v1/apps/wx2/registrations/14d6abda-16de-4e02-bf7c-6d2a0e77ec38/messages',
58
- messageTtl: 900000,
59
- };
60
-
61
- sinon.stub(Socket, 'getWebSocketConstructor').returns(() => mockWebSocket);
62
-
63
- // add mocked socket to collection
64
- mockRealtimeChannel = new RealtimeChannel({
65
- socketUrl: registrationRes.webSocketUrl,
66
- binding: registrationRes.bindings[0],
67
- channelId: channel.channelId,
68
- });
69
-
70
- mockRealtimeChannel.socket = mockWebSocket;
71
- webex.internal.board.realtime.realtimeChannels.add(mockRealtimeChannel);
72
-
73
- const origOpen = Socket.prototype.open;
74
-
75
- socketOpenStub = sinon.stub(Socket.prototype, 'open').callsFake(function (...args) {
76
- const promise = Reflect.apply(origOpen, this, args);
77
-
78
- process.nextTick(() => mockWebSocket.open());
79
-
80
- return promise;
81
- });
82
-
83
- sinon
84
- .stub(webex.internal.board.realtime.realtimeChannels, 'get')
85
- .returns(mockRealtimeChannel);
86
- sinon.stub(webex.internal.board.realtime.realtimeChannels, 'add');
87
- sinon.stub(webex.internal.board.realtime.realtimeChannels, 'remove');
88
-
89
- sinon.stub(webex.internal.board, 'register').returns(Promise.resolve(registrationRes));
90
- });
91
-
92
- afterEach(() => {
93
- webex.internal.board.realtime.realtimeChannels.get.restore();
94
- webex.internal.board.realtime.realtimeChannels.add.restore();
95
- webex.internal.board.realtime.realtimeChannels.remove.restore();
96
- // mockSocket.open.reset();
97
-
98
- if (socketOpenStub) {
99
- socketOpenStub.restore();
100
- }
101
-
102
- if (Socket.getWebSocketConstructor.restore) {
103
- Socket.getWebSocketConstructor.restore();
104
- }
105
- });
106
-
107
- describe('#publish()', () => {
108
- const message = {
109
- payload: {
110
- data: 'fake',
111
- },
112
- envelope: {},
113
- };
114
-
115
- beforeEach(() => {
116
- sinon.stub(uuid, 'v4').returns('stubbedUUIDv4');
117
-
118
- return webex.internal.board.realtime.publish(channel, message);
119
- });
120
-
121
- afterEach(() => {
122
- uuid.v4.restore();
123
- webex.internal.encryption.encryptText.reset();
124
- });
125
-
126
- it('sends encrypted data on the socket', () => {
127
- assert.calledOnce(webex.internal.encryption.encryptText);
128
- assert.calledWith(mockRealtimeChannel.socket.send, {
129
- id: uuid.v4(),
130
- type: 'publishRequest',
131
- recipients: [
132
- {
133
- alertType: 'none',
134
- route: mockRealtimeChannel.binding,
135
- headers: {},
136
- },
137
- ],
138
- data: {
139
- eventType: 'board.activity',
140
- payload: 'encryptedData',
141
- envelope: {
142
- encryptionKeyUrl: 'fakeURL',
143
- channelId: mockRealtimeChannel.channelId,
144
- },
145
- contentType: 'STRING',
146
- },
147
- });
148
- });
149
- });
150
-
151
- describe('#publishEncrypted()', () => {
152
- beforeEach(() => {
153
- sinon.stub(uuid, 'v4').returns('stubbedUUIDv4');
154
-
155
- return webex.internal.board.realtime.publishEncrypted(
156
- channel,
157
- {
158
- encryptedData: 'encryptedData',
159
- encryptedKeyUrl: 'fakeURL',
160
- },
161
- 'STRING'
162
- );
163
- });
164
-
165
- afterEach(() => {
166
- webex.internal.board.realtime.boardBindings = [];
167
- uuid.v4.restore();
168
- webex.internal.encryption.encryptText.reset();
169
- });
170
-
171
- it('sends encrypted data on the socket', () => {
172
- assert.notCalled(webex.internal.encryption.encryptText);
173
- assert.calledWith(mockRealtimeChannel.socket.send, {
174
- id: uuid.v4(),
175
- type: 'publishRequest',
176
- recipients: [
177
- {
178
- alertType: 'none',
179
- headers: {},
180
- route: mockRealtimeChannel.binding,
181
- },
182
- ],
183
- data: {
184
- contentType: 'STRING',
185
- eventType: 'board.activity',
186
- envelope: {
187
- encryptionKeyUrl: 'fakeURL',
188
- channelId: channel.channelId,
189
- },
190
- payload: 'encryptedData',
191
- },
192
- });
193
- });
194
-
195
- it('rejects when socket not found', () => {
196
- mockRealtimeChannel.socket.send.restore();
197
- mockRealtimeChannel.socket.send = sinon.stub().returns(Promise.resolve());
198
- webex.internal.board.realtime.realtimeChannels.get.returns(null);
199
-
200
- return assert.isRejected(
201
- webex.internal.board.realtime.publishEncrypted(
202
- {
203
- encryptedData: 'encryptedData',
204
- encryptedKeyUrl: 'fakeURL',
205
- },
206
- 'STRING'
207
- )
208
- );
209
- });
210
- });
211
-
212
- describe('#connectByOpenNewMercuryConnection()', () => {
213
- it('opens new connections using the provided socket urls', () =>
214
- webex.internal.board.realtime.connectByOpenNewMercuryConnection(channel).then(() => {
215
- assert.calledWith(webex.internal.board.realtime.realtimeChannels.get, channel.channelId);
216
- assert.called(mockRealtimeChannel.socket.open);
217
- }));
218
-
219
- it('creates new channel if realtime channel not found', () => {
220
- webex.internal.board.realtime.realtimeChannels.get
221
- .onFirstCall()
222
- .returns(null)
223
- .onSecondCall()
224
- .returns(mockRealtimeChannel);
225
-
226
- return webex.internal.board.realtime.connectByOpenNewMercuryConnection(channel).then(() => {
227
- assert.calledWith(webex.internal.board.realtime.realtimeChannels.add, {
228
- channelId: channel.channelId,
229
- socketUrl: registrationRes.webSocketUrl,
230
- binding: registrationRes.bindings[0],
231
- });
232
-
233
- assert.calledWith(webex.internal.board.realtime.realtimeChannels.get, channel.channelId);
234
- assert.called(mockRealtimeChannel.socket.open);
235
- });
236
- });
237
- });
238
-
239
- describe('#disconnectMercuryConnection', () => {
240
- it('disconnects the mercury connection', () => {
241
- sinon.stub(mockRealtimeChannel, 'disconnect').returns(Promise.resolve());
242
-
243
- return webex.internal.board.realtime.disconnectMercuryConnection(channel).then(() => {
244
- assert.called(mockRealtimeChannel.disconnect);
245
- assert.called(webex.internal.board.realtime.realtimeChannels.remove);
246
- });
247
- });
248
-
249
- it('rejects if channel not found', () => {
250
- webex.internal.board.realtime.realtimeChannels.get.returns(null);
251
-
252
- return assert.isRejected(webex.internal.board.realtime.disconnectMercuryConnection({}));
253
- });
254
- });
255
-
256
- describe('when trying to share mercury connection', () => {
257
- let replaceBindingRes;
258
- let removeBindingRes;
259
-
260
- beforeEach(() => {
261
- replaceBindingRes = {
262
- mercuryConnectionServiceClusterUrl: 'https://mercury-connection-a5.wbx2.com/v1',
263
- binding: 'board.a85e2f70-528d-11e6-ad98-bd2acefef905',
264
- webSocketUrl:
265
- 'wss://mercury-connection-a.wbx2.com/v1/apps/wx2/registrations/14d6abda-16de-4e02-bf7c-6d2a0e77ec38/messages',
266
- sharedWebSocket: true,
267
- action: 'ADD',
268
- };
269
-
270
- removeBindingRes = {
271
- binding: 'board.a85e2f70-528d-11e6-ad98-bd2acefef905',
272
- webSocketUrl:
273
- 'wss://mercury-connection-a.wbx2.com/v1/apps/wx2/registrations/14d6abda-16de-4e02-bf7c-6d2a0e77ec38/messages',
274
- sharedWebSocket: false,
275
- action: 'REMOVE',
276
- };
277
-
278
- sinon
279
- .stub(webex.internal.board, 'registerToShareMercury')
280
- .returns(Promise.resolve(replaceBindingRes));
281
- sinon
282
- .stub(webex.internal.board, 'unregisterFromSharedMercury')
283
- .returns(Promise.resolve(removeBindingRes));
284
- });
285
-
286
- afterEach(() => {
287
- webex.internal.board.registerToShareMercury.restore();
288
- });
289
-
290
- describe('#connectToSharedMercury', () => {
291
- it('registers and gets board binding', () =>
292
- webex.internal.board.realtime.connectToSharedMercury(channel).then((res) => {
293
- assert.isTrue(mockRealtimeChannel.isSharingMercury);
294
- assert.deepEqual(res, replaceBindingRes);
295
- }));
296
-
297
- describe('when connection cannot be shared', () => {
298
- it('opens a second socket with provided webSocketUrl', () => {
299
- replaceBindingRes.sharedWebSocket = false;
300
-
301
- return webex.internal.board.realtime
302
- .connectToSharedMercury(channel)
303
- .then((res) => {
304
- assert.isFalse(mockRealtimeChannel.isSharingMercury);
305
- assert.deepEqual(res, replaceBindingRes);
306
- assert.match(socketOpenStub.args[0][0], new RegExp(replaceBindingRes.webSocketUrl));
307
- assert.calledWith(
308
- socketOpenStub,
309
- sinon.match(replaceBindingRes.webSocketUrl),
310
- sinon.match.any
311
- );
312
- })
313
- .then(() => {
314
- const channel2 = Object.assign({}, channel, {
315
- channelId: 'channel2-id',
316
- });
317
-
318
- sinon.stub(mockRealtimeChannel, 'connect').returns(Promise.resolve());
319
-
320
- return webex.internal.board.realtime.connectToSharedMercury(channel2);
321
- })
322
- .then((res) => {
323
- assert.isFalse(mockRealtimeChannel.isSharingMercury);
324
- assert.deepEqual(res, replaceBindingRes);
325
- assert.called(mockRealtimeChannel.connect);
326
- mockRealtimeChannel.connect.restore();
327
- });
328
- });
329
- });
330
- });
331
-
332
- describe('#disconnectFromSharedMercury()', () => {
333
- it('requests to remove board bindings', () =>
334
- webex.internal.board.realtime
335
- .connectToSharedMercury(channel)
336
- .then(() => webex.internal.board.realtime.disconnectFromSharedMercury(channel))
337
- .then((res) => {
338
- assert.deepEqual(res, removeBindingRes);
339
- assert.called(webex.internal.board.realtime.realtimeChannels.remove);
340
- }));
341
-
342
- describe('when a second connection is open', () => {
343
- it('disconnects the second socket', () => {
344
- sinon.stub(mockRealtimeChannel, 'disconnect').returns(Promise.resolve());
345
- replaceBindingRes.sharedWebSocket = false;
346
-
347
- return webex.internal.board.realtime
348
- .connectToSharedMercury(channel)
349
- .then(() => {
350
- assert.isFalse(mockRealtimeChannel.isSharingMercury);
351
-
352
- return webex.internal.board.realtime.disconnectFromSharedMercury(channel);
353
- })
354
- .then(() => {
355
- assert.called(webex.internal.board.realtime.realtimeChannels.remove);
356
- assert.called(mockRealtimeChannel.disconnect);
357
- mockRealtimeChannel.disconnect.restore();
358
- });
359
- });
360
- });
361
- });
362
- });
363
-
364
- describe('#_boardChannelIdToMercuryBinding', () => {
365
- it('adds board. binding prefix', () => {
366
- assert.equal(
367
- webex.internal.board.realtime._boardChannelIdToMercuryBinding('test'),
368
- 'board.test'
369
- );
370
- });
371
-
372
- it("replaces '-' with '.' and '_' with '#'", () => {
373
- assert.equal(
374
- webex.internal.board.realtime._boardChannelIdToMercuryBinding('abc-1234_bcd'),
375
- 'board.abc.1234#bcd'
376
- );
377
- });
378
-
379
- it('leaves strings without - and _ alone', () => {
380
- assert.equal(
381
- webex.internal.board.realtime._boardChannelIdToMercuryBinding(
382
- 'abcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()+='
383
- ),
384
- 'board.abcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()+='
385
- );
386
- });
387
- });
388
-
389
- describe('#handleBoardActivityMessages', () => {
390
- it('finds the realtimeChannel from the envelope', () => {
391
- let realtime1, realtime2;
392
- const boardEvent = {
393
- id: uuid.v4(),
394
- data: {
395
- eventType: 'board.activity',
396
- actor: {
397
- id: 'actorId',
398
- },
399
- envelope: {
400
- channelId: '1',
401
- },
402
- },
403
- };
404
-
405
- webex.internal.board.realtime.realtimeChannels.get.restore();
406
- webex.internal.board.realtime.realtimeChannels.add.restore();
407
- webex.internal.board.realtime.realtimeChannels.remove.restore();
408
-
409
- return webex.internal.board.realtime
410
- .createRealtimeChannel({channelId: '1'})
411
- .then((realtimeChannel) => {
412
- realtime1 = realtimeChannel;
413
-
414
- return webex.internal.board.realtime.createRealtimeChannel({channelId: '2'});
415
- })
416
- .then((realtimeChannel) => {
417
- realtime2 = realtimeChannel;
418
- realtime1._emit = sinon.stub();
419
- realtime2._emit = sinon.stub();
420
-
421
- webex.internal.board.realtime.handleBoardActivityMessages(boardEvent);
422
- boardEvent.data.envelope.channelId = 3;
423
- webex.internal.board.realtime.handleBoardActivityMessages(boardEvent);
424
-
425
- assert.calledOnce(realtime1._emit);
426
- assert.notCalled(realtime2._emit);
427
-
428
- sinon.stub(webex.internal.board.realtime.realtimeChannels, 'get');
429
- sinon.stub(webex.internal.board.realtime.realtimeChannels, 'add');
430
- sinon.stub(webex.internal.board.realtime.realtimeChannels, 'remove');
431
- });
432
- });
433
- });
434
-
435
- describe('on messages', () => {
436
- let fakeEvent;
437
-
438
- beforeEach(() => {
439
- fakeEvent = {
440
- id: uuid.v4(),
441
- data: {
442
- eventType: 'board.activity',
443
- actor: {
444
- id: 'actorId',
445
- },
446
- conversationId: uuid.v4(),
447
- },
448
- timestamp: Date.now(),
449
- trackingId: `suffix_${uuid.v4()}_${Date.now()}`,
450
- };
451
- });
452
-
453
- it('emits message', () => {
454
- const spy = sinon.spy();
455
-
456
- return webex.internal.board.realtime
457
- .createRealtimeChannel(channel)
458
- .then((realtimeChannel) => {
459
- realtimeChannel.on('event:board.activity', spy);
460
- })
461
- .then(() => webex.internal.board.realtime.connectByOpenNewMercuryConnection(channel))
462
- .then(() => {
463
- mockRealtimeChannel.socket.emit('message', {data: fakeEvent});
464
-
465
- return delay(0);
466
- })
467
- .then(() => {
468
- assert.called(spy);
469
- });
470
- });
471
- });
472
- });
473
- });
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import Mercury, {Socket} from '@webex/internal-plugin-mercury';
6
+ import {assert} from '@webex/test-helper-chai';
7
+ import MockWebex from '@webex/test-helper-mock-webex';
8
+ import MockWebSocket from '@webex/test-helper-mock-web-socket';
9
+ import sinon from 'sinon';
10
+ import Board, {config, RealtimeChannel} from '@webex/internal-plugin-board';
11
+ import uuid from 'uuid';
12
+
13
+ function delay(timeout) {
14
+ return new Promise((resolve) => {
15
+ setTimeout(resolve, timeout);
16
+ });
17
+ }
18
+
19
+ describe('plugin-board', () => {
20
+ describe('realtime', () => {
21
+ let webex;
22
+ let mockRealtimeChannel;
23
+ let registrationRes;
24
+ let socketOpenStub;
25
+ const encryptedData = 'encryptedData';
26
+ const fakeURL = 'fakeURL';
27
+ const mockWebSocket = new MockWebSocket();
28
+ const channel = {
29
+ channelId: '1234-channel-id',
30
+ defaultEncryptionKeyUrl: fakeURL,
31
+ };
32
+
33
+ beforeEach(() => {
34
+ webex = new MockWebex({
35
+ children: {
36
+ board: Board,
37
+ mercury: Mercury,
38
+ },
39
+ config: {
40
+ board: config.board,
41
+ },
42
+ });
43
+ Object.assign(webex.internal, {
44
+ encryption: {
45
+ encryptText: sinon.stub().returns(Promise.resolve(encryptedData)),
46
+ },
47
+ metrics: {
48
+ submitClientMetrics: sinon.stub(),
49
+ },
50
+ });
51
+
52
+ registrationRes = {
53
+ id: '14d6abda-16de-4e02-bf7c-6d2a0e77ec38',
54
+ url: 'https://mercury-api-a.wbx2.com/v1/apps/wx2/registrations/14d6abda-16de-4e02-bf7c-6d2a0e77ec38',
55
+ bindings: ['board.0609e520.a21a.11e6.912e.e9562ab65926'],
56
+ webSocketUrl:
57
+ 'wss://mercury-connection-a.wbx2.com/v1/apps/wx2/registrations/14d6abda-16de-4e02-bf7c-6d2a0e77ec38/messages',
58
+ messageTtl: 900000,
59
+ };
60
+
61
+ sinon.stub(Socket, 'getWebSocketConstructor').returns(() => mockWebSocket);
62
+
63
+ // add mocked socket to collection
64
+ mockRealtimeChannel = new RealtimeChannel({
65
+ socketUrl: registrationRes.webSocketUrl,
66
+ binding: registrationRes.bindings[0],
67
+ channelId: channel.channelId,
68
+ });
69
+
70
+ mockRealtimeChannel.socket = mockWebSocket;
71
+ webex.internal.board.realtime.realtimeChannels.add(mockRealtimeChannel);
72
+
73
+ const origOpen = Socket.prototype.open;
74
+
75
+ socketOpenStub = sinon.stub(Socket.prototype, 'open').callsFake(function (...args) {
76
+ const promise = Reflect.apply(origOpen, this, args);
77
+
78
+ process.nextTick(() => mockWebSocket.open());
79
+
80
+ return promise;
81
+ });
82
+
83
+ sinon
84
+ .stub(webex.internal.board.realtime.realtimeChannels, 'get')
85
+ .returns(mockRealtimeChannel);
86
+ sinon.stub(webex.internal.board.realtime.realtimeChannels, 'add');
87
+ sinon.stub(webex.internal.board.realtime.realtimeChannels, 'remove');
88
+
89
+ sinon.stub(webex.internal.board, 'register').returns(Promise.resolve(registrationRes));
90
+ });
91
+
92
+ afterEach(() => {
93
+ webex.internal.board.realtime.realtimeChannels.get.restore();
94
+ webex.internal.board.realtime.realtimeChannels.add.restore();
95
+ webex.internal.board.realtime.realtimeChannels.remove.restore();
96
+ // mockSocket.open.reset();
97
+
98
+ if (socketOpenStub) {
99
+ socketOpenStub.restore();
100
+ }
101
+
102
+ if (Socket.getWebSocketConstructor.restore) {
103
+ Socket.getWebSocketConstructor.restore();
104
+ }
105
+ });
106
+
107
+ describe('#publish()', () => {
108
+ const message = {
109
+ payload: {
110
+ data: 'fake',
111
+ },
112
+ envelope: {},
113
+ };
114
+
115
+ beforeEach(() => {
116
+ sinon.stub(uuid, 'v4').returns('stubbedUUIDv4');
117
+
118
+ return webex.internal.board.realtime.publish(channel, message);
119
+ });
120
+
121
+ afterEach(() => {
122
+ uuid.v4.restore();
123
+ webex.internal.encryption.encryptText.reset();
124
+ });
125
+
126
+ it('sends encrypted data on the socket', () => {
127
+ assert.calledOnce(webex.internal.encryption.encryptText);
128
+ assert.calledWith(mockRealtimeChannel.socket.send, {
129
+ id: uuid.v4(),
130
+ type: 'publishRequest',
131
+ recipients: [
132
+ {
133
+ alertType: 'none',
134
+ route: mockRealtimeChannel.binding,
135
+ headers: {},
136
+ },
137
+ ],
138
+ data: {
139
+ eventType: 'board.activity',
140
+ payload: 'encryptedData',
141
+ envelope: {
142
+ encryptionKeyUrl: 'fakeURL',
143
+ channelId: mockRealtimeChannel.channelId,
144
+ },
145
+ contentType: 'STRING',
146
+ },
147
+ });
148
+ });
149
+ });
150
+
151
+ describe('#publishEncrypted()', () => {
152
+ beforeEach(() => {
153
+ sinon.stub(uuid, 'v4').returns('stubbedUUIDv4');
154
+
155
+ return webex.internal.board.realtime.publishEncrypted(
156
+ channel,
157
+ {
158
+ encryptedData: 'encryptedData',
159
+ encryptedKeyUrl: 'fakeURL',
160
+ },
161
+ 'STRING'
162
+ );
163
+ });
164
+
165
+ afterEach(() => {
166
+ webex.internal.board.realtime.boardBindings = [];
167
+ uuid.v4.restore();
168
+ webex.internal.encryption.encryptText.reset();
169
+ });
170
+
171
+ it('sends encrypted data on the socket', () => {
172
+ assert.notCalled(webex.internal.encryption.encryptText);
173
+ assert.calledWith(mockRealtimeChannel.socket.send, {
174
+ id: uuid.v4(),
175
+ type: 'publishRequest',
176
+ recipients: [
177
+ {
178
+ alertType: 'none',
179
+ headers: {},
180
+ route: mockRealtimeChannel.binding,
181
+ },
182
+ ],
183
+ data: {
184
+ contentType: 'STRING',
185
+ eventType: 'board.activity',
186
+ envelope: {
187
+ encryptionKeyUrl: 'fakeURL',
188
+ channelId: channel.channelId,
189
+ },
190
+ payload: 'encryptedData',
191
+ },
192
+ });
193
+ });
194
+
195
+ it('rejects when socket not found', () => {
196
+ mockRealtimeChannel.socket.send.restore();
197
+ mockRealtimeChannel.socket.send = sinon.stub().returns(Promise.resolve());
198
+ webex.internal.board.realtime.realtimeChannels.get.returns(null);
199
+
200
+ return assert.isRejected(
201
+ webex.internal.board.realtime.publishEncrypted(
202
+ {
203
+ encryptedData: 'encryptedData',
204
+ encryptedKeyUrl: 'fakeURL',
205
+ },
206
+ 'STRING'
207
+ )
208
+ );
209
+ });
210
+ });
211
+
212
+ describe('#connectByOpenNewMercuryConnection()', () => {
213
+ it('opens new connections using the provided socket urls', () =>
214
+ webex.internal.board.realtime.connectByOpenNewMercuryConnection(channel).then(() => {
215
+ assert.calledWith(webex.internal.board.realtime.realtimeChannels.get, channel.channelId);
216
+ assert.called(mockRealtimeChannel.socket.open);
217
+ }));
218
+
219
+ it('creates new channel if realtime channel not found', () => {
220
+ webex.internal.board.realtime.realtimeChannels.get
221
+ .onFirstCall()
222
+ .returns(null)
223
+ .onSecondCall()
224
+ .returns(mockRealtimeChannel);
225
+
226
+ return webex.internal.board.realtime.connectByOpenNewMercuryConnection(channel).then(() => {
227
+ assert.calledWith(webex.internal.board.realtime.realtimeChannels.add, {
228
+ channelId: channel.channelId,
229
+ socketUrl: registrationRes.webSocketUrl,
230
+ binding: registrationRes.bindings[0],
231
+ });
232
+
233
+ assert.calledWith(webex.internal.board.realtime.realtimeChannels.get, channel.channelId);
234
+ assert.called(mockRealtimeChannel.socket.open);
235
+ });
236
+ });
237
+ });
238
+
239
+ describe('#disconnectMercuryConnection', () => {
240
+ it('disconnects the mercury connection', () => {
241
+ sinon.stub(mockRealtimeChannel, 'disconnect').returns(Promise.resolve());
242
+
243
+ return webex.internal.board.realtime.disconnectMercuryConnection(channel).then(() => {
244
+ assert.called(mockRealtimeChannel.disconnect);
245
+ assert.called(webex.internal.board.realtime.realtimeChannels.remove);
246
+ });
247
+ });
248
+
249
+ it('rejects if channel not found', () => {
250
+ webex.internal.board.realtime.realtimeChannels.get.returns(null);
251
+
252
+ return assert.isRejected(webex.internal.board.realtime.disconnectMercuryConnection({}));
253
+ });
254
+ });
255
+
256
+ describe('when trying to share mercury connection', () => {
257
+ let replaceBindingRes;
258
+ let removeBindingRes;
259
+
260
+ beforeEach(() => {
261
+ replaceBindingRes = {
262
+ mercuryConnectionServiceClusterUrl: 'https://mercury-connection-a5.wbx2.com/v1',
263
+ binding: 'board.a85e2f70-528d-11e6-ad98-bd2acefef905',
264
+ webSocketUrl:
265
+ 'wss://mercury-connection-a.wbx2.com/v1/apps/wx2/registrations/14d6abda-16de-4e02-bf7c-6d2a0e77ec38/messages',
266
+ sharedWebSocket: true,
267
+ action: 'ADD',
268
+ };
269
+
270
+ removeBindingRes = {
271
+ binding: 'board.a85e2f70-528d-11e6-ad98-bd2acefef905',
272
+ webSocketUrl:
273
+ 'wss://mercury-connection-a.wbx2.com/v1/apps/wx2/registrations/14d6abda-16de-4e02-bf7c-6d2a0e77ec38/messages',
274
+ sharedWebSocket: false,
275
+ action: 'REMOVE',
276
+ };
277
+
278
+ sinon
279
+ .stub(webex.internal.board, 'registerToShareMercury')
280
+ .returns(Promise.resolve(replaceBindingRes));
281
+ sinon
282
+ .stub(webex.internal.board, 'unregisterFromSharedMercury')
283
+ .returns(Promise.resolve(removeBindingRes));
284
+ });
285
+
286
+ afterEach(() => {
287
+ webex.internal.board.registerToShareMercury.restore();
288
+ });
289
+
290
+ describe('#connectToSharedMercury', () => {
291
+ it('registers and gets board binding', () =>
292
+ webex.internal.board.realtime.connectToSharedMercury(channel).then((res) => {
293
+ assert.isTrue(mockRealtimeChannel.isSharingMercury);
294
+ assert.deepEqual(res, replaceBindingRes);
295
+ }));
296
+
297
+ describe('when connection cannot be shared', () => {
298
+ it('opens a second socket with provided webSocketUrl', () => {
299
+ replaceBindingRes.sharedWebSocket = false;
300
+
301
+ return webex.internal.board.realtime
302
+ .connectToSharedMercury(channel)
303
+ .then((res) => {
304
+ assert.isFalse(mockRealtimeChannel.isSharingMercury);
305
+ assert.deepEqual(res, replaceBindingRes);
306
+ assert.match(socketOpenStub.args[0][0], new RegExp(replaceBindingRes.webSocketUrl));
307
+ assert.calledWith(
308
+ socketOpenStub,
309
+ sinon.match(replaceBindingRes.webSocketUrl),
310
+ sinon.match.any
311
+ );
312
+ })
313
+ .then(() => {
314
+ const channel2 = Object.assign({}, channel, {
315
+ channelId: 'channel2-id',
316
+ });
317
+
318
+ sinon.stub(mockRealtimeChannel, 'connect').returns(Promise.resolve());
319
+
320
+ return webex.internal.board.realtime.connectToSharedMercury(channel2);
321
+ })
322
+ .then((res) => {
323
+ assert.isFalse(mockRealtimeChannel.isSharingMercury);
324
+ assert.deepEqual(res, replaceBindingRes);
325
+ assert.called(mockRealtimeChannel.connect);
326
+ mockRealtimeChannel.connect.restore();
327
+ });
328
+ });
329
+ });
330
+ });
331
+
332
+ describe('#disconnectFromSharedMercury()', () => {
333
+ it('requests to remove board bindings', () =>
334
+ webex.internal.board.realtime
335
+ .connectToSharedMercury(channel)
336
+ .then(() => webex.internal.board.realtime.disconnectFromSharedMercury(channel))
337
+ .then((res) => {
338
+ assert.deepEqual(res, removeBindingRes);
339
+ assert.called(webex.internal.board.realtime.realtimeChannels.remove);
340
+ }));
341
+
342
+ describe('when a second connection is open', () => {
343
+ it('disconnects the second socket', () => {
344
+ sinon.stub(mockRealtimeChannel, 'disconnect').returns(Promise.resolve());
345
+ replaceBindingRes.sharedWebSocket = false;
346
+
347
+ return webex.internal.board.realtime
348
+ .connectToSharedMercury(channel)
349
+ .then(() => {
350
+ assert.isFalse(mockRealtimeChannel.isSharingMercury);
351
+
352
+ return webex.internal.board.realtime.disconnectFromSharedMercury(channel);
353
+ })
354
+ .then(() => {
355
+ assert.called(webex.internal.board.realtime.realtimeChannels.remove);
356
+ assert.called(mockRealtimeChannel.disconnect);
357
+ mockRealtimeChannel.disconnect.restore();
358
+ });
359
+ });
360
+ });
361
+ });
362
+ });
363
+
364
+ describe('#_boardChannelIdToMercuryBinding', () => {
365
+ it('adds board. binding prefix', () => {
366
+ assert.equal(
367
+ webex.internal.board.realtime._boardChannelIdToMercuryBinding('test'),
368
+ 'board.test'
369
+ );
370
+ });
371
+
372
+ it("replaces '-' with '.' and '_' with '#'", () => {
373
+ assert.equal(
374
+ webex.internal.board.realtime._boardChannelIdToMercuryBinding('abc-1234_bcd'),
375
+ 'board.abc.1234#bcd'
376
+ );
377
+ });
378
+
379
+ it('leaves strings without - and _ alone', () => {
380
+ assert.equal(
381
+ webex.internal.board.realtime._boardChannelIdToMercuryBinding(
382
+ 'abcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()+='
383
+ ),
384
+ 'board.abcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()+='
385
+ );
386
+ });
387
+ });
388
+
389
+ describe('#handleBoardActivityMessages', () => {
390
+ it('finds the realtimeChannel from the envelope', () => {
391
+ let realtime1, realtime2;
392
+ const boardEvent = {
393
+ id: uuid.v4(),
394
+ data: {
395
+ eventType: 'board.activity',
396
+ actor: {
397
+ id: 'actorId',
398
+ },
399
+ envelope: {
400
+ channelId: '1',
401
+ },
402
+ },
403
+ };
404
+
405
+ webex.internal.board.realtime.realtimeChannels.get.restore();
406
+ webex.internal.board.realtime.realtimeChannels.add.restore();
407
+ webex.internal.board.realtime.realtimeChannels.remove.restore();
408
+
409
+ return webex.internal.board.realtime
410
+ .createRealtimeChannel({channelId: '1'})
411
+ .then((realtimeChannel) => {
412
+ realtime1 = realtimeChannel;
413
+
414
+ return webex.internal.board.realtime.createRealtimeChannel({channelId: '2'});
415
+ })
416
+ .then((realtimeChannel) => {
417
+ realtime2 = realtimeChannel;
418
+ realtime1._emit = sinon.stub();
419
+ realtime2._emit = sinon.stub();
420
+
421
+ webex.internal.board.realtime.handleBoardActivityMessages(boardEvent);
422
+ boardEvent.data.envelope.channelId = 3;
423
+ webex.internal.board.realtime.handleBoardActivityMessages(boardEvent);
424
+
425
+ assert.calledOnce(realtime1._emit);
426
+ assert.notCalled(realtime2._emit);
427
+
428
+ sinon.stub(webex.internal.board.realtime.realtimeChannels, 'get');
429
+ sinon.stub(webex.internal.board.realtime.realtimeChannels, 'add');
430
+ sinon.stub(webex.internal.board.realtime.realtimeChannels, 'remove');
431
+ });
432
+ });
433
+ });
434
+
435
+ describe('on messages', () => {
436
+ let fakeEvent;
437
+
438
+ beforeEach(() => {
439
+ fakeEvent = {
440
+ id: uuid.v4(),
441
+ data: {
442
+ eventType: 'board.activity',
443
+ actor: {
444
+ id: 'actorId',
445
+ },
446
+ conversationId: uuid.v4(),
447
+ },
448
+ timestamp: Date.now(),
449
+ trackingId: `suffix_${uuid.v4()}_${Date.now()}`,
450
+ };
451
+ });
452
+
453
+ it('emits message', () => {
454
+ const spy = sinon.spy();
455
+
456
+ return webex.internal.board.realtime
457
+ .createRealtimeChannel(channel)
458
+ .then((realtimeChannel) => {
459
+ realtimeChannel.on('event:board.activity', spy);
460
+ })
461
+ .then(() => webex.internal.board.realtime.connectByOpenNewMercuryConnection(channel))
462
+ .then(() => {
463
+ mockRealtimeChannel.socket.emit('message', {data: fakeEvent});
464
+
465
+ return delay(0);
466
+ })
467
+ .then(() => {
468
+ assert.called(spy);
469
+ });
470
+ });
471
+ });
472
+ });
473
+ });