@webex/plugin-meetings 2.12.1 → 2.14.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.
@@ -0,0 +1,292 @@
1
+ /* eslint-disable camelcase */
2
+ import {assert} from '@webex/test-helper-chai';
3
+ import sinon from 'sinon';
4
+ import MockWebex from '@webex/test-helper-mock-webex';
5
+
6
+ import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
7
+ import {BNR_STATUS} from '@webex/plugin-meetings/src/constants';
8
+ import Meeting from '@webex/plugin-meetings/src/meeting';
9
+ import Meetings from '@webex/plugin-meetings';
10
+ import Metrics from '@webex/plugin-meetings/src/metrics';
11
+ import MediaUtil from '@webex/plugin-meetings/src/media/util';
12
+ import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
13
+ import createEffectsState from '@webex/plugin-meetings/src/meeting/effectsState';
14
+ import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
15
+ import LoggerConfig from '@webex/plugin-meetings/src/common/logs/logger-config';
16
+
17
+ describe('plugin-meetings', () => {
18
+ const logger = {
19
+ info: () => {},
20
+ log: () => {},
21
+ error: () => {},
22
+ warn: () => {},
23
+ trace: () => {},
24
+ debug: () => {}
25
+ };
26
+
27
+ beforeEach(() => {
28
+ sinon.stub(Metrics, 'sendBehavioralMetric');
29
+ });
30
+ afterEach(() => {
31
+ sinon.restore();
32
+ });
33
+
34
+ Object.defineProperty(global.window.navigator, 'mediaDevices', {
35
+ writable: true,
36
+ value: {
37
+ getSupportedConstraints: sinon.stub().returns({
38
+ sampleRate: true
39
+ })
40
+ },
41
+ });
42
+ LoggerConfig.set({verboseEvents: true, enable: false});
43
+ LoggerProxy.set(logger);
44
+
45
+ let webex;
46
+ let meeting;
47
+ let uuid1;
48
+
49
+ const fakeMediaTrack = () => ({
50
+ id: Date.now().toString(),
51
+ stop: () => {},
52
+ readyState: 'live',
53
+ enabled: true,
54
+ getSettings: () => ({
55
+ sampleRate: 48000
56
+ })
57
+ });
58
+
59
+ class FakeMediaStream {
60
+ constructor(tracks) {
61
+ this.active = false;
62
+ this.id = '5146425f-c240-48cc-b86b-27d422988fb7';
63
+ this.tracks = tracks;
64
+ }
65
+
66
+ addTrack = () => undefined;
67
+
68
+ getAudioTracks = () => this.tracks;
69
+ }
70
+
71
+ class FakeAudioContext {
72
+ constructor() {
73
+ this.state = 'running';
74
+ this.baseLatency = 0.005333333333333333;
75
+ this.currentTime = 2.7946666666666666;
76
+ this.sampleRate = 48000;
77
+ this.audioWorklet = {
78
+ addModule: async () => undefined,
79
+ };
80
+ }
81
+
82
+ onstatechange = null;
83
+
84
+ createMediaStreamSource() {
85
+ return {
86
+ connect: () => undefined,
87
+ mediaStream: {
88
+ getAudioTracks() {
89
+ // eslint-disable-next-line no-undef
90
+ return [new MediaStreamTrack()];
91
+ },
92
+ },
93
+ };
94
+ }
95
+
96
+ createMediaStreamDestination() {
97
+ return {
98
+ stream: {
99
+ getAudioTracks() {
100
+ // eslint-disable-next-line no-undef
101
+ return [new MediaStreamTrack()];
102
+ },
103
+ },
104
+ };
105
+ }
106
+ }
107
+
108
+ class FakeAudioWorkletNode {
109
+ constructor() {
110
+ this.port = {
111
+ postMessage: () => undefined,
112
+ };
113
+ }
114
+
115
+ connect() {
116
+ /* placeholder method */
117
+ }
118
+ }
119
+
120
+ class FakeMediaStreamTrack {
121
+ constructor() {
122
+ this.kind = 'audio';
123
+ this.enabled = true;
124
+ this.label = 'Default - MacBook Pro Microphone (Built-in)';
125
+ this.muted = false;
126
+ this.readyState = 'live';
127
+ this.contentHint = '';
128
+ }
129
+
130
+ getSettings() {
131
+ return {
132
+ sampleRate: 48000
133
+ };
134
+ }
135
+ }
136
+ Object.defineProperty(global, 'MediaStream', {
137
+ writable: true,
138
+ value: FakeMediaStream,
139
+ });
140
+
141
+ Object.defineProperty(global, 'AudioContext', {
142
+ writable: true,
143
+ value: FakeAudioContext,
144
+ });
145
+
146
+ Object.defineProperty(global, 'AudioWorkletNode', {
147
+ writable: true,
148
+ value: FakeAudioWorkletNode,
149
+ });
150
+
151
+ Object.defineProperty(global, 'MediaStreamTrack', {
152
+ writable: true,
153
+ value: FakeMediaStreamTrack,
154
+ });
155
+
156
+ let effects;
157
+
158
+ beforeEach(() => {
159
+ webex = new MockWebex({
160
+ children: {
161
+ meetings: Meetings
162
+ }
163
+ });
164
+ MediaUtil.createPeerConnection = sinon.stub().returns({});
165
+ meeting = new Meeting(
166
+ {
167
+ userId: uuid1
168
+ },
169
+ {
170
+ parent: webex
171
+ }
172
+ );
173
+
174
+ effects = createEffectsState('BNR');
175
+ meeting.canUpdateMedia = sinon.stub().returns(true);
176
+ MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
177
+ MeetingUtil.updateTransceiver = sinon.stub();
178
+
179
+ meeting.addMedia = sinon.stub().returns(Promise.resolve());
180
+ meeting.getMediaStreams = sinon.stub().returns(Promise.resolve());
181
+ sinon.replace(meeting, 'addMedia', () => {
182
+ sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
183
+ sinon.stub(meeting.mediaProperties, 'mediaDirection').value({
184
+ receiveAudio: true
185
+ });
186
+ });
187
+ });
188
+
189
+ describe('bnr effect library', () => {
190
+ beforeEach(async () => {
191
+ await meeting.getMediaStreams();
192
+ await meeting.addMedia();
193
+ });
194
+ describe('#enableBNR', () => {
195
+ it('should have #enableBnr', () => {
196
+ assert.exists(effects.enableBNR);
197
+ });
198
+
199
+ it('does bnr effect enable on audio track', async () => {
200
+ assert.isTrue(await effects.handleClientRequest(true, meeting));
201
+ assert.equal(effects.state.bnr.enabled, BNR_STATUS.ENABLED);
202
+
203
+ assert(Metrics.sendBehavioralMetric.calledOnce);
204
+ assert.calledWith(
205
+ Metrics.sendBehavioralMetric,
206
+ BEHAVIORAL_METRICS.ENABLE_BNR_SUCCESS,
207
+ );
208
+ });
209
+
210
+ it('does resolve request if bnr is already enabled', async () => {
211
+ effects.state.bnr.enabled = BNR_STATUS.ENABLED;
212
+ assert.isTrue(await effects.handleClientRequest(true, meeting));
213
+ assert.equal(effects.state.bnr.enabled, BNR_STATUS.ENABLED);
214
+ });
215
+
216
+ it('if called twice, does bnr effect enable on audio track for the first request and resolves second', async () => {
217
+ Promise.all([effects.handleClientRequest(true, meeting), effects.handleClientRequest(true, meeting)])
218
+ .then((resolveFirst, resolveSecond) => {
219
+ assert.isTrue(resolveFirst);
220
+ assert.isTrue(resolveSecond);
221
+ assert.calledOnce(MediaUtil.createMediaStream);
222
+ });
223
+ });
224
+
225
+ it('should throw error for inappropriate sample rate and send error metrics', async () => {
226
+ const fakeMediaTrack1 = () => ({
227
+ id: Date.now().toString(),
228
+ stop: () => {},
229
+ readyState: 'live',
230
+ getSettings: () => ({
231
+ sampleRate: 49000
232
+ })
233
+ });
234
+
235
+ sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack1());
236
+
237
+ // eslint-disable-next-line no-undef
238
+ MediaUtil.createMediaStream = sinon.stub().returns(new MediaStream([fakeMediaTrack1()]));
239
+ try {
240
+ await effects.handleClientRequest(true, meeting);
241
+ }
242
+ catch (err) {
243
+ assert(Metrics.sendBehavioralMetric.calledOnce);
244
+ assert.calledWith(
245
+ Metrics.sendBehavioralMetric,
246
+ BEHAVIORAL_METRICS.ENABLE_BNR_FAILURE, {
247
+ reason: err.message,
248
+ stack: err.stack
249
+ }
250
+ );
251
+ assert.equal(err.message, 'Sample rate of 49000 is not supported.');
252
+ }
253
+ });
254
+ });
255
+
256
+ describe('#disableBNR', () => {
257
+ beforeEach(() => {
258
+ effects.state.bnr.enabled = BNR_STATUS.ENABLED;
259
+ });
260
+ it('should have #disableBnr', () => {
261
+ assert.exists(effects.disableBNR);
262
+ });
263
+
264
+ it('does bnr disable on audio track', async () => {
265
+ assert.isTrue(await effects.handleClientRequest(false, meeting));
266
+ assert.equal(effects.state.bnr.enabled, BNR_STATUS.NOT_ENABLED);
267
+
268
+ assert(Metrics.sendBehavioralMetric.calledOnce);
269
+ assert.calledWith(
270
+ Metrics.sendBehavioralMetric,
271
+ BEHAVIORAL_METRICS.DISABLE_BNR_SUCCESS,
272
+ );
273
+ });
274
+
275
+ it('reject request for disable bnr if not enabled', async () => {
276
+ try {
277
+ await effects.handleClientRequest(false, meeting);
278
+ }
279
+ catch (e) {
280
+ assert.equal(e.message, 'Can not disable as BNR is not enabled');
281
+ assert.equal(effects.state.bnr.enabled, BNR_STATUS.ENABLED);
282
+
283
+ assert(Metrics.sendBehavioralMetric.calledOnce);
284
+ assert.calledWith(
285
+ Metrics.sendBehavioralMetric,
286
+ BEHAVIORAL_METRICS.DISABLE_BNR_FAILURE,
287
+ );
288
+ }
289
+ });
290
+ });
291
+ });
292
+ });
@@ -456,114 +456,25 @@ describe('plugin-meetings', () => {
456
456
  });
457
457
  });
458
458
  describe('BNR', () => {
459
- class FakeMediaStream {
460
- constructor() {
461
- this.active = false;
462
- this.id = '5146425f-c240-48cc-b86b-27d422988fb7';
463
- }
464
-
465
- addTrack = () => undefined;
466
- }
467
-
468
- class FakeAudioContext {
469
- constructor() {
470
- this.state = 'running';
471
- this.baseLatency = 0.005333333333333333;
472
- this.currentTime = 2.7946666666666666;
473
- this.sampleRate = 48000;
474
- this.audioWorklet = {
475
- addModule: async () => undefined,
476
- };
477
- }
478
-
479
- onstatechange = null;
480
-
481
- createMediaStreamSource() {
482
- return {
483
- connect: () => undefined,
484
- mediaStream: {
485
- getAudioTracks() {
486
- // eslint-disable-next-line no-undef
487
- return [new MediaStreamTrack()];
488
- },
489
- },
490
- };
491
- }
492
-
493
- createMediaStreamDestination() {
494
- return {
495
- stream: {
496
- getAudioTracks() {
497
- // eslint-disable-next-line no-undef
498
- return [new MediaStreamTrack()];
499
- },
500
- },
501
- };
502
- }
503
- }
504
-
505
- class FakeAudioWorkletNode {
506
- constructor() {
507
- this.port = {
508
- postMessage: () => undefined,
509
- };
510
- }
511
-
512
- connect() {
513
- /* placeholder method */
514
- }
515
- }
516
-
517
- class FakeMediaStreamTrack {
518
- constructor() {
519
- this.kind = 'audio';
520
- this.enabled = true;
521
- this.label = 'Default - MacBook Pro Microphone (Built-in)';
522
- this.muted = false;
523
- this.readyState = 'live';
524
- this.contentHint = '';
525
- }
526
- }
527
- Object.defineProperty(global, 'MediaStream', {
528
- writable: true,
529
- value: FakeMediaStream,
530
- });
531
-
532
- Object.defineProperty(global, 'AudioContext', {
533
- writable: true,
534
- value: FakeAudioContext,
535
- });
536
-
537
- Object.defineProperty(global, 'AudioWorkletNode', {
538
- writable: true,
539
- value: FakeAudioWorkletNode,
540
- });
541
-
542
- Object.defineProperty(global, 'MediaStreamTrack', {
543
- writable: true,
544
- value: FakeMediaStreamTrack,
459
+ const fakeMediaTrack = () => ({
460
+ id: Date.now().toString(),
461
+ stop: () => {},
462
+ readyState: 'live',
463
+ enabled: true,
464
+ getSettings: () => ({
465
+ sampleRate: 48000
466
+ })
545
467
  });
546
468
 
547
- beforeEach(async () => {
548
- meeting.canUpdateMedia = sinon.stub().returns(true);
549
- MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
550
- MeetingUtil.updateTransceiver = sinon.stub();
551
- const fakeMediaTrack = () => ({
552
- stop: () => {},
553
- readyState: 'live',
554
- getSettings: () => ({
555
- sampleRate: 48000
556
- })
557
- });
558
-
469
+ beforeEach(() => {
559
470
  meeting.getMediaStreams = sinon.stub().returns(Promise.resolve());
560
471
  sinon.replace(meeting, 'addMedia', () => {
561
472
  sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
473
+ sinon.stub(meeting.mediaProperties, 'mediaDirection').value({
474
+ receiveAudio: true
475
+ });
562
476
  });
563
- await meeting.getMediaStreams();
564
- await meeting.addMedia();
565
477
  });
566
-
567
478
  describe('#enableBNR', () => {
568
479
  it('should have #enableBnr', () => {
569
480
  assert.exists(meeting.enableBNR);
@@ -578,67 +489,68 @@ describe('plugin-meetings', () => {
578
489
  });
579
490
 
580
491
  describe('after audio attached to meeting', () => {
581
- it('should return true for appropriate sample rate', async () => {
582
- const response = await meeting.enableBNR();
492
+ let handleClientRequest;
583
493
 
584
- assert.equal(response, true);
494
+ beforeEach(async () => {
495
+ await meeting.getMediaStreams();
496
+ await meeting.addMedia();
585
497
  });
586
498
 
587
- it('should throw error for inappropriate sample rate and send error metrics', async () => {
588
- const fakeMediaTrack = () => ({
589
- stop: () => {},
590
- readyState: 'live',
591
- getSettings: () => ({
592
- sampleRate: 49000
593
- })
594
- });
499
+ it('should throw error if meeting audio is muted', async () => {
500
+ const handleClientRequest = (meeting, mute) => {
501
+ meeting.mediaProperties.audioTrack.enabled = !mute;
502
+
503
+ return Promise.resolve();
504
+ };
505
+ const isMuted = () => !meeting.mediaProperties.audioTrack.enabled;
595
506
 
596
- sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
507
+ meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
508
+ meeting.mediaId = 'mediaId';
509
+ meeting.audio = {handleClientRequest, isMuted};
510
+ await meeting.muteAudio();
597
511
  await meeting.enableBNR().catch((err) => {
598
- assert(Metrics.sendBehavioralMetric.calledOnce);
599
- assert.calledWith(
600
- Metrics.sendBehavioralMetric,
601
- BEHAVIORAL_METRICS.ENABLE_BNR_FAILURE, {
602
- reason: err.message,
603
- stack: err.stack
604
- }
605
- );
606
- assert.equal(err.message, 'Sample rate of 49000 is not supported.');
512
+ assert.equal(err.message, 'Cannot enable BNR while meeting is muted');
607
513
  });
608
514
  });
609
515
 
610
- it('should send metrics for enable bnr success', async () => {
516
+ it('should return true on enable bnr success', async () => {
517
+ handleClientRequest = sinon.stub().returns(Promise.resolve(true));
518
+ meeting.effects = {handleClientRequest};
611
519
  const response = await meeting.enableBNR();
612
520
 
613
- assert(Metrics.sendBehavioralMetric.calledOnce);
614
- assert.calledWith(
615
- Metrics.sendBehavioralMetric,
616
- BEHAVIORAL_METRICS.ENABLE_BNR_SUCCESS,
617
- );
618
521
  assert.equal(response, true);
619
522
  });
620
523
  });
621
524
  });
622
525
 
623
526
  describe('#disableBNR', () => {
624
- it('should have #disableBnr', () => {
625
- assert.exists(meeting.disableBNR);
527
+ describe('before audio attached to meeting', () => {
528
+ it('should have #disableBnr', () => {
529
+ assert.exists(meeting.disableBNR);
530
+ });
531
+
532
+ it('should throw no audio error', async () => {
533
+ await meeting.disableBNR().catch((err) => {
534
+ assert.equal(err.toString(), 'Error: Meeting doesn\'t have an audioTrack attached');
535
+ });
536
+ });
626
537
  });
538
+ describe('after audio attached to meeting', () => {
539
+ beforeEach(async () => {
540
+ await meeting.getMediaStreams();
541
+ await meeting.addMedia();
542
+ });
627
543
 
628
- it('should return true if bnr is disabled on bnr enabled track', async () => {
629
- const response = await meeting.disableBNR();
544
+ let handleClientRequest;
545
+ let isBnrEnabled;
630
546
 
631
- assert.equal(response, true);
632
- });
547
+ it('should return true on disable bnr success', async () => {
548
+ handleClientRequest = sinon.stub().returns(Promise.resolve(true));
549
+ isBnrEnabled = sinon.stub().returns(Promise.resolve(true));
550
+ meeting.effects = {handleClientRequest, isBnrEnabled};
551
+ const response = await meeting.disableBNR();
633
552
 
634
- it('should throw error if bnr is not enabled before disabling and send error metrics', async () => {
635
- await meeting.disableBNR().catch((err) => {
636
- assert(Metrics.sendBehavioralMetric.calledOnce);
637
- assert.calledWith(
638
- Metrics.sendBehavioralMetric,
639
- BEHAVIORAL_METRICS.DISABLE_BNR_FAILURE,
640
- );
641
- assert.equal(err.message, 'Can not disable as BNR is not enabled');
553
+ assert.equal(response, true);
642
554
  });
643
555
  });
644
556
  });
@@ -1420,7 +1332,7 @@ describe('plugin-meetings', () => {
1420
1332
 
1421
1333
  sandbox.stub(meeting.mediaProperties, 'peerConnection').value({shareTransceiver: true});
1422
1334
  sandbox.stub(MeetingUtil, 'getTrack').returns({videoTrack: true});
1423
- sandbox.stub(MeetingUtil, 'validateOptions').resolves(true);
1335
+ MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve(true));
1424
1336
  sandbox.stub(meeting, 'canUpdateMedia').returns(true);
1425
1337
  sandbox.stub(meeting, 'setLocalShareTrack');
1426
1338
 
@@ -1489,13 +1401,14 @@ describe('plugin-meetings', () => {
1489
1401
  const SENDRECV = 'sendrecv';
1490
1402
  const delay = 1e3;
1491
1403
 
1404
+ MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve(true));
1405
+ MeetingUtil.updateTransceiver = sinon.stub().returns(Promise.resolve(true));
1492
1406
  sandbox.stub(meeting, 'canUpdateMedia').returns(true);
1493
1407
  sandbox.stub(MeetingUtil, 'getTrack').returns({videoTrack: null});
1494
1408
  sandbox.stub(meeting, 'setLocalShareTrack');
1495
1409
  sandbox.stub(meeting, 'unsetLocalShareTrack');
1496
- sandbox.stub(MeetingUtil, 'validateOptions').resolves(true);
1497
1410
  sandbox.stub(meeting, 'checkForStopShare').returns(false);
1498
- sandbox.stub(MeetingUtil, 'updateTransceiver').resolves(true);
1411
+
1499
1412
  sandbox.stub(meeting, 'isLocalShareLive').value(false);
1500
1413
  sandbox.stub(meeting, 'handleShareTrackEnded');
1501
1414
  sandbox.stub(meeting.mediaProperties, 'peerConnection').value({
@@ -2366,11 +2279,6 @@ describe('plugin-meetings', () => {
2366
2279
  assert.equal(meeting.requiredCaptcha, null);
2367
2280
  assert.calledTwice(TriggerProxy.trigger);
2368
2281
  assert.calledWith(TriggerProxy.trigger, meeting, {file: 'meetings', function: 'fetchMeetingInfo'}, 'meeting:meetingInfoAvailable');
2369
- assert(Metrics.sendBehavioralMetric.calledOnce);
2370
- assert.calledWith(
2371
- Metrics.sendBehavioralMetric,
2372
- BEHAVIORAL_METRICS.VERIFY_PASSWORD_SUCCESS,
2373
- );
2374
2282
  });
2375
2283
 
2376
2284
  it('calls meetingInfoProvider with all the right parameters and parses the result when random delay is applied', async () => {
@@ -2442,11 +2350,6 @@ describe('plugin-meetings', () => {
2442
2350
 
2443
2351
  assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, null, null);
2444
2352
 
2445
- assert(Metrics.sendBehavioralMetric.calledOnce);
2446
- assert.calledWith(
2447
- Metrics.sendBehavioralMetric,
2448
- BEHAVIORAL_METRICS.VERIFY_PASSWORD_ERROR,
2449
- );
2450
2353
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
2451
2354
  assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
2452
2355
  assert.equal(meeting.requiredCaptcha, null);
@@ -2467,11 +2370,7 @@ describe('plugin-meetings', () => {
2467
2370
 
2468
2371
  assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', null);
2469
2372
 
2470
- assert(Metrics.sendBehavioralMetric.calledOnce);
2471
- assert.calledWith(
2472
- Metrics.sendBehavioralMetric,
2473
- BEHAVIORAL_METRICS.VERIFY_CAPTCHA_ERROR,
2474
- );
2373
+
2475
2374
  assert.deepEqual(meeting.meetingInfo, {});
2476
2375
  assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
2477
2376
  assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
@@ -2625,6 +2524,11 @@ describe('plugin-meetings', () => {
2625
2524
  meeting.fetchMeetingInfo = sinon.stub().resolves();
2626
2525
  const result = await meeting.verifyPassword('password', 'captcha id');
2627
2526
 
2527
+ assert(Metrics.sendBehavioralMetric.calledOnce);
2528
+ assert.calledWith(
2529
+ Metrics.sendBehavioralMetric,
2530
+ BEHAVIORAL_METRICS.VERIFY_PASSWORD_SUCCESS,
2531
+ );
2628
2532
  assert.equal(result.isPasswordValid, true);
2629
2533
  assert.equal(result.requiredCaptcha, null);
2630
2534
  assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.NONE);
@@ -177,16 +177,17 @@ describe('plugin-meetings', () => {
177
177
  meetingInfo.createAdhocSpaceMeeting.restore();
178
178
  });
179
179
 
180
+
180
181
  it('should throw an error MeetingInfoV2AdhocMeetingError if not able to start adhoc meeting for a conversation', async () => {
181
182
  webex.config.meetings.experimental.enableAdhocMeetings = true;
182
183
 
183
- webex.request = sinon.stub().rejects({statusCode: 403, body: {code: 400000, message: 'Input is invalid'}});
184
+ webex.request = sinon.stub().rejects({statusCode: 403, body: {code: 400000}});
184
185
  try {
185
186
  await meetingInfo.createAdhocSpaceMeeting('conversationUrl');
186
187
  }
187
188
  catch (err) {
188
189
  assert.instanceOf(err, MeetingInfoV2AdhocMeetingError);
189
- assert.deepEqual(err.message, 'Input is invalid, code=400000');
190
+ assert.deepEqual(err.message, 'Failed starting the adhoc meeting, Please contact support team , code=400000');
190
191
  assert.equal(err.wbxAppApiCode, 400000);
191
192
  }
192
193
  });
@@ -201,6 +202,11 @@ describe('plugin-meetings', () => {
201
202
  assert.fail('fetchMeetingInfo should have thrown, but has not done that');
202
203
  }
203
204
  catch (err) {
205
+ assert(Metrics.sendBehavioralMetric.calledOnce);
206
+ assert.calledWith(
207
+ Metrics.sendBehavioralMetric,
208
+ BEHAVIORAL_METRICS.VERIFY_PASSWORD_ERROR,
209
+ );
204
210
  assert.instanceOf(err, MeetingInfoV2PasswordError);
205
211
  assert.deepEqual(err.meetingInfo, FAKE_MEETING_INFO);
206
212
  assert.equal(err.wbxAppApiCode, 403000);
@@ -226,6 +232,11 @@ describe('plugin-meetings', () => {
226
232
  assert.fail('fetchMeetingInfo should have thrown, but has not done that');
227
233
  }
228
234
  catch (err) {
235
+ assert(Metrics.sendBehavioralMetric.calledOnce);
236
+ assert.calledWith(
237
+ Metrics.sendBehavioralMetric,
238
+ BEHAVIORAL_METRICS.VERIFY_CAPTCHA_ERROR,
239
+ );
229
240
  assert.instanceOf(err, MeetingInfoV2CaptchaError);
230
241
  assert.deepEqual(err.captchaInfo, {
231
242
  captchaId: 'fake_captcha_id',