@webex/internal-plugin-board 2.59.3-next.1 → 2.59.4-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,717 +1,717 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- import '@webex/internal-plugin-board';
6
-
7
- import {assert} from '@webex/test-helper-chai';
8
- import WebexCore from '@webex/webex-core';
9
- import testUsers from '@webex/test-helper-test-users';
10
- import fh from '@webex/test-helper-file';
11
- import {find, map} from 'lodash';
12
- import uuid from 'uuid';
13
-
14
- function generateTonsOfContents(numOfContents) {
15
- return new Promise((resolve) => {
16
- const contents = [];
17
-
18
- for (let i = 0; i < numOfContents; i += 1) {
19
- contents.push({
20
- type: 'curve',
21
- payload: JSON.stringify({id: i, type: 'curve'}),
22
- });
23
- }
24
- resolve(contents);
25
- });
26
- }
27
-
28
- describe('plugin-board', () => {
29
- describe('service', () => {
30
- let board, conversation, fixture, participants;
31
-
32
- before('create users', () =>
33
- testUsers.create({count: 3}).then((users) => {
34
- participants = users;
35
-
36
- return Promise.all(
37
- map(participants, (participant) => {
38
- participant.webex = new WebexCore({
39
- credentials: {
40
- authorization: participant.token,
41
- },
42
- });
43
-
44
- return participant.webex.internal.device
45
- .register()
46
- .then(() =>
47
- participant.webex.internal.feature.setFeature('developer', 'files-acl-write', true)
48
- );
49
- })
50
- );
51
- })
52
- );
53
-
54
- before('create conversation', () =>
55
- participants[0].webex.internal.conversation
56
- .create({
57
- displayName: 'Test Board Conversation',
58
- participants,
59
- })
60
- .then((c) => {
61
- conversation = c;
62
-
63
- return conversation;
64
- })
65
- );
66
-
67
- before('create channel (board)', () =>
68
- participants[0].webex.internal.board.createChannel(conversation).then((channel) => {
69
- board = channel;
70
-
71
- return channel;
72
- })
73
- );
74
-
75
- before('load fixture image', () =>
76
- fh.fetch('sample-image-small-one.png').then((fetchedFixture) => {
77
- fixture = fetchedFixture;
78
-
79
- return fetchedFixture;
80
- })
81
- );
82
-
83
- after('disconnect mercury', () =>
84
- Promise.all(
85
- map(participants, (participant) => participant.webex.internal.mercury.disconnect())
86
- )
87
- );
88
-
89
- describe('#getChannel', () => {
90
- it('gets the channel metadata', () =>
91
- participants[0].webex.internal.board.getChannel(board).then((channel) => {
92
- assert.property(channel, 'kmsResourceUrl');
93
- assert.property(channel, 'aclUrl');
94
-
95
- assert.equal(channel.channelUrl, board.channelUrl);
96
- assert.equal(channel.aclUrlLink, conversation.aclUrl);
97
- assert.notEqual(channel.kmsResourceUrl, conversation.kmsResourceObjectUrl);
98
- assert.notEqual(channel.aclUrl, conversation.aclUrl);
99
- assert.notEqual(
100
- channel.defaultEncryptionKeyUrl,
101
- conversation.defaultActivityEncryptionKeyUrl
102
- );
103
- }));
104
- });
105
-
106
- describe('#_uploadImage()', () => {
107
- after(() => participants[0].webex.internal.board.deleteAllContent(board));
108
-
109
- it('uploads image to webex files', () =>
110
- participants[0].webex.internal.board
111
- ._uploadImage(board, fixture)
112
- .then((scr) => participants[1].webex.internal.encryption.download(scr))
113
- .then((downloadedFile) =>
114
- fh
115
- .isMatchingFile(downloadedFile, fixture)
116
- .then((result) => assert.deepEqual(result, true))
117
- ));
118
- });
119
-
120
- describe('#setSnapshotImage()', () => {
121
- after(() => participants[0].webex.internal.board.deleteAllContent(board));
122
-
123
- it('uploads image to webex files and adds to channel', () => {
124
- let imageRes;
125
-
126
- return participants[0].webex.internal.board
127
- .setSnapshotImage(board, fixture)
128
- .then((res) => {
129
- imageRes = res.image;
130
- assert.isDefined(res.image, 'image field is included');
131
- assert.equal(res.image.encryptionKeyUrl, board.defaultEncryptionKeyUrl);
132
- assert.isAbove(res.image.scr.length, 0, 'scr string exists');
133
-
134
- return participants[1].webex.internal.board.getChannel(board);
135
- })
136
- .then((res) => {
137
- assert.deepEqual(imageRes, res.image);
138
-
139
- // ensure others can download the image
140
- return participants[2].webex.internal.encryption.decryptScr(
141
- board.defaultEncryptionKeyUrl,
142
- res.image.scr
143
- );
144
- })
145
- .then((decryptedScr) => participants[2].webex.internal.encryption.download(decryptedScr))
146
- .then((file) =>
147
- fh.isMatchingFile(file, fixture).then((result) => assert.deepEqual(result, true))
148
- );
149
- });
150
- });
151
-
152
- describe('#ping()', () => {
153
- it('pings board service', () => participants[0].webex.internal.board.ping());
154
- });
155
-
156
- describe('#addImage()', () => {
157
- let testContent, testScr;
158
-
159
- after(() => participants[0].webex.internal.board.deleteAllContent(board));
160
-
161
- it('uploads image to webex files', () =>
162
- participants[0].webex.internal.board
163
- .addImage(board, fixture, {displayName: fixture.name})
164
- .then((fileContent) => {
165
- testContent = fileContent[0].items[0];
166
- assert.equal(testContent.type, 'FILE', 'content type should be image');
167
- assert.property(testContent, 'contentUrl', 'content should contain contentId property');
168
- assert.property(
169
- testContent,
170
- 'channelUrl',
171
- 'content should contain contentUrl property'
172
- );
173
- assert.property(testContent, 'metadata', 'content should contain metadata property');
174
- assert.property(testContent, 'file', 'content should contain file property');
175
- assert.property(testContent.file, 'scr', 'content file should contain scr property');
176
- }));
177
-
178
- it('adds to presistence', () =>
179
- participants[0].webex.internal.board.getContents(board).then((allContents) => {
180
- const imageContent = find(allContents.items, {contentId: testContent.contentId});
181
-
182
- assert.isDefined(imageContent);
183
- assert.property(imageContent, 'file');
184
- assert.property(imageContent.file, 'scr');
185
- assert.equal(imageContent.metadata.displayName, 'sample-image-small-one.png');
186
- testScr = imageContent.file.scr;
187
-
188
- return imageContent.file.scr;
189
- }));
190
-
191
- it('matches file file downloaded', () =>
192
- participants[0].webex.internal.encryption
193
- .download(testScr)
194
- .then((downloadedFile) =>
195
- fh
196
- .isMatchingFile(downloadedFile, fixture)
197
- .then((result) => assert.deepEqual(result, true))
198
- ));
199
-
200
- it('allows others to download image', () =>
201
- participants[2].webex.internal.encryption
202
- .download(testScr)
203
- .then((downloadedFile) =>
204
- fh
205
- .isMatchingFile(downloadedFile, fixture)
206
- .then((result) => assert.deepEqual(result, true))
207
- ));
208
-
209
- describe('when image content has no metadata', () => {
210
- before(() => participants[0].webex.internal.board.deleteAllContent(board));
211
-
212
- it('decrypts no meta', () => {
213
- let testContent, testScr;
214
-
215
- return participants[0].webex.internal.board
216
- .addImage(board, fixture)
217
- .then((fileContent) => {
218
- testContent = fileContent[0].items[0];
219
- assert.equal(testContent.type, 'FILE', 'content type should be image');
220
- assert.property(
221
- testContent,
222
- 'contentUrl',
223
- 'content should contain contentId property'
224
- );
225
- assert.property(
226
- testContent,
227
- 'channelUrl',
228
- 'content should contain contentUrl property'
229
- );
230
- assert.property(testContent, 'file', 'content should contain file property');
231
- assert.property(testContent.file, 'scr', 'content file should contain scr property');
232
- assert.deepEqual(testContent.metadata, {});
233
-
234
- return participants[0].webex.internal.board.getContents(board);
235
- })
236
- .then((allContents) => {
237
- const imageContent = find(allContents.items, {contentId: testContent.contentId});
238
-
239
- assert.isDefined(imageContent);
240
- assert.property(imageContent, 'file');
241
- assert.property(imageContent.file, 'scr');
242
- testScr = imageContent.file.scr;
243
-
244
- return imageContent.file.scr;
245
- })
246
- .then(() =>
247
- participants[0].webex.internal.encryption
248
- .download(testScr)
249
- .then((downloadedFile) => fh.isMatchingFile(downloadedFile, fixture))
250
- .then((res) => assert.isTrue(res))
251
- );
252
- });
253
- });
254
- });
255
-
256
- describe('#getChannels()', () => {
257
- it('retrieves a newly created board for a specified conversation within a single page', () =>
258
- participants[0].webex.internal.board.getChannels(conversation).then((getChannelsResp) => {
259
- const channelFound = find(getChannelsResp.items, {channelId: board.channelId});
260
-
261
- assert.isDefined(channelFound);
262
- assert.notProperty(getChannelsResp.links, 'next');
263
- }));
264
-
265
- it('retrieves annotated board', () => {
266
- let annotatedBoard;
267
-
268
- return participants[0].webex.internal.board
269
- .createChannel(conversation, {type: 'annotated'})
270
- .then((res) => {
271
- annotatedBoard = res;
272
-
273
- return participants[0].webex.internal.board.getChannels(conversation, {
274
- type: 'annotated',
275
- });
276
- })
277
- .then((getChannelsResp) => {
278
- const channelFound = find(getChannelsResp.items, {channelId: annotatedBoard.channelId});
279
-
280
- assert.isUndefined(find(getChannelsResp.items, {channelId: board.channelId}));
281
- assert.isDefined(channelFound);
282
- assert.notProperty(getChannelsResp.links, 'next');
283
- });
284
- });
285
-
286
- it('retrieves all boards for a specified conversation across multiple pages', () => {
287
- const numChannelsToAdd = 12;
288
- const pageLimit = 5;
289
- const channelsCreated = [];
290
- let channelsReceived = [];
291
- let convo;
292
-
293
- return (
294
- participants[0].webex.internal.conversation
295
- .create({
296
- displayName: `Test Get Channels Conversation ${uuid.v4()}`,
297
- participants,
298
- })
299
- .then((c) => {
300
- convo = c;
301
- const promises = [];
302
-
303
- for (let i = 0; i < numChannelsToAdd; i += 1) {
304
- promises.push(
305
- participants[0].webex.internal.board.createChannel(convo).then((channel) => {
306
- Reflect.deleteProperty(channel, 'kmsMessage');
307
- channelsCreated.push(channel);
308
- })
309
- );
310
- }
311
-
312
- return Promise.all(promises);
313
- })
314
- // get boards, page 1
315
- .then(() =>
316
- participants[0].webex.internal.board.getChannels(convo, {
317
- channelsLimit: pageLimit,
318
- })
319
- )
320
- // get boards, page 2
321
- .then((channelPage) => {
322
- assert.lengthOf(channelPage.items, pageLimit);
323
- assert.isTrue(channelPage.hasNext());
324
- channelsReceived = channelsReceived.concat(channelPage.items);
325
-
326
- return channelPage.next();
327
- })
328
- // get boards, page 3
329
- .then((channelPage) => {
330
- assert.lengthOf(channelPage.items, pageLimit);
331
- assert.isTrue(channelPage.hasNext());
332
- channelsReceived = channelsReceived.concat(channelPage.items);
333
-
334
- return channelPage.next();
335
- })
336
- .then((channelPage) => {
337
- assert.lengthOf(channelPage, 2);
338
- assert.isFalse(channelPage.hasNext());
339
- channelsReceived = channelsReceived.concat(channelPage.items);
340
-
341
- channelsCreated.sort((a, b) => a.createdTime - b.createdTime);
342
- channelsReceived.sort((a, b) => a.createdTime - b.createdTime);
343
-
344
- if (channelsCreated.length === channelsReceived.length) {
345
- channelsReceived.forEach((channel, i) => {
346
- delete channel.format;
347
- assert.deepEqual(channel, channelsCreated[i]);
348
- });
349
- }
350
- })
351
- );
352
- });
353
- });
354
-
355
- describe('#getContents()', () => {
356
- afterEach(() => participants[0].webex.internal.board.deleteAllContent(board));
357
-
358
- it('adds and gets contents from the specified board', () => {
359
- const contents = [{type: 'curve'}];
360
- const data = [
361
- {
362
- type: contents[0].type,
363
- payload: JSON.stringify(contents[0]),
364
- },
365
- ];
366
-
367
- return participants[0].webex.internal.board
368
- .deleteAllContent(board)
369
- .then(() => participants[0].webex.internal.board.addContent(board, data))
370
- .then(() => participants[0].webex.internal.board.getContents(board))
371
- .then((contentPage) => {
372
- assert.equal(contentPage.length, data.length);
373
- assert.equal(contentPage.items[0].payload, data[0].payload);
374
- assert.equal(contentPage.items[0].type, data[0].type);
375
- })
376
- .then(() => participants[0].webex.internal.board.deleteAllContent(board));
377
- });
378
-
379
- it('allows other people to read contents', () => {
380
- const contents = [{type: 'curve', points: [1, 2, 3, 4]}];
381
- const data = [
382
- {
383
- type: contents[0].type,
384
- payload: JSON.stringify(contents[0]),
385
- },
386
- ];
387
-
388
- return participants[0].webex.internal.board
389
- .addContent(board, data)
390
- .then(() => participants[1].webex.internal.board.getContents(board))
391
- .then((contentPage) => {
392
- assert.equal(contentPage.length, data.length);
393
- assert.equal(contentPage.items[0].payload, data[0].payload);
394
-
395
- return participants[2].webex.internal.board.getContents(board);
396
- })
397
- .then((contentPage) => {
398
- assert.equal(contentPage.length, data.length);
399
- assert.equal(contentPage.items[0].payload, data[0].payload);
400
- });
401
- });
402
-
403
- it('allows other people to write contents', () => {
404
- const contents = [{type: 'curve', points: [1, 2, 3, 4]}];
405
- const data = [
406
- {
407
- type: contents[0].type,
408
- payload: JSON.stringify(contents[0]),
409
- },
410
- ];
411
-
412
- return participants[2].webex.internal.board
413
- .addContent(board, data)
414
- .then(() => participants[1].webex.internal.board.getContents(board))
415
- .then((contentPage) => {
416
- assert.equal(contentPage.length, data.length);
417
- assert.equal(contentPage.items[0].payload, data[0].payload);
418
- });
419
- });
420
-
421
- describe('handles large data sets', () => {
422
- const numberOfContents = 30;
423
- let tonsOfContents;
424
-
425
- before('generate contents', () =>
426
- generateTonsOfContents(numberOfContents).then((res) => {
427
- tonsOfContents = res;
428
- })
429
- );
430
-
431
- beforeEach('create contents', () =>
432
- participants[0].webex.internal.board.addContent(board, tonsOfContents)
433
- );
434
-
435
- it('using the default page limit', () =>
436
- participants[0].webex.internal.board.getContents(board).then((res) => {
437
- assert.lengthOf(res, numberOfContents);
438
- assert.isFalse(res.hasNext());
439
-
440
- for (let i = 0; i < res.length; i += 1) {
441
- assert.equal(res.items[i].payload, tonsOfContents[i].payload, 'payload data matches');
442
- }
443
- }));
444
-
445
- it('using a client defined page limit', () =>
446
- participants[0].webex.internal.board
447
- .getContents(board, {contentsLimit: 25})
448
- .then((res) => {
449
- assert.lengthOf(res, 25);
450
- assert.isTrue(res.hasNext());
451
-
452
- return res.next();
453
- })
454
- .then((res) => {
455
- assert.lengthOf(res, numberOfContents - 25);
456
- assert.isFalse(res.hasNext());
457
- }));
458
- });
459
- });
460
-
461
- describe('#deleteAllContent()', () => {
462
- after(() => participants[0].webex.internal.board.deleteAllContent(board));
463
-
464
- it('delete all contents from the specified board', () => {
465
- const channel = board;
466
- const contents = [
467
- {
468
- id: uuid.v4(),
469
- type: 'file',
470
- },
471
- {
472
- id: uuid.v4(),
473
- type: 'string',
474
- },
475
- ];
476
- const data = [
477
- {
478
- type: contents[0].type,
479
- payload: JSON.stringify(contents[0]),
480
- },
481
- {
482
- type: contents[1].type,
483
- payload: JSON.stringify(contents[1]),
484
- },
485
- ];
486
-
487
- return participants[0].webex.internal.board
488
- .addContent(channel, data)
489
- .then(() => participants[0].webex.internal.board.deleteAllContent(channel))
490
- .then(() => participants[0].webex.internal.board.getContents(channel))
491
- .then((res) => {
492
- assert.lengthOf(res, 0);
493
-
494
- return res;
495
- });
496
- });
497
- });
498
-
499
- describe('#deletePartialContent()', () => {
500
- after(() => participants[0].webex.internal.board.deleteAllContent(board));
501
-
502
- it('deletes some contents from the specified board', () => {
503
- const channel = board;
504
- const data = [
505
- {
506
- type: 'STRING',
507
- payload: JSON.stringify({id: uuid.v4()}),
508
- },
509
- {
510
- type: 'FILE',
511
- payload: JSON.stringify({id: uuid.v4()}),
512
- },
513
- ];
514
- const contentsToKeep = [];
515
-
516
- return participants[0].webex.internal.board
517
- .addContent(channel, data)
518
- .then(([firstPageRes]) => {
519
- contentsToKeep.push(firstPageRes.items[1]);
520
- })
521
- .then(() =>
522
- participants[0].webex.internal.board.deletePartialContent(channel, contentsToKeep)
523
- )
524
- .then(() => participants[0].webex.internal.board.getContents(channel))
525
- .then((page) => {
526
- assert.lengthOf(page, 1);
527
- delete page.items[0].format;
528
- assert.deepEqual(page.items[0], contentsToKeep[0]);
529
-
530
- return page;
531
- });
532
- });
533
- });
534
-
535
- describe('when a user leaves conversation', () => {
536
- it('does not allow board user to create board', () => {
537
- let currentConvo;
538
-
539
- return participants[0].webex.internal.conversation
540
- .create({
541
- displayName: 'Test Board Member Leave Conversation',
542
- participants,
543
- })
544
- .then((c) => {
545
- currentConvo = c;
546
-
547
- return participants[1].webex.internal.conversation.leave(currentConvo);
548
- })
549
- .then(() =>
550
- assert.isRejected(participants[1].webex.internal.board.createChannel(currentConvo))
551
- );
552
- });
553
-
554
- it('does not allow board creator to access and decrypt contents', () => {
555
- let currentConvo;
556
- let currentBoard;
557
- const encryptedBoardContent = {};
558
- const data = [
559
- {
560
- type: 'curve',
561
- payload: JSON.stringify({type: 'curve'}),
562
- },
563
- ];
564
-
565
- return (
566
- participants[1].webex.internal.conversation
567
- .create({
568
- displayName: 'Test Board Creator Leave Conversation',
569
- participants,
570
- })
571
- .then((c) => {
572
- currentConvo = c;
573
-
574
- return participants[1].webex.internal.board.createChannel(currentConvo);
575
- })
576
- .then((b) => {
577
- currentBoard = b;
578
-
579
- return participants[1].webex.internal.conversation.leave(currentConvo);
580
- })
581
- .then(() =>
582
- participants[0].webex.internal.board.encryptContents(
583
- currentBoard.defaultEncryptionKeyUrl,
584
- data
585
- )
586
- )
587
- .then((encryptedData) => {
588
- encryptedBoardContent.items = encryptedData;
589
-
590
- return assert.isRejected(
591
- participants[1].webex.internal.board.getContents(currentBoard)
592
- );
593
- })
594
- // ensure keys aren't cached
595
- .then(() => participants[1].webex.unboundedStorage.clear())
596
- .then(() =>
597
- assert.isRejected(
598
- participants[1].webex.internal.board.decryptContents(encryptedBoardContent)
599
- )
600
- )
601
- );
602
- });
603
- });
604
-
605
- describe('#deleteChannel()', () => {
606
- it('deletes channel', () => {
607
- let newChannel;
608
-
609
- return participants[1].webex.internal.board
610
- .createChannel(conversation)
611
- .then((res) => {
612
- newChannel = res;
613
-
614
- return participants[1].webex.internal.board.deleteChannel(conversation, newChannel);
615
- })
616
- .then(() =>
617
- assert.isRejected(participants[1].webex.internal.board.getChannel(newChannel))
618
- );
619
- });
620
-
621
- describe('when preventDeleteActiveChannel is enabled', () => {
622
- it('does not delete when a channel is being used', () => {
623
- let activeChannel;
624
-
625
- return participants[1].webex.internal.board
626
- .createChannel(conversation)
627
- .then((res) => {
628
- activeChannel = res;
629
- const data = [
630
- {
631
- type: 'curve',
632
- payload: JSON.stringify({type: 'curve'}),
633
- },
634
- ];
635
-
636
- // this will mark the channel as being used
637
- return participants[0].webex.internal.board.addContent(activeChannel, data);
638
- })
639
- .then(() =>
640
- assert.isRejected(
641
- participants[1].webex.internal.board.deleteChannel(conversation, activeChannel, {
642
- preventDeleteActiveChannel: true,
643
- })
644
- )
645
- )
646
- .then(() => participants[1].webex.internal.board.getChannel(activeChannel));
647
- });
648
-
649
- it('deletes inactive channel', () => {
650
- let inActiveChannel;
651
-
652
- return participants[1].webex.internal.board
653
- .createChannel(conversation)
654
- .then((res) => {
655
- inActiveChannel = res;
656
-
657
- return participants[1].webex.internal.board.deleteChannel(
658
- conversation,
659
- inActiveChannel,
660
- {preventDeleteActiveChannel: true}
661
- );
662
- })
663
- .then(() =>
664
- assert.isRejected(participants[1].webex.internal.board.getChannel(inActiveChannel))
665
- );
666
- });
667
- });
668
- });
669
-
670
- describe('#lockChannelForDeletion()', () => {
671
- it('locks a channel for deletion which rejects any incoming activities', () => {
672
- let newChannel;
673
-
674
- return participants[1].webex.internal.board
675
- .createChannel(conversation)
676
- .then((res) => {
677
- newChannel = res;
678
-
679
- return participants[1].webex.internal.board.lockChannelForDeletion(newChannel);
680
- })
681
- .then(() => {
682
- const data = [
683
- {
684
- type: 'curve',
685
- payload: JSON.stringify({type: 'curve'}),
686
- },
687
- ];
688
-
689
- return assert.isRejected(
690
- participants[0].webex.internal.board.addContent(newChannel, data)
691
- );
692
- });
693
- });
694
- });
695
-
696
- describe('#keepActive()', () => {
697
- it('keeps a channel status as active', () => {
698
- let newChannel;
699
-
700
- return participants[1].webex.internal.board
701
- .createChannel(conversation)
702
- .then((res) => {
703
- newChannel = res;
704
-
705
- return participants[1].webex.internal.board.keepActive(newChannel);
706
- })
707
- .then(() =>
708
- assert.isRejected(
709
- participants[0].webex.internal.board.deleteChannel(conversation, newChannel, {
710
- preventDeleteActiveChannel: true,
711
- })
712
- )
713
- );
714
- });
715
- });
716
- });
717
- });
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import '@webex/internal-plugin-board';
6
+
7
+ import {assert} from '@webex/test-helper-chai';
8
+ import WebexCore from '@webex/webex-core';
9
+ import testUsers from '@webex/test-helper-test-users';
10
+ import fh from '@webex/test-helper-file';
11
+ import {find, map} from 'lodash';
12
+ import uuid from 'uuid';
13
+
14
+ function generateTonsOfContents(numOfContents) {
15
+ return new Promise((resolve) => {
16
+ const contents = [];
17
+
18
+ for (let i = 0; i < numOfContents; i += 1) {
19
+ contents.push({
20
+ type: 'curve',
21
+ payload: JSON.stringify({id: i, type: 'curve'}),
22
+ });
23
+ }
24
+ resolve(contents);
25
+ });
26
+ }
27
+
28
+ describe('plugin-board', () => {
29
+ describe('service', () => {
30
+ let board, conversation, fixture, participants;
31
+
32
+ before('create users', () =>
33
+ testUsers.create({count: 3}).then((users) => {
34
+ participants = users;
35
+
36
+ return Promise.all(
37
+ map(participants, (participant) => {
38
+ participant.webex = new WebexCore({
39
+ credentials: {
40
+ authorization: participant.token,
41
+ },
42
+ });
43
+
44
+ return participant.webex.internal.device
45
+ .register()
46
+ .then(() =>
47
+ participant.webex.internal.feature.setFeature('developer', 'files-acl-write', true)
48
+ );
49
+ })
50
+ );
51
+ })
52
+ );
53
+
54
+ before('create conversation', () =>
55
+ participants[0].webex.internal.conversation
56
+ .create({
57
+ displayName: 'Test Board Conversation',
58
+ participants,
59
+ })
60
+ .then((c) => {
61
+ conversation = c;
62
+
63
+ return conversation;
64
+ })
65
+ );
66
+
67
+ before('create channel (board)', () =>
68
+ participants[0].webex.internal.board.createChannel(conversation).then((channel) => {
69
+ board = channel;
70
+
71
+ return channel;
72
+ })
73
+ );
74
+
75
+ before('load fixture image', () =>
76
+ fh.fetch('sample-image-small-one.png').then((fetchedFixture) => {
77
+ fixture = fetchedFixture;
78
+
79
+ return fetchedFixture;
80
+ })
81
+ );
82
+
83
+ after('disconnect mercury', () =>
84
+ Promise.all(
85
+ map(participants, (participant) => participant.webex.internal.mercury.disconnect())
86
+ )
87
+ );
88
+
89
+ describe('#getChannel', () => {
90
+ it('gets the channel metadata', () =>
91
+ participants[0].webex.internal.board.getChannel(board).then((channel) => {
92
+ assert.property(channel, 'kmsResourceUrl');
93
+ assert.property(channel, 'aclUrl');
94
+
95
+ assert.equal(channel.channelUrl, board.channelUrl);
96
+ assert.equal(channel.aclUrlLink, conversation.aclUrl);
97
+ assert.notEqual(channel.kmsResourceUrl, conversation.kmsResourceObjectUrl);
98
+ assert.notEqual(channel.aclUrl, conversation.aclUrl);
99
+ assert.notEqual(
100
+ channel.defaultEncryptionKeyUrl,
101
+ conversation.defaultActivityEncryptionKeyUrl
102
+ );
103
+ }));
104
+ });
105
+
106
+ describe('#_uploadImage()', () => {
107
+ after(() => participants[0].webex.internal.board.deleteAllContent(board));
108
+
109
+ it('uploads image to webex files', () =>
110
+ participants[0].webex.internal.board
111
+ ._uploadImage(board, fixture)
112
+ .then((scr) => participants[1].webex.internal.encryption.download(scr))
113
+ .then((downloadedFile) =>
114
+ fh
115
+ .isMatchingFile(downloadedFile, fixture)
116
+ .then((result) => assert.deepEqual(result, true))
117
+ ));
118
+ });
119
+
120
+ describe('#setSnapshotImage()', () => {
121
+ after(() => participants[0].webex.internal.board.deleteAllContent(board));
122
+
123
+ it('uploads image to webex files and adds to channel', () => {
124
+ let imageRes;
125
+
126
+ return participants[0].webex.internal.board
127
+ .setSnapshotImage(board, fixture)
128
+ .then((res) => {
129
+ imageRes = res.image;
130
+ assert.isDefined(res.image, 'image field is included');
131
+ assert.equal(res.image.encryptionKeyUrl, board.defaultEncryptionKeyUrl);
132
+ assert.isAbove(res.image.scr.length, 0, 'scr string exists');
133
+
134
+ return participants[1].webex.internal.board.getChannel(board);
135
+ })
136
+ .then((res) => {
137
+ assert.deepEqual(imageRes, res.image);
138
+
139
+ // ensure others can download the image
140
+ return participants[2].webex.internal.encryption.decryptScr(
141
+ board.defaultEncryptionKeyUrl,
142
+ res.image.scr
143
+ );
144
+ })
145
+ .then((decryptedScr) => participants[2].webex.internal.encryption.download(decryptedScr))
146
+ .then((file) =>
147
+ fh.isMatchingFile(file, fixture).then((result) => assert.deepEqual(result, true))
148
+ );
149
+ });
150
+ });
151
+
152
+ describe('#ping()', () => {
153
+ it('pings board service', () => participants[0].webex.internal.board.ping());
154
+ });
155
+
156
+ describe('#addImage()', () => {
157
+ let testContent, testScr;
158
+
159
+ after(() => participants[0].webex.internal.board.deleteAllContent(board));
160
+
161
+ it('uploads image to webex files', () =>
162
+ participants[0].webex.internal.board
163
+ .addImage(board, fixture, {displayName: fixture.name})
164
+ .then((fileContent) => {
165
+ testContent = fileContent[0].items[0];
166
+ assert.equal(testContent.type, 'FILE', 'content type should be image');
167
+ assert.property(testContent, 'contentUrl', 'content should contain contentId property');
168
+ assert.property(
169
+ testContent,
170
+ 'channelUrl',
171
+ 'content should contain contentUrl property'
172
+ );
173
+ assert.property(testContent, 'metadata', 'content should contain metadata property');
174
+ assert.property(testContent, 'file', 'content should contain file property');
175
+ assert.property(testContent.file, 'scr', 'content file should contain scr property');
176
+ }));
177
+
178
+ it('adds to presistence', () =>
179
+ participants[0].webex.internal.board.getContents(board).then((allContents) => {
180
+ const imageContent = find(allContents.items, {contentId: testContent.contentId});
181
+
182
+ assert.isDefined(imageContent);
183
+ assert.property(imageContent, 'file');
184
+ assert.property(imageContent.file, 'scr');
185
+ assert.equal(imageContent.metadata.displayName, 'sample-image-small-one.png');
186
+ testScr = imageContent.file.scr;
187
+
188
+ return imageContent.file.scr;
189
+ }));
190
+
191
+ it('matches file file downloaded', () =>
192
+ participants[0].webex.internal.encryption
193
+ .download(testScr)
194
+ .then((downloadedFile) =>
195
+ fh
196
+ .isMatchingFile(downloadedFile, fixture)
197
+ .then((result) => assert.deepEqual(result, true))
198
+ ));
199
+
200
+ it('allows others to download image', () =>
201
+ participants[2].webex.internal.encryption
202
+ .download(testScr)
203
+ .then((downloadedFile) =>
204
+ fh
205
+ .isMatchingFile(downloadedFile, fixture)
206
+ .then((result) => assert.deepEqual(result, true))
207
+ ));
208
+
209
+ describe('when image content has no metadata', () => {
210
+ before(() => participants[0].webex.internal.board.deleteAllContent(board));
211
+
212
+ it('decrypts no meta', () => {
213
+ let testContent, testScr;
214
+
215
+ return participants[0].webex.internal.board
216
+ .addImage(board, fixture)
217
+ .then((fileContent) => {
218
+ testContent = fileContent[0].items[0];
219
+ assert.equal(testContent.type, 'FILE', 'content type should be image');
220
+ assert.property(
221
+ testContent,
222
+ 'contentUrl',
223
+ 'content should contain contentId property'
224
+ );
225
+ assert.property(
226
+ testContent,
227
+ 'channelUrl',
228
+ 'content should contain contentUrl property'
229
+ );
230
+ assert.property(testContent, 'file', 'content should contain file property');
231
+ assert.property(testContent.file, 'scr', 'content file should contain scr property');
232
+ assert.deepEqual(testContent.metadata, {});
233
+
234
+ return participants[0].webex.internal.board.getContents(board);
235
+ })
236
+ .then((allContents) => {
237
+ const imageContent = find(allContents.items, {contentId: testContent.contentId});
238
+
239
+ assert.isDefined(imageContent);
240
+ assert.property(imageContent, 'file');
241
+ assert.property(imageContent.file, 'scr');
242
+ testScr = imageContent.file.scr;
243
+
244
+ return imageContent.file.scr;
245
+ })
246
+ .then(() =>
247
+ participants[0].webex.internal.encryption
248
+ .download(testScr)
249
+ .then((downloadedFile) => fh.isMatchingFile(downloadedFile, fixture))
250
+ .then((res) => assert.isTrue(res))
251
+ );
252
+ });
253
+ });
254
+ });
255
+
256
+ describe('#getChannels()', () => {
257
+ it('retrieves a newly created board for a specified conversation within a single page', () =>
258
+ participants[0].webex.internal.board.getChannels(conversation).then((getChannelsResp) => {
259
+ const channelFound = find(getChannelsResp.items, {channelId: board.channelId});
260
+
261
+ assert.isDefined(channelFound);
262
+ assert.notProperty(getChannelsResp.links, 'next');
263
+ }));
264
+
265
+ it('retrieves annotated board', () => {
266
+ let annotatedBoard;
267
+
268
+ return participants[0].webex.internal.board
269
+ .createChannel(conversation, {type: 'annotated'})
270
+ .then((res) => {
271
+ annotatedBoard = res;
272
+
273
+ return participants[0].webex.internal.board.getChannels(conversation, {
274
+ type: 'annotated',
275
+ });
276
+ })
277
+ .then((getChannelsResp) => {
278
+ const channelFound = find(getChannelsResp.items, {channelId: annotatedBoard.channelId});
279
+
280
+ assert.isUndefined(find(getChannelsResp.items, {channelId: board.channelId}));
281
+ assert.isDefined(channelFound);
282
+ assert.notProperty(getChannelsResp.links, 'next');
283
+ });
284
+ });
285
+
286
+ it('retrieves all boards for a specified conversation across multiple pages', () => {
287
+ const numChannelsToAdd = 12;
288
+ const pageLimit = 5;
289
+ const channelsCreated = [];
290
+ let channelsReceived = [];
291
+ let convo;
292
+
293
+ return (
294
+ participants[0].webex.internal.conversation
295
+ .create({
296
+ displayName: `Test Get Channels Conversation ${uuid.v4()}`,
297
+ participants,
298
+ })
299
+ .then((c) => {
300
+ convo = c;
301
+ const promises = [];
302
+
303
+ for (let i = 0; i < numChannelsToAdd; i += 1) {
304
+ promises.push(
305
+ participants[0].webex.internal.board.createChannel(convo).then((channel) => {
306
+ Reflect.deleteProperty(channel, 'kmsMessage');
307
+ channelsCreated.push(channel);
308
+ })
309
+ );
310
+ }
311
+
312
+ return Promise.all(promises);
313
+ })
314
+ // get boards, page 1
315
+ .then(() =>
316
+ participants[0].webex.internal.board.getChannels(convo, {
317
+ channelsLimit: pageLimit,
318
+ })
319
+ )
320
+ // get boards, page 2
321
+ .then((channelPage) => {
322
+ assert.lengthOf(channelPage.items, pageLimit);
323
+ assert.isTrue(channelPage.hasNext());
324
+ channelsReceived = channelsReceived.concat(channelPage.items);
325
+
326
+ return channelPage.next();
327
+ })
328
+ // get boards, page 3
329
+ .then((channelPage) => {
330
+ assert.lengthOf(channelPage.items, pageLimit);
331
+ assert.isTrue(channelPage.hasNext());
332
+ channelsReceived = channelsReceived.concat(channelPage.items);
333
+
334
+ return channelPage.next();
335
+ })
336
+ .then((channelPage) => {
337
+ assert.lengthOf(channelPage, 2);
338
+ assert.isFalse(channelPage.hasNext());
339
+ channelsReceived = channelsReceived.concat(channelPage.items);
340
+
341
+ channelsCreated.sort((a, b) => a.createdTime - b.createdTime);
342
+ channelsReceived.sort((a, b) => a.createdTime - b.createdTime);
343
+
344
+ if (channelsCreated.length === channelsReceived.length) {
345
+ channelsReceived.forEach((channel, i) => {
346
+ delete channel.format;
347
+ assert.deepEqual(channel, channelsCreated[i]);
348
+ });
349
+ }
350
+ })
351
+ );
352
+ });
353
+ });
354
+
355
+ describe('#getContents()', () => {
356
+ afterEach(() => participants[0].webex.internal.board.deleteAllContent(board));
357
+
358
+ it('adds and gets contents from the specified board', () => {
359
+ const contents = [{type: 'curve'}];
360
+ const data = [
361
+ {
362
+ type: contents[0].type,
363
+ payload: JSON.stringify(contents[0]),
364
+ },
365
+ ];
366
+
367
+ return participants[0].webex.internal.board
368
+ .deleteAllContent(board)
369
+ .then(() => participants[0].webex.internal.board.addContent(board, data))
370
+ .then(() => participants[0].webex.internal.board.getContents(board))
371
+ .then((contentPage) => {
372
+ assert.equal(contentPage.length, data.length);
373
+ assert.equal(contentPage.items[0].payload, data[0].payload);
374
+ assert.equal(contentPage.items[0].type, data[0].type);
375
+ })
376
+ .then(() => participants[0].webex.internal.board.deleteAllContent(board));
377
+ });
378
+
379
+ it('allows other people to read contents', () => {
380
+ const contents = [{type: 'curve', points: [1, 2, 3, 4]}];
381
+ const data = [
382
+ {
383
+ type: contents[0].type,
384
+ payload: JSON.stringify(contents[0]),
385
+ },
386
+ ];
387
+
388
+ return participants[0].webex.internal.board
389
+ .addContent(board, data)
390
+ .then(() => participants[1].webex.internal.board.getContents(board))
391
+ .then((contentPage) => {
392
+ assert.equal(contentPage.length, data.length);
393
+ assert.equal(contentPage.items[0].payload, data[0].payload);
394
+
395
+ return participants[2].webex.internal.board.getContents(board);
396
+ })
397
+ .then((contentPage) => {
398
+ assert.equal(contentPage.length, data.length);
399
+ assert.equal(contentPage.items[0].payload, data[0].payload);
400
+ });
401
+ });
402
+
403
+ it('allows other people to write contents', () => {
404
+ const contents = [{type: 'curve', points: [1, 2, 3, 4]}];
405
+ const data = [
406
+ {
407
+ type: contents[0].type,
408
+ payload: JSON.stringify(contents[0]),
409
+ },
410
+ ];
411
+
412
+ return participants[2].webex.internal.board
413
+ .addContent(board, data)
414
+ .then(() => participants[1].webex.internal.board.getContents(board))
415
+ .then((contentPage) => {
416
+ assert.equal(contentPage.length, data.length);
417
+ assert.equal(contentPage.items[0].payload, data[0].payload);
418
+ });
419
+ });
420
+
421
+ describe('handles large data sets', () => {
422
+ const numberOfContents = 30;
423
+ let tonsOfContents;
424
+
425
+ before('generate contents', () =>
426
+ generateTonsOfContents(numberOfContents).then((res) => {
427
+ tonsOfContents = res;
428
+ })
429
+ );
430
+
431
+ beforeEach('create contents', () =>
432
+ participants[0].webex.internal.board.addContent(board, tonsOfContents)
433
+ );
434
+
435
+ it('using the default page limit', () =>
436
+ participants[0].webex.internal.board.getContents(board).then((res) => {
437
+ assert.lengthOf(res, numberOfContents);
438
+ assert.isFalse(res.hasNext());
439
+
440
+ for (let i = 0; i < res.length; i += 1) {
441
+ assert.equal(res.items[i].payload, tonsOfContents[i].payload, 'payload data matches');
442
+ }
443
+ }));
444
+
445
+ it('using a client defined page limit', () =>
446
+ participants[0].webex.internal.board
447
+ .getContents(board, {contentsLimit: 25})
448
+ .then((res) => {
449
+ assert.lengthOf(res, 25);
450
+ assert.isTrue(res.hasNext());
451
+
452
+ return res.next();
453
+ })
454
+ .then((res) => {
455
+ assert.lengthOf(res, numberOfContents - 25);
456
+ assert.isFalse(res.hasNext());
457
+ }));
458
+ });
459
+ });
460
+
461
+ describe('#deleteAllContent()', () => {
462
+ after(() => participants[0].webex.internal.board.deleteAllContent(board));
463
+
464
+ it('delete all contents from the specified board', () => {
465
+ const channel = board;
466
+ const contents = [
467
+ {
468
+ id: uuid.v4(),
469
+ type: 'file',
470
+ },
471
+ {
472
+ id: uuid.v4(),
473
+ type: 'string',
474
+ },
475
+ ];
476
+ const data = [
477
+ {
478
+ type: contents[0].type,
479
+ payload: JSON.stringify(contents[0]),
480
+ },
481
+ {
482
+ type: contents[1].type,
483
+ payload: JSON.stringify(contents[1]),
484
+ },
485
+ ];
486
+
487
+ return participants[0].webex.internal.board
488
+ .addContent(channel, data)
489
+ .then(() => participants[0].webex.internal.board.deleteAllContent(channel))
490
+ .then(() => participants[0].webex.internal.board.getContents(channel))
491
+ .then((res) => {
492
+ assert.lengthOf(res, 0);
493
+
494
+ return res;
495
+ });
496
+ });
497
+ });
498
+
499
+ describe('#deletePartialContent()', () => {
500
+ after(() => participants[0].webex.internal.board.deleteAllContent(board));
501
+
502
+ it('deletes some contents from the specified board', () => {
503
+ const channel = board;
504
+ const data = [
505
+ {
506
+ type: 'STRING',
507
+ payload: JSON.stringify({id: uuid.v4()}),
508
+ },
509
+ {
510
+ type: 'FILE',
511
+ payload: JSON.stringify({id: uuid.v4()}),
512
+ },
513
+ ];
514
+ const contentsToKeep = [];
515
+
516
+ return participants[0].webex.internal.board
517
+ .addContent(channel, data)
518
+ .then(([firstPageRes]) => {
519
+ contentsToKeep.push(firstPageRes.items[1]);
520
+ })
521
+ .then(() =>
522
+ participants[0].webex.internal.board.deletePartialContent(channel, contentsToKeep)
523
+ )
524
+ .then(() => participants[0].webex.internal.board.getContents(channel))
525
+ .then((page) => {
526
+ assert.lengthOf(page, 1);
527
+ delete page.items[0].format;
528
+ assert.deepEqual(page.items[0], contentsToKeep[0]);
529
+
530
+ return page;
531
+ });
532
+ });
533
+ });
534
+
535
+ describe('when a user leaves conversation', () => {
536
+ it('does not allow board user to create board', () => {
537
+ let currentConvo;
538
+
539
+ return participants[0].webex.internal.conversation
540
+ .create({
541
+ displayName: 'Test Board Member Leave Conversation',
542
+ participants,
543
+ })
544
+ .then((c) => {
545
+ currentConvo = c;
546
+
547
+ return participants[1].webex.internal.conversation.leave(currentConvo);
548
+ })
549
+ .then(() =>
550
+ assert.isRejected(participants[1].webex.internal.board.createChannel(currentConvo))
551
+ );
552
+ });
553
+
554
+ it('does not allow board creator to access and decrypt contents', () => {
555
+ let currentConvo;
556
+ let currentBoard;
557
+ const encryptedBoardContent = {};
558
+ const data = [
559
+ {
560
+ type: 'curve',
561
+ payload: JSON.stringify({type: 'curve'}),
562
+ },
563
+ ];
564
+
565
+ return (
566
+ participants[1].webex.internal.conversation
567
+ .create({
568
+ displayName: 'Test Board Creator Leave Conversation',
569
+ participants,
570
+ })
571
+ .then((c) => {
572
+ currentConvo = c;
573
+
574
+ return participants[1].webex.internal.board.createChannel(currentConvo);
575
+ })
576
+ .then((b) => {
577
+ currentBoard = b;
578
+
579
+ return participants[1].webex.internal.conversation.leave(currentConvo);
580
+ })
581
+ .then(() =>
582
+ participants[0].webex.internal.board.encryptContents(
583
+ currentBoard.defaultEncryptionKeyUrl,
584
+ data
585
+ )
586
+ )
587
+ .then((encryptedData) => {
588
+ encryptedBoardContent.items = encryptedData;
589
+
590
+ return assert.isRejected(
591
+ participants[1].webex.internal.board.getContents(currentBoard)
592
+ );
593
+ })
594
+ // ensure keys aren't cached
595
+ .then(() => participants[1].webex.unboundedStorage.clear())
596
+ .then(() =>
597
+ assert.isRejected(
598
+ participants[1].webex.internal.board.decryptContents(encryptedBoardContent)
599
+ )
600
+ )
601
+ );
602
+ });
603
+ });
604
+
605
+ describe('#deleteChannel()', () => {
606
+ it('deletes channel', () => {
607
+ let newChannel;
608
+
609
+ return participants[1].webex.internal.board
610
+ .createChannel(conversation)
611
+ .then((res) => {
612
+ newChannel = res;
613
+
614
+ return participants[1].webex.internal.board.deleteChannel(conversation, newChannel);
615
+ })
616
+ .then(() =>
617
+ assert.isRejected(participants[1].webex.internal.board.getChannel(newChannel))
618
+ );
619
+ });
620
+
621
+ describe('when preventDeleteActiveChannel is enabled', () => {
622
+ it('does not delete when a channel is being used', () => {
623
+ let activeChannel;
624
+
625
+ return participants[1].webex.internal.board
626
+ .createChannel(conversation)
627
+ .then((res) => {
628
+ activeChannel = res;
629
+ const data = [
630
+ {
631
+ type: 'curve',
632
+ payload: JSON.stringify({type: 'curve'}),
633
+ },
634
+ ];
635
+
636
+ // this will mark the channel as being used
637
+ return participants[0].webex.internal.board.addContent(activeChannel, data);
638
+ })
639
+ .then(() =>
640
+ assert.isRejected(
641
+ participants[1].webex.internal.board.deleteChannel(conversation, activeChannel, {
642
+ preventDeleteActiveChannel: true,
643
+ })
644
+ )
645
+ )
646
+ .then(() => participants[1].webex.internal.board.getChannel(activeChannel));
647
+ });
648
+
649
+ it('deletes inactive channel', () => {
650
+ let inActiveChannel;
651
+
652
+ return participants[1].webex.internal.board
653
+ .createChannel(conversation)
654
+ .then((res) => {
655
+ inActiveChannel = res;
656
+
657
+ return participants[1].webex.internal.board.deleteChannel(
658
+ conversation,
659
+ inActiveChannel,
660
+ {preventDeleteActiveChannel: true}
661
+ );
662
+ })
663
+ .then(() =>
664
+ assert.isRejected(participants[1].webex.internal.board.getChannel(inActiveChannel))
665
+ );
666
+ });
667
+ });
668
+ });
669
+
670
+ describe('#lockChannelForDeletion()', () => {
671
+ it('locks a channel for deletion which rejects any incoming activities', () => {
672
+ let newChannel;
673
+
674
+ return participants[1].webex.internal.board
675
+ .createChannel(conversation)
676
+ .then((res) => {
677
+ newChannel = res;
678
+
679
+ return participants[1].webex.internal.board.lockChannelForDeletion(newChannel);
680
+ })
681
+ .then(() => {
682
+ const data = [
683
+ {
684
+ type: 'curve',
685
+ payload: JSON.stringify({type: 'curve'}),
686
+ },
687
+ ];
688
+
689
+ return assert.isRejected(
690
+ participants[0].webex.internal.board.addContent(newChannel, data)
691
+ );
692
+ });
693
+ });
694
+ });
695
+
696
+ describe('#keepActive()', () => {
697
+ it('keeps a channel status as active', () => {
698
+ let newChannel;
699
+
700
+ return participants[1].webex.internal.board
701
+ .createChannel(conversation)
702
+ .then((res) => {
703
+ newChannel = res;
704
+
705
+ return participants[1].webex.internal.board.keepActive(newChannel);
706
+ })
707
+ .then(() =>
708
+ assert.isRejected(
709
+ participants[0].webex.internal.board.deleteChannel(conversation, newChannel, {
710
+ preventDeleteActiveChannel: true,
711
+ })
712
+ )
713
+ );
714
+ });
715
+ });
716
+ });
717
+ });