@webex/plugin-meetings 1.161.0 → 2.0.0

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.
@@ -10,6 +10,7 @@ import {assert} from '@webex/test-helper-chai';
10
10
  import {Credentials} from '@webex/webex-core';
11
11
  import Support from '@webex/internal-plugin-support';
12
12
  import MockWebex from '@webex/test-helper-mock-webex';
13
+
13
14
  import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
14
15
  import Meeting from '@webex/plugin-meetings/src/meeting';
15
16
  import Members from '@webex/plugin-meetings/src/members';
@@ -228,6 +229,7 @@ describe('plugin-meetings', () => {
228
229
  assert.isNull(meeting.policy);
229
230
  assert.instanceOf(meeting.meetingRequest, MeetingRequest);
230
231
  assert.instanceOf(meeting.locusInfo, LocusInfo);
232
+ assert.equal(meeting.fetchMeetingInfoTimeoutId, undefined);
231
233
  assert.instanceOf(meeting.mediaProperties, MediaProperties);
232
234
  assert.equal(meeting.passwordStatus, PASSWORD_STATUS.UNKNOWN);
233
235
  assert.equal(meeting.requiredCaptcha, null);
@@ -2118,6 +2120,7 @@ describe('plugin-meetings', () => {
2118
2120
  describe('#fetchMeetingInfo', () => {
2119
2121
  const FAKE_DESTINATION = 'something@somecompany.com';
2120
2122
  const FAKE_TYPE = _SIP_URI_;
2123
+ const FAKE_TIMEOUT_FETCHMEETINGINFO_ID = '123456';
2121
2124
  const FAKE_PASSWORD = '123abc';
2122
2125
  const FAKE_CAPTCHA_CODE = 'a1b2c3XYZ';
2123
2126
  const FAKE_CAPTCHA_ID = '987654321';
@@ -2150,6 +2153,7 @@ describe('plugin-meetings', () => {
2150
2153
  meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
2151
2154
  meeting.destination = FAKE_DESTINATION;
2152
2155
  meeting.destinationType = FAKE_TYPE;
2156
+ meeting.parseMeetingInfo = sinon.stub().returns(undefined);
2153
2157
 
2154
2158
  await meeting.fetchMeetingInfo({
2155
2159
  password: FAKE_PASSWORD, captchaCode: FAKE_CAPTCHA_CODE
@@ -2157,10 +2161,45 @@ describe('plugin-meetings', () => {
2157
2161
 
2158
2162
  assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, FAKE_PASSWORD, {code: FAKE_CAPTCHA_CODE, id: FAKE_CAPTCHA_ID});
2159
2163
 
2164
+ assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO}, FAKE_DESTINATION);
2160
2165
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
2161
2166
  assert.equal(meeting.passwordStatus, PASSWORD_STATUS.NOT_REQUIRED);
2162
2167
  assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
2163
2168
  assert.equal(meeting.requiredCaptcha, null);
2169
+ assert.calledTwice(TriggerProxy.trigger);
2170
+ assert.calledWith(TriggerProxy.trigger, meeting, {file: 'meetings', function: 'fetchMeetingInfo'}, 'meeting:meetingInfoAvailable');
2171
+ });
2172
+
2173
+ it('calls meetingInfoProvider with all the right parameters and parses the result when random delay is applied', async () => {
2174
+ meeting.attrs.meetingInfoProvider = {fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO})};
2175
+ meeting.destination = FAKE_DESTINATION;
2176
+ meeting.destinationType = FAKE_TYPE;
2177
+ meeting.parseMeetingInfo = sinon.stub().returns(undefined);
2178
+ meeting.fetchMeetingInfoTimeoutId = FAKE_TIMEOUT_FETCHMEETINGINFO_ID;
2179
+
2180
+ const clock = sinon.useFakeTimers();
2181
+ const clearTimeoutSpy = sinon.spy(clock, 'clearTimeout');
2182
+
2183
+ await meeting.fetchMeetingInfo({});
2184
+
2185
+ // clear timer
2186
+ assert.calledWith(clearTimeoutSpy, FAKE_TIMEOUT_FETCHMEETINGINFO_ID);
2187
+ clock.restore();
2188
+ assert.isUndefined(meeting.fetchMeetingInfoTimeoutId);
2189
+
2190
+ // meeting info provider
2191
+ assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, null, null);
2192
+
2193
+ // parseMeeting info
2194
+ assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO}, FAKE_DESTINATION);
2195
+
2196
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
2197
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
2198
+ assert.equal(meeting.requiredCaptcha, null);
2199
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.NOT_REQUIRED);
2200
+
2201
+ assert.calledTwice(TriggerProxy.trigger);
2202
+ assert.calledWith(TriggerProxy.trigger, meeting, {file: 'meetings', function: 'fetchMeetingInfo'}, 'meeting:meetingInfoAvailable');
2164
2203
  });
2165
2204
 
2166
2205
  it('fails if captchaCode is provided when captcha not needed', async () => {
@@ -2428,6 +2467,64 @@ describe('plugin-meetings', () => {
2428
2467
  );
2429
2468
  });
2430
2469
  });
2470
+
2471
+ describe('#endMeeting for all', () => {
2472
+ let sandbox;
2473
+
2474
+ it('should have #endMeetingForAll', () => {
2475
+ assert.exists(meeting.endMeetingForAll);
2476
+ });
2477
+
2478
+ it('should reject if meeting is already ended', async () => {
2479
+ await meeting.endMeetingForAll().catch((err) => {
2480
+ assert.instanceOf(err, MeetingNotActiveError);
2481
+ });
2482
+ });
2483
+ beforeEach(() => {
2484
+ sandbox = sinon.createSandbox();
2485
+ meeting.meetingFiniteStateMachine.ring();
2486
+ meeting.meetingFiniteStateMachine.join();
2487
+ meeting.meetingRequest.endMeetingForAll = sinon.stub().returns(Promise.resolve({body: 'test'}));
2488
+ meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
2489
+ meeting.closeLocalStream = sinon.stub().returns(Promise.resolve());
2490
+ meeting.closeLocalShare = sinon.stub().returns(Promise.resolve());
2491
+ meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
2492
+ sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
2493
+ meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
2494
+ meeting.unsetLocalVideoTrack = sinon.stub().returns(true);
2495
+ meeting.unsetLocalShareTrack = sinon.stub().returns(true);
2496
+ meeting.unsetRemoteTracks = sinon.stub();
2497
+ meeting.statsAnalyzer = {stopAnalyzer: sinon.stub()};
2498
+ meeting.unsetRemoteStream = sinon.stub().returns(true);
2499
+ meeting.unsetPeerConnections = sinon.stub().returns(true);
2500
+ meeting.roap.stop = sinon.stub().returns(Promise.resolve());
2501
+ meeting.logger.error = sinon.stub().returns(true);
2502
+
2503
+ // A meeting needs to be joined to end
2504
+ meeting.meetingState = 'ACTIVE';
2505
+ meeting.state = 'JOINED';
2506
+ });
2507
+ afterEach(() => {
2508
+ sandbox.restore();
2509
+ sandbox = null;
2510
+ });
2511
+ it('should end the meeting for all and return promise', async () => {
2512
+ const endMeetingForAll = meeting.endMeetingForAll();
2513
+
2514
+ assert.exists(endMeetingForAll.then);
2515
+ await endMeetingForAll;
2516
+ assert.calledOnce(meeting?.meetingRequest?.endMeetingForAll);
2517
+ assert.calledOnce(meeting?.closeLocalStream);
2518
+ assert.calledOnce(meeting?.closeLocalShare);
2519
+ assert.calledOnce(meeting?.closeRemoteTracks);
2520
+ assert.calledOnce(meeting?.closePeerConnections);
2521
+ assert.calledOnce(meeting?.unsetLocalVideoTrack);
2522
+ assert.calledOnce(meeting?.unsetLocalShareTrack);
2523
+ assert.calledOnce(meeting?.unsetRemoteTracks);
2524
+ assert.calledOnce(meeting?.unsetPeerConnections);
2525
+ assert.calledOnce(meeting?.roap?.stop);
2526
+ });
2527
+ });
2431
2528
  });
2432
2529
 
2433
2530
  describe('Public Event Triggers', () => {
@@ -2879,21 +2976,141 @@ describe('plugin-meetings', () => {
2879
2976
  });
2880
2977
  });
2881
2978
  describe('#parseMeetingInfo', () => {
2882
- it('should parse meeting info, set values, and return null', () => {
2979
+ const checkParseMeetingInfo = (expectedInfoToParse) => {
2980
+ assert.equal(meeting.conversationUrl, expectedInfoToParse.conversationUrl);
2981
+ assert.equal(meeting.locusUrl, expectedInfoToParse.locusUrl);
2982
+ assert.equal(meeting.sipUri, expectedInfoToParse.sipUri);
2983
+ assert.equal(meeting.meetingNumber, expectedInfoToParse.meetingNumber);
2984
+ assert.equal(meeting.meetingJoinUrl, expectedInfoToParse.meetingJoinUrl);
2985
+ assert.equal(meeting.owner, expectedInfoToParse.owner);
2986
+ assert.equal(meeting.permissionToken, expectedInfoToParse.permissionToken);
2987
+ };
2988
+
2989
+ it('should parse meeting info from api return when locus meeting object is not available, set values, and return null', () => {
2883
2990
  meeting.config.experimental = {enableMediaNegotiatedEvent: true};
2991
+ meeting.config.experimental.enableUnifiedMeetings = true;
2992
+ const FAKE_MEETING_INFO = {
2993
+ body: {
2994
+ conversationUrl: uuid1,
2995
+ locusUrl: url1,
2996
+ meetingJoinUrl: url2,
2997
+ meetingNumber: '12345',
2998
+ permissionToken: 'abc',
2999
+ sipMeetingUri: test1,
3000
+ sipUrl: test1,
3001
+ owner: test2
3002
+ }
3003
+ };
2884
3004
 
2885
- meeting.parseMeetingInfo({
3005
+ meeting.parseMeetingInfo(FAKE_MEETING_INFO);
3006
+ const expectedInfoToParse = {
3007
+ conversationUrl: uuid1,
3008
+ locusUrl: url1,
3009
+ sipUri: test1,
3010
+ meetingNumber: '12345',
3011
+ meetingJoinUrl: url2,
3012
+ owner: test2,
3013
+ permissionToken: 'abc'
3014
+ };
3015
+
3016
+ checkParseMeetingInfo(expectedInfoToParse);
3017
+ });
3018
+ it('should parse meeting info from locus meeting object if possible, else from api return, set values, and return null', () => {
3019
+ meeting.config.experimental = {enableMediaNegotiatedEvent: true};
3020
+ meeting.config.experimental.enableUnifiedMeetings = true;
3021
+ const FAKE_LOCUS_MEETING = {
3022
+ conversationUrl: 'locusConvURL',
3023
+ url: 'locusUrl',
3024
+ info: {
3025
+ webExMeetingId: 'locusMeetingId',
3026
+ sipUri: 'locusSipUri',
3027
+ owner: 'locusOwner'
3028
+ }
3029
+ };
3030
+ const FAKE_MEETING_INFO = {
2886
3031
  body: {
2887
3032
  conversationUrl: uuid1,
2888
3033
  locusUrl: url1,
3034
+ meetingJoinUrl: url2,
3035
+ meetingNumber: '12345',
3036
+ permissionToken: 'abc',
2889
3037
  sipMeetingUri: test1,
3038
+ sipUrl: test1,
2890
3039
  owner: test2
2891
3040
  }
2892
- });
2893
- assert.equal(meeting.conversationUrl, uuid1);
2894
- assert.equal(meeting.locusUrl, url1);
2895
- assert.equal(meeting.sipUri, test1);
2896
- assert.equal(meeting.owner, test2);
3041
+ };
3042
+
3043
+ meeting.parseMeetingInfo(FAKE_MEETING_INFO, FAKE_LOCUS_MEETING);
3044
+ const expectedInfoToParse = {
3045
+ conversationUrl: 'locusConvURL',
3046
+ locusUrl: 'locusUrl',
3047
+ sipUri: 'locusSipUri',
3048
+ meetingNumber: 'locusMeetingId',
3049
+ meetingJoinUrl: url2,
3050
+ owner: 'locusOwner',
3051
+ permissionToken: 'abc'
3052
+ };
3053
+
3054
+ checkParseMeetingInfo(expectedInfoToParse);
3055
+ });
3056
+ it('should parse meeting info from api return, set values, and return null', () => {
3057
+ meeting.config.experimental = {enableMediaNegotiatedEvent: true};
3058
+ meeting.config.experimental.enableUnifiedMeetings = true;
3059
+ const FAKE_MEETING_INFO = {
3060
+ body: {
3061
+ conversationUrl: uuid1,
3062
+ locusUrl: url1,
3063
+ meetingJoinUrl: url2,
3064
+ meetingNumber: '12345',
3065
+ permissionToken: 'abc',
3066
+ sipMeetingUri: test1,
3067
+ sipUrl: test1,
3068
+ owner: test2
3069
+ }
3070
+ };
3071
+
3072
+ meeting.parseMeetingInfo(FAKE_MEETING_INFO);
3073
+ const expectedInfoToParse = {
3074
+ conversationUrl: uuid1,
3075
+ locusUrl: url1,
3076
+ sipUri: test1,
3077
+ meetingNumber: '12345',
3078
+ meetingJoinUrl: url2,
3079
+ owner: test2,
3080
+ permissionToken: 'abc'
3081
+ };
3082
+
3083
+ checkParseMeetingInfo(expectedInfoToParse);
3084
+ });
3085
+ it('should parse meeting info, set values, and return null when destination is a string', () => {
3086
+ meeting.config.experimental = {enableMediaNegotiatedEvent: true};
3087
+ meeting.config.experimental.enableUnifiedMeetings = true;
3088
+ const FAKE_STRING_DESTINATION = 'sipUrl';
3089
+ const FAKE_MEETING_INFO = {
3090
+ body: {
3091
+ conversationUrl: uuid1,
3092
+ locusUrl: url1,
3093
+ meetingJoinUrl: url2,
3094
+ meetingNumber: '12345',
3095
+ permissionToken: 'abc',
3096
+ sipMeetingUri: test1,
3097
+ sipUrl: test1,
3098
+ owner: test2
3099
+ }
3100
+ };
3101
+
3102
+ meeting.parseMeetingInfo(FAKE_MEETING_INFO, FAKE_STRING_DESTINATION);
3103
+ const expectedInfoToParse = {
3104
+ conversationUrl: uuid1,
3105
+ locusUrl: url1,
3106
+ sipUri: test1,
3107
+ meetingNumber: '12345',
3108
+ meetingJoinUrl: url2,
3109
+ owner: test2,
3110
+ permissionToken: 'abc'
3111
+ };
3112
+
3113
+ checkParseMeetingInfo(expectedInfoToParse);
2897
3114
  });
2898
3115
  });
2899
3116
  describe('#parseLocus', () => {
@@ -249,5 +249,19 @@ describe('plugin-meetings', () => {
249
249
  assert.equal(requestParams.body.device.deviceType, 'PROVISIONAL');
250
250
  });
251
251
  });
252
+
253
+ describe('#endMeetingForAll', () => {
254
+ it('sends request to endMeetingForAll', async () => {
255
+ const locusUrl = 'locusURL';
256
+
257
+ await meetingsRequest.endMeetingForAll({
258
+ locusUrl,
259
+ });
260
+ const requestParams = meetingsRequest.request.getCall(0).args[0];
261
+
262
+ assert.equal(requestParams.method, 'POST');
263
+ assert.equal(requestParams.uri, `${locusUrl}/end`);
264
+ });
265
+ });
252
266
  });
253
267
  });
@@ -5,6 +5,11 @@ import 'jsdom-global/register';
5
5
 
6
6
  import Device from '@webex/internal-plugin-device';
7
7
  import Mercury from '@webex/internal-plugin-mercury';
8
+ import {assert} from '@webex/test-helper-chai';
9
+ import MockWebex from '@webex/test-helper-mock-webex';
10
+ import sinon from 'sinon';
11
+ import uuid from 'uuid';
12
+
8
13
  import StaticConfig from '@webex/plugin-meetings/src/common/config';
9
14
  import TriggerProxy from '@webex/plugin-meetings/src/common/events/trigger-proxy';
10
15
  import LoggerConfig from '@webex/plugin-meetings/src/common/logs/logger-config';
@@ -16,11 +21,8 @@ import MeetingCollection from '@webex/plugin-meetings/src/meetings/collection';
16
21
  import MeetingsUtil from '@webex/plugin-meetings/src/meetings/util';
17
22
  import PersonalMeetingRoom from '@webex/plugin-meetings/src/personal-meeting-room';
18
23
  import Reachability from '@webex/plugin-meetings/src/reachability';
19
- import {assert} from '@webex/test-helper-chai';
20
- import MockWebex from '@webex/test-helper-mock-webex';
21
- import sinon from 'sinon';
22
- import uuid from 'uuid';
23
24
 
25
+ import testUtils from '../../../utils/testUtils';
24
26
  import {
25
27
  LOCUSEVENT,
26
28
  OFFLINE,
@@ -489,12 +491,13 @@ describe('plugin-meetings', () => {
489
491
  });
490
492
 
491
493
  it('calls createMeeting and returns its promise', async () => {
492
- const create = webex.meetings.create(test1, test2);
494
+ const FAKE_USE_RANDOM_DELAY = true;
495
+ const create = webex.meetings.create(test1, test2, FAKE_USE_RANDOM_DELAY);
493
496
 
494
497
  assert.exists(create.then);
495
498
  await create;
496
499
  assert.calledOnce(webex.meetings.createMeeting);
497
- assert.calledWith(webex.meetings.createMeeting, test1, test2);
500
+ assert.calledWith(webex.meetings.createMeeting, test1, test2, FAKE_USE_RANDOM_DELAY);
498
501
  });
499
502
 
500
503
  it('creates a new meeting when a scheduled meeting exists in the conversation', async () => {
@@ -582,7 +585,7 @@ describe('plugin-meetings', () => {
582
585
  assert.calledOnce(webex.meetings.handleLocusEvent);
583
586
  assert.calledWith(webex.meetings.handleLocusEvent, {
584
587
  eventType: test1
585
- });
588
+ }, true);
586
589
  });
587
590
  });
588
591
  describe('#handleLocusEvent', () => {
@@ -753,43 +756,208 @@ describe('plugin-meetings', () => {
753
756
  TriggerProxy.trigger.reset();
754
757
  });
755
758
  describe('successful MeetingInfo.#fetchMeetingInfo', () => {
759
+ let clock, setTimeoutSpy, fakeMeetingStartTimeString, FAKE_TIME_TO_START;
760
+
756
761
  beforeEach(() => {
762
+ clock = sinon.useFakeTimers();
763
+ setTimeoutSpy = sinon.spy(clock, 'setTimeout');
757
764
  webex.meetings.meetingInfo.fetchMeetingInfo = sinon.stub().returns(Promise.resolve({
758
765
  body: {
759
766
  permissionToken: 'PT', meetingJoinUrl: 'meetingJoinUrl'
760
767
  }
761
768
  }));
769
+ const nowTimeStamp = Date.now();
770
+
771
+ FAKE_TIME_TO_START = 0.1 * 60 * 1000;
772
+ const fakeMeetingStartTimeStamp = nowTimeStamp + FAKE_TIME_TO_START;
773
+ const fakeMeetingStartTimeDate = new Date(fakeMeetingStartTimeStamp);
774
+
775
+ fakeMeetingStartTimeString = fakeMeetingStartTimeDate.toISOString();
776
+ });
777
+
778
+ afterEach(() => {
779
+ clock.restore();
762
780
  });
763
- it('creates the meeting from a successful meeting info fetch promise testing', async () => {
764
- const meeting = await webex.meetings.createMeeting('test destination', 'test type');
765
781
 
782
+ const checkCreateWithoutDelay = (meeting, destination, type, expectedMeetingData = {}) => {
766
783
  assert.calledOnce(webex.meetings.meetingInfo.fetchMeetingInfo);
767
784
  assert.calledOnce(MeetingsUtil.getMeetingAddedType);
768
- assert.calledTwice(TriggerProxy.trigger);
769
- assert.calledWith(webex.meetings.meetingInfo.fetchMeetingInfo, 'test destination', 'test type');
785
+ assert.notCalled(setTimeoutSpy);
786
+ assert.calledThrice(TriggerProxy.trigger);
787
+ assert.calledWith(webex.meetings.meetingInfo.fetchMeetingInfo, destination, type);
770
788
  assert.calledWith(MeetingsUtil.getMeetingAddedType, 'test type');
771
- assert.equal(meeting.permissionToken, 'PT');
772
- assert.equal(meeting.meetingJoinUrl, 'meetingJoinUrl');
773
- assert.equal(meeting.destination, 'test destination');
774
- assert.equal(meeting.destinationType, 'test type');
789
+
790
+ if (expectedMeetingData.permissionToken) {
791
+ assert.equal(meeting.permissionToken, expectedMeetingData.permissionToken);
792
+ }
793
+ if (expectedMeetingData.meetingJoinUrl) {
794
+ assert.equal(meeting.meetingJoinUrl, expectedMeetingData.meetingJoinUrl);
795
+ }
796
+ assert.equal(meeting.destination, destination);
797
+ assert.equal(meeting.destinationType, type);
798
+ assert.calledWith(TriggerProxy.trigger, sinon.match.instanceOf(Meetings), {
799
+ file: 'meetings', function: 'createMeeting'
800
+ }, 'meeting:added', {
801
+ meeting: sinon.match.instanceOf(Meeting), type: 'test meeting added type'
802
+ });
803
+ assert.calledWith(TriggerProxy.trigger, meeting, {file: 'meetings', function: 'fetchMeetingInfo'}, 'meeting:meetingInfoAvailable');
804
+ };
805
+
806
+ it('creates the meeting from a successful meeting info fetch promise testing', async () => {
807
+ const meeting = await webex.meetings.createMeeting('test destination', 'test type');
808
+
809
+ const expectedMeetingData = {
810
+ permissionToken: 'PT',
811
+ meetingJoinUrl: 'meetingJoinUrl'
812
+ };
813
+
814
+ checkCreateWithoutDelay(meeting, 'test destination', 'test type', expectedMeetingData);
775
815
  });
776
816
 
777
817
  it('creates the meeting from a successful meeting info fetch meeting resolve testing', async () => {
778
818
  const meeting = await webex.meetings.createMeeting('test destination', 'test type');
819
+ const expectedMeetingData = {
820
+ permissionToken: 'PT',
821
+ meetingJoinUrl: 'meetingJoinUrl'
822
+ };
779
823
 
780
824
  assert.instanceOf(meeting, Meeting, 'createMeeting should eventually resolve to a Meeting Object');
781
- assert.calledOnce(webex.meetings.meetingInfo.fetchMeetingInfo);
782
- assert.calledOnce(MeetingsUtil.getMeetingAddedType);
783
- assert.calledTwice(TriggerProxy.trigger);
784
- assert.calledWith(webex.meetings.meetingInfo.fetchMeetingInfo, 'test destination', 'test type');
825
+ checkCreateWithoutDelay(meeting, 'test destination', 'test type', expectedMeetingData);
826
+ });
827
+
828
+ it('creates the meeting from a successful meeting info fetch with random delay', async () => {
829
+ const FAKE_LOCUS_MEETING = {
830
+ conversationUrl: 'locusConvURL',
831
+ url: 'locusUrl',
832
+ info: {
833
+ webExMeetingId: 'locusMeetingId',
834
+ sipUri: 'locusSipUri',
835
+ owner: 'locusOwner'
836
+ },
837
+ meeting: {
838
+ startTime: fakeMeetingStartTimeString
839
+ },
840
+ fullState: {
841
+ active: false
842
+ }
843
+ };
844
+
845
+ const meeting = await webex.meetings.createMeeting(FAKE_LOCUS_MEETING, 'test type', true);
846
+
847
+ assert.instanceOf(meeting, Meeting, 'createMeeting should eventually resolve to a Meeting Object');
848
+ assert.notCalled(webex.meetings.meetingInfo.fetchMeetingInfo);
849
+ assert.calledOnce(setTimeoutSpy);
850
+
851
+ // Parse meeting info with locus object
852
+ assert.equal(meeting.conversationUrl, 'locusConvURL');
853
+ assert.equal(meeting.locusUrl, 'locusUrl');
854
+ assert.equal(meeting.sipUri, 'locusSipUri');
855
+ assert.equal(meeting.meetingNumber, 'locusMeetingId');
856
+ assert.isUndefined(meeting.meetingJoinUrl);
857
+ assert.equal(meeting.owner, 'locusOwner');
858
+ assert.isUndefined(meeting.permissionToken);
859
+
860
+ // Add meeting and send trigger
785
861
  assert.calledWith(MeetingsUtil.getMeetingAddedType, 'test type');
862
+ assert.calledTwice(TriggerProxy.trigger);
786
863
  assert.calledWith(TriggerProxy.trigger, sinon.match.instanceOf(Meetings), {
787
864
  file: 'meetings', function: 'createMeeting'
788
865
  }, 'meeting:added', {
789
866
  meeting: sinon.match.instanceOf(Meeting), type: 'test meeting added type'
790
867
  });
868
+
869
+ // When timer expires
870
+ clock.tick(FAKE_TIME_TO_START);
871
+ assert.calledWith(webex.meetings.meetingInfo.fetchMeetingInfo, FAKE_LOCUS_MEETING, 'test type');
872
+
873
+ // Parse meeting info is called again with new meeting info
874
+ await testUtils.flushPromises();
875
+ assert.equal(meeting.conversationUrl, 'locusConvURL');
876
+ assert.equal(meeting.locusUrl, 'locusUrl');
877
+ assert.equal(meeting.sipUri, 'locusSipUri');
878
+ assert.equal(meeting.meetingNumber, 'locusMeetingId');
879
+ assert.equal(meeting.meetingJoinUrl, 'meetingJoinUrl');
880
+ assert.equal(meeting.owner, 'locusOwner');
881
+ assert.equal(meeting.permissionToken, 'PT');
882
+
883
+ assert.calledWith(TriggerProxy.trigger, meeting, {file: 'meetings', function: 'fetchMeetingInfo'}, 'meeting:meetingInfoAvailable');
884
+ });
885
+
886
+ it('creates the meeting from a successful meeting info fetch that has no random delay because it is active', async () => {
887
+ const FAKE_LOCUS_MEETING = {
888
+ conversationUrl: 'locusConvURL',
889
+ url: 'locusUrl',
890
+ info: {
891
+ webExMeetingId: 'locusMeetingId',
892
+ sipUri: 'locusSipUri',
893
+ owner: 'locusOwner'
894
+ },
895
+ meeting: {
896
+ startTime: fakeMeetingStartTimeString
897
+ },
898
+ fullState: {
899
+ active: true
900
+ }
901
+ };
902
+
903
+ const meeting = await webex.meetings.createMeeting(FAKE_LOCUS_MEETING, 'test type', true);
904
+
905
+ assert.instanceOf(meeting, Meeting, 'createMeeting should eventually resolve to a Meeting Object');
906
+ checkCreateWithoutDelay(meeting, FAKE_LOCUS_MEETING, 'test type');
907
+ });
908
+
909
+ it('creates the meeting from a successful meeting info fetch that has no random delay because meeting start time is in the past', async () => {
910
+ const FAKE_LOCUS_MEETING = {
911
+ conversationUrl: 'locusConvURL',
912
+ url: 'locusUrl',
913
+ info: {
914
+ webExMeetingId: 'locusMeetingId',
915
+ sipUri: 'locusSipUri',
916
+ owner: 'locusOwner'
917
+ },
918
+ meeting: {
919
+ startTime: fakeMeetingStartTimeString - (1 * 60 * 60 * 1000)
920
+ },
921
+ fullState: {
922
+ active: false
923
+ }
924
+ };
925
+
926
+ const meeting = await webex.meetings.createMeeting(FAKE_LOCUS_MEETING, 'test type', true);
927
+
928
+ assert.instanceOf(meeting, Meeting, 'createMeeting should eventually resolve to a Meeting Object');
929
+ checkCreateWithoutDelay(meeting, FAKE_LOCUS_MEETING, 'test type');
930
+ });
931
+
932
+ it('creates the meeting from a successful meeting info fetch that has no random delay because enableUnifiedMeetings is disabled', async () => {
933
+ Object.assign(webex.meetings.config, {
934
+ experimental: {
935
+ enableUnifiedMeetings: false
936
+ }
937
+ });
938
+ const FAKE_LOCUS_MEETING = {
939
+ conversationUrl: 'locusConvURL',
940
+ url: 'locusUrl',
941
+ info: {
942
+ webExMeetingId: 'locusMeetingId',
943
+ sipUri: 'locusSipUri',
944
+ owner: 'locusOwner'
945
+ },
946
+ meeting: {
947
+ startTime: fakeMeetingStartTimeString
948
+ },
949
+ fullState: {
950
+ active: false
951
+ }
952
+ };
953
+
954
+ const meeting = await webex.meetings.createMeeting(FAKE_LOCUS_MEETING, 'test type', true);
955
+
956
+ assert.instanceOf(meeting, Meeting, 'createMeeting should eventually resolve to a Meeting Object');
957
+ checkCreateWithoutDelay(meeting, FAKE_LOCUS_MEETING, 'test type');
791
958
  });
792
959
  });
960
+
793
961
  describe('rejected MeetingInfo.#fetchMeetingInfo', () => {
794
962
  beforeEach(() => {
795
963
  console.error = sinon.stub().returns(false);