@webex/plugin-meetings 3.0.0-beta.88 → 3.0.0-beta.89

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.
@@ -0,0 +1,420 @@
1
+ import MockWebex from '@webex/test-helper-mock-webex';
2
+ import MockWebSocket from '@webex/test-helper-mock-web-socket';
3
+ import {assert} from '@webex/test-helper-chai';
4
+ import sinon from 'sinon';
5
+ import Mercury from '@webex/internal-plugin-mercury';
6
+ import LLMChannel from '@webex/internal-plugin-llm';
7
+
8
+ import AnnotationService from '../../../../src/annotation/index';
9
+ import {ANNOTATION_RELAY_TYPES, ANNOTATION_REQUEST_TYPE, EVENT_TRIGGERS} from '../../../../src/annotation/constants';
10
+
11
+
12
+ describe('live-annotation', () => {
13
+ const locusUrl = 'https://locus.wbx2.com/locus/api/v1/loci/163c1787-c1f5-47cc-95eb-ab2d660999e6';
14
+
15
+ describe('annotation', () => {
16
+ let webex, annotationService;
17
+
18
+ beforeEach(() => {
19
+ webex = new MockWebex({
20
+ children: {
21
+ mercury: Mercury,
22
+ llm: LLMChannel,
23
+ annotation: AnnotationService,
24
+ },
25
+ });
26
+
27
+
28
+ annotationService = webex.internal.annotation;
29
+ annotationService.connect = sinon.stub().resolves(true);
30
+ annotationService.webex.internal.llm.isConnected = sinon.stub().returns(true);
31
+ annotationService.webex.internal.llm.getBinding = sinon.stub().returns(undefined);
32
+ annotationService.webex.internal.llm.getLocusUrl = sinon.stub().returns(locusUrl);
33
+ annotationService.approvalUrl = 'url/approval';
34
+ annotationService.locusUrl = locusUrl;
35
+
36
+
37
+ webex.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
38
+ annotationService.register = sinon.stub().resolves({
39
+ body: {
40
+ binding: 'binding',
41
+ webSocketUrl: 'url',
42
+ },
43
+ });
44
+ });
45
+
46
+
47
+ describe('event message processing', () => {
48
+ beforeEach(async () => {
49
+ annotationService.decryptContent = sinon.stub().returns(Promise.resolve('decryptedContent'));
50
+ });
51
+
52
+ it('eventCommandProcessor call failed', () => {
53
+ const spy = sinon.spy();
54
+ annotationService.on(EVENT_TRIGGERS.ANNOTATION_COMMAND, spy);
55
+ annotationService.eventCommandProcessor({});
56
+ assert.notCalled(spy);
57
+
58
+
59
+ annotationService.eventCommandProcessor({
60
+ data: {
61
+ }
62
+ });
63
+ assert.notCalled(spy);
64
+
65
+ annotationService.eventCommandProcessor({
66
+ data: {
67
+ eventType: 'not:locus.approval_request',
68
+ approval: {
69
+ resourceType: 'AnnotationOnShare',
70
+ actionType: 'actionType'
71
+ }
72
+ }
73
+ });
74
+ assert.notCalled(spy);
75
+
76
+ annotationService.eventCommandProcessor({
77
+ data: {
78
+ eventType: 'locus.approval_request',
79
+ approval: {
80
+ resourceType: 'not:AnnotationOnShare',
81
+ actionType: 'actionType'
82
+ }
83
+ }
84
+ });
85
+ assert.notCalled(spy);
86
+
87
+ annotationService.eventCommandProcessor({
88
+ data: {
89
+ eventType: 'locus.approval_request',
90
+ approval: {
91
+ resourceType: 'AnnotationOnShare',
92
+ }
93
+ }
94
+ });
95
+ assert.notCalled(spy)
96
+ });
97
+
98
+
99
+
100
+ it('eventCommandProcessor call success', () => {
101
+ const spy = sinon.spy();
102
+ annotationService.on(EVENT_TRIGGERS.ANNOTATION_COMMAND, spy);
103
+
104
+ annotationService.eventCommandProcessor({
105
+ data: {
106
+ eventType: 'locus.approval_request',
107
+ approval: {
108
+ resourceType: 'AnnotationOnShare',
109
+ actionType: 'actionType'
110
+ }
111
+ }
112
+ });
113
+
114
+ assert.calledOnceWithExactly(spy, {
115
+ type: 'actionType',
116
+ payload: {
117
+ resourceType: 'AnnotationOnShare',
118
+ actionType: 'actionType',
119
+ },
120
+ });
121
+ });
122
+
123
+ it('eventDataProcessor call failed', () => {
124
+
125
+ const spy = sinon.spy(annotationService, "processStrokeMessage");
126
+
127
+ annotationService.eventDataProcessor();
128
+
129
+ assert.notCalled(spy);
130
+
131
+ annotationService.eventDataProcessor({data: {}});
132
+
133
+ assert.notCalled(spy);
134
+
135
+ annotationService.eventDataProcessor({data: {relayType: 'NOT:annotation.client'}});
136
+
137
+ assert.notCalled(spy);
138
+ });
139
+
140
+
141
+ it('eventDataProcessor call success', () => {
142
+
143
+ const spy = sinon.spy(annotationService, "processStrokeMessage");
144
+
145
+ annotationService.eventDataProcessor({data: {relayType: 'annotation.client', request: 'request'}});
146
+
147
+ assert.calledOnceWithExactly(spy, 'request');
148
+
149
+ });
150
+
151
+
152
+ it('processStrokeMessage', async () => {
153
+ const spy = sinon.spy();
154
+ annotationService.on(EVENT_TRIGGERS.ANNOTATION_STROKE_DATA, spy);
155
+
156
+ await annotationService.processStrokeMessage({encryptionKeyUrl: 'encryptionKeyUrl', content: 'content'});
157
+
158
+ assert.calledOnceWithExactly(spy, {
159
+ data: {encryptionKeyUrl: 'encryptionKeyUrl', content: 'decryptedContent'},
160
+ });
161
+
162
+ });
163
+
164
+ });
165
+
166
+ describe('event message processing',() =>{
167
+
168
+ it('listens to mercury events once', () => {
169
+
170
+ const spy = sinon.spy(webex.internal.mercury, 'on');
171
+
172
+ annotationService.listenToEvents();
173
+
174
+ assert.calledOnceWithExactly(spy, 'event:locus', sinon.match.func);
175
+ });
176
+
177
+ it('listens to llm events once', () => {
178
+
179
+ const spy = sinon.spy(webex.internal.llm, 'on');
180
+
181
+ annotationService.listenToEvents();
182
+
183
+ assert.calledOnceWithExactly(spy, 'event:relay.event', sinon.match.func);
184
+ });
185
+
186
+ });
187
+
188
+
189
+ describe('encrypt/decrypt Content ', () => {
190
+ beforeEach(async () => {
191
+ annotationService.webex.internal.encryption.encryptText = sinon.stub().returns(Promise.resolve('RETURN_VALUE'));
192
+ annotationService.webex.internal.encryption.decryptText = sinon.stub().returns(Promise.resolve('RETURN_VALUE'));
193
+ });
194
+
195
+ it('encryptContent', async () => {
196
+ const result = await annotationService.encryptContent("encryptionKeyUrl", "content");
197
+ assert.calledOnceWithExactly(webex.internal.encryption.encryptText, "encryptionKeyUrl", "content");
198
+ assert.equal(result, 'RETURN_VALUE')
199
+ });
200
+
201
+ it('decryptContent ', async() => {
202
+ const result = await annotationService.decryptContent("decryptionKeyUrl", "content");
203
+ assert.calledOnceWithExactly(webex.internal.encryption.decryptText, "decryptionKeyUrl", "content");
204
+ assert.equal(result, 'RETURN_VALUE')
205
+ });
206
+
207
+ });
208
+
209
+
210
+
211
+ describe('publish Stroke Data with LLM is not connected', () => {
212
+
213
+ beforeEach(async () => {
214
+ annotationService.webex.internal.llm.socket = new MockWebSocket();
215
+ annotationService.webex.internal.llm.isConnected = sinon.stub().returns(false);
216
+ });
217
+
218
+ it('publish Stroke Data with LLM is not connected', async () => {
219
+ annotationService.sendStrokeData("", {});
220
+ assert.notCalled(annotationService.webex.internal.llm.socket.send);
221
+ });
222
+
223
+ });
224
+
225
+ describe('sendStrokeData', () => {
226
+
227
+ beforeEach(async () => {
228
+ annotationService.webex.internal.llm.socket = new MockWebSocket();
229
+ });
230
+
231
+
232
+ it('works on publish Stroke Data', async () => {
233
+ const strokeData = {
234
+ content: {
235
+ "contentsBuffer": [{
236
+ "contentArray": [{
237
+ "curveId": "58dcf45c-1fc5-46bf-a902-053621dd8977",
238
+ "curvePoints": [592.593, 352.963, 3.400, 596.710, 352.963, 3.400, 600.000, 352.963, 3.398, 600.000, 352.963, 3.398],
239
+ "stride": 3
240
+ }], "type": "curve", "name": "contentUpdate"
241
+ }], "action": "contentUpdate", "sender": {"name": "perfectt", "id": "4dd5eaf9-4cf8-4f0f-a1d6-014bf5d00741"}
242
+ },
243
+ fromUserId: "525ead98-6c93-4fcb-899d-517305c47503",
244
+ fromDeviceUrl: "https://wdm.wbx2.com/wdm/api/v1/devices/0bbecbf8-59ac-410a-b906-b310d1a50867",
245
+ toUserId: "987ead98-6c93-4fcb-899d-517305c47503",
246
+ locusUrl: "https://locus.wbx2.com/locus/api/v1/loci/163c1787-c1f5-47cc-95eb-ab2d660999e6",
247
+ shareInstanceId: "7fa6fe07-dcb1-41ad-973d-7bcf65fab55d",
248
+ encryptionKeyUrl: "encryptionKeyUrl",
249
+ };
250
+
251
+ annotationService.publishEncrypted(strokeData.content, strokeData);
252
+
253
+ const sendObject = {
254
+ id: sinon.match.string,
255
+ type: 'publishRequest',
256
+ recipients: {route: undefined},
257
+ headers: {to: '987ead98-6c93-4fcb-899d-517305c47503'},
258
+ data: {
259
+ eventType: 'relay.event',
260
+ relayType: ANNOTATION_RELAY_TYPES.ANNOTATION_CLIENT,
261
+ request: {
262
+ value: {
263
+ sessionId: sinon.match.string,
264
+ type: ANNOTATION_REQUEST_TYPE.ANNOTATION_MESSAGE,
265
+ locusUrl: locusUrl,
266
+ content: strokeData.content,
267
+ version: "mVersion",
268
+ fromUserId: strokeData.fromUserId,
269
+ fromDeviceUrl: strokeData.fromDeviceUrl,
270
+ shareInstanceId: strokeData.shareInstanceId,
271
+ locusId: '163c1787-c1f5-47cc-95eb-ab2d660999e6',
272
+ encryptionKeyUrl: 'encryptionKeyUrl',
273
+ }
274
+ }
275
+ },
276
+ trackingId: sinon.match.string,
277
+ timestamp: sinon.match.number,
278
+ sequenceNumber: 1,
279
+ filterMessage: false,
280
+ };
281
+
282
+ assert.calledOnceWithExactly(annotationService.webex.internal.llm.socket.send, sendObject);
283
+ });
284
+
285
+ });
286
+
287
+
288
+ describe('Locus API collection', () => {
289
+
290
+ describe('send approve request', () => {
291
+ it('makes send request approved annotation as expected', async () => {
292
+ const
293
+ requestData = {
294
+ toUserId: '4dd5eaf9-4cf8-4f0f-a1d6-014bf5d00741',
295
+ toDeviceUrl: 'https://wdm-a.wbx2.com/wdm/api/v1/devices/a3018aa9-70cb-4142-ae9a-f03db4fe1057',
296
+ shareInstanceId: '9428c492-da14-476f-a36c-b377ee8c4009',
297
+ };
298
+
299
+
300
+ const result = await annotationService.approveAnnotation(requestData);
301
+ assert.calledOnceWithExactly(webex.request, {
302
+ method: 'POST',
303
+ url: 'url/approval',
304
+ body: {
305
+ actionType: 'REQUESTED',
306
+ resourceType: 'AnnotationOnShare',
307
+ shareInstanceId: '9428c492-da14-476f-a36c-b377ee8c4009',
308
+ receivers: [{
309
+ participantId: '4dd5eaf9-4cf8-4f0f-a1d6-014bf5d00741',
310
+ deviceUrl: 'https://wdm-a.wbx2.com/wdm/api/v1/devices/a3018aa9-70cb-4142-ae9a-f03db4fe1057'
311
+ }],
312
+ }
313
+ });
314
+
315
+ assert.equal(result, 'REQUEST_RETURN_VALUE')
316
+ });
317
+ });
318
+
319
+ describe('cancel Approve request', () => {
320
+ it('makes the cancel Approve request annotation as expected', async () => {
321
+ const
322
+ requestData = {
323
+ toUserId: '4dd5eaf9-4cf8-4f0f-a1d6-014bf5d00741',
324
+ toDeviceUrl: 'https://wdm-a.wbx2.com/wdm/api/v1/devices/a3018aa9-70cb-4142-ae9a-f03db4fe1057',
325
+ shareInstanceId: '9428c492-da14-476f-a36c-b377ee8c4009',
326
+ };
327
+
328
+
329
+ const result = await annotationService.cancelApproveAnnotation(requestData);
330
+ assert.calledOnceWithExactly(webex.request, {
331
+ method: 'POST',
332
+ url: 'url/approval',
333
+ body: {
334
+ actionType: 'CANCELED',
335
+ resourceType: 'AnnotationOnShare',
336
+ shareInstanceId: '9428c492-da14-476f-a36c-b377ee8c4009',
337
+ receivers: [{
338
+ participantId: '4dd5eaf9-4cf8-4f0f-a1d6-014bf5d00741',
339
+ deviceUrl: 'https://wdm-a.wbx2.com/wdm/api/v1/devices/a3018aa9-70cb-4142-ae9a-f03db4fe1057'
340
+ }],
341
+ }
342
+ });
343
+
344
+ assert.equal(result, 'REQUEST_RETURN_VALUE')
345
+ });
346
+ });
347
+
348
+
349
+ describe('# close annotation', () => {
350
+ it('makes the close annotation as expected', async () => {
351
+ const
352
+ requestData = {
353
+ toUserId: '4dd5eaf9-4cf8-4f0f-a1d6-014bf5d00741',
354
+ toDeviceUrl: 'https://wdm-a.wbx2.com/wdm/api/v1/devices/a3018aa9-70cb-4142-ae9a-f03db4fe1057',
355
+ shareInstanceId: '9428c492-da14-476f-a36c-b377ee8c4009',
356
+ };
357
+
358
+
359
+ const result = await annotationService.closeAnnotation(requestData);
360
+ assert.calledOnceWithExactly(webex.request, {
361
+ method: 'POST',
362
+ url: 'url/approval',
363
+ body: {
364
+ actionType: 'CLOSED',
365
+ resourceType: 'AnnotationOnShare',
366
+ shareInstanceId: '9428c492-da14-476f-a36c-b377ee8c4009',
367
+ receivers: [{
368
+ participantId: '4dd5eaf9-4cf8-4f0f-a1d6-014bf5d00741',
369
+ deviceUrl: 'https://wdm-a.wbx2.com/wdm/api/v1/devices/a3018aa9-70cb-4142-ae9a-f03db4fe1057'
370
+ }],
371
+ }
372
+ });
373
+
374
+ assert.equal(result, 'REQUEST_RETURN_VALUE')
375
+ });
376
+ });
377
+
378
+
379
+ describe('declined annotation', () => {
380
+ it('makes the declined annotation as expected', async () => {
381
+ const approval = {
382
+ url: 'approvalUrl'
383
+ }
384
+ const result = await annotationService.declineRequest(approval);
385
+ assert.calledOnceWithExactly(webex.request, {
386
+ method: 'PUT',
387
+ url: 'approvalUrl',
388
+ body: {
389
+ actionType: 'DECLINED',
390
+ resourceType: 'AnnotationOnShare',
391
+ }
392
+ });
393
+
394
+ assert.equal(result, 'REQUEST_RETURN_VALUE')
395
+ });
396
+ });
397
+
398
+ describe('# accept annotation', () => {
399
+ it('makes the accepted annotation as expected', async () => {
400
+ const approval = {
401
+ url: 'approvalUrl'
402
+ }
403
+ const result = await annotationService.acceptRequest(approval);
404
+ assert.calledOnceWithExactly(webex.request, {
405
+ method: 'PUT',
406
+ url: 'approvalUrl',
407
+ body: {
408
+ actionType: 'ACCEPTED',
409
+ resourceType: 'AnnotationOnShare',
410
+ }
411
+ });
412
+
413
+ assert.equal(result, 'REQUEST_RETURN_VALUE')
414
+ });
415
+ });
416
+
417
+ });
418
+ });
419
+
420
+ });
@@ -4720,6 +4720,7 @@ describe('plugin-meetings', () => {
4720
4720
  meeting.controlsOptionsManager = {setLocusUrl: sinon.stub().returns(undefined)};
4721
4721
 
4722
4722
  meeting.breakouts.locusUrlUpdate = sinon.stub();
4723
+ meeting.annotation.locusUrlUpdate = sinon.stub();
4723
4724
 
4724
4725
  meeting.locusInfo.emit(
4725
4726
  {function: 'test', file: 'test'},
@@ -4728,6 +4729,7 @@ describe('plugin-meetings', () => {
4728
4729
  );
4729
4730
  assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
4730
4731
  assert.calledOnceWithExactly(meeting.breakouts.locusUrlUpdate, newLocusUrl);
4732
+ assert.calledOnceWithExactly(meeting.annotation.locusUrlUpdate, newLocusUrl);
4731
4733
  assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
4732
4734
  assert.calledWith(meeting.recordingController.setLocusUrl, newLocusUrl);
4733
4735
  assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl);
@@ -4744,6 +4746,9 @@ describe('plugin-meetings', () => {
4744
4746
  record: {
4745
4747
  url: 'url',
4746
4748
  },
4749
+ approval: {
4750
+ url: 'url',
4751
+ },
4747
4752
  },
4748
4753
  };
4749
4754
 
@@ -4751,6 +4756,9 @@ describe('plugin-meetings', () => {
4751
4756
  setServiceUrl: sinon.stub().returns(undefined),
4752
4757
  setSessionId: sinon.stub().returns(undefined),
4753
4758
  };
4759
+ meeting.annotation = {
4760
+ approvalUrlUpdate: sinon.stub().returns(undefined),
4761
+ };
4754
4762
 
4755
4763
  meeting.locusInfo.emit(
4756
4764
  {function: 'test', file: 'test'},
@@ -4760,7 +4768,11 @@ describe('plugin-meetings', () => {
4760
4768
 
4761
4769
  assert.calledWith(
4762
4770
  meeting.recordingController.setServiceUrl,
4763
- newLocusServices.services.record.url
4771
+ newLocusServices.services.record.url,
4772
+ );
4773
+ assert.calledWith(
4774
+ meeting.annotation.approvalUrlUpdate,
4775
+ newLocusServices.services.approval.url,
4764
4776
  );
4765
4777
  assert.calledOnce(meeting.recordingController.setSessionId);
4766
4778
  done();
@@ -202,7 +202,22 @@ describe('plugin-meetings', () => {
202
202
  assert.deepEqual(requestParams.body.deviceCapabilities, ['BREAKOUTS_SUPPORTED']);
203
203
  });
204
204
 
205
- it('does not add deviceCapabilities to request when breakouts are not supported', async () => {
205
+ it('adds deviceCapabilities to request when live annotation are supported', async () => {
206
+ await meetingsRequest.joinMeeting({
207
+ liveAnnotationSupported: true
208
+ });
209
+ const requestParams = meetingsRequest.request.getCall(0).args[0];
210
+ assert.deepEqual(requestParams.body.deviceCapabilities, ['ANNOTATION_ON_SHARE_SUPPORTED']);
211
+ });
212
+ it('adds deviceCapabilities to request when breakouts and live annotation are supported', async () => {
213
+ await meetingsRequest.joinMeeting({
214
+ liveAnnotationSupported: true,
215
+ breakoutsSupported: true,
216
+ });
217
+ const requestParams = meetingsRequest.request.getCall(0).args[0];
218
+ assert.deepEqual(requestParams.body.deviceCapabilities, ['BREAKOUTS_SUPPORTED','ANNOTATION_ON_SHARE_SUPPORTED']);
219
+ });
220
+ it('does not add deviceCapabilities to request when breakouts and live annotation are not supported', async () => {
206
221
  await meetingsRequest.joinMeeting({});
207
222
 
208
223
  const requestParams = meetingsRequest.request.getCall(0).args[0];
@@ -389,4 +404,53 @@ describe('plugin-meetings', () => {
389
404
  })
390
405
  });
391
406
  });
407
+ describe('#changeMeetingFloor', () => {
408
+
409
+ it('change meeting floor', async () => {
410
+ const options = {
411
+ disposition: 'GRANTED',
412
+ personUrl: 'personUrl',
413
+ deviceUrl: 'deviceUrl',
414
+ resourceId: 'resourceId',
415
+ resourceUrl: 'resourceUrl',
416
+ uri: 'optionsUrl',
417
+ annotation:{
418
+ version: '1',
419
+ policy: 'Approval',
420
+ },
421
+ }
422
+
423
+ const expectBody = {
424
+ annotation: {
425
+ policy: 'Approval',
426
+ version: '1',
427
+ },
428
+ floor: {
429
+ beneficiary: {
430
+ devices: [
431
+ {
432
+ deviceType: undefined,
433
+ url: "deviceUrl"
434
+ }
435
+ ],
436
+ url: 'personUrl',
437
+ },
438
+ disposition: 'GRANTED',
439
+ requester: {
440
+ "url": "personUrl"
441
+ }
442
+ },
443
+ resourceUrl: 'resourceUrl',
444
+ };
445
+
446
+
447
+ await meetingsRequest.changeMeetingFloor(options);
448
+
449
+ assert.deepEqual(meetingsRequest.request.getCall(0).args[0], {
450
+ method: 'PUT',
451
+ uri: 'optionsUrl',
452
+ body: expectBody,
453
+ })
454
+ });
455
+ });
392
456
  });
@@ -39,6 +39,7 @@ describe('plugin-meetings', () => {
39
39
  meeting.stopKeepAlive = sinon.stub();
40
40
  meeting.updateLLMConnection = sinon.stub();
41
41
  meeting.breakouts = {cleanUp: sinon.stub()};
42
+ meeting.annotaion = {cleanUp: sinon.stub()};
42
43
  });
43
44
 
44
45
  afterEach(() => {