@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,635 +1,635 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- import querystring from 'querystring';
6
-
7
- import {assert} from '@webex/test-helper-chai';
8
- import MockWebex from '@webex/test-helper-mock-webex';
9
- import sinon from 'sinon';
10
- import Board, {config as boardConfig} from '@webex/internal-plugin-board';
11
-
12
- describe('plugin-board', () => {
13
- let webex;
14
- const encryptedData = 'encryptedData';
15
- const decryptedText = 'decryptedText';
16
- const fakeURL = `${
17
- process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
18
- }/encryption/api/v1/keys/8a7d3d78-ce75-48aa-a943-2e8acf63fbc9`;
19
- const file = 'dataURL://base64;';
20
- const boardServiceUrl = 'https://awesome.service.url';
21
- const boardId = 'boardId';
22
-
23
- const mockKey = {
24
- uri: `${
25
- process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
26
- }/encryption/api/v1/keys/7ad503ec-854b-4fce-a7f0-182e1997bdb6`,
27
- };
28
-
29
- const image = {
30
- height: 900,
31
- width: 1600,
32
- size: 15000,
33
- };
34
-
35
- const conversation = {
36
- id: '7c7e69a0-a086-11e6-8670-d7b4b51d7641',
37
- defaultActivityEncryptionKeyUrl: fakeURL,
38
- kmsResourceObjectUrl: `${
39
- process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
40
- }/encryption/api/v1/resources/8693f702-2012-40c6-9ec4-f1392f0a620a`,
41
- aclUrl: 'https://acl-a.wbx2.com/acl/api/v1/acls/7ca94a30-a086-11e6-b599-d90deb9846ed',
42
- };
43
-
44
- const channel = {
45
- channelUrl: `${boardServiceUrl}/channels/${boardId}`,
46
- channelId: boardId,
47
- aclUrlLink: conversation.aclUrl,
48
- aclUrl: 'https://acl-a.wbx2.com/acl/api/v1/acls/e2947ee0-972b-11e7-a041-d564bb1fbb45',
49
- defaultEncryptionKeyUrl: mockKey.uri,
50
- kmsResourceUrl: `${
51
- process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
52
- }/encryption/api/v1/resources/18f7c618-2eff-461e-ac46-819a0fd2b476`,
53
- kmsMessage: {
54
- method: 'create',
55
- uri: '/resources',
56
- userIds: [conversation.kmsResourceObjectUrl],
57
- keyUris: [],
58
- },
59
- };
60
-
61
- const channelRes = {
62
- channelUrl: `${boardServiceUrl}/channels/${boardId}`,
63
- channelId: boardId,
64
- aclUrlLink: conversation.aclUrl,
65
- aclUrl: 'https://acl-a.wbx2.com/acl/api/v1/acls/e2947ee0-972b-11e7-a041-d564bb1fbb45',
66
- defaultEncryptionKeyUrl: mockKey.uri,
67
- creatorId: 'c321e329-28d6-4d52-a9d1-374010411530',
68
- kmsResourceUrl: `${
69
- process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
70
- }/encryption/api/v1/resources/18f7c618-2eff-461e-ac46-819a0fd2b476`,
71
- kmsMessage: {
72
- status: 201,
73
- resource: {
74
- uri: `${
75
- process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
76
- }/encryption/api/v1/resources/2853285c-c46b-4b35-9542-9a81d4e3c87f`,
77
- keyUris: [
78
- `${
79
- process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
80
- }/encryption/api/v1/keys/5042787d-510b-46f3-b83c-ea73032de851`,
81
- ],
82
- authorizationUris: [
83
- `${
84
- process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
85
- }/encryption/api/v1/authorizations/aHR0cHM6Ly9lbmNyeXB0aW9uLWEud2J4Mi5jb20vZW5jcnlwdGlvbi9hcGkvdjEvcmVzb3VyY2VzLzI3OWIyMjgyLWZmYTItNGM3ZC04NGRmLTRkNDVlZmUzYTMzNQBodHRwczovL2VuY3J5cHRpb24tYS53YngyLmNvbS9lbmNyeXB0aW9uL2FwaS92MS9yZXNvdXJjZXMvMjg1MzI4NWMtYzQ2Yi00YjM1LTk1NDItOWE4MWQ0ZTNjODdm`,
86
- `${
87
- process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
88
- }/encryption/api/v1/authorizations/YzMyMWUzMjktMjhkNi00ZDUyLWE5ZDEtMzc0MDEwNDExNTMwAGh0dHBzOi8vZW5jcnlwdGlvbi1hLndieDIuY29tL2VuY3J5cHRpb24vYXBpL3YxL3Jlc291cmNlcy8yODUzMjg1Yy1jNDZiLTRiMzUtOTU0Mi05YTgxZDRlM2M4N2Y`,
89
- ],
90
- },
91
- },
92
- };
93
-
94
- const data1 = {
95
- contentUrl: `${channel.channelUrl}/contents/data1`,
96
- contentId: 'data1',
97
- type: 'test',
98
- data: 'data1',
99
- };
100
-
101
- const data2 = {
102
- type: 'test',
103
- data: 'data2',
104
- };
105
-
106
- beforeAll(() => {
107
- webex = new MockWebex({
108
- children: {
109
- board: Board,
110
- },
111
- request: sinon.stub().returns(
112
- Promise.resolve({
113
- headers: {},
114
- body: '',
115
- })
116
- ),
117
- upload: sinon.stub().returns(Promise.resolve({body: {downloadUrl: fakeURL}})),
118
- });
119
-
120
- Object.assign(webex.internal, {
121
- device: {
122
- deviceType: 'FAKE_DEVICE',
123
- getServiceUrl: () => boardServiceUrl,
124
- },
125
- encryption: {
126
- decryptText: sinon.stub().returns(Promise.resolve(decryptedText)),
127
- encryptText: sinon.stub().returns(Promise.resolve(encryptedData)),
128
- encryptBinary: sinon.stub().returns(
129
- Promise.resolve({
130
- scr: {},
131
- cdata: encryptedData,
132
- })
133
- ),
134
- download: sinon.stub().returns(
135
- Promise.resolve({
136
- toArrayBuffer: sinon.stub(),
137
- })
138
- ),
139
- decryptScr: sinon.stub().returns(Promise.resolve('decryptedFoo')),
140
- encryptScr: sinon.stub().returns(Promise.resolve('encryptedFoo')),
141
- },
142
- });
143
-
144
- webex.config.board = boardConfig.board;
145
- });
146
-
147
- describe('#addContent()', () => {
148
- beforeEach(() => {
149
- webex.request.resetHistory();
150
- });
151
-
152
- it('requests POST all contents to contents', () =>
153
- webex.internal.board.addContent(channel, [data1, data2]).then(() => {
154
- assert.calledWith(
155
- webex.request,
156
- sinon.match({
157
- method: 'POST',
158
- uri: `${boardServiceUrl}/channels/${boardId}/contents`,
159
- body: [
160
- {
161
- device: 'FAKE_DEVICE',
162
- type: 'STRING',
163
- encryptionKeyUrl: mockKey.uri,
164
- payload: 'encryptedData',
165
- },
166
- {
167
- device: 'FAKE_DEVICE',
168
- type: 'STRING',
169
- encryptionKeyUrl: mockKey.uri,
170
- payload: 'encryptedData',
171
- },
172
- ],
173
- })
174
- );
175
- }));
176
-
177
- it('sends large data using multiple requests', () => {
178
- const largeData = [];
179
-
180
- for (let i = 0; i < 400; i += 1) {
181
- largeData.push({data: i});
182
- }
183
-
184
- return webex.internal.board.addContent(channel, largeData).then(() => {
185
- assert.equal(webex.request.callCount, 3);
186
- });
187
- });
188
- });
189
-
190
- describe('#setSnapshotImage()', () => {
191
- beforeEach(() => {
192
- webex.request.resetHistory();
193
- sinon.stub(webex.internal.board, '_uploadImageToWebexFiles').returns(
194
- Promise.resolve({
195
- downloadUrl: fakeURL,
196
- })
197
- );
198
- webex.internal.encryption.encryptScr.resetHistory();
199
- });
200
-
201
- afterEach(() => {
202
- webex.internal.board._uploadImageToWebexFiles.restore();
203
- webex.internal.encryption.encryptScr.resetHistory();
204
- });
205
-
206
- it('requests PATCH to board service', () =>
207
- webex.internal.board.setSnapshotImage(channel, image).then(() => {
208
- assert.calledWith(
209
- webex.request,
210
- sinon.match({
211
- method: 'PATCH',
212
- uri: channel.channelUrl,
213
- body: {
214
- image: {
215
- url: fakeURL,
216
- height: image.height,
217
- width: image.width,
218
- mimeType: 'image/png',
219
- scr: 'encryptedFoo',
220
- encryptionKeyUrl: channel.defaultEncryptionKeyUrl,
221
- fileSize: image.size,
222
- },
223
- },
224
- })
225
- );
226
- }));
227
- });
228
-
229
- describe('#createChannel()', () => {
230
- const channelRequestBody = {
231
- aclUrlLink: channel.aclUrlLink,
232
- kmsMessage: channel.kmsMessage,
233
- };
234
-
235
- beforeAll(() => {
236
- webex.request.resetHistory();
237
- webex.request.returns(Promise.resolve({statusCode: 200, body: channelRes}));
238
-
239
- return webex.internal.board.createChannel(conversation);
240
- });
241
-
242
- afterAll(() => {
243
- // reset request to its original behavior
244
- webex.request.returns(
245
- Promise.resolve({
246
- headers: {},
247
- body: '',
248
- })
249
- );
250
- });
251
-
252
- it('requests POST to channels service', () => {
253
- assert.calledWith(
254
- webex.request,
255
- sinon.match({
256
- method: 'POST',
257
- api: 'board',
258
- resource: '/channels',
259
- body: channelRequestBody,
260
- })
261
- );
262
- });
263
- });
264
-
265
- describe('#deleteChannel()', () => {
266
- it('requests PUT to ACL service to remove the link between conversation and board', () => {
267
- webex.request.resetHistory();
268
-
269
- return webex.internal.board.deleteChannel(conversation, channel).then(() => {
270
- assert.calledWith(
271
- webex.request,
272
- sinon.match({
273
- method: 'PUT',
274
- uri: `${channel.aclUrl}/links`,
275
- body: {
276
- aclLinkType: 'INCOMING',
277
- linkedAcl: conversation.aclUrl,
278
- kmsMessage: {
279
- method: 'delete',
280
- uri: `${channel.kmsResourceUrl}/authorizations?${querystring.stringify({
281
- authId: conversation.kmsResourceObjectUrl,
282
- })}`,
283
- },
284
- aclLinkOperation: 'DELETE',
285
- },
286
- })
287
- );
288
- });
289
- });
290
-
291
- it('requests locks channel before delete when preventDeleteActiveChannel is enabled', () => {
292
- webex.request.resetHistory();
293
-
294
- return webex.internal.board
295
- .deleteChannel(conversation, channel, {preventDeleteActiveChannel: true})
296
- .then(() => {
297
- assert.calledWith(
298
- webex.request,
299
- sinon.match({
300
- method: 'POST',
301
- uri: `${channel.channelUrl}/lock`,
302
- qs: {
303
- intent: 'delete',
304
- },
305
- })
306
- );
307
-
308
- assert.calledWith(
309
- webex.request,
310
- sinon.match({
311
- method: 'PUT',
312
- uri: `${channel.aclUrl}/links`,
313
- body: {
314
- aclLinkType: 'INCOMING',
315
- linkedAcl: conversation.aclUrl,
316
- kmsMessage: {
317
- method: 'delete',
318
- uri: `${channel.kmsResourceUrl}/authorizations?${querystring.stringify({
319
- authId: conversation.kmsResourceObjectUrl,
320
- })}`,
321
- },
322
- aclLinkOperation: 'DELETE',
323
- },
324
- })
325
- );
326
- });
327
- });
328
- });
329
-
330
- describe('#lockChannelForDeletion()', () => {
331
- it('requests POST with delete lock intent', () => {
332
- webex.request.resetHistory();
333
-
334
- return webex.internal.board.lockChannelForDeletion(channel).then(() => {
335
- assert.calledWith(
336
- webex.request,
337
- sinon.match({
338
- method: 'POST',
339
- uri: `${channel.channelUrl}/lock`,
340
- qs: {
341
- intent: 'delete',
342
- },
343
- })
344
- );
345
- });
346
- });
347
- });
348
-
349
- describe('#keepActive()', () => {
350
- it('requests POST to keep channel status active', () => {
351
- webex.request.resetHistory();
352
-
353
- return webex.internal.board.keepActive(channel).then(() => {
354
- assert.calledWith(
355
- webex.request,
356
- sinon.match({
357
- method: 'POST',
358
- uri: `${channel.channelUrl}/keepAlive`,
359
- })
360
- );
361
- });
362
- });
363
- });
364
-
365
- describe('#deleteAllContent()', () => {
366
- beforeAll(() => {
367
- webex.request.resetHistory();
368
-
369
- return webex.internal.board.deleteAllContent(channel);
370
- });
371
-
372
- it('requests DELETE contents', () => {
373
- assert.calledWith(
374
- webex.request,
375
- sinon.match({
376
- method: 'DELETE',
377
- uri: `${boardServiceUrl}/channels/${boardId}/contents`,
378
- })
379
- );
380
- });
381
- });
382
-
383
- describe('#deletePartialContent()', () => {
384
- it('requests POST contents with body contains the content to keep', () => {
385
- webex.request.resetHistory();
386
-
387
- return webex.internal.board.deletePartialContent(channel, [data1]).then(() => {
388
- assert.calledWith(
389
- webex.request,
390
- sinon.match({
391
- method: 'POST',
392
- uri: `${boardServiceUrl}/channels/${boardId}/contents`,
393
- qs: {clearBoard: true},
394
- body: [
395
- {
396
- contentId: data1.contentId,
397
- },
398
- ],
399
- })
400
- );
401
- });
402
- });
403
- });
404
-
405
- describe('#_uploadImage()', () => {
406
- let uploadImageToWebexFiles = null;
407
-
408
- beforeAll(() => {
409
- uploadImageToWebexFiles = sinon.stub(webex.internal.board, '_uploadImageToWebexFiles').returns(Promise.resolve({
410
- downloadUrl: fakeURL
411
- }));
412
-
413
- return webex.internal.board._uploadImage(conversation, file);
414
- });
415
-
416
- afterAll(() => {
417
- uploadImageToWebexFiles.restore();
418
- });
419
-
420
- it('encrypts binary file', () => {
421
- assert.calledWith(webex.internal.encryption.encryptBinary, file);
422
- });
423
-
424
- it('uploads to webex files', () => {
425
- assert.calledWith(webex.internal.board._uploadImageToWebexFiles, conversation, encryptedData);
426
- });
427
- });
428
-
429
- describe('#_uploadImageToWebexFiles()', () => {
430
- beforeAll(() => {
431
- sinon.stub(webex.internal.board, '_getSpaceUrl').returns(Promise.resolve(fakeURL));
432
-
433
- return webex.internal.board._uploadImage(conversation, file);
434
- });
435
-
436
- afterAll(() => webex.internal.board._getSpaceUrl.restore());
437
-
438
- afterEach(() => {
439
- webex.upload.resetHistory();
440
- webex.internal.board._getSpaceUrl.resetHistory();
441
- });
442
-
443
- it('uses length for upload filesize', () => {
444
- const blob = {
445
- length: 4444,
446
- size: 3333,
447
- byteLength: 2222,
448
- };
449
-
450
- return webex.internal.board._uploadImageToWebexFiles(conversation, blob).then(() => {
451
- assert.calledWith(
452
- webex.upload,
453
- sinon.match({
454
- phases: {
455
- initialize: {
456
- fileSize: 4444,
457
- },
458
- finalize: {
459
- body: {
460
- fileSize: 4444,
461
- },
462
- },
463
- },
464
- })
465
- );
466
- });
467
- });
468
-
469
- it('uses size for upload filesize when length is not available', () => {
470
- const blob = {
471
- size: 3333,
472
- byteLength: 2222,
473
- };
474
-
475
- return webex.internal.board._uploadImageToWebexFiles(conversation, blob).then(() => {
476
- assert.calledWith(
477
- webex.upload,
478
- sinon.match({
479
- phases: {
480
- initialize: {
481
- fileSize: 3333,
482
- },
483
- finalize: {
484
- body: {
485
- fileSize: 3333,
486
- },
487
- },
488
- },
489
- })
490
- );
491
- });
492
- });
493
-
494
- it('uses byteLenght for upload filesize when length and size are not available', () => {
495
- const blob = {
496
- byteLength: 2222,
497
- };
498
-
499
- return webex.internal.board._uploadImageToWebexFiles(conversation, blob).then(() => {
500
- assert.calledWith(
501
- webex.upload,
502
- sinon.match({
503
- phases: {
504
- initialize: {
505
- fileSize: 2222,
506
- },
507
- finalize: {
508
- body: {
509
- fileSize: 2222,
510
- },
511
- },
512
- },
513
- })
514
- );
515
- });
516
- });
517
- });
518
-
519
- describe('#children', () => {
520
- it('has a child of realtime', () => {
521
- assert.isDefined(webex.internal.board.realtime);
522
- });
523
- });
524
-
525
- describe('#getChannel()', () => {
526
- beforeAll(() => {
527
- webex.request.resetHistory();
528
-
529
- return webex.internal.board.getChannel(channel);
530
- });
531
-
532
- it('requests GET to channels service', () => {
533
- assert.calledWith(
534
- webex.request,
535
- sinon.match({
536
- method: 'GET',
537
- uri: `${boardServiceUrl}/channels/${boardId}`,
538
- })
539
- );
540
- });
541
-
542
- it('requires conversationId', () =>
543
- assert.isRejected(webex.internal.board.getChannels(), '`conversation` is required'));
544
- });
545
-
546
- describe('#getContents()', () => {
547
- beforeEach(() => {
548
- sinon.stub(webex.internal.board, 'decryptContents').returns(['foo']);
549
- webex.request.resetHistory();
550
- });
551
-
552
- afterEach(() => {
553
- webex.internal.board.decryptContents.restore();
554
- });
555
-
556
- it('requests GET contents with default page size', () =>
557
- webex.internal.board.getContents(channel).then(() =>
558
- assert.calledWith(webex.request, {
559
- uri: `${boardServiceUrl}/channels/${boardId}/contents`,
560
- qs: {
561
- contentsLimit: boardConfig.board.numberContentsPerPageForGet,
562
- },
563
- })
564
- ));
565
-
566
- it('requests GET contents with client defined page size', () =>
567
- webex.internal.board.getContents(channel, {contentsLimit: 25}).then(() =>
568
- assert.calledWith(webex.request, {
569
- uri: `${boardServiceUrl}/channels/${boardId}/contents`,
570
- qs: {
571
- contentsLimit: 25,
572
- },
573
- })
574
- ));
575
- });
576
-
577
- describe('#register()', () => {
578
- beforeAll(() => {
579
- webex.request.resetHistory();
580
-
581
- return webex.internal.board.register({data: 'data'});
582
- });
583
-
584
- it('requests POST data to registration service', () => {
585
- assert.calledWith(
586
- webex.request,
587
- sinon.match({
588
- method: 'POST',
589
- api: 'board',
590
- resource: '/registrations',
591
- })
592
- );
593
- });
594
- });
595
-
596
- describe('#registerToShareMercury()', () => {
597
- beforeEach(() => {
598
- webex.request.resetHistory();
599
- webex.internal.mercury.localClusterServiceUrls = {
600
- mercuryApiServiceClusterUrl: 'https://mercury-api-a5.wbx2.com/v1',
601
- mercuryConnectionServiceClusterUrl: 'https://mercury-connection-a5.wbx2.com/v1',
602
- };
603
- webex.internal.feature.getFeature.returns(Promise.resolve(true));
604
- });
605
-
606
- it('requests POST data to registration service', () =>
607
- webex.internal.board.registerToShareMercury(channel).then(() => {
608
- assert.calledWith(
609
- webex.request,
610
- sinon.match({
611
- method: 'POST',
612
- uri: `${channel.channelUrl}/register`,
613
- body: {
614
- mercuryConnectionServiceClusterUrl:
615
- webex.internal.mercury.localClusterServiceUrls.mercuryConnectionServiceClusterUrl,
616
- webSocketUrl: webex.internal.device.webSocketUrl,
617
- action: 'ADD',
618
- },
619
- })
620
- );
621
- }));
622
-
623
- it('rejects when localClusterServiceUrls is null', () => {
624
- webex.internal.mercury.localClusterServiceUrls = null;
625
-
626
- return assert.isRejected(webex.internal.board.registerToShareMercury(channel));
627
- });
628
-
629
- it('rejects when web-shared-mercury is not enabled', () => {
630
- webex.internal.feature.getFeature.returns(Promise.resolve(false));
631
-
632
- return assert.isRejected(webex.internal.board.registerToShareMercury(channel));
633
- });
634
- });
635
- });
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import querystring from 'querystring';
6
+
7
+ import {assert} from '@webex/test-helper-chai';
8
+ import MockWebex from '@webex/test-helper-mock-webex';
9
+ import sinon from 'sinon';
10
+ import Board, {config as boardConfig} from '@webex/internal-plugin-board';
11
+
12
+ describe('plugin-board', () => {
13
+ let webex;
14
+ const encryptedData = 'encryptedData';
15
+ const decryptedText = 'decryptedText';
16
+ const fakeURL = `${
17
+ process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
18
+ }/encryption/api/v1/keys/8a7d3d78-ce75-48aa-a943-2e8acf63fbc9`;
19
+ const file = 'dataURL://base64;';
20
+ const boardServiceUrl = 'https://awesome.service.url';
21
+ const boardId = 'boardId';
22
+
23
+ const mockKey = {
24
+ uri: `${
25
+ process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
26
+ }/encryption/api/v1/keys/7ad503ec-854b-4fce-a7f0-182e1997bdb6`,
27
+ };
28
+
29
+ const image = {
30
+ height: 900,
31
+ width: 1600,
32
+ size: 15000,
33
+ };
34
+
35
+ const conversation = {
36
+ id: '7c7e69a0-a086-11e6-8670-d7b4b51d7641',
37
+ defaultActivityEncryptionKeyUrl: fakeURL,
38
+ kmsResourceObjectUrl: `${
39
+ process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
40
+ }/encryption/api/v1/resources/8693f702-2012-40c6-9ec4-f1392f0a620a`,
41
+ aclUrl: 'https://acl-a.wbx2.com/acl/api/v1/acls/7ca94a30-a086-11e6-b599-d90deb9846ed',
42
+ };
43
+
44
+ const channel = {
45
+ channelUrl: `${boardServiceUrl}/channels/${boardId}`,
46
+ channelId: boardId,
47
+ aclUrlLink: conversation.aclUrl,
48
+ aclUrl: 'https://acl-a.wbx2.com/acl/api/v1/acls/e2947ee0-972b-11e7-a041-d564bb1fbb45',
49
+ defaultEncryptionKeyUrl: mockKey.uri,
50
+ kmsResourceUrl: `${
51
+ process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
52
+ }/encryption/api/v1/resources/18f7c618-2eff-461e-ac46-819a0fd2b476`,
53
+ kmsMessage: {
54
+ method: 'create',
55
+ uri: '/resources',
56
+ userIds: [conversation.kmsResourceObjectUrl],
57
+ keyUris: [],
58
+ },
59
+ };
60
+
61
+ const channelRes = {
62
+ channelUrl: `${boardServiceUrl}/channels/${boardId}`,
63
+ channelId: boardId,
64
+ aclUrlLink: conversation.aclUrl,
65
+ aclUrl: 'https://acl-a.wbx2.com/acl/api/v1/acls/e2947ee0-972b-11e7-a041-d564bb1fbb45',
66
+ defaultEncryptionKeyUrl: mockKey.uri,
67
+ creatorId: 'c321e329-28d6-4d52-a9d1-374010411530',
68
+ kmsResourceUrl: `${
69
+ process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
70
+ }/encryption/api/v1/resources/18f7c618-2eff-461e-ac46-819a0fd2b476`,
71
+ kmsMessage: {
72
+ status: 201,
73
+ resource: {
74
+ uri: `${
75
+ process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
76
+ }/encryption/api/v1/resources/2853285c-c46b-4b35-9542-9a81d4e3c87f`,
77
+ keyUris: [
78
+ `${
79
+ process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
80
+ }/encryption/api/v1/keys/5042787d-510b-46f3-b83c-ea73032de851`,
81
+ ],
82
+ authorizationUris: [
83
+ `${
84
+ process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
85
+ }/encryption/api/v1/authorizations/aHR0cHM6Ly9lbmNyeXB0aW9uLWEud2J4Mi5jb20vZW5jcnlwdGlvbi9hcGkvdjEvcmVzb3VyY2VzLzI3OWIyMjgyLWZmYTItNGM3ZC04NGRmLTRkNDVlZmUzYTMzNQBodHRwczovL2VuY3J5cHRpb24tYS53YngyLmNvbS9lbmNyeXB0aW9uL2FwaS92MS9yZXNvdXJjZXMvMjg1MzI4NWMtYzQ2Yi00YjM1LTk1NDItOWE4MWQ0ZTNjODdm`,
86
+ `${
87
+ process.env.ENCRYPTION_SERVICE_URL || 'https://encryption-a.wbx2.com'
88
+ }/encryption/api/v1/authorizations/YzMyMWUzMjktMjhkNi00ZDUyLWE5ZDEtMzc0MDEwNDExNTMwAGh0dHBzOi8vZW5jcnlwdGlvbi1hLndieDIuY29tL2VuY3J5cHRpb24vYXBpL3YxL3Jlc291cmNlcy8yODUzMjg1Yy1jNDZiLTRiMzUtOTU0Mi05YTgxZDRlM2M4N2Y`,
89
+ ],
90
+ },
91
+ },
92
+ };
93
+
94
+ const data1 = {
95
+ contentUrl: `${channel.channelUrl}/contents/data1`,
96
+ contentId: 'data1',
97
+ type: 'test',
98
+ data: 'data1',
99
+ };
100
+
101
+ const data2 = {
102
+ type: 'test',
103
+ data: 'data2',
104
+ };
105
+
106
+ beforeAll(() => {
107
+ webex = new MockWebex({
108
+ children: {
109
+ board: Board,
110
+ },
111
+ request: sinon.stub().returns(
112
+ Promise.resolve({
113
+ headers: {},
114
+ body: '',
115
+ })
116
+ ),
117
+ upload: sinon.stub().returns(Promise.resolve({body: {downloadUrl: fakeURL}})),
118
+ });
119
+
120
+ Object.assign(webex.internal, {
121
+ device: {
122
+ deviceType: 'FAKE_DEVICE',
123
+ getServiceUrl: () => boardServiceUrl,
124
+ },
125
+ encryption: {
126
+ decryptText: sinon.stub().returns(Promise.resolve(decryptedText)),
127
+ encryptText: sinon.stub().returns(Promise.resolve(encryptedData)),
128
+ encryptBinary: sinon.stub().returns(
129
+ Promise.resolve({
130
+ scr: {},
131
+ cdata: encryptedData,
132
+ })
133
+ ),
134
+ download: sinon.stub().returns(
135
+ Promise.resolve({
136
+ toArrayBuffer: sinon.stub(),
137
+ })
138
+ ),
139
+ decryptScr: sinon.stub().returns(Promise.resolve('decryptedFoo')),
140
+ encryptScr: sinon.stub().returns(Promise.resolve('encryptedFoo')),
141
+ },
142
+ });
143
+
144
+ webex.config.board = boardConfig.board;
145
+ });
146
+
147
+ describe('#addContent()', () => {
148
+ beforeEach(() => {
149
+ webex.request.resetHistory();
150
+ });
151
+
152
+ it('requests POST all contents to contents', () =>
153
+ webex.internal.board.addContent(channel, [data1, data2]).then(() => {
154
+ assert.calledWith(
155
+ webex.request,
156
+ sinon.match({
157
+ method: 'POST',
158
+ uri: `${boardServiceUrl}/channels/${boardId}/contents`,
159
+ body: [
160
+ {
161
+ device: 'FAKE_DEVICE',
162
+ type: 'STRING',
163
+ encryptionKeyUrl: mockKey.uri,
164
+ payload: 'encryptedData',
165
+ },
166
+ {
167
+ device: 'FAKE_DEVICE',
168
+ type: 'STRING',
169
+ encryptionKeyUrl: mockKey.uri,
170
+ payload: 'encryptedData',
171
+ },
172
+ ],
173
+ })
174
+ );
175
+ }));
176
+
177
+ it('sends large data using multiple requests', () => {
178
+ const largeData = [];
179
+
180
+ for (let i = 0; i < 400; i += 1) {
181
+ largeData.push({data: i});
182
+ }
183
+
184
+ return webex.internal.board.addContent(channel, largeData).then(() => {
185
+ assert.equal(webex.request.callCount, 3);
186
+ });
187
+ });
188
+ });
189
+
190
+ describe('#setSnapshotImage()', () => {
191
+ beforeEach(() => {
192
+ webex.request.resetHistory();
193
+ sinon.stub(webex.internal.board, '_uploadImageToWebexFiles').returns(
194
+ Promise.resolve({
195
+ downloadUrl: fakeURL,
196
+ })
197
+ );
198
+ webex.internal.encryption.encryptScr.resetHistory();
199
+ });
200
+
201
+ afterEach(() => {
202
+ webex.internal.board._uploadImageToWebexFiles.restore();
203
+ webex.internal.encryption.encryptScr.resetHistory();
204
+ });
205
+
206
+ it('requests PATCH to board service', () =>
207
+ webex.internal.board.setSnapshotImage(channel, image).then(() => {
208
+ assert.calledWith(
209
+ webex.request,
210
+ sinon.match({
211
+ method: 'PATCH',
212
+ uri: channel.channelUrl,
213
+ body: {
214
+ image: {
215
+ url: fakeURL,
216
+ height: image.height,
217
+ width: image.width,
218
+ mimeType: 'image/png',
219
+ scr: 'encryptedFoo',
220
+ encryptionKeyUrl: channel.defaultEncryptionKeyUrl,
221
+ fileSize: image.size,
222
+ },
223
+ },
224
+ })
225
+ );
226
+ }));
227
+ });
228
+
229
+ describe('#createChannel()', () => {
230
+ const channelRequestBody = {
231
+ aclUrlLink: channel.aclUrlLink,
232
+ kmsMessage: channel.kmsMessage,
233
+ };
234
+
235
+ beforeAll(() => {
236
+ webex.request.resetHistory();
237
+ webex.request.returns(Promise.resolve({statusCode: 200, body: channelRes}));
238
+
239
+ return webex.internal.board.createChannel(conversation);
240
+ });
241
+
242
+ afterAll(() => {
243
+ // reset request to its original behavior
244
+ webex.request.returns(
245
+ Promise.resolve({
246
+ headers: {},
247
+ body: '',
248
+ })
249
+ );
250
+ });
251
+
252
+ it('requests POST to channels service', () => {
253
+ assert.calledWith(
254
+ webex.request,
255
+ sinon.match({
256
+ method: 'POST',
257
+ api: 'board',
258
+ resource: '/channels',
259
+ body: channelRequestBody,
260
+ })
261
+ );
262
+ });
263
+ });
264
+
265
+ describe('#deleteChannel()', () => {
266
+ it('requests PUT to ACL service to remove the link between conversation and board', () => {
267
+ webex.request.resetHistory();
268
+
269
+ return webex.internal.board.deleteChannel(conversation, channel).then(() => {
270
+ assert.calledWith(
271
+ webex.request,
272
+ sinon.match({
273
+ method: 'PUT',
274
+ uri: `${channel.aclUrl}/links`,
275
+ body: {
276
+ aclLinkType: 'INCOMING',
277
+ linkedAcl: conversation.aclUrl,
278
+ kmsMessage: {
279
+ method: 'delete',
280
+ uri: `${channel.kmsResourceUrl}/authorizations?${querystring.stringify({
281
+ authId: conversation.kmsResourceObjectUrl,
282
+ })}`,
283
+ },
284
+ aclLinkOperation: 'DELETE',
285
+ },
286
+ })
287
+ );
288
+ });
289
+ });
290
+
291
+ it('requests locks channel before delete when preventDeleteActiveChannel is enabled', () => {
292
+ webex.request.resetHistory();
293
+
294
+ return webex.internal.board
295
+ .deleteChannel(conversation, channel, {preventDeleteActiveChannel: true})
296
+ .then(() => {
297
+ assert.calledWith(
298
+ webex.request,
299
+ sinon.match({
300
+ method: 'POST',
301
+ uri: `${channel.channelUrl}/lock`,
302
+ qs: {
303
+ intent: 'delete',
304
+ },
305
+ })
306
+ );
307
+
308
+ assert.calledWith(
309
+ webex.request,
310
+ sinon.match({
311
+ method: 'PUT',
312
+ uri: `${channel.aclUrl}/links`,
313
+ body: {
314
+ aclLinkType: 'INCOMING',
315
+ linkedAcl: conversation.aclUrl,
316
+ kmsMessage: {
317
+ method: 'delete',
318
+ uri: `${channel.kmsResourceUrl}/authorizations?${querystring.stringify({
319
+ authId: conversation.kmsResourceObjectUrl,
320
+ })}`,
321
+ },
322
+ aclLinkOperation: 'DELETE',
323
+ },
324
+ })
325
+ );
326
+ });
327
+ });
328
+ });
329
+
330
+ describe('#lockChannelForDeletion()', () => {
331
+ it('requests POST with delete lock intent', () => {
332
+ webex.request.resetHistory();
333
+
334
+ return webex.internal.board.lockChannelForDeletion(channel).then(() => {
335
+ assert.calledWith(
336
+ webex.request,
337
+ sinon.match({
338
+ method: 'POST',
339
+ uri: `${channel.channelUrl}/lock`,
340
+ qs: {
341
+ intent: 'delete',
342
+ },
343
+ })
344
+ );
345
+ });
346
+ });
347
+ });
348
+
349
+ describe('#keepActive()', () => {
350
+ it('requests POST to keep channel status active', () => {
351
+ webex.request.resetHistory();
352
+
353
+ return webex.internal.board.keepActive(channel).then(() => {
354
+ assert.calledWith(
355
+ webex.request,
356
+ sinon.match({
357
+ method: 'POST',
358
+ uri: `${channel.channelUrl}/keepAlive`,
359
+ })
360
+ );
361
+ });
362
+ });
363
+ });
364
+
365
+ describe('#deleteAllContent()', () => {
366
+ beforeAll(() => {
367
+ webex.request.resetHistory();
368
+
369
+ return webex.internal.board.deleteAllContent(channel);
370
+ });
371
+
372
+ it('requests DELETE contents', () => {
373
+ assert.calledWith(
374
+ webex.request,
375
+ sinon.match({
376
+ method: 'DELETE',
377
+ uri: `${boardServiceUrl}/channels/${boardId}/contents`,
378
+ })
379
+ );
380
+ });
381
+ });
382
+
383
+ describe('#deletePartialContent()', () => {
384
+ it('requests POST contents with body contains the content to keep', () => {
385
+ webex.request.resetHistory();
386
+
387
+ return webex.internal.board.deletePartialContent(channel, [data1]).then(() => {
388
+ assert.calledWith(
389
+ webex.request,
390
+ sinon.match({
391
+ method: 'POST',
392
+ uri: `${boardServiceUrl}/channels/${boardId}/contents`,
393
+ qs: {clearBoard: true},
394
+ body: [
395
+ {
396
+ contentId: data1.contentId,
397
+ },
398
+ ],
399
+ })
400
+ );
401
+ });
402
+ });
403
+ });
404
+
405
+ describe('#_uploadImage()', () => {
406
+ let uploadImageToWebexFiles = null;
407
+
408
+ beforeAll(() => {
409
+ uploadImageToWebexFiles = sinon.stub(webex.internal.board, '_uploadImageToWebexFiles').returns(Promise.resolve({
410
+ downloadUrl: fakeURL
411
+ }));
412
+
413
+ return webex.internal.board._uploadImage(conversation, file);
414
+ });
415
+
416
+ afterAll(() => {
417
+ uploadImageToWebexFiles.restore();
418
+ });
419
+
420
+ it('encrypts binary file', () => {
421
+ assert.calledWith(webex.internal.encryption.encryptBinary, file);
422
+ });
423
+
424
+ it('uploads to webex files', () => {
425
+ assert.calledWith(webex.internal.board._uploadImageToWebexFiles, conversation, encryptedData);
426
+ });
427
+ });
428
+
429
+ describe('#_uploadImageToWebexFiles()', () => {
430
+ beforeAll(() => {
431
+ sinon.stub(webex.internal.board, '_getSpaceUrl').returns(Promise.resolve(fakeURL));
432
+
433
+ return webex.internal.board._uploadImage(conversation, file);
434
+ });
435
+
436
+ afterAll(() => webex.internal.board._getSpaceUrl.restore());
437
+
438
+ afterEach(() => {
439
+ webex.upload.resetHistory();
440
+ webex.internal.board._getSpaceUrl.resetHistory();
441
+ });
442
+
443
+ it('uses length for upload filesize', () => {
444
+ const blob = {
445
+ length: 4444,
446
+ size: 3333,
447
+ byteLength: 2222,
448
+ };
449
+
450
+ return webex.internal.board._uploadImageToWebexFiles(conversation, blob).then(() => {
451
+ assert.calledWith(
452
+ webex.upload,
453
+ sinon.match({
454
+ phases: {
455
+ initialize: {
456
+ fileSize: 4444,
457
+ },
458
+ finalize: {
459
+ body: {
460
+ fileSize: 4444,
461
+ },
462
+ },
463
+ },
464
+ })
465
+ );
466
+ });
467
+ });
468
+
469
+ it('uses size for upload filesize when length is not available', () => {
470
+ const blob = {
471
+ size: 3333,
472
+ byteLength: 2222,
473
+ };
474
+
475
+ return webex.internal.board._uploadImageToWebexFiles(conversation, blob).then(() => {
476
+ assert.calledWith(
477
+ webex.upload,
478
+ sinon.match({
479
+ phases: {
480
+ initialize: {
481
+ fileSize: 3333,
482
+ },
483
+ finalize: {
484
+ body: {
485
+ fileSize: 3333,
486
+ },
487
+ },
488
+ },
489
+ })
490
+ );
491
+ });
492
+ });
493
+
494
+ it('uses byteLenght for upload filesize when length and size are not available', () => {
495
+ const blob = {
496
+ byteLength: 2222,
497
+ };
498
+
499
+ return webex.internal.board._uploadImageToWebexFiles(conversation, blob).then(() => {
500
+ assert.calledWith(
501
+ webex.upload,
502
+ sinon.match({
503
+ phases: {
504
+ initialize: {
505
+ fileSize: 2222,
506
+ },
507
+ finalize: {
508
+ body: {
509
+ fileSize: 2222,
510
+ },
511
+ },
512
+ },
513
+ })
514
+ );
515
+ });
516
+ });
517
+ });
518
+
519
+ describe('#children', () => {
520
+ it('has a child of realtime', () => {
521
+ assert.isDefined(webex.internal.board.realtime);
522
+ });
523
+ });
524
+
525
+ describe('#getChannel()', () => {
526
+ beforeAll(() => {
527
+ webex.request.resetHistory();
528
+
529
+ return webex.internal.board.getChannel(channel);
530
+ });
531
+
532
+ it('requests GET to channels service', () => {
533
+ assert.calledWith(
534
+ webex.request,
535
+ sinon.match({
536
+ method: 'GET',
537
+ uri: `${boardServiceUrl}/channels/${boardId}`,
538
+ })
539
+ );
540
+ });
541
+
542
+ it('requires conversationId', () =>
543
+ assert.isRejected(webex.internal.board.getChannels(), '`conversation` is required'));
544
+ });
545
+
546
+ describe('#getContents()', () => {
547
+ beforeEach(() => {
548
+ sinon.stub(webex.internal.board, 'decryptContents').returns(['foo']);
549
+ webex.request.resetHistory();
550
+ });
551
+
552
+ afterEach(() => {
553
+ webex.internal.board.decryptContents.restore();
554
+ });
555
+
556
+ it('requests GET contents with default page size', () =>
557
+ webex.internal.board.getContents(channel).then(() =>
558
+ assert.calledWith(webex.request, {
559
+ uri: `${boardServiceUrl}/channels/${boardId}/contents`,
560
+ qs: {
561
+ contentsLimit: boardConfig.board.numberContentsPerPageForGet,
562
+ },
563
+ })
564
+ ));
565
+
566
+ it('requests GET contents with client defined page size', () =>
567
+ webex.internal.board.getContents(channel, {contentsLimit: 25}).then(() =>
568
+ assert.calledWith(webex.request, {
569
+ uri: `${boardServiceUrl}/channels/${boardId}/contents`,
570
+ qs: {
571
+ contentsLimit: 25,
572
+ },
573
+ })
574
+ ));
575
+ });
576
+
577
+ describe('#register()', () => {
578
+ beforeAll(() => {
579
+ webex.request.resetHistory();
580
+
581
+ return webex.internal.board.register({data: 'data'});
582
+ });
583
+
584
+ it('requests POST data to registration service', () => {
585
+ assert.calledWith(
586
+ webex.request,
587
+ sinon.match({
588
+ method: 'POST',
589
+ api: 'board',
590
+ resource: '/registrations',
591
+ })
592
+ );
593
+ });
594
+ });
595
+
596
+ describe('#registerToShareMercury()', () => {
597
+ beforeEach(() => {
598
+ webex.request.resetHistory();
599
+ webex.internal.mercury.localClusterServiceUrls = {
600
+ mercuryApiServiceClusterUrl: 'https://mercury-api-a5.wbx2.com/v1',
601
+ mercuryConnectionServiceClusterUrl: 'https://mercury-connection-a5.wbx2.com/v1',
602
+ };
603
+ webex.internal.feature.getFeature.returns(Promise.resolve(true));
604
+ });
605
+
606
+ it('requests POST data to registration service', () =>
607
+ webex.internal.board.registerToShareMercury(channel).then(() => {
608
+ assert.calledWith(
609
+ webex.request,
610
+ sinon.match({
611
+ method: 'POST',
612
+ uri: `${channel.channelUrl}/register`,
613
+ body: {
614
+ mercuryConnectionServiceClusterUrl:
615
+ webex.internal.mercury.localClusterServiceUrls.mercuryConnectionServiceClusterUrl,
616
+ webSocketUrl: webex.internal.device.webSocketUrl,
617
+ action: 'ADD',
618
+ },
619
+ })
620
+ );
621
+ }));
622
+
623
+ it('rejects when localClusterServiceUrls is null', () => {
624
+ webex.internal.mercury.localClusterServiceUrls = null;
625
+
626
+ return assert.isRejected(webex.internal.board.registerToShareMercury(channel));
627
+ });
628
+
629
+ it('rejects when web-shared-mercury is not enabled', () => {
630
+ webex.internal.feature.getFeature.returns(Promise.resolve(false));
631
+
632
+ return assert.isRejected(webex.internal.board.registerToShareMercury(channel));
633
+ });
634
+ });
635
+ });