@webex/plugin-meetings 3.0.0-beta.174 → 3.0.0-beta.175

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.
@@ -218,6 +218,7 @@ export declare const EVENT_TRIGGERS: {
218
218
  MEETING_BREAKOUTS_PRE_ASSIGNMENTS_UPDATE: string;
219
219
  MEETING_INTERPRETATION_UPDATE: string;
220
220
  MEETING_INTERPRETATION_SUPPORT_LANGUAGES_UPDATE: string;
221
+ MEETING_INTERPRETATION_HANDOFF_REQUESTS_ARRIVED: string;
221
222
  MEMBERS_UPDATE: string;
222
223
  MEMBERS_CLEAR: string;
223
224
  MEMBERS_CONTENT_UPDATE: string;
@@ -463,7 +464,15 @@ export declare const BREAKOUTS: {
463
464
  export declare const INTERPRETATION: {
464
465
  EVENTS: {
465
466
  SUPPORT_LANGUAGES_UPDATE: string;
467
+ HANDOFF_REQUESTS_ARRIVED: string;
466
468
  };
469
+ ACTION_TYPE: {
470
+ OFFERED: string;
471
+ ACCEPTED: string;
472
+ REQUESTED: string;
473
+ DECLINED: string;
474
+ };
475
+ RESOURCE_TYPE: string;
467
476
  };
468
477
  export declare const LOCUSINFO: {
469
478
  EVENTS: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
- "version": "3.0.0-beta.174",
3
+ "version": "3.0.0-beta.175",
4
4
  "description": "",
5
5
  "license": "Cisco EULA (https://www.cisco.com/c/en/us/products/end-user-license-agreement.html)",
6
6
  "contributors": [
@@ -32,12 +32,12 @@
32
32
  "build": "yarn run -T tsc --declaration true --declarationDir ./dist/types"
33
33
  },
34
34
  "devDependencies": {
35
- "@webex/plugin-meetings": "3.0.0-beta.174",
36
- "@webex/test-helper-chai": "3.0.0-beta.174",
37
- "@webex/test-helper-mocha": "3.0.0-beta.174",
38
- "@webex/test-helper-mock-webex": "3.0.0-beta.174",
39
- "@webex/test-helper-retry": "3.0.0-beta.174",
40
- "@webex/test-helper-test-users": "3.0.0-beta.174",
35
+ "@webex/plugin-meetings": "3.0.0-beta.175",
36
+ "@webex/test-helper-chai": "3.0.0-beta.175",
37
+ "@webex/test-helper-mocha": "3.0.0-beta.175",
38
+ "@webex/test-helper-mock-webex": "3.0.0-beta.175",
39
+ "@webex/test-helper-retry": "3.0.0-beta.175",
40
+ "@webex/test-helper-test-users": "3.0.0-beta.175",
41
41
  "chai": "^4.3.4",
42
42
  "chai-as-promised": "^7.1.1",
43
43
  "jsdom-global": "3.0.2",
@@ -46,19 +46,19 @@
46
46
  "typescript": "^4.7.4"
47
47
  },
48
48
  "dependencies": {
49
- "@webex/common": "3.0.0-beta.174",
49
+ "@webex/common": "3.0.0-beta.175",
50
50
  "@webex/internal-media-core": "1.39.1",
51
- "@webex/internal-plugin-conversation": "3.0.0-beta.174",
52
- "@webex/internal-plugin-device": "3.0.0-beta.174",
53
- "@webex/internal-plugin-llm": "3.0.0-beta.174",
54
- "@webex/internal-plugin-mercury": "3.0.0-beta.174",
55
- "@webex/internal-plugin-metrics": "3.0.0-beta.174",
56
- "@webex/internal-plugin-support": "3.0.0-beta.174",
57
- "@webex/internal-plugin-user": "3.0.0-beta.174",
58
- "@webex/media-helpers": "3.0.0-beta.174",
59
- "@webex/plugin-people": "3.0.0-beta.174",
60
- "@webex/plugin-rooms": "3.0.0-beta.174",
61
- "@webex/webex-core": "3.0.0-beta.174",
51
+ "@webex/internal-plugin-conversation": "3.0.0-beta.175",
52
+ "@webex/internal-plugin-device": "3.0.0-beta.175",
53
+ "@webex/internal-plugin-llm": "3.0.0-beta.175",
54
+ "@webex/internal-plugin-mercury": "3.0.0-beta.175",
55
+ "@webex/internal-plugin-metrics": "3.0.0-beta.175",
56
+ "@webex/internal-plugin-support": "3.0.0-beta.175",
57
+ "@webex/internal-plugin-user": "3.0.0-beta.175",
58
+ "@webex/media-helpers": "3.0.0-beta.175",
59
+ "@webex/plugin-people": "3.0.0-beta.175",
60
+ "@webex/plugin-rooms": "3.0.0-beta.175",
61
+ "@webex/webex-core": "3.0.0-beta.175",
62
62
  "ampersand-collection": "^2.0.2",
63
63
  "bowser": "^2.11.0",
64
64
  "btoa": "^1.2.1",
package/src/constants.ts CHANGED
@@ -319,6 +319,7 @@ export const EVENT_TRIGGERS = {
319
319
  MEETING_BREAKOUTS_PRE_ASSIGNMENTS_UPDATE: 'meeting:breakouts:preAssignmentsUpdate',
320
320
  MEETING_INTERPRETATION_UPDATE: 'meeting:interpretation:update',
321
321
  MEETING_INTERPRETATION_SUPPORT_LANGUAGES_UPDATE: 'meeting:interpretation:supportLanguagesUpdate',
322
+ MEETING_INTERPRETATION_HANDOFF_REQUESTS_ARRIVED: 'meeting:interpretation:handoffRequestsArrived',
322
323
  MEMBERS_UPDATE: 'members:update',
323
324
  MEMBERS_CLEAR: 'members:clear',
324
325
  MEMBERS_CONTENT_UPDATE: 'members:content:update',
@@ -597,7 +598,15 @@ export const BREAKOUTS = {
597
598
  export const INTERPRETATION = {
598
599
  EVENTS: {
599
600
  SUPPORT_LANGUAGES_UPDATE: 'SUPPORT_LANGUAGES_UPDATE',
601
+ HANDOFF_REQUESTS_ARRIVED: 'HANDOFF_REQUESTS_ARRIVED',
600
602
  },
603
+ ACTION_TYPE: {
604
+ OFFERED: 'OFFERED',
605
+ ACCEPTED: 'ACCEPTED',
606
+ REQUESTED: 'REQUESTED',
607
+ DECLINED: 'DECLINED',
608
+ },
609
+ RESOURCE_TYPE: 'SiHandover',
601
610
  };
602
611
 
603
612
  export const LOCUSINFO = {
@@ -45,7 +45,16 @@ The following are methods available to the interpreters of a meeting.
45
45
  //Change direction of interpretation for an interpreter participant
46
46
  interpretation.changeDirection();
47
47
 
48
- //Handoff between interpreters, will implement them later
49
- interpretation.handoff(participantId)
48
+ //Handoff between interpreters, input paramerter participantId is the target to handoff
49
+ interpretation.handoffInterpreter(participantId);
50
+
51
+ //in-active interpreter request to handoff to self
52
+ interpretation.requestHandoff();
53
+
54
+ //accept the request of handoff, input paramter url is from last approval event which generate by server side
55
+ interpretation.acceptRequest(url);
56
+
57
+ //decline the request of handoff, input paramter url is from last approval event which generate by server side
58
+ interpretation.declineRequest(url);
50
59
 
51
60
  ```
@@ -18,6 +18,7 @@ const SimultaneousInterpretation = WebexPlugin.extend({
18
18
 
19
19
  props: {
20
20
  locusUrl: 'string', // appears current meeting's locus url
21
+ approvalUrl: 'string', // appears current meeting's approval url for handoff between interpreters
21
22
  originalLanguage: 'string', // appears current meeting's original language
22
23
  sourceLanguage: 'string', // appears self interpreter's source language
23
24
  targetLanguage: 'string', // appears self interpreter's target language
@@ -27,18 +28,18 @@ const SimultaneousInterpretation = WebexPlugin.extend({
27
28
  selfParticipantId: 'string', // appears the self participant id
28
29
  canManageInterpreters: 'boolean', // appears the ability to manage interpreters
29
30
  supportLanguages: 'array', // appears the support languages
30
- siEnabled: 'boolean', // appears the meeting enabled SI
31
+ hostSIEnabled: 'boolean', // appears the meeting host feature of SI enabled
31
32
  },
32
33
  derived: {
33
34
  shouldQuerySupportLanguages: {
34
35
  cache: false,
35
- deps: ['canManageInterpreters', 'siEnabled'],
36
+ deps: ['canManageInterpreters', 'hostSIEnabled'],
36
37
  /**
37
38
  * Returns should query support languages or not
38
39
  * @returns {boolean}
39
40
  */
40
41
  fn() {
41
- return !!(this.canManageInterpreters && this.siEnabled);
42
+ return !!(this.canManageInterpreters && this.hostSIEnabled);
42
43
  },
43
44
  },
44
45
  },
@@ -52,6 +53,7 @@ const SimultaneousInterpretation = WebexPlugin.extend({
52
53
  this.querySupportLanguages();
53
54
  }
54
55
  });
56
+ this.listenToHandoffRequests();
55
57
  },
56
58
 
57
59
  /**
@@ -69,6 +71,14 @@ const SimultaneousInterpretation = WebexPlugin.extend({
69
71
  locusUrlUpdate(locusUrl) {
70
72
  this.set('locusUrl', locusUrl);
71
73
  },
74
+ /**
75
+ * Update the approval url for handoff
76
+ * @param {string} approvalUrl // approval url
77
+ * @returns {void}
78
+ */
79
+ approvalUrlUpdate(approvalUrl) {
80
+ this.set('approvalUrl', approvalUrl);
81
+ },
72
82
  /**
73
83
  * Update whether self has capability to manage interpreters (only host can manage it)
74
84
  * @param {boolean} canManageInterpreters
@@ -77,13 +87,20 @@ const SimultaneousInterpretation = WebexPlugin.extend({
77
87
  updateCanManageInterpreters(canManageInterpreters) {
78
88
  this.set('canManageInterpreters', canManageInterpreters);
79
89
  },
90
+ /**
91
+ * Update whether the meeting's host si is enabled or not
92
+ * @param {boolean} hostSIEnabled
93
+ * @returns {void}
94
+ */
95
+ updateHostSIEnabled(hostSIEnabled) {
96
+ this.set('hostSIEnabled', hostSIEnabled);
97
+ },
80
98
  /**
81
99
  * Update the interpretation languages channels which user can choose to subscribe
82
100
  * @param {Object} interpretation
83
101
  * @returns {void}
84
102
  */
85
103
  updateInterpretation(interpretation) {
86
- this.set('siEnabled', !!interpretation);
87
104
  this.siLanguages.set(interpretation?.siLanguages || []);
88
105
  },
89
106
  /**
@@ -177,6 +194,125 @@ const SimultaneousInterpretation = WebexPlugin.extend({
177
194
  throw error;
178
195
  });
179
196
  },
197
+ /**
198
+ * Sets up a listener for handoff requests from mercury
199
+ * @returns {void}
200
+ */
201
+ listenToHandoffRequests() {
202
+ this.listenTo(this.webex.internal.mercury, 'event:locus.approval_request', (event) => {
203
+ if (event?.data?.approval?.resourceType === INTERPRETATION.RESOURCE_TYPE) {
204
+ const {receivers, initiator, actionType, url} = event.data.approval;
205
+ const receiverId = receivers?.[0]?.participantId;
206
+ const isReceiver = !!receiverId && receiverId === this.selfParticipantId;
207
+ const senderId = initiator?.participantId;
208
+ const isSender = !!senderId && senderId === this.selfParticipantId;
209
+ if (!isReceiver && !isSender) {
210
+ return;
211
+ }
212
+ this.trigger(INTERPRETATION.EVENTS.HANDOFF_REQUESTS_ARRIVED, {
213
+ actionType,
214
+ isReceiver,
215
+ isSender,
216
+ senderId,
217
+ receiverId,
218
+ url,
219
+ });
220
+ }
221
+ });
222
+ },
223
+ /**
224
+ * handoff the active interpreter role to another interpreter in same group, only the interpreter is allowed to call this api
225
+ * @param {string} participantId the participant id you want to hand off
226
+ * @returns {Promise}
227
+ */
228
+ handoffInterpreter(participantId) {
229
+ if (!participantId) {
230
+ return Promise.reject(new Error('Missing target participant id'));
231
+ }
232
+ if (!this.approvalUrl) {
233
+ return Promise.reject(new Error('Missing approval url'));
234
+ }
235
+
236
+ return this.request({
237
+ method: HTTP_VERBS.POST,
238
+ uri: this.approvalUrl,
239
+ body: {
240
+ actionType: INTERPRETATION.ACTION_TYPE.OFFERED,
241
+ resourceType: INTERPRETATION.RESOURCE_TYPE,
242
+ receivers: [
243
+ {
244
+ participantId,
245
+ },
246
+ ],
247
+ },
248
+ }).catch((error) => {
249
+ LoggerProxy.logger.error('Meeting:interpretation#handoffInterpreter failed', error);
250
+ throw error;
251
+ });
252
+ },
253
+ /**
254
+ * the in-active interpreter request to hand off the active role to self
255
+ * @returns {Promise}
256
+ */
257
+ requestHandoff() {
258
+ if (!this.approvalUrl) {
259
+ return Promise.reject(new Error('Missing approval url'));
260
+ }
261
+
262
+ return this.request({
263
+ method: HTTP_VERBS.POST,
264
+ uri: this.approvalUrl,
265
+ body: {
266
+ actionType: INTERPRETATION.ACTION_TYPE.REQUESTED,
267
+ resourceType: INTERPRETATION.RESOURCE_TYPE,
268
+ },
269
+ }).catch((error) => {
270
+ LoggerProxy.logger.error('Meeting:interpretation#requestHandoff failed', error);
271
+ throw error;
272
+ });
273
+ },
274
+ /**
275
+ * accept the request of handoff
276
+ * @param {String} url the url get from last approval event
277
+ * @returns {Promise}
278
+ */
279
+ acceptRequest(url) {
280
+ if (!url) {
281
+ return Promise.reject(new Error('Missing the url to accept'));
282
+ }
283
+
284
+ return this.request({
285
+ method: HTTP_VERBS.PUT,
286
+ uri: url,
287
+ body: {
288
+ actionType: INTERPRETATION.ACTION_TYPE.ACCEPTED,
289
+ },
290
+ }).catch((error) => {
291
+ LoggerProxy.logger.error('Meeting:interpretation#acceptRequest failed', error);
292
+ throw error;
293
+ });
294
+ },
295
+ /**
296
+ * decline the request of handoff
297
+ * @param {String} url the url get from last approval event
298
+ * @returns {Promise}
299
+ */
300
+ declineRequest(url) {
301
+ if (!url) {
302
+ return Promise.reject(new Error('Missing the url to decline'));
303
+ }
304
+
305
+ return this.request({
306
+ method: HTTP_VERBS.PUT,
307
+ uri: url,
308
+ body: {
309
+ actionType: INTERPRETATION.ACTION_TYPE.DECLINED,
310
+ },
311
+ }).catch((error) => {
312
+ LoggerProxy.logger.error('Meeting:interpretation#declineRequest failed', error);
313
+ throw error;
314
+ });
315
+ },
180
316
  });
181
317
 
182
318
  export default SimultaneousInterpretation;
@@ -1608,6 +1608,21 @@ export default class Meeting extends StatelessWebexPlugin {
1608
1608
  EVENT_TRIGGERS.MEETING_INTERPRETATION_SUPPORT_LANGUAGES_UPDATE
1609
1609
  );
1610
1610
  });
1611
+
1612
+ this.simultaneousInterpretation.on(
1613
+ INTERPRETATION.EVENTS.HANDOFF_REQUESTS_ARRIVED,
1614
+ (payload) => {
1615
+ Trigger.trigger(
1616
+ this,
1617
+ {
1618
+ file: 'meeting/index',
1619
+ function: 'setUpInterpretationListener',
1620
+ },
1621
+ EVENT_TRIGGERS.MEETING_INTERPRETATION_HANDOFF_REQUESTS_ARRIVED,
1622
+ payload
1623
+ );
1624
+ }
1625
+ );
1611
1626
  }
1612
1627
 
1613
1628
  /**
@@ -2374,6 +2389,7 @@ export default class Meeting extends StatelessWebexPlugin {
2374
2389
  this.recordingController.setSessionId(this.locusInfo?.fullState?.sessionId);
2375
2390
  this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
2376
2391
  this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
2392
+ this.simultaneousInterpretation.approvalUrlUpdate(payload?.services?.approval?.url);
2377
2393
  });
2378
2394
  }
2379
2395
 
@@ -3157,6 +3173,9 @@ export default class Meeting extends StatelessWebexPlugin {
3157
3173
  // Need to populate environment when sending CA event
3158
3174
  this.environment = locusMeetingObject?.info.channel || webexMeetingInfo?.channel;
3159
3175
  }
3176
+ this.simultaneousInterpretation.updateHostSIEnabled(
3177
+ !!webexMeetingInfo?.meetingSiteSetting?.enableHostInterpreterControlSI
3178
+ );
3160
3179
  }
3161
3180
 
3162
3181
  /**
@@ -12,6 +12,7 @@ describe('plugin-meetings', () => {
12
12
  beforeEach(() => {
13
13
  // @ts-ignore
14
14
  webex = new MockWebex({});
15
+ webex.internal.mercury.on = sinon.stub();
15
16
  interpretation = new SimultaneousInterpretation({}, {parent: webex});
16
17
  interpretation.locusUrl = 'locusUrl';
17
18
  webex.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
@@ -50,6 +51,14 @@ describe('plugin-meetings', () => {
50
51
  });
51
52
  });
52
53
 
54
+ describe('#approvalUrlUpdate', () => {
55
+ it('sets the approval url', () => {
56
+ interpretation.approvalUrlUpdate('newUrl');
57
+
58
+ assert.equal(interpretation.approvalUrl, 'newUrl');
59
+ });
60
+ });
61
+
53
62
  describe('#updateCanManageInterpreters', () => {
54
63
  it('update canManageInterpreters', () => {
55
64
  interpretation.updateCanManageInterpreters(true);
@@ -62,6 +71,18 @@ describe('plugin-meetings', () => {
62
71
  });
63
72
  });
64
73
 
74
+ describe('#updateHostSIEnabled', () => {
75
+ it('update hostSI feature is on or off', () => {
76
+ interpretation.updateHostSIEnabled(true);
77
+
78
+ assert.equal(interpretation.hostSIEnabled, true);
79
+
80
+ interpretation.updateHostSIEnabled(false);
81
+
82
+ assert.equal(interpretation.hostSIEnabled, false);
83
+ });
84
+ });
85
+
65
86
  describe('#updateInterpretation', () => {
66
87
  const checkSILanguage = (siLanguage, expectResult) => {
67
88
  return siLanguage?.languageCode === expectResult.languageCode && siLanguage?.languageName === expectResult.languageName
@@ -69,15 +90,6 @@ describe('plugin-meetings', () => {
69
90
  it('update interpretation correctly', () => {
70
91
  interpretation.updateInterpretation({siLanguages: [{languageName: 'en', languageCode: 1}]});
71
92
  checkSILanguage(interpretation.siLanguages.en, {languageName: 'en', languageCode: 1});
72
- assert.equal(interpretation.siEnabled, true);
73
- });
74
-
75
- it('check siEnable as false if input param interpretation is null/undefined', () => {
76
- interpretation.updateInterpretation(null);
77
- assert.equal(interpretation.siEnabled, false);
78
-
79
- interpretation.updateInterpretation(undefined);
80
- assert.equal(interpretation.siEnabled, false);
81
93
  });
82
94
  });
83
95
 
@@ -325,5 +337,234 @@ describe('plugin-meetings', () => {
325
337
  });
326
338
  });
327
339
  });
340
+
341
+ describe('#listenToHandoffRequests', () => {
342
+ it('triggers handoff update event when the approval is related with self', () => {
343
+ const call = webex.internal.mercury.on.getCall(0);
344
+ const callback = call.args[1];
345
+
346
+ assert.equal(call.args[0], 'event:locus.approval_request');
347
+ interpretation.set('selfParticipantId', 'p123');
348
+
349
+ let called = false;
350
+ const triggerSpy = sinon.spy(interpretation, 'trigger');
351
+
352
+ interpretation.listenTo(interpretation, 'HANDOFF_REQUESTS_ARRIVED', () => {
353
+ called = true;
354
+ });
355
+
356
+ callback({
357
+ data: {
358
+ approval: {
359
+ actionType: 'OFFERED',
360
+ resourceType: 'SiHandover',
361
+ receivers: [{
362
+ participantId: 'p123',
363
+ }],
364
+ initiator: {participantId: 'p123'},
365
+ url: 'testUrl',
366
+ },
367
+ }
368
+ });
369
+
370
+ assert.isTrue(called);
371
+ assert.calledWithExactly(triggerSpy, 'HANDOFF_REQUESTS_ARRIVED', {
372
+ actionType: 'OFFERED',
373
+ isReceiver: true,
374
+ isSender: true,
375
+ senderId: 'p123',
376
+ receiverId: 'p123',
377
+ url: 'testUrl',
378
+ });
379
+ });
380
+
381
+ it('not triggers handoff update event when the approval is not related with self', () => {
382
+ const call = webex.internal.mercury.on.getCall(0);
383
+ const callback = call.args[1];
384
+
385
+ interpretation.set('selfParticipantId', 'p123');
386
+
387
+ let called = false;
388
+
389
+ interpretation.listenTo(interpretation, 'HANDOFF_REQUESTS_ARRIVED', () => {
390
+ called = true;
391
+ });
392
+
393
+ callback({
394
+ data: {
395
+ approval: {
396
+ actionType: 'OFFERED',
397
+ resourceType: 'SiHandover',
398
+ receivers: [{
399
+ participantId: 'p444',
400
+ }],
401
+ initiator: {participantId: 'p444'},
402
+ url: 'testUrl',
403
+ },
404
+ }
405
+ });
406
+
407
+ assert.isFalse(called);
408
+ });
409
+ });
410
+
411
+ describe('#handoffInterpreter', () => {
412
+ it('makes the request as expected', async () => {
413
+ interpretation.approvalUrlUpdate('approvalUrl');
414
+ await interpretation.handoffInterpreter('participant2');
415
+ assert.calledOnceWithExactly(webex.request, {
416
+ method: 'POST',
417
+ uri: 'approvalUrl',
418
+ body: {
419
+ actionType: 'OFFERED',
420
+ resourceType: 'SiHandover',
421
+ receivers: [
422
+ {
423
+ participantId: 'participant2',
424
+ },
425
+ ],
426
+ },
427
+ });
428
+ });
429
+
430
+ it('rejects with error', async () => {
431
+ const mockError = new Error('something wrong');
432
+ webex.request.returns(Promise.reject(mockError));
433
+ LoggerProxy.logger.error = sinon.stub();
434
+ interpretation.approvalUrlUpdate('approvalUrl');
435
+
436
+ await assert.isRejected(interpretation.handoffInterpreter('p2'), mockError, 'something wrong');
437
+
438
+ assert.calledOnceWithExactly(
439
+ LoggerProxy.logger.error,
440
+ 'Meeting:interpretation#handoffInterpreter failed',
441
+ mockError
442
+ );
443
+ });
444
+
445
+ it('rejects error when no target participant id', async () => {
446
+ LoggerProxy.logger.error = sinon.stub();
447
+
448
+ await interpretation.handoffInterpreter().catch((error) => {
449
+ assert.equal(error.toString(), 'Error: Missing target participant id');
450
+ });
451
+ });
452
+
453
+ it('rejects error when no approval url', async () => {
454
+ LoggerProxy.logger.error = sinon.stub();
455
+
456
+ await interpretation.handoffInterpreter('p2').catch((error) => {
457
+ assert.equal(error.toString(), 'Error: Missing approval url');
458
+ });
459
+ });
460
+ });
461
+
462
+ describe('#requestHandoff', () => {
463
+ it('makes the request as expected', async () => {
464
+ interpretation.approvalUrlUpdate('approvalUrl');
465
+ await interpretation.requestHandoff();
466
+ assert.calledOnceWithExactly(webex.request, {
467
+ method: 'POST',
468
+ uri: 'approvalUrl',
469
+ body: {
470
+ actionType: 'REQUESTED',
471
+ resourceType: 'SiHandover',
472
+ },
473
+ });
474
+ });
475
+
476
+ it('rejects with error', async () => {
477
+ const mockError = new Error('something wrong');
478
+ webex.request.returns(Promise.reject(mockError));
479
+ LoggerProxy.logger.error = sinon.stub();
480
+ interpretation.approvalUrlUpdate('approvalUrl');
481
+
482
+ await assert.isRejected(interpretation.requestHandoff(), mockError, 'something wrong');
483
+
484
+ assert.calledOnceWithExactly(
485
+ LoggerProxy.logger.error,
486
+ 'Meeting:interpretation#requestHandoff failed',
487
+ mockError
488
+ );
489
+ });
490
+
491
+ it('rejects error when no approval url', async () => {
492
+ LoggerProxy.logger.error = sinon.stub();
493
+
494
+ await interpretation.requestHandoff().catch((error) => {
495
+ assert.equal(error.toString(), 'Error: Missing approval url');
496
+ });
497
+ });
498
+ });
499
+
500
+ describe('#acceptRequest', () => {
501
+ it('makes the request as expected', async () => {
502
+ await interpretation.acceptRequest('testUrl');
503
+ assert.calledOnceWithExactly(webex.request, {
504
+ method: 'PUT',
505
+ uri: 'testUrl',
506
+ body: {
507
+ actionType: 'ACCEPTED',
508
+ },
509
+ });
510
+ });
511
+
512
+ it('rejects with error', async () => {
513
+ const mockError = new Error('something wrong');
514
+ webex.request.returns(Promise.reject(mockError));
515
+ LoggerProxy.logger.error = sinon.stub();
516
+
517
+ await assert.isRejected(interpretation.acceptRequest('testUrl'), mockError, 'something wrong');
518
+
519
+ assert.calledOnceWithExactly(
520
+ LoggerProxy.logger.error,
521
+ 'Meeting:interpretation#acceptRequest failed',
522
+ mockError
523
+ );
524
+ });
525
+
526
+ it('rejects error when no url passed', async () => {
527
+ LoggerProxy.logger.error = sinon.stub();
528
+
529
+ await interpretation.acceptRequest().catch((error) => {
530
+ assert.equal(error.toString(), 'Error: Missing the url to accept');
531
+ });
532
+ });
533
+ });
534
+
535
+ describe('#declineRequest', () => {
536
+ it('makes the request as expected', async () => {
537
+ await interpretation.declineRequest('testUrl');
538
+ assert.calledOnceWithExactly(webex.request, {
539
+ method: 'PUT',
540
+ uri: 'testUrl',
541
+ body: {
542
+ actionType: 'DECLINED',
543
+ },
544
+ });
545
+ });
546
+
547
+ it('rejects with error', async () => {
548
+ const mockError = new Error('something wrong');
549
+ webex.request.returns(Promise.reject(mockError));
550
+ LoggerProxy.logger.error = sinon.stub();
551
+
552
+ await assert.isRejected(interpretation.declineRequest('testUrl'), mockError, 'something wrong');
553
+
554
+ assert.calledOnceWithExactly(
555
+ LoggerProxy.logger.error,
556
+ 'Meeting:interpretation#declineRequest failed',
557
+ mockError
558
+ );
559
+ });
560
+
561
+ it('rejects error when no url passed', async () => {
562
+ LoggerProxy.logger.error = sinon.stub();
563
+
564
+ await interpretation.declineRequest().catch((error) => {
565
+ assert.equal(error.toString(), 'Error: Missing the url to decline');
566
+ });
567
+ });
568
+ });
328
569
  });
329
570
  });
@@ -2,6 +2,7 @@ import {assert} from '@webex/test-helper-chai';
2
2
  import SILanguage from '@webex/plugin-meetings/src/interpretation/siLanguage';
3
3
  import SimultaneousInterpretation from '@webex/plugin-meetings/src/interpretation';
4
4
  import MockWebex from '@webex/test-helper-mock-webex';
5
+ import sinon from 'sinon';
5
6
 
6
7
  describe('plugin-meetings', () => {
7
8
  describe('SILanguage', () => {
@@ -11,6 +12,7 @@ describe('plugin-meetings', () => {
11
12
  beforeEach(() => {
12
13
  // @ts-ignore
13
14
  webex = new MockWebex({});
15
+ webex.internal.mercury.on = sinon.stub();
14
16
  interpretation = new SimultaneousInterpretation({}, {parent: webex});
15
17
  siLanguage = new SILanguage({}, {parent: interpretation});
16
18
  });