@webex/plugin-meetings 2.14.0 → 2.14.3
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.
- package/dist/common/errors/parameter.js +1 -1
- package/dist/common/errors/parameter.js.map +1 -1
- package/dist/constants.js +7 -3
- package/dist/constants.js.map +1 -1
- package/dist/locus-info/controlsUtils.js +12 -2
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +21 -0
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +14 -0
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/meeting/index.js +159 -108
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +6 -10
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/metrics/constants.js +5 -1
- package/dist/metrics/constants.js.map +1 -1
- package/package.json +6 -6
- package/src/common/errors/parameter.js +1 -1
- package/src/constants.js +3 -0
- package/src/locus-info/controlsUtils.js +11 -0
- package/src/locus-info/index.js +26 -0
- package/src/locus-info/selfUtils.js +12 -0
- package/src/meeting/index.js +115 -53
- package/src/meeting-info/meeting-info-v2.js +1 -4
- package/src/metrics/constants.js +5 -1
- package/test/unit/spec/fixture/locus.js +404 -0
- package/test/unit/spec/locus-info/index.js +61 -0
- package/test/unit/spec/locus-info/selfConstant.js +1 -0
- package/test/unit/spec/meeting/index.js +230 -24
- package/test/unit/spec/meeting-info/meetinginfov2.js +13 -2
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from '@webex/plugin-meetings/src/constants';
|
|
43
43
|
import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
|
|
44
44
|
|
|
45
|
+
import locus from '../fixture/locus';
|
|
45
46
|
import {
|
|
46
47
|
UserNotJoinedError,
|
|
47
48
|
MeetingNotActiveError,
|
|
@@ -724,16 +725,12 @@ describe('plugin-meetings', () => {
|
|
|
724
725
|
});
|
|
725
726
|
describe('#isTranscriptionSupported', () => {
|
|
726
727
|
it('should return false if the feature is not supported for the meeting', () => {
|
|
727
|
-
meeting.
|
|
728
|
-
WEBEX_ASSISTANT_STATUS_INACTIVE: true
|
|
729
|
-
};
|
|
728
|
+
meeting.locusInfo.controls = {transcribe: {transcribing: false}};
|
|
730
729
|
|
|
731
730
|
assert.equal(meeting.isTranscriptionSupported(), false);
|
|
732
731
|
});
|
|
733
732
|
it('should return true if webex assitant is enabled', () => {
|
|
734
|
-
meeting.
|
|
735
|
-
WEBEX_ASSISTANT_STATUS_ACTIVE: true
|
|
736
|
-
};
|
|
733
|
+
meeting.locusInfo.controls = {transcribe: {transcribing: true}};
|
|
737
734
|
|
|
738
735
|
assert.equal(meeting.isTranscriptionSupported(), true);
|
|
739
736
|
});
|
|
@@ -1332,7 +1329,7 @@ describe('plugin-meetings', () => {
|
|
|
1332
1329
|
|
|
1333
1330
|
sandbox.stub(meeting.mediaProperties, 'peerConnection').value({shareTransceiver: true});
|
|
1334
1331
|
sandbox.stub(MeetingUtil, 'getTrack').returns({videoTrack: true});
|
|
1335
|
-
|
|
1332
|
+
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve(true));
|
|
1336
1333
|
sandbox.stub(meeting, 'canUpdateMedia').returns(true);
|
|
1337
1334
|
sandbox.stub(meeting, 'setLocalShareTrack');
|
|
1338
1335
|
|
|
@@ -1401,13 +1398,14 @@ describe('plugin-meetings', () => {
|
|
|
1401
1398
|
const SENDRECV = 'sendrecv';
|
|
1402
1399
|
const delay = 1e3;
|
|
1403
1400
|
|
|
1401
|
+
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve(true));
|
|
1402
|
+
MeetingUtil.updateTransceiver = sinon.stub().returns(Promise.resolve(true));
|
|
1404
1403
|
sandbox.stub(meeting, 'canUpdateMedia').returns(true);
|
|
1405
1404
|
sandbox.stub(MeetingUtil, 'getTrack').returns({videoTrack: null});
|
|
1406
1405
|
sandbox.stub(meeting, 'setLocalShareTrack');
|
|
1407
1406
|
sandbox.stub(meeting, 'unsetLocalShareTrack');
|
|
1408
|
-
sandbox.stub(MeetingUtil, 'validateOptions').resolves(true);
|
|
1409
1407
|
sandbox.stub(meeting, 'checkForStopShare').returns(false);
|
|
1410
|
-
|
|
1408
|
+
|
|
1411
1409
|
sandbox.stub(meeting, 'isLocalShareLive').value(false);
|
|
1412
1410
|
sandbox.stub(meeting, 'handleShareTrackEnded');
|
|
1413
1411
|
sandbox.stub(meeting.mediaProperties, 'peerConnection').value({
|
|
@@ -2278,11 +2276,6 @@ describe('plugin-meetings', () => {
|
|
|
2278
2276
|
assert.equal(meeting.requiredCaptcha, null);
|
|
2279
2277
|
assert.calledTwice(TriggerProxy.trigger);
|
|
2280
2278
|
assert.calledWith(TriggerProxy.trigger, meeting, {file: 'meetings', function: 'fetchMeetingInfo'}, 'meeting:meetingInfoAvailable');
|
|
2281
|
-
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
2282
|
-
assert.calledWith(
|
|
2283
|
-
Metrics.sendBehavioralMetric,
|
|
2284
|
-
BEHAVIORAL_METRICS.VERIFY_PASSWORD_SUCCESS,
|
|
2285
|
-
);
|
|
2286
2279
|
});
|
|
2287
2280
|
|
|
2288
2281
|
it('calls meetingInfoProvider with all the right parameters and parses the result when random delay is applied', async () => {
|
|
@@ -2354,11 +2347,6 @@ describe('plugin-meetings', () => {
|
|
|
2354
2347
|
|
|
2355
2348
|
assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, null, null);
|
|
2356
2349
|
|
|
2357
|
-
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
2358
|
-
assert.calledWith(
|
|
2359
|
-
Metrics.sendBehavioralMetric,
|
|
2360
|
-
BEHAVIORAL_METRICS.VERIFY_PASSWORD_ERROR,
|
|
2361
|
-
);
|
|
2362
2350
|
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
2363
2351
|
assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
|
|
2364
2352
|
assert.equal(meeting.requiredCaptcha, null);
|
|
@@ -2379,11 +2367,7 @@ describe('plugin-meetings', () => {
|
|
|
2379
2367
|
|
|
2380
2368
|
assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', null);
|
|
2381
2369
|
|
|
2382
|
-
|
|
2383
|
-
assert.calledWith(
|
|
2384
|
-
Metrics.sendBehavioralMetric,
|
|
2385
|
-
BEHAVIORAL_METRICS.VERIFY_CAPTCHA_ERROR,
|
|
2386
|
-
);
|
|
2370
|
+
|
|
2387
2371
|
assert.deepEqual(meeting.meetingInfo, {});
|
|
2388
2372
|
assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
|
|
2389
2373
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
|
|
@@ -2537,6 +2521,11 @@ describe('plugin-meetings', () => {
|
|
|
2537
2521
|
meeting.fetchMeetingInfo = sinon.stub().resolves();
|
|
2538
2522
|
const result = await meeting.verifyPassword('password', 'captcha id');
|
|
2539
2523
|
|
|
2524
|
+
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
2525
|
+
assert.calledWith(
|
|
2526
|
+
Metrics.sendBehavioralMetric,
|
|
2527
|
+
BEHAVIORAL_METRICS.VERIFY_PASSWORD_SUCCESS,
|
|
2528
|
+
);
|
|
2540
2529
|
assert.equal(result.isPasswordValid, true);
|
|
2541
2530
|
assert.equal(result.requiredCaptcha, null);
|
|
2542
2531
|
assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.NONE);
|
|
@@ -2573,6 +2562,7 @@ describe('plugin-meetings', () => {
|
|
|
2573
2562
|
assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA);
|
|
2574
2563
|
});
|
|
2575
2564
|
});
|
|
2565
|
+
|
|
2576
2566
|
describe('#mediaNegotiatedEvent', () => {
|
|
2577
2567
|
it('should have #mediaNegotiatedEvent', () => {
|
|
2578
2568
|
assert.exists(meeting.mediaNegotiatedEvent);
|
|
@@ -2650,6 +2640,222 @@ describe('plugin-meetings', () => {
|
|
|
2650
2640
|
assert.calledOnce(meeting?.roap?.stop);
|
|
2651
2641
|
});
|
|
2652
2642
|
});
|
|
2643
|
+
|
|
2644
|
+
describe('#moveTo', () => {
|
|
2645
|
+
let sandbox;
|
|
2646
|
+
|
|
2647
|
+
beforeEach(() => {
|
|
2648
|
+
sandbox = sinon.createSandbox();
|
|
2649
|
+
sandbox.stub(meeting, 'closeLocalStream');
|
|
2650
|
+
sandbox.stub(meeting, 'closeLocalShare');
|
|
2651
|
+
|
|
2652
|
+
sandbox.stub(meeting.mediaProperties, 'setMediaDirection');
|
|
2653
|
+
sandbox.stub(meeting.mediaProperties, 'unsetMediaTracks');
|
|
2654
|
+
|
|
2655
|
+
sandbox.stub(meeting.reconnectionManager, 'reconnectMedia').returns(Promise.resolve());
|
|
2656
|
+
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(MeetingUtil.parseLocusJoin({body: {locus, mediaConnections: []}})));
|
|
2657
|
+
});
|
|
2658
|
+
|
|
2659
|
+
afterEach(() => {
|
|
2660
|
+
sandbox.restore();
|
|
2661
|
+
sandbox = null;
|
|
2662
|
+
});
|
|
2663
|
+
|
|
2664
|
+
it('should throw an error if resourceId not passed', async () => {
|
|
2665
|
+
try {
|
|
2666
|
+
await meeting.moveTo();
|
|
2667
|
+
}
|
|
2668
|
+
catch (err) {
|
|
2669
|
+
assert.instanceOf(err, ParameterError);
|
|
2670
|
+
assert.equal(err.sdkMessage, 'Cannot move call without a resourceId.');
|
|
2671
|
+
}
|
|
2672
|
+
});
|
|
2673
|
+
|
|
2674
|
+
it('should postEvent on moveTo ', async () => {
|
|
2675
|
+
await meeting.moveTo('resourceId');
|
|
2676
|
+
assert.calledWithMatch(Metrics.postEvent, {
|
|
2677
|
+
event: eventType.MEDIA_CAPABILITIES,
|
|
2678
|
+
data: {
|
|
2679
|
+
mediaCapabilities: {
|
|
2680
|
+
rx: {
|
|
2681
|
+
audio: false,
|
|
2682
|
+
share: true,
|
|
2683
|
+
share_audio: false,
|
|
2684
|
+
video: false,
|
|
2685
|
+
whiteboard: false
|
|
2686
|
+
},
|
|
2687
|
+
tx: {
|
|
2688
|
+
audio: false,
|
|
2689
|
+
share: false,
|
|
2690
|
+
share_audio: false,
|
|
2691
|
+
video: false,
|
|
2692
|
+
whiteboard: false
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
});
|
|
2697
|
+
assert.calledWithMatch(Metrics.postEvent, {event: eventType.MOVE_MEDIA});
|
|
2698
|
+
});
|
|
2699
|
+
|
|
2700
|
+
it('should call `MeetingUtil.joinMeetingOptions` with resourceId', async () => {
|
|
2701
|
+
sinon.spy(MeetingUtil, 'joinMeetingOptions');
|
|
2702
|
+
await meeting.moveTo('resourceId');
|
|
2703
|
+
|
|
2704
|
+
assert.calledWith(MeetingUtil.joinMeetingOptions, meeting, {resourceId: 'resourceId', moveToResource: true});
|
|
2705
|
+
});
|
|
2706
|
+
|
|
2707
|
+
it('should reconnectMedia after DX joins after moveTo', async () => {
|
|
2708
|
+
await meeting.moveTo('resourceId');
|
|
2709
|
+
|
|
2710
|
+
|
|
2711
|
+
await meeting.locusInfo.emitScoped(
|
|
2712
|
+
{
|
|
2713
|
+
file: 'locus-info',
|
|
2714
|
+
function: 'updateSelf'
|
|
2715
|
+
},
|
|
2716
|
+
'SELF_OBSERVING'
|
|
2717
|
+
);
|
|
2718
|
+
|
|
2719
|
+
// beacuse we are calling callback so we need to wait
|
|
2720
|
+
|
|
2721
|
+
assert.called(meeting.closeLocalStream);
|
|
2722
|
+
assert.called(meeting.closeLocalShare);
|
|
2723
|
+
|
|
2724
|
+
// give queued Promise callbacks a chance to run
|
|
2725
|
+
await Promise.resolve();
|
|
2726
|
+
|
|
2727
|
+
assert.called(meeting.mediaProperties.setMediaDirection);
|
|
2728
|
+
assert.called(meeting.mediaProperties.unsetMediaTracks);
|
|
2729
|
+
|
|
2730
|
+
assert.calledWith(meeting.reconnectionManager.reconnectMedia,
|
|
2731
|
+
{
|
|
2732
|
+
mediaDirection: {
|
|
2733
|
+
sendVideo: false,
|
|
2734
|
+
receiveVideo: false,
|
|
2735
|
+
sendAudio: false,
|
|
2736
|
+
receiveAudio: false,
|
|
2737
|
+
sendShare: false,
|
|
2738
|
+
receiveShare: true
|
|
2739
|
+
}
|
|
2740
|
+
});
|
|
2741
|
+
});
|
|
2742
|
+
|
|
2743
|
+
it('should throw an error if moveTo call fails', async () => {
|
|
2744
|
+
MeetingUtil.joinMeeting = sinon.stub().returns(Promise.reject());
|
|
2745
|
+
try {
|
|
2746
|
+
await meeting.moveTo('resourceId');
|
|
2747
|
+
}
|
|
2748
|
+
catch {
|
|
2749
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
2750
|
+
assert.calledWith(
|
|
2751
|
+
Metrics.sendBehavioralMetric,
|
|
2752
|
+
BEHAVIORAL_METRICS.MOVE_TO_FAILURE,
|
|
2753
|
+
{
|
|
2754
|
+
correlation_id: meeting.correlationId,
|
|
2755
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
2756
|
+
reason: sinon.match.any,
|
|
2757
|
+
stack: sinon.match.any
|
|
2758
|
+
}
|
|
2759
|
+
);
|
|
2760
|
+
}
|
|
2761
|
+
Metrics.sendBehavioralMetric.reset();
|
|
2762
|
+
meeting.reconnectionManager.reconnectMedia = sinon.stub().returns(Promise.reject());
|
|
2763
|
+
try {
|
|
2764
|
+
await meeting.moveTo('resourceId');
|
|
2765
|
+
|
|
2766
|
+
await meeting.locusInfo.emitScoped(
|
|
2767
|
+
{
|
|
2768
|
+
file: 'locus-info',
|
|
2769
|
+
function: 'updateSelf'
|
|
2770
|
+
},
|
|
2771
|
+
'SELF_OBSERVING'
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2774
|
+
catch {
|
|
2775
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
2776
|
+
assert.calledWith(
|
|
2777
|
+
Metrics.sendBehavioralMetric,
|
|
2778
|
+
BEHAVIORAL_METRICS.MOVE_TO_FAILURE,
|
|
2779
|
+
{
|
|
2780
|
+
correlation_id: meeting.correlationId,
|
|
2781
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
2782
|
+
reason: sinon.match.any,
|
|
2783
|
+
stack: sinon.match.any
|
|
2784
|
+
}
|
|
2785
|
+
);
|
|
2786
|
+
}
|
|
2787
|
+
});
|
|
2788
|
+
});
|
|
2789
|
+
|
|
2790
|
+
describe('#moveFrom', () => {
|
|
2791
|
+
let sandbox;
|
|
2792
|
+
|
|
2793
|
+
beforeEach(() => {
|
|
2794
|
+
sandbox = sinon.createSandbox();
|
|
2795
|
+
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(MeetingUtil.parseLocusJoin({body: {locus, mediaConnections: []}})));
|
|
2796
|
+
sandbox.stub(MeetingUtil, 'leaveMeeting').returns(Promise.resolve());
|
|
2797
|
+
});
|
|
2798
|
+
|
|
2799
|
+
afterEach(() => {
|
|
2800
|
+
sandbox.restore();
|
|
2801
|
+
sandbox = null;
|
|
2802
|
+
});
|
|
2803
|
+
|
|
2804
|
+
it('should throw an error if resourceId not passed', async () => {
|
|
2805
|
+
try {
|
|
2806
|
+
await meeting.moveFrom();
|
|
2807
|
+
}
|
|
2808
|
+
catch (err) {
|
|
2809
|
+
assert.instanceOf(err, ParameterError);
|
|
2810
|
+
|
|
2811
|
+
assert.equal(err.sdkMessage, 'Cannot move call without a resourceId.');
|
|
2812
|
+
}
|
|
2813
|
+
});
|
|
2814
|
+
|
|
2815
|
+
it('should postEvent on moveFrom ', async () => {
|
|
2816
|
+
await meeting.moveFrom('resourceId');
|
|
2817
|
+
|
|
2818
|
+
assert.calledWithMatch(Metrics.postEvent, {event: eventType.MOVE_MEDIA});
|
|
2819
|
+
});
|
|
2820
|
+
|
|
2821
|
+
it('should call `MeetingUtil.joinMeetingOptions` with resourceId', async () => {
|
|
2822
|
+
sinon.spy(MeetingUtil, 'joinMeetingOptions');
|
|
2823
|
+
await meeting.moveFrom('resourceId');
|
|
2824
|
+
|
|
2825
|
+
assert.calledWith(MeetingUtil.joinMeetingOptions, meeting);
|
|
2826
|
+
assert.calledWith(MeetingUtil.leaveMeeting, meeting, {
|
|
2827
|
+
resourceId: 'resourceId',
|
|
2828
|
+
correlationId: meeting.correlationId,
|
|
2829
|
+
moveMeeting: true
|
|
2830
|
+
});
|
|
2831
|
+
|
|
2832
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
2833
|
+
assert.calledWith(
|
|
2834
|
+
Metrics.sendBehavioralMetric,
|
|
2835
|
+
BEHAVIORAL_METRICS.MOVE_FROM_SUCCESS,
|
|
2836
|
+
);
|
|
2837
|
+
});
|
|
2838
|
+
|
|
2839
|
+
it('should throw an error if moveFrom call fails', async () => {
|
|
2840
|
+
MeetingUtil.joinMeeting = sinon.stub().returns(Promise.reject());
|
|
2841
|
+
try {
|
|
2842
|
+
await meeting.moveFrom('resourceId');
|
|
2843
|
+
}
|
|
2844
|
+
catch {
|
|
2845
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
2846
|
+
assert.calledWith(
|
|
2847
|
+
Metrics.sendBehavioralMetric,
|
|
2848
|
+
BEHAVIORAL_METRICS.MOVE_FROM_FAILURE,
|
|
2849
|
+
{
|
|
2850
|
+
correlation_id: meeting.correlationId,
|
|
2851
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
2852
|
+
reason: sinon.match.any,
|
|
2853
|
+
stack: sinon.match.any
|
|
2854
|
+
}
|
|
2855
|
+
);
|
|
2856
|
+
}
|
|
2857
|
+
});
|
|
2858
|
+
});
|
|
2653
2859
|
});
|
|
2654
2860
|
|
|
2655
2861
|
describe('Public Event Triggers', () => {
|
|
@@ -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
|
|
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, '
|
|
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',
|