@webex/plugin-messages 2.59.3-next.1 → 2.59.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,752 +1,752 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- import '@webex/internal-plugin-device';
6
- import '@webex/plugin-logger';
7
- import '@webex/plugin-rooms';
8
- import '@webex/plugin-people';
9
- import '@webex/plugin-messages';
10
- import WebexCore, { WebexHttpError } from '@webex/webex-core';
11
- import { SDK_EVENT } from '@webex/common';
12
- import { assert } from '@webex/test-helper-chai';
13
- import sinon from 'sinon';
14
- import testUsers from '@webex/test-helper-test-users';
15
- import fh from '@webex/test-helper-file';
16
- import { browserOnly, flaky, nodeOnly } from '@webex/test-helper-mocha';
17
-
18
- const debug = require('debug')('messages');
19
-
20
- const KNOWN_HOSTED_IMAGE_URL = 'https://download.ciscospark.com/test/photo.png';
21
-
22
- describe('plugin-messages', function () {
23
- this.timeout(60000);
24
-
25
- let webex;
26
- let webexEU;
27
- let actor;
28
- let actorEU;
29
-
30
- before(() =>
31
- Promise.all([
32
- testUsers.create({ count: 1 }),
33
- testUsers.create({ count: 1, config: { orgId: process.env.EU_PRIMARY_ORG_ID } }),
34
- ]).then(([user, usersEU]) => {
35
- [actor] = user;
36
- [actorEU] = usersEU;
37
-
38
- webex = new WebexCore({ credentials: actor.token });
39
- webexEU = new WebexCore({ credentials: actorEU.token });
40
-
41
- webex.people.get('me').then((person) => {
42
- actor = person;
43
- });
44
-
45
- webexEU.people.get('me').then((person) => {
46
- actorEU = person;
47
- });
48
- })
49
- );
50
-
51
- describe('#messages', () => {
52
- let room;
53
- let roomEU;
54
-
55
- before(() =>
56
- Promise.all([
57
- webex.rooms.create({ title: 'Webex Test Room' }),
58
- webexEU.rooms.create({ title: 'Webex Test Room for EU' }),
59
- ]).then(([r, rEU]) => {
60
- room = r;
61
- roomEU = rEU;
62
- const text = 'First Message';
63
-
64
- webex.messages
65
- .create({
66
- roomId: room.id,
67
- text,
68
- })
69
- .then((message) => {
70
- validateMessage(message, text);
71
- });
72
-
73
- webexEU.messages
74
- .create({
75
- roomId: roomEU.id,
76
- text,
77
- })
78
- .then((message) => {
79
- validateMessage(message, text);
80
- });
81
- })
82
- );
83
-
84
- // eslint-disable-next-line consistent-return
85
- after(() => Promise.all([webex.rooms.remove(room), webexEU.rooms.remove(roomEU)]));
86
-
87
- afterEach(() => webex.messages.stopListening());
88
-
89
- describe('#create()', () => {
90
- it('posts a message in a room and validates the messages:created event', () => {
91
- let message;
92
-
93
- // "Block" this test with a promise that will
94
- // resolve after the messages:created arrives.
95
- const created = new Promise((resolve) => {
96
- webex.messages.on('created', (event) => {
97
- debug('message created event called');
98
- resolve(event);
99
- });
100
- });
101
-
102
- const text = 'A test message';
103
-
104
- return webex.messages.listen().then(() =>
105
- webex.messages
106
- .create({
107
- roomId: room.id,
108
- text,
109
- })
110
- .then(async (m) => {
111
- message = m;
112
- validateMessage(message, text);
113
- const event = await created;
114
-
115
- validateMessageEvent(event, message, actor);
116
- })
117
- );
118
- });
119
-
120
- it('posts a message by an EU user in a room and validates the messages:created event', () => {
121
- let message;
122
-
123
- // "Block" this test with a promise that will
124
- // resolve after the messages:created arrives.
125
- const created = new Promise((resolve) => {
126
- webexEU.messages.on('created', (event) => {
127
- debug('message created event called');
128
- resolve(event);
129
- });
130
- });
131
-
132
- const text = 'A test message';
133
-
134
- return webexEU.messages.listen().then(() =>
135
- webexEU.messages
136
- .create({
137
- roomId: roomEU.id,
138
- text,
139
- })
140
- .then(async (m) => {
141
- message = m;
142
- validateMessage(message, text);
143
- const event = await created;
144
-
145
- validateMessageEvent(event, message, actorEU);
146
- })
147
- );
148
- });
149
-
150
- it("posts a file to a room by specifying the file's url and validates the event", () => {
151
- const created = new Promise((resolve) => {
152
- webex.messages.on('created', (event) => {
153
- debug('message created event called');
154
- resolve(event);
155
- });
156
- });
157
-
158
- return webex.messages.listen().then(() =>
159
- webex.messages
160
- .create({
161
- roomId: room.id,
162
- files: [KNOWN_HOSTED_IMAGE_URL],
163
- })
164
- .then(async (message) => {
165
- validateMessage(message);
166
- const event = await created;
167
-
168
- validateMessageEvent(event, message, actor);
169
- })
170
- );
171
- });
172
-
173
- let blob, buffer;
174
- const text = 'A File';
175
-
176
- browserOnly(before)(() =>
177
- fh.fetch('sample-image-small-one.png').then((file) => {
178
- blob = file;
179
-
180
- return new Promise((resolve) => {
181
- /* global FileReader */
182
- const fileReader = new FileReader();
183
-
184
- fileReader.onload = function () {
185
- buffer = this.result;
186
- resolve();
187
- };
188
- fileReader.readAsArrayBuffer(blob);
189
- });
190
- })
191
- );
192
-
193
- nodeOnly(before)(() =>
194
- fh.fetchWithoutMagic('sample-image-small-one.png').then((file) => {
195
- buffer = file;
196
- })
197
- );
198
-
199
- browserOnly(it)(
200
- 'posts a file to a room by directly supplying its blob and validates the event',
201
- () => {
202
- const created = new Promise((resolve) => {
203
- webex.messages.on('created', (event) => {
204
- debug('message created event called');
205
- resolve(event);
206
- });
207
- });
208
-
209
- return webex.messages.listen().then(() =>
210
- webex.messages
211
- .create({
212
- roomId: room.id,
213
- files: [blob],
214
- text,
215
- })
216
- .then(async (message) => {
217
- validateMessage(message);
218
- const event = await created;
219
-
220
- validateMessageEvent(event, message, actor);
221
- })
222
- );
223
- }
224
- );
225
-
226
- // Disabling it gating pipelines because it failes a lot and we get
227
- // mostly adequate coverage via blob upload
228
- flaky(it, process.env.SKIP_FLAKY_TESTS)(
229
- 'posts a file to a room by directly supplying its buffer and validates the event',
230
- () =>
231
- webex.messages
232
- .create({
233
- roomId: room.id,
234
- files: [buffer],
235
- })
236
- .then((message) => {
237
- validateMessage(message, '', 1);
238
- })
239
- );
240
-
241
- it("posts a file with a message to a room by specifying the file's url and validates the event", () => {
242
- const created = new Promise((resolve) => {
243
- webex.messages.on('created', (event) => {
244
- debug('message created event called');
245
- resolve(event);
246
- });
247
- });
248
-
249
- return webex.messages.listen().then(() =>
250
- webex.messages
251
- .create({
252
- roomId: room.id,
253
- files: [KNOWN_HOSTED_IMAGE_URL],
254
- text,
255
- })
256
- .then(async (message) => {
257
- validateMessage(message);
258
- let event = await created;
259
-
260
- // When using this method to attach a file to
261
- // a message, sometimes, the first event does not
262
- // include all the data included in the message.
263
- // kms then triggers a second event that includes
264
- // all of the data in the message object.
265
- if (event.data.id !== message.id) {
266
- const createdCombined = new Promise((resolve) => {
267
- webex.messages.on('created', (e) => {
268
- debug('message created event called');
269
- resolve(e);
270
- });
271
- });
272
-
273
- event = await createdCombined;
274
- }
275
-
276
- validateMessageEvent(event, message, actor);
277
- })
278
- );
279
- });
280
-
281
- it('posts a message to a card to a room validates the event', () => {
282
- const created = new Promise((resolve) => {
283
- webex.messages.on('created', (event) => {
284
- debug('message created event called');
285
- resolve(event);
286
- });
287
- });
288
- const attachment = {
289
- contentType: 'application/vnd.microsoft.card.adaptive',
290
- content: {
291
- type: 'AdaptiveCard',
292
- version: '1.0',
293
- body: [
294
- {
295
- type: 'TextBlock',
296
- text: 'Here is an image',
297
- },
298
- {
299
- type: 'Image',
300
- url: KNOWN_HOSTED_IMAGE_URL,
301
- size: 'small',
302
- },
303
- ],
304
- },
305
- };
306
-
307
- return webex.messages.listen().then(() =>
308
- webex.messages
309
- .create({
310
- roomId: room.id,
311
- text,
312
- attachments: [attachment],
313
- })
314
- .then(async (message) => {
315
- // // Assert that the message shape is valid and contains attachment data.
316
- validateMessage(message, text, 0, attachment);
317
- let event = await created;
318
-
319
- // When using this method to attach a file to
320
- // a message, sometimes, the first event does not
321
- // include all the data included in the message.
322
- // kms then triggers a second event that includes
323
- // all of the data in the message object.
324
- if (event.data.id !== message.id) {
325
- const createdCombined = new Promise((resolve) => {
326
- webex.messages.on('created', (e) => {
327
- debug('message created event called');
328
- resolve(e);
329
- });
330
- });
331
-
332
- event = await createdCombined;
333
- }
334
-
335
- validateMessageEvent(event, message, actor);
336
- })
337
- );
338
- });
339
- });
340
- describe('#update()', () => {
341
- it('update a message in a room and validates the messages:update event, params message and altMessage objects', () => {
342
- let message;
343
- const text = 'This message will be updated';
344
-
345
- beforeEach(() =>
346
- webex.messages
347
- .create({
348
- roomId: room.id,
349
- text,
350
- })
351
- .then((m) => {
352
- message = m;
353
- validateMessage(m, text);
354
- })
355
- );
356
-
357
- // "Block" this test with a promise that will
358
- // resolve after the messages:created arrives.
359
- const updated = new Promise((resolve) => {
360
- webex.messages.on('updated', (event) => {
361
- debug('message updated event called');
362
- resolve(event);
363
- });
364
- });
365
-
366
- text = 'This is updated message';
367
-
368
- return webex.messages.listen().then(() =>
369
- webex.messages
370
- .update({
371
- message: message,
372
- altMessage: { text: text },
373
- })
374
- .then(async (m) => {
375
- message = m;
376
- validateMessage(message, text);
377
- const event = await updated;
378
-
379
- validateMessageEvent(event, message, actor);
380
- })
381
- );
382
- });
383
- it('update a message in a room and validates the messages:update event, parameter messageId, and altMessage with text and roomId', () => {
384
- let message;
385
- const text = 'This message will be updated';
386
-
387
- beforeEach(() =>
388
- webex.messages
389
- .create({
390
- roomId: room.id,
391
- text,
392
- })
393
- .then((m) => {
394
- message = m;
395
- validateMessage(m, text);
396
- })
397
- );
398
-
399
- // "Block" this test with a promise that will
400
- // resolve after the messages:created arrives.
401
- const updated = new Promise((resolve) => {
402
- webex.messages.on('updated', (event) => {
403
- debug('message updated event called');
404
- resolve(event);
405
- });
406
- });
407
-
408
- text = 'This is updated message';
409
-
410
- return webex.messages.listen().then(() =>
411
- webex.messages
412
- .update({
413
- message: message.id,
414
- altMessage: {
415
- roomId: room.id,
416
- text: text
417
- },
418
- })
419
- .then(async (m) => {
420
- message = m;
421
- validateMessage(message, text);
422
- const event = await updated;
423
- validateMessageEvent(event, message, actor);
424
- })
425
- );
426
- });
427
- });
428
-
429
- describe('#remove()', () => {
430
- let message;
431
- const text = 'This message will be deleted';
432
-
433
- beforeEach(() =>
434
- webex.messages
435
- .create({
436
- roomId: room.id,
437
- text,
438
- })
439
- .then((m) => {
440
- message = m;
441
- validateMessage(m, text);
442
- })
443
- );
444
-
445
- it('deletes a single message and validates the message:deleted event', () => {
446
- const deleted = new Promise((resolve) => {
447
- webex.messages.on('deleted', (event) => {
448
- debug('message deleted event called');
449
- resolve(event);
450
- });
451
- });
452
-
453
- return webex.messages.listen().then(() =>
454
- webex.messages
455
- .remove(message)
456
- .then((body) => {
457
- assert.notOk(body);
458
-
459
- return assert.isRejected(webex.messages.get(message));
460
- })
461
- .then(async (reason) => {
462
- assert.instanceOf(reason, WebexHttpError.NotFound);
463
- const event = await deleted;
464
-
465
- validateMessageEvent(event, message, actor);
466
- })
467
- );
468
- });
469
- });
470
-
471
- describe('get()', () => {
472
- let message;
473
- const text = 'A test message';
474
-
475
- before(() => {
476
- // The above tests validate all the events
477
- // Turn off the event listener for the remainder of the tests
478
- webex.messages.off('created');
479
- webex.messages.off('deleted');
480
-
481
- return webex.messages
482
- .create({
483
- roomId: room.id,
484
- text,
485
- })
486
- .then((m) => {
487
- message = m;
488
- validateMessage(message, text);
489
- });
490
- });
491
-
492
- it('returns a single message', () =>
493
- webex.messages.get(message).then((m) => {
494
- assert.isMessage(m);
495
- assert.deepEqual(m, message);
496
- }));
497
- });
498
-
499
- describe('#list()', () => {
500
- before(() =>
501
- webex.rooms
502
- .create({
503
- title: 'Room List Test',
504
- })
505
- .then((r) => {
506
- room = r;
507
- })
508
- );
509
-
510
- before(() =>
511
- [1, 2, 3].reduce(
512
- (promise, value) =>
513
- promise.then(() =>
514
- webex.messages.create({
515
- roomId: room.id,
516
- text: `message: ${value}`,
517
- })
518
- ),
519
- Promise.resolve()
520
- )
521
- );
522
-
523
- it('returns all messages for a room', () =>
524
- webex.messages.list({ roomId: room.id }).then((messages) => {
525
- assert.isDefined(messages);
526
- assert.lengthOf(messages, 3);
527
- for (const message of messages) {
528
- assert.isMessage(message);
529
- }
530
- }));
531
-
532
- it('returns a bounded set of messages for a room', () => {
533
- const spy = sinon.spy();
534
-
535
- return webex.messages
536
- .list({ roomId: room.id, max: 2 })
537
- .then((messages) => {
538
- assert.lengthOf(messages, 2);
539
-
540
- return (function f(page) {
541
- for (const message of page) {
542
- spy(message.id);
543
- }
544
-
545
- if (page.hasNext()) {
546
- return page.next().then(f);
547
- }
548
-
549
- return Promise.resolve();
550
- })(messages);
551
- })
552
- .then(() => {
553
- assert.calledThrice(spy);
554
- });
555
- });
556
-
557
- describe('when a message is threaded', () => {
558
- let parentId;
559
-
560
- before(() =>
561
- webex.rooms
562
- .create({
563
- title: 'Room List Test',
564
- })
565
- .then((r) => {
566
- room = r;
567
- })
568
- );
569
-
570
- before(() => {
571
- const createdParent = new Promise((resolve) => {
572
- webex.messages.once('created', (event) => {
573
- debug('Threaded Test: parent message created event called');
574
- resolve(event);
575
- });
576
- });
577
-
578
- return webex.messages.listen().then(() =>
579
- webex.messages
580
- .create({
581
- roomId: room.id,
582
- text: 'This is the parent message',
583
- })
584
- .then(async (message) => {
585
- parentId = message.id;
586
-
587
- validateMessage(message);
588
- const event = await createdParent;
589
-
590
- validateMessageEvent(event, message, actor);
591
- const createdReply = new Promise((resolve) => {
592
- webex.messages.once('created', (e) => {
593
- debug('Threaded Test: reply message created event called');
594
- resolve(e);
595
- });
596
- });
597
-
598
- return webex.messages
599
- .create({
600
- roomId: room.id,
601
- text: 'This is the reply',
602
- parentId,
603
- })
604
- .then(async (message2) => {
605
- validateMessage(message2);
606
- const event2 = await createdReply;
607
-
608
- return Promise.resolve(validateMessageEvent(event2, message2, actor));
609
- });
610
- })
611
- );
612
- });
613
-
614
- it('returns all messages for a room', () =>
615
- webex.messages.list({ roomId: room.id }).then((messages) => {
616
- assert.isDefined(messages);
617
- assert.lengthOf(messages.items, 2);
618
- for (const message of messages.items) {
619
- assert.isMessage(message);
620
- if (message.parentId) {
621
- assert.equal(message.parentId, parentId);
622
- }
623
- }
624
- }));
625
-
626
- it('returns only the replies for particular message thread', () =>
627
- webex.messages.list({ roomId: room.id, parentId }).then((messages) => {
628
- assert.lengthOf(messages.items, 1);
629
- const message = messages.items[0];
630
-
631
- assert.isMessage(message);
632
- assert.strictEqual(message.parentId, parentId);
633
- }));
634
- });
635
- });
636
- });
637
- });
638
-
639
- /**
640
- * Validate a Message object.
641
- * @param {Object} message
642
- * @param {String} text -- optional message text to check
643
- * @param {Boolean} numFiles
644
- * @param {Object} attachment
645
- * @returns {void}
646
- */
647
- function validateMessage(message, text = '', numFiles = 0, attachment = null) {
648
- assert.isDefined(message);
649
- assert.isMessage(message);
650
- if (text) {
651
- assert.equal(message.text, text);
652
- }
653
- if (attachment) {
654
- validateAdaptiveCard(message, attachment);
655
- }
656
- if (numFiles) {
657
- assert.property(message, 'files');
658
- assert.isDefined(message.files);
659
- assert.isArray(message.files);
660
- assert.lengthOf(message.files, numFiles);
661
- }
662
- debug('message validated');
663
- }
664
-
665
- /**
666
- * Validate a Attachment Action.
667
- * @param {Object} message -- message returned from the API
668
- * @param {Object} attachment - adaptive card object that was sent to the API
669
- * @returns {void}
670
- */
671
- function validateAdaptiveCard(message, attachment) {
672
- assert.isArray(message.attachments);
673
- assert.isDefined(message.attachments.length);
674
- const card = message.attachments[0];
675
-
676
- // Cannot do a deepEqual compare because the image URLs are remapped
677
- // Validate some aspects of the card data
678
-
679
- assert.isDefined(card.contentType);
680
- assert.isDefined(attachment.contentType);
681
- assert.equal(card.contentType, attachment.contentType);
682
- assert.isDefined(card.content);
683
- assert.isDefined(attachment.content);
684
- assert.equal(card.content.type, attachment.content.type);
685
- assert.equal(card.content.version, attachment.content.version);
686
- assert.isDefined(card.content.body);
687
- assert.isArray(attachment.content.body);
688
- assert.isDefined(card.content.body.length);
689
- assert.equal(card.content.body.length, attachment.content.body.length);
690
- for (let i = 0; i < card.content.body.length; i += 1) {
691
- if (card.content.body[i].type.toLowerCase() === 'textblock') {
692
- assert.deepEqual(card.content.body[i], attachment.content.body[i]);
693
- }
694
- }
695
- }
696
-
697
- /**
698
- * Validate a Message event.
699
- * @param {Object} event - message event
700
- * @param {Object} message -- return from the API that generate this event
701
- * @param {Object} actor - person object for user who performed action
702
- * @returns {void}
703
- */
704
- function validateMessageEvent(event, message, actor) {
705
- assert.equal(event.resource, SDK_EVENT.EXTERNAL.RESOURCE.MESSAGES, 'not a message event');
706
- assert.isDefined(event.event, 'message event type not set');
707
- assert.isDefined(event.created, 'event listener created date not set');
708
- assert.equal(event.createdBy, actor.id, 'event listener createdBy not set to our actor');
709
- assert.equal(event.orgId, actor.orgId, "event listener orgId not === to our actor's");
710
- assert.equal(event.ownedBy, 'creator', 'event listener not owned by creator');
711
- assert.equal(event.status, 'active', 'event listener status not active');
712
- assert.equal(event.actorId, actor.id, "event actorId not equal to our actor's id");
713
-
714
- // Ensure event data matches data returned from function call
715
- assert.equal(event.data.id, message.id, 'event/message.id not equal');
716
- assert.equal(event.data.roomId, message.roomId, 'event/message.roomId not equal');
717
- assert.equal(event.data.personId, message.personId, 'event/message.personId not equal');
718
- assert.equal(event.data.personEmail, message.personEmail, 'event/message.personEmail not equal');
719
- assert.equal(event.data.roomType, message.roomType, 'event/message.roomType not equal');
720
- if (event.event === SDK_EVENT.EXTERNAL.EVENT_TYPE.DELETED) {
721
- return;
722
- }
723
- if (message.text) {
724
- assert.equal(event.data.text, message.text, 'event/message.text not equal');
725
- }
726
- if (message.files) {
727
- assert.isArray(event.data.files, 'event.data.files is not array');
728
- assert.isArray(message.files, 'message.files is not array');
729
- assert.equal(
730
- event.data.files.length,
731
- message.files.length,
732
- 'event/message file arrays are different lengths'
733
- );
734
- for (let i = 0; i < message.files.length; i += 1) {
735
- // The gateway returned by the API is apialpha.ciscospark.com
736
- // The gateway returned in the event is api.ciscospark.com -- expected?
737
- assert.equal(
738
- event.data.files[i].substr(event.data.files[i].lastIndexOf('/') + 1),
739
- message.files[i].substr(message.files[i].lastIndexOf('/') + 1),
740
- 'event/message file urls do not match'
741
- );
742
- }
743
- }
744
- if (message.attachments) {
745
- assert.isArray(event.data.attachments);
746
- assert.isDefined(event.data.attachments.length);
747
- validateAdaptiveCard(message, event.data.attachments[0]);
748
- }
749
- if (message.parentId) {
750
- assert.equal(message.parentId, event.data.parentId);
751
- }
752
- }
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import '@webex/internal-plugin-device';
6
+ import '@webex/plugin-logger';
7
+ import '@webex/plugin-rooms';
8
+ import '@webex/plugin-people';
9
+ import '@webex/plugin-messages';
10
+ import WebexCore, { WebexHttpError } from '@webex/webex-core';
11
+ import { SDK_EVENT } from '@webex/common';
12
+ import { assert } from '@webex/test-helper-chai';
13
+ import sinon from 'sinon';
14
+ import testUsers from '@webex/test-helper-test-users';
15
+ import fh from '@webex/test-helper-file';
16
+ import { browserOnly, flaky, nodeOnly } from '@webex/test-helper-mocha';
17
+
18
+ const debug = require('debug')('messages');
19
+
20
+ const KNOWN_HOSTED_IMAGE_URL = 'https://download.ciscospark.com/test/photo.png';
21
+
22
+ describe('plugin-messages', function () {
23
+ this.timeout(60000);
24
+
25
+ let webex;
26
+ let webexEU;
27
+ let actor;
28
+ let actorEU;
29
+
30
+ before(() =>
31
+ Promise.all([
32
+ testUsers.create({ count: 1 }),
33
+ testUsers.create({ count: 1, config: { orgId: process.env.EU_PRIMARY_ORG_ID } }),
34
+ ]).then(([user, usersEU]) => {
35
+ [actor] = user;
36
+ [actorEU] = usersEU;
37
+
38
+ webex = new WebexCore({ credentials: actor.token });
39
+ webexEU = new WebexCore({ credentials: actorEU.token });
40
+
41
+ webex.people.get('me').then((person) => {
42
+ actor = person;
43
+ });
44
+
45
+ webexEU.people.get('me').then((person) => {
46
+ actorEU = person;
47
+ });
48
+ })
49
+ );
50
+
51
+ describe('#messages', () => {
52
+ let room;
53
+ let roomEU;
54
+
55
+ before(() =>
56
+ Promise.all([
57
+ webex.rooms.create({ title: 'Webex Test Room' }),
58
+ webexEU.rooms.create({ title: 'Webex Test Room for EU' }),
59
+ ]).then(([r, rEU]) => {
60
+ room = r;
61
+ roomEU = rEU;
62
+ const text = 'First Message';
63
+
64
+ webex.messages
65
+ .create({
66
+ roomId: room.id,
67
+ text,
68
+ })
69
+ .then((message) => {
70
+ validateMessage(message, text);
71
+ });
72
+
73
+ webexEU.messages
74
+ .create({
75
+ roomId: roomEU.id,
76
+ text,
77
+ })
78
+ .then((message) => {
79
+ validateMessage(message, text);
80
+ });
81
+ })
82
+ );
83
+
84
+ // eslint-disable-next-line consistent-return
85
+ after(() => Promise.all([webex.rooms.remove(room), webexEU.rooms.remove(roomEU)]));
86
+
87
+ afterEach(() => webex.messages.stopListening());
88
+
89
+ describe('#create()', () => {
90
+ it('posts a message in a room and validates the messages:created event', () => {
91
+ let message;
92
+
93
+ // "Block" this test with a promise that will
94
+ // resolve after the messages:created arrives.
95
+ const created = new Promise((resolve) => {
96
+ webex.messages.on('created', (event) => {
97
+ debug('message created event called');
98
+ resolve(event);
99
+ });
100
+ });
101
+
102
+ const text = 'A test message';
103
+
104
+ return webex.messages.listen().then(() =>
105
+ webex.messages
106
+ .create({
107
+ roomId: room.id,
108
+ text,
109
+ })
110
+ .then(async (m) => {
111
+ message = m;
112
+ validateMessage(message, text);
113
+ const event = await created;
114
+
115
+ validateMessageEvent(event, message, actor);
116
+ })
117
+ );
118
+ });
119
+
120
+ it('posts a message by an EU user in a room and validates the messages:created event', () => {
121
+ let message;
122
+
123
+ // "Block" this test with a promise that will
124
+ // resolve after the messages:created arrives.
125
+ const created = new Promise((resolve) => {
126
+ webexEU.messages.on('created', (event) => {
127
+ debug('message created event called');
128
+ resolve(event);
129
+ });
130
+ });
131
+
132
+ const text = 'A test message';
133
+
134
+ return webexEU.messages.listen().then(() =>
135
+ webexEU.messages
136
+ .create({
137
+ roomId: roomEU.id,
138
+ text,
139
+ })
140
+ .then(async (m) => {
141
+ message = m;
142
+ validateMessage(message, text);
143
+ const event = await created;
144
+
145
+ validateMessageEvent(event, message, actorEU);
146
+ })
147
+ );
148
+ });
149
+
150
+ it("posts a file to a room by specifying the file's url and validates the event", () => {
151
+ const created = new Promise((resolve) => {
152
+ webex.messages.on('created', (event) => {
153
+ debug('message created event called');
154
+ resolve(event);
155
+ });
156
+ });
157
+
158
+ return webex.messages.listen().then(() =>
159
+ webex.messages
160
+ .create({
161
+ roomId: room.id,
162
+ files: [KNOWN_HOSTED_IMAGE_URL],
163
+ })
164
+ .then(async (message) => {
165
+ validateMessage(message);
166
+ const event = await created;
167
+
168
+ validateMessageEvent(event, message, actor);
169
+ })
170
+ );
171
+ });
172
+
173
+ let blob, buffer;
174
+ const text = 'A File';
175
+
176
+ browserOnly(before)(() =>
177
+ fh.fetch('sample-image-small-one.png').then((file) => {
178
+ blob = file;
179
+
180
+ return new Promise((resolve) => {
181
+ /* global FileReader */
182
+ const fileReader = new FileReader();
183
+
184
+ fileReader.onload = function () {
185
+ buffer = this.result;
186
+ resolve();
187
+ };
188
+ fileReader.readAsArrayBuffer(blob);
189
+ });
190
+ })
191
+ );
192
+
193
+ nodeOnly(before)(() =>
194
+ fh.fetchWithoutMagic('sample-image-small-one.png').then((file) => {
195
+ buffer = file;
196
+ })
197
+ );
198
+
199
+ browserOnly(it)(
200
+ 'posts a file to a room by directly supplying its blob and validates the event',
201
+ () => {
202
+ const created = new Promise((resolve) => {
203
+ webex.messages.on('created', (event) => {
204
+ debug('message created event called');
205
+ resolve(event);
206
+ });
207
+ });
208
+
209
+ return webex.messages.listen().then(() =>
210
+ webex.messages
211
+ .create({
212
+ roomId: room.id,
213
+ files: [blob],
214
+ text,
215
+ })
216
+ .then(async (message) => {
217
+ validateMessage(message);
218
+ const event = await created;
219
+
220
+ validateMessageEvent(event, message, actor);
221
+ })
222
+ );
223
+ }
224
+ );
225
+
226
+ // Disabling it gating pipelines because it failes a lot and we get
227
+ // mostly adequate coverage via blob upload
228
+ flaky(it, process.env.SKIP_FLAKY_TESTS)(
229
+ 'posts a file to a room by directly supplying its buffer and validates the event',
230
+ () =>
231
+ webex.messages
232
+ .create({
233
+ roomId: room.id,
234
+ files: [buffer],
235
+ })
236
+ .then((message) => {
237
+ validateMessage(message, '', 1);
238
+ })
239
+ );
240
+
241
+ it("posts a file with a message to a room by specifying the file's url and validates the event", () => {
242
+ const created = new Promise((resolve) => {
243
+ webex.messages.on('created', (event) => {
244
+ debug('message created event called');
245
+ resolve(event);
246
+ });
247
+ });
248
+
249
+ return webex.messages.listen().then(() =>
250
+ webex.messages
251
+ .create({
252
+ roomId: room.id,
253
+ files: [KNOWN_HOSTED_IMAGE_URL],
254
+ text,
255
+ })
256
+ .then(async (message) => {
257
+ validateMessage(message);
258
+ let event = await created;
259
+
260
+ // When using this method to attach a file to
261
+ // a message, sometimes, the first event does not
262
+ // include all the data included in the message.
263
+ // kms then triggers a second event that includes
264
+ // all of the data in the message object.
265
+ if (event.data.id !== message.id) {
266
+ const createdCombined = new Promise((resolve) => {
267
+ webex.messages.on('created', (e) => {
268
+ debug('message created event called');
269
+ resolve(e);
270
+ });
271
+ });
272
+
273
+ event = await createdCombined;
274
+ }
275
+
276
+ validateMessageEvent(event, message, actor);
277
+ })
278
+ );
279
+ });
280
+
281
+ it('posts a message to a card to a room validates the event', () => {
282
+ const created = new Promise((resolve) => {
283
+ webex.messages.on('created', (event) => {
284
+ debug('message created event called');
285
+ resolve(event);
286
+ });
287
+ });
288
+ const attachment = {
289
+ contentType: 'application/vnd.microsoft.card.adaptive',
290
+ content: {
291
+ type: 'AdaptiveCard',
292
+ version: '1.0',
293
+ body: [
294
+ {
295
+ type: 'TextBlock',
296
+ text: 'Here is an image',
297
+ },
298
+ {
299
+ type: 'Image',
300
+ url: KNOWN_HOSTED_IMAGE_URL,
301
+ size: 'small',
302
+ },
303
+ ],
304
+ },
305
+ };
306
+
307
+ return webex.messages.listen().then(() =>
308
+ webex.messages
309
+ .create({
310
+ roomId: room.id,
311
+ text,
312
+ attachments: [attachment],
313
+ })
314
+ .then(async (message) => {
315
+ // // Assert that the message shape is valid and contains attachment data.
316
+ validateMessage(message, text, 0, attachment);
317
+ let event = await created;
318
+
319
+ // When using this method to attach a file to
320
+ // a message, sometimes, the first event does not
321
+ // include all the data included in the message.
322
+ // kms then triggers a second event that includes
323
+ // all of the data in the message object.
324
+ if (event.data.id !== message.id) {
325
+ const createdCombined = new Promise((resolve) => {
326
+ webex.messages.on('created', (e) => {
327
+ debug('message created event called');
328
+ resolve(e);
329
+ });
330
+ });
331
+
332
+ event = await createdCombined;
333
+ }
334
+
335
+ validateMessageEvent(event, message, actor);
336
+ })
337
+ );
338
+ });
339
+ });
340
+ describe('#update()', () => {
341
+ it('update a message in a room and validates the messages:update event, params message and altMessage objects', () => {
342
+ let message;
343
+ const text = 'This message will be updated';
344
+
345
+ beforeEach(() =>
346
+ webex.messages
347
+ .create({
348
+ roomId: room.id,
349
+ text,
350
+ })
351
+ .then((m) => {
352
+ message = m;
353
+ validateMessage(m, text);
354
+ })
355
+ );
356
+
357
+ // "Block" this test with a promise that will
358
+ // resolve after the messages:created arrives.
359
+ const updated = new Promise((resolve) => {
360
+ webex.messages.on('updated', (event) => {
361
+ debug('message updated event called');
362
+ resolve(event);
363
+ });
364
+ });
365
+
366
+ text = 'This is updated message';
367
+
368
+ return webex.messages.listen().then(() =>
369
+ webex.messages
370
+ .update({
371
+ message: message,
372
+ altMessage: { text: text },
373
+ })
374
+ .then(async (m) => {
375
+ message = m;
376
+ validateMessage(message, text);
377
+ const event = await updated;
378
+
379
+ validateMessageEvent(event, message, actor);
380
+ })
381
+ );
382
+ });
383
+ it('update a message in a room and validates the messages:update event, parameter messageId, and altMessage with text and roomId', () => {
384
+ let message;
385
+ const text = 'This message will be updated';
386
+
387
+ beforeEach(() =>
388
+ webex.messages
389
+ .create({
390
+ roomId: room.id,
391
+ text,
392
+ })
393
+ .then((m) => {
394
+ message = m;
395
+ validateMessage(m, text);
396
+ })
397
+ );
398
+
399
+ // "Block" this test with a promise that will
400
+ // resolve after the messages:created arrives.
401
+ const updated = new Promise((resolve) => {
402
+ webex.messages.on('updated', (event) => {
403
+ debug('message updated event called');
404
+ resolve(event);
405
+ });
406
+ });
407
+
408
+ text = 'This is updated message';
409
+
410
+ return webex.messages.listen().then(() =>
411
+ webex.messages
412
+ .update({
413
+ message: message.id,
414
+ altMessage: {
415
+ roomId: room.id,
416
+ text: text
417
+ },
418
+ })
419
+ .then(async (m) => {
420
+ message = m;
421
+ validateMessage(message, text);
422
+ const event = await updated;
423
+ validateMessageEvent(event, message, actor);
424
+ })
425
+ );
426
+ });
427
+ });
428
+
429
+ describe('#remove()', () => {
430
+ let message;
431
+ const text = 'This message will be deleted';
432
+
433
+ beforeEach(() =>
434
+ webex.messages
435
+ .create({
436
+ roomId: room.id,
437
+ text,
438
+ })
439
+ .then((m) => {
440
+ message = m;
441
+ validateMessage(m, text);
442
+ })
443
+ );
444
+
445
+ it('deletes a single message and validates the message:deleted event', () => {
446
+ const deleted = new Promise((resolve) => {
447
+ webex.messages.on('deleted', (event) => {
448
+ debug('message deleted event called');
449
+ resolve(event);
450
+ });
451
+ });
452
+
453
+ return webex.messages.listen().then(() =>
454
+ webex.messages
455
+ .remove(message)
456
+ .then((body) => {
457
+ assert.notOk(body);
458
+
459
+ return assert.isRejected(webex.messages.get(message));
460
+ })
461
+ .then(async (reason) => {
462
+ assert.instanceOf(reason, WebexHttpError.NotFound);
463
+ const event = await deleted;
464
+
465
+ validateMessageEvent(event, message, actor);
466
+ })
467
+ );
468
+ });
469
+ });
470
+
471
+ describe('get()', () => {
472
+ let message;
473
+ const text = 'A test message';
474
+
475
+ before(() => {
476
+ // The above tests validate all the events
477
+ // Turn off the event listener for the remainder of the tests
478
+ webex.messages.off('created');
479
+ webex.messages.off('deleted');
480
+
481
+ return webex.messages
482
+ .create({
483
+ roomId: room.id,
484
+ text,
485
+ })
486
+ .then((m) => {
487
+ message = m;
488
+ validateMessage(message, text);
489
+ });
490
+ });
491
+
492
+ it('returns a single message', () =>
493
+ webex.messages.get(message).then((m) => {
494
+ assert.isMessage(m);
495
+ assert.deepEqual(m, message);
496
+ }));
497
+ });
498
+
499
+ describe('#list()', () => {
500
+ before(() =>
501
+ webex.rooms
502
+ .create({
503
+ title: 'Room List Test',
504
+ })
505
+ .then((r) => {
506
+ room = r;
507
+ })
508
+ );
509
+
510
+ before(() =>
511
+ [1, 2, 3].reduce(
512
+ (promise, value) =>
513
+ promise.then(() =>
514
+ webex.messages.create({
515
+ roomId: room.id,
516
+ text: `message: ${value}`,
517
+ })
518
+ ),
519
+ Promise.resolve()
520
+ )
521
+ );
522
+
523
+ it('returns all messages for a room', () =>
524
+ webex.messages.list({ roomId: room.id }).then((messages) => {
525
+ assert.isDefined(messages);
526
+ assert.lengthOf(messages, 3);
527
+ for (const message of messages) {
528
+ assert.isMessage(message);
529
+ }
530
+ }));
531
+
532
+ it('returns a bounded set of messages for a room', () => {
533
+ const spy = sinon.spy();
534
+
535
+ return webex.messages
536
+ .list({ roomId: room.id, max: 2 })
537
+ .then((messages) => {
538
+ assert.lengthOf(messages, 2);
539
+
540
+ return (function f(page) {
541
+ for (const message of page) {
542
+ spy(message.id);
543
+ }
544
+
545
+ if (page.hasNext()) {
546
+ return page.next().then(f);
547
+ }
548
+
549
+ return Promise.resolve();
550
+ })(messages);
551
+ })
552
+ .then(() => {
553
+ assert.calledThrice(spy);
554
+ });
555
+ });
556
+
557
+ describe('when a message is threaded', () => {
558
+ let parentId;
559
+
560
+ before(() =>
561
+ webex.rooms
562
+ .create({
563
+ title: 'Room List Test',
564
+ })
565
+ .then((r) => {
566
+ room = r;
567
+ })
568
+ );
569
+
570
+ before(() => {
571
+ const createdParent = new Promise((resolve) => {
572
+ webex.messages.once('created', (event) => {
573
+ debug('Threaded Test: parent message created event called');
574
+ resolve(event);
575
+ });
576
+ });
577
+
578
+ return webex.messages.listen().then(() =>
579
+ webex.messages
580
+ .create({
581
+ roomId: room.id,
582
+ text: 'This is the parent message',
583
+ })
584
+ .then(async (message) => {
585
+ parentId = message.id;
586
+
587
+ validateMessage(message);
588
+ const event = await createdParent;
589
+
590
+ validateMessageEvent(event, message, actor);
591
+ const createdReply = new Promise((resolve) => {
592
+ webex.messages.once('created', (e) => {
593
+ debug('Threaded Test: reply message created event called');
594
+ resolve(e);
595
+ });
596
+ });
597
+
598
+ return webex.messages
599
+ .create({
600
+ roomId: room.id,
601
+ text: 'This is the reply',
602
+ parentId,
603
+ })
604
+ .then(async (message2) => {
605
+ validateMessage(message2);
606
+ const event2 = await createdReply;
607
+
608
+ return Promise.resolve(validateMessageEvent(event2, message2, actor));
609
+ });
610
+ })
611
+ );
612
+ });
613
+
614
+ it('returns all messages for a room', () =>
615
+ webex.messages.list({ roomId: room.id }).then((messages) => {
616
+ assert.isDefined(messages);
617
+ assert.lengthOf(messages.items, 2);
618
+ for (const message of messages.items) {
619
+ assert.isMessage(message);
620
+ if (message.parentId) {
621
+ assert.equal(message.parentId, parentId);
622
+ }
623
+ }
624
+ }));
625
+
626
+ it('returns only the replies for particular message thread', () =>
627
+ webex.messages.list({ roomId: room.id, parentId }).then((messages) => {
628
+ assert.lengthOf(messages.items, 1);
629
+ const message = messages.items[0];
630
+
631
+ assert.isMessage(message);
632
+ assert.strictEqual(message.parentId, parentId);
633
+ }));
634
+ });
635
+ });
636
+ });
637
+ });
638
+
639
+ /**
640
+ * Validate a Message object.
641
+ * @param {Object} message
642
+ * @param {String} text -- optional message text to check
643
+ * @param {Boolean} numFiles
644
+ * @param {Object} attachment
645
+ * @returns {void}
646
+ */
647
+ function validateMessage(message, text = '', numFiles = 0, attachment = null) {
648
+ assert.isDefined(message);
649
+ assert.isMessage(message);
650
+ if (text) {
651
+ assert.equal(message.text, text);
652
+ }
653
+ if (attachment) {
654
+ validateAdaptiveCard(message, attachment);
655
+ }
656
+ if (numFiles) {
657
+ assert.property(message, 'files');
658
+ assert.isDefined(message.files);
659
+ assert.isArray(message.files);
660
+ assert.lengthOf(message.files, numFiles);
661
+ }
662
+ debug('message validated');
663
+ }
664
+
665
+ /**
666
+ * Validate a Attachment Action.
667
+ * @param {Object} message -- message returned from the API
668
+ * @param {Object} attachment - adaptive card object that was sent to the API
669
+ * @returns {void}
670
+ */
671
+ function validateAdaptiveCard(message, attachment) {
672
+ assert.isArray(message.attachments);
673
+ assert.isDefined(message.attachments.length);
674
+ const card = message.attachments[0];
675
+
676
+ // Cannot do a deepEqual compare because the image URLs are remapped
677
+ // Validate some aspects of the card data
678
+
679
+ assert.isDefined(card.contentType);
680
+ assert.isDefined(attachment.contentType);
681
+ assert.equal(card.contentType, attachment.contentType);
682
+ assert.isDefined(card.content);
683
+ assert.isDefined(attachment.content);
684
+ assert.equal(card.content.type, attachment.content.type);
685
+ assert.equal(card.content.version, attachment.content.version);
686
+ assert.isDefined(card.content.body);
687
+ assert.isArray(attachment.content.body);
688
+ assert.isDefined(card.content.body.length);
689
+ assert.equal(card.content.body.length, attachment.content.body.length);
690
+ for (let i = 0; i < card.content.body.length; i += 1) {
691
+ if (card.content.body[i].type.toLowerCase() === 'textblock') {
692
+ assert.deepEqual(card.content.body[i], attachment.content.body[i]);
693
+ }
694
+ }
695
+ }
696
+
697
+ /**
698
+ * Validate a Message event.
699
+ * @param {Object} event - message event
700
+ * @param {Object} message -- return from the API that generate this event
701
+ * @param {Object} actor - person object for user who performed action
702
+ * @returns {void}
703
+ */
704
+ function validateMessageEvent(event, message, actor) {
705
+ assert.equal(event.resource, SDK_EVENT.EXTERNAL.RESOURCE.MESSAGES, 'not a message event');
706
+ assert.isDefined(event.event, 'message event type not set');
707
+ assert.isDefined(event.created, 'event listener created date not set');
708
+ assert.equal(event.createdBy, actor.id, 'event listener createdBy not set to our actor');
709
+ assert.equal(event.orgId, actor.orgId, "event listener orgId not === to our actor's");
710
+ assert.equal(event.ownedBy, 'creator', 'event listener not owned by creator');
711
+ assert.equal(event.status, 'active', 'event listener status not active');
712
+ assert.equal(event.actorId, actor.id, "event actorId not equal to our actor's id");
713
+
714
+ // Ensure event data matches data returned from function call
715
+ assert.equal(event.data.id, message.id, 'event/message.id not equal');
716
+ assert.equal(event.data.roomId, message.roomId, 'event/message.roomId not equal');
717
+ assert.equal(event.data.personId, message.personId, 'event/message.personId not equal');
718
+ assert.equal(event.data.personEmail, message.personEmail, 'event/message.personEmail not equal');
719
+ assert.equal(event.data.roomType, message.roomType, 'event/message.roomType not equal');
720
+ if (event.event === SDK_EVENT.EXTERNAL.EVENT_TYPE.DELETED) {
721
+ return;
722
+ }
723
+ if (message.text) {
724
+ assert.equal(event.data.text, message.text, 'event/message.text not equal');
725
+ }
726
+ if (message.files) {
727
+ assert.isArray(event.data.files, 'event.data.files is not array');
728
+ assert.isArray(message.files, 'message.files is not array');
729
+ assert.equal(
730
+ event.data.files.length,
731
+ message.files.length,
732
+ 'event/message file arrays are different lengths'
733
+ );
734
+ for (let i = 0; i < message.files.length; i += 1) {
735
+ // The gateway returned by the API is apialpha.ciscospark.com
736
+ // The gateway returned in the event is api.ciscospark.com -- expected?
737
+ assert.equal(
738
+ event.data.files[i].substr(event.data.files[i].lastIndexOf('/') + 1),
739
+ message.files[i].substr(message.files[i].lastIndexOf('/') + 1),
740
+ 'event/message file urls do not match'
741
+ );
742
+ }
743
+ }
744
+ if (message.attachments) {
745
+ assert.isArray(event.data.attachments);
746
+ assert.isDefined(event.data.attachments.length);
747
+ validateAdaptiveCard(message, event.data.attachments[0]);
748
+ }
749
+ if (message.parentId) {
750
+ assert.equal(message.parentId, event.data.parentId);
751
+ }
752
+ }