@webex/plugin-meetings 2.10.0 → 2.12.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "contributors": [
@@ -24,19 +24,19 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@babel/runtime-corejs2": "^7.14.8",
27
- "@webex/webex-core": "2.10.0",
28
- "@webex/internal-plugin-mercury": "2.10.0",
29
- "@webex/internal-plugin-conversation": "2.10.0",
27
+ "@webex/webex-core": "2.12.0",
28
+ "@webex/internal-plugin-mercury": "2.12.0",
29
+ "@webex/internal-plugin-conversation": "2.12.0",
30
30
  "webrtc-adapter": "^7.7.0",
31
31
  "lodash": "^4.17.21",
32
32
  "uuid": "^3.3.2",
33
33
  "global": "^4.4.0",
34
34
  "ip-anonymize": "^0.1.0",
35
- "@webex/common": "2.10.0",
35
+ "@webex/common": "2.12.0",
36
36
  "bowser": "^2.11.0",
37
37
  "sdp-transform": "^2.12.0",
38
38
  "readable-stream": "^3.6.0",
39
- "@webex/common-timers": "2.10.0",
39
+ "@webex/common-timers": "2.12.0",
40
40
  "btoa": "^1.2.1",
41
41
  "@webex/internal-media-core": "^0.0.4-beta",
42
42
  "javascript-state-machine": "^3.1.0",
@@ -1023,7 +1023,13 @@ export default class Meeting extends StatelessWebexPlugin {
1023
1023
  return this.fetchMeetingInfo({
1024
1024
  password, captchaCode
1025
1025
  })
1026
- .then(() => ({isPasswordValid: true, requiredCaptcha: null, failureReason: MEETING_INFO_FAILURE_REASON.NONE}))
1026
+ .then(() => {
1027
+ Metrics.sendBehavioralMetric(
1028
+ BEHAVIORAL_METRICS.VERIFY_PASSWORD_SUCCESS
1029
+ );
1030
+
1031
+ return {isPasswordValid: true, requiredCaptcha: null, failureReason: MEETING_INFO_FAILURE_REASON.NONE};
1032
+ })
1027
1033
  .catch((error) => {
1028
1034
  if (error instanceof PasswordError || error instanceof CaptchaError) {
1029
1035
  return {
@@ -1,5 +1,7 @@
1
1
 
2
2
  import {HTTP_VERBS, _CONVERSATION_URL_} from '../constants';
3
+ import Metrics from '../metrics';
4
+ import BEHAVIORAL_METRICS from '../metrics/constants';
3
5
 
4
6
  import MeetingInfoUtil from './utilv2';
5
7
 
@@ -141,6 +143,10 @@ export default class MeetingInfoV2 {
141
143
  const uri = this.webex.meetings.preferredWebexSite ?
142
144
  `https://${this.webex.meetings.preferredWebexSite}/wbxappapi/v2/meetings/spaceInstant` : '';
143
145
 
146
+ Metrics.sendBehavioralMetric(
147
+ BEHAVIORAL_METRICS.ADHOC_MEETING_SUCCESS
148
+ );
149
+
144
150
  return this.webex.request({
145
151
  method: HTTP_VERBS.POST,
146
152
  uri,
@@ -149,6 +155,16 @@ export default class MeetingInfoV2 {
149
155
  .catch((err) => {
150
156
  throw new MeetingInfoV2AdhocMeetingError(err.body?.code, err.body?.message);
151
157
  });
158
+ })
159
+ .catch((err) => {
160
+ Metrics.sendBehavioralMetric(
161
+ BEHAVIORAL_METRICS.ADHOC_MEETING_FAILURE,
162
+ {
163
+ reason: err.message,
164
+ stack: err.stack
165
+ }
166
+ );
167
+ throw new MeetingInfoV2AdhocMeetingError(err.body?.code, err.body?.message);
152
168
  });
153
169
  }
154
170
 
@@ -189,11 +205,32 @@ export default class MeetingInfoV2 {
189
205
  if (directURI) options.directURI = directURI;
190
206
 
191
207
  return this.webex.request(options)
208
+ .then(() => {
209
+ Metrics.sendBehavioralMetric(
210
+ BEHAVIORAL_METRICS.FETCH_MEETING_INFO_V1_SUCCESS
211
+ );
212
+ })
192
213
  .catch((err) => {
193
214
  if (err?.statusCode === 403) {
215
+ Metrics.sendBehavioralMetric(
216
+ BEHAVIORAL_METRICS.VERIFY_PASSWORD_ERROR,
217
+ {
218
+ reason: err.message,
219
+ stack: err.stack
220
+ }
221
+ );
222
+
194
223
  throw new MeetingInfoV2PasswordError(err.body?.code, err.body?.data?.meetingInfo);
195
224
  }
196
225
  if (err?.statusCode === 423) {
226
+ Metrics.sendBehavioralMetric(
227
+ BEHAVIORAL_METRICS.VERIFY_CAPTCHA_ERROR,
228
+ {
229
+ reason: err.message,
230
+ stack: err.stack
231
+ }
232
+ );
233
+
197
234
  throw new MeetingInfoV2CaptchaError(err.body?.code, {
198
235
  captchaId: err.body.captchaID,
199
236
  verificationImageURL: err.body.verificationImageURL,
@@ -201,6 +238,14 @@ export default class MeetingInfoV2 {
201
238
  refreshURL: err.body.refreshURL
202
239
  });
203
240
  }
241
+
242
+ Metrics.sendBehavioralMetric(
243
+ BEHAVIORAL_METRICS.FETCH_MEETING_INFO_V1_FAILURE,
244
+ {
245
+ reason: err.message,
246
+ stack: err.stack
247
+ }
248
+ );
204
249
  throw err;
205
250
  });
206
251
  }
@@ -756,9 +756,29 @@ export default class Members extends StatelessWebexPlugin {
756
756
  }
757
757
  const options = MembersUtil.generateRaiseHandMemberOptions(memberId, raise, this.locusUrl);
758
758
 
759
- return this.membersRequest.raiseLowerHandMember(options);
759
+ return this.membersRequest.raiseOrLowerHandMember(options);
760
760
  }
761
761
 
762
+ /**
763
+ * Lower all hands of members in a meeting
764
+ * @param {String} requestingMemberId - id of the participant which requested the lower all hands
765
+ * @returns {Promise}
766
+ * @public
767
+ * @memberof Members
768
+ */
769
+ lowerAllHands(requestingMemberId) {
770
+ if (!this.locusUrl) {
771
+ return Promise.reject(new ParameterError('The associated locus url for this meetings members object must be defined.'));
772
+ }
773
+ if (!requestingMemberId) {
774
+ return Promise.reject(new ParameterError('The requestingMemberId must be defined to lower all hands in a meeting.'));
775
+ }
776
+ const options = MembersUtil.generateLowerAllHandsMemberOptions(requestingMemberId, this.locusUrl);
777
+
778
+ return this.membersRequest.lowerAllHandsMember(options);
779
+ }
780
+
781
+
762
782
  /**
763
783
  * Transfers the host to another member
764
784
  * @param {String} memberId
@@ -74,6 +74,16 @@ export default class MembersRequest extends StatelessWebexPlugin {
74
74
  return this.request(requestParams);
75
75
  }
76
76
 
77
+ lowerAllHandsMember(options) {
78
+ if (!options || !options.locusUrl || !options.requestingParticipantId) {
79
+ throw new ParameterError('requestingParticipantId must be defined, and the associated locus url for this meeting object must be defined.');
80
+ }
81
+
82
+ const requestParams = MembersUtil.getLowerAllHandsMemberRequestParams(options);
83
+
84
+ return this.request(requestParams);
85
+ }
86
+
77
87
  transferHostToMember(options) {
78
88
  if (!options || !options.locusUrl || !options.memberId || !options.moderator) {
79
89
  throw new ParameterError('memberId must be defined, the associated locus url, and the moderator for this meeting object must be defined.');
@@ -136,6 +136,11 @@ MembersUtil.generateRaiseHandMemberOptions = (memberId, status, locusUrl) => ({
136
136
  locusUrl
137
137
  });
138
138
 
139
+ MembersUtil.generateLowerAllHandsMemberOptions = (requestingParticipantId, locusUrl) => ({
140
+ requestingParticipantId,
141
+ locusUrl
142
+ });
143
+
139
144
  MembersUtil.getMuteMemberRequestParams = (options) => {
140
145
  const body = {
141
146
  audio: {
@@ -166,6 +171,22 @@ MembersUtil.getRaiseHandMemberRequestParams = (options) => {
166
171
  };
167
172
  };
168
173
 
174
+ MembersUtil.getLowerAllHandsMemberRequestParams = (options) => {
175
+ const body = {
176
+ hand: {
177
+ raised: false
178
+ },
179
+ requestingParticipantId: options.requestingParticipantId
180
+ };
181
+ const uri = `${options.locusUrl}/${CONTROLS}`;
182
+
183
+ return {
184
+ method: HTTP_VERBS.PATCH,
185
+ uri,
186
+ body
187
+ };
188
+ };
189
+
169
190
  MembersUtil.getTransferHostToMemberRequestParams = (options) => {
170
191
  const body = {
171
192
  role: {
@@ -44,7 +44,14 @@ const BEHAVIORAL_METRICS = {
44
44
  ENABLE_BNR_SUCCESS: 'js_sdk_enable_bnr_success',
45
45
  ENABLE_BNR_FAILURE: 'js_sdk_enable_bnr_failure',
46
46
  DISABLE_BNR_SUCCESS: 'js_sdk_disable_bnr_success',
47
- DISABLE_BNR_FAILURE: 'js_sdk_disable_bnr_failure'
47
+ DISABLE_BNR_FAILURE: 'js_sdk_disable_bnr_failure',
48
+ FETCH_MEETING_INFO_V1_SUCCESS: 'js_sdk_fetch_meeting_info_v1_success',
49
+ FETCH_MEETING_INFO_V1_FAILURE: 'js_sdk_fetch_meeting_info_v1_failure',
50
+ ADHOC_MEETING_SUCCESS: 'js_sdk_adhoc_meeting_success',
51
+ ADHOC_MEETING_FAILURE: 'js_sdk_adhoc_meeting_failure',
52
+ VERIFY_PASSWORD_SUCCESS: 'js_sdk_verify_password_success',
53
+ VERIFY_PASSWORD_ERROR: 'js_sdk_verify_password_error',
54
+ VERIFY_CAPTCHA_ERROR: 'js_sdk_verify_captcha_error'
48
55
  };
49
56
 
50
57
 
@@ -2366,6 +2366,11 @@ describe('plugin-meetings', () => {
2366
2366
  assert.equal(meeting.requiredCaptcha, null);
2367
2367
  assert.calledTwice(TriggerProxy.trigger);
2368
2368
  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
+ );
2369
2374
  });
2370
2375
 
2371
2376
  it('calls meetingInfoProvider with all the right parameters and parses the result when random delay is applied', async () => {
@@ -2437,6 +2442,11 @@ describe('plugin-meetings', () => {
2437
2442
 
2438
2443
  assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, null, null);
2439
2444
 
2445
+ assert(Metrics.sendBehavioralMetric.calledOnce);
2446
+ assert.calledWith(
2447
+ Metrics.sendBehavioralMetric,
2448
+ BEHAVIORAL_METRICS.VERIFY_PASSWORD_ERROR,
2449
+ );
2440
2450
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
2441
2451
  assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
2442
2452
  assert.equal(meeting.requiredCaptcha, null);
@@ -2457,6 +2467,11 @@ describe('plugin-meetings', () => {
2457
2467
 
2458
2468
  assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', null);
2459
2469
 
2470
+ assert(Metrics.sendBehavioralMetric.calledOnce);
2471
+ assert.calledWith(
2472
+ Metrics.sendBehavioralMetric,
2473
+ BEHAVIORAL_METRICS.VERIFY_CAPTCHA_ERROR,
2474
+ );
2460
2475
  assert.deepEqual(meeting.meetingInfo, {});
2461
2476
  assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
2462
2477
  assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
@@ -16,6 +16,8 @@ import {
16
16
  } from '@webex/plugin-meetings/src/constants';
17
17
  import MeetingInfo, {MeetingInfoV2PasswordError, MeetingInfoV2CaptchaError, MeetingInfoV2AdhocMeetingError} from '@webex/plugin-meetings/src/meeting-info/meeting-info-v2';
18
18
  import MeetingInfoUtil from '@webex/plugin-meetings/src/meeting-info/utilv2';
19
+ import Metrics from '@webex/plugin-meetings/src/metrics';
20
+ import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
19
21
 
20
22
  describe('plugin-meetings', () => {
21
23
  const conversation = {
@@ -42,6 +44,13 @@ describe('plugin-meetings', () => {
42
44
  let webex;
43
45
  let meetingInfo = null;
44
46
 
47
+ beforeEach(() => {
48
+ sinon.stub(Metrics, 'sendBehavioralMetric');
49
+ });
50
+ afterEach(() => {
51
+ sinon.restore();
52
+ });
53
+
45
54
  describe('Meeting Info V2', () => {
46
55
  beforeEach(() => {
47
56
  webex = new MockWebex({
@@ -125,6 +134,11 @@ describe('plugin-meetings', () => {
125
134
  captchaVerifyCode: 'aabbcc11'
126
135
  }
127
136
  });
137
+ assert(Metrics.sendBehavioralMetric.calledOnce);
138
+ assert.calledWith(
139
+ Metrics.sendBehavioralMetric,
140
+ BEHAVIORAL_METRICS.FETCH_MEETING_INFO_V1_SUCCESS,
141
+ );
128
142
  });
129
143
 
130
144
  it('create adhoc meeting when conversationUrl passed with enableAdhocMeetings toggle', async () => {
@@ -254,6 +268,11 @@ describe('plugin-meetings', () => {
254
268
  invitees: invitee
255
269
  }
256
270
  });
271
+ assert(Metrics.sendBehavioralMetric.calledOnce);
272
+ assert.calledWith(
273
+ Metrics.sendBehavioralMetric,
274
+ BEHAVIORAL_METRICS.ADHOC_MEETING_SUCCESS,
275
+ );
257
276
  });
258
277
  });
259
278
  });
@@ -10,8 +10,9 @@ import chaiAsPromised from 'chai-as-promised';
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
- import Meetings from '@webex/plugin-meetings';
14
13
 
14
+ import Meetings from '@webex/plugin-meetings';
15
+ import ParameterError from '@webex/plugin-meetings/src/common/errors/parameter';
15
16
  import Members from '@webex/plugin-meetings/src/members';
16
17
  import MembersUtil from '@webex/plugin-meetings/src/members/util';
17
18
 
@@ -191,25 +192,123 @@ describe('plugin-meetings', () => {
191
192
  });
192
193
  });
193
194
 
194
- describe('#raiseHand', () => {
195
- it('should fire spies correctly when raiseOrLowerHand is called with valid params', async () => {
196
- const members = createMembers({url: url1});
195
+ describe('#raiseOrLowerHand', () => {
196
+ const setup = (locusUrl) => {
197
+ const members = createMembers({url: locusUrl});
197
198
 
198
- const {membersRequest} = members;
199
+ const spies = {
200
+ generateRaiseHandMemberOptions: sandbox.spy(MembersUtil, 'generateRaiseHandMemberOptions'),
201
+ raiseOrLowerHandMember: sandbox.spy(members.membersRequest, 'raiseOrLowerHandMember'),
202
+ };
203
+
204
+ return {members, spies};
205
+ };
199
206
 
200
- const generateOptionsSpy = sandbox.spy(MembersUtil, 'generateRaiseHandMemberOptions');
201
- const raiseOrLowerHandMemberSpy = sandbox.spy(membersRequest, 'raiseOrLowerHandMember');
207
+ const checkInvalid = async (resultPromise, expectedMessage, spies) => {
208
+ await assert.isRejected(resultPromise, ParameterError, expectedMessage);
209
+ assert.notCalled(spies.generateRaiseHandMemberOptions);
210
+ assert.notCalled(spies.raiseOrLowerHandMember);
211
+ };
202
212
 
203
- await members.raiseOrLowerHand('test1', true);
213
+ const checkValid = async (resultPromise, spies, expectedMemberId, expectedRaise, expectedLocusUrl) => {
214
+ await assert.isFulfilled(resultPromise);
215
+ assert.calledOnceWithExactly(spies.generateRaiseHandMemberOptions, expectedMemberId, expectedRaise, expectedLocusUrl);
216
+ assert.calledOnceWithExactly(spies.raiseOrLowerHandMember, {memberId: expectedMemberId, raised: expectedRaise, locusUrl: expectedLocusUrl});
217
+ assert.strictEqual(resultPromise, spies.raiseOrLowerHandMember.getCall(0).returnValue);
218
+ };
204
219
 
205
- assert.calledOnce(generateOptionsSpy);
206
- assert.calledOnce(raiseOrLowerHandMemberSpy);
220
+ it('should not make a request if there is no member id', async () => {
221
+ const {members, spies} = setup(url1);
222
+
223
+ const resultPromise = members.raiseOrLowerHand();
224
+
225
+ await checkInvalid(resultPromise, 'The member id must be defined to raise/lower the hand of the member.', spies);
207
226
  });
208
227
 
209
- it('should throw a rejection if there is no locus url', async () => {
210
- const members = createMembers({url: false});
228
+ it('should not make a request if there is no locus url', async () => {
229
+ const {members, spies} = setup();
230
+
231
+ const resultPromise = members.raiseOrLowerHand(uuid.v4());
232
+
233
+ await checkInvalid(resultPromise, 'The associated locus url for this meetings members object must be defined.', spies);
234
+ });
235
+
236
+ it('should make the correct request when called with raise as true', async () => {
237
+ const memberId = uuid.v4();
238
+ const {members, spies} = setup(url1);
239
+
240
+ const resultPromise = members.raiseOrLowerHand(memberId, true);
241
+
242
+ await checkValid(resultPromise, spies, memberId, true, url1);
243
+ });
244
+
245
+ it('should make the correct request when called with raise as false', async () => {
246
+ const memberId = uuid.v4();
247
+ const {members, spies} = setup(url1);
248
+
249
+ const resultPromise = members.raiseOrLowerHand(memberId, false);
250
+
251
+ await checkValid(resultPromise, spies, memberId, false, url1);
252
+ });
253
+
254
+ it('should make the correct request when called with raise as default', async () => {
255
+ const memberId = uuid.v4();
256
+ const {members, spies} = setup(url1);
257
+
258
+ const resultPromise = members.raiseOrLowerHand(memberId);
259
+
260
+ await checkValid(resultPromise, spies, memberId, true, url1);
261
+ });
262
+ });
263
+
264
+ describe('#lowerAllHands', () => {
265
+ const setup = (locusUrl) => {
266
+ const members = createMembers({url: locusUrl});
267
+
268
+ const spies = {
269
+ generateLowerAllHandsMemberOptions: sandbox.spy(MembersUtil, 'generateLowerAllHandsMemberOptions'),
270
+ lowerAllHandsMember: sandbox.spy(members.membersRequest, 'lowerAllHandsMember'),
271
+ };
272
+
273
+ return {members, spies};
274
+ };
275
+
276
+ const checkInvalid = async (resultPromise, expectedMessage, spies) => {
277
+ await assert.isRejected(resultPromise, ParameterError, expectedMessage);
278
+ assert.notCalled(spies.generateLowerAllHandsMemberOptions);
279
+ assert.notCalled(spies.lowerAllHandsMember);
280
+ };
281
+
282
+ const checkValid = async (resultPromise, spies, expectedRequestingMemberId, expectedLocusUrl) => {
283
+ await assert.isFulfilled(resultPromise);
284
+ assert.calledOnceWithExactly(spies.generateLowerAllHandsMemberOptions, expectedRequestingMemberId, expectedLocusUrl);
285
+ assert.calledOnceWithExactly(spies.lowerAllHandsMember, {requestingParticipantId: expectedRequestingMemberId, locusUrl: expectedLocusUrl});
286
+ assert.strictEqual(resultPromise, spies.lowerAllHandsMember.getCall(0).returnValue);
287
+ };
288
+
289
+ it('should not make a request if there is no requestingMemberId', async () => {
290
+ const {members, spies} = setup(url1);
291
+
292
+ const resultPromise = members.lowerAllHands();
293
+
294
+ await checkInvalid(resultPromise, 'The requestingMemberId must be defined to lower all hands in a meeting.', spies);
295
+ });
296
+
297
+ it('should not make a request if there is no locus url', async () => {
298
+ const {members, spies} = setup();
299
+
300
+ const resultPromise = members.lowerAllHands(uuid.v4());
301
+
302
+ await checkInvalid(resultPromise, 'The associated locus url for this meetings members object must be defined.', spies);
303
+ });
304
+
305
+ it('should make the correct request when called with requestingMemberId', async () => {
306
+ const requestingMemberId = uuid.v4();
307
+ const {members, spies} = setup(url1);
308
+
309
+ const resultPromise = members.lowerAllHands(requestingMemberId);
211
310
 
212
- assert.isRejected(members.raiseOrLowerHand('test1', true));
311
+ await checkValid(resultPromise, spies, requestingMemberId, url1);
213
312
  });
214
313
  });
215
314
  });
@@ -3,9 +3,11 @@ import chai from 'chai';
3
3
  import uuid from 'uuid';
4
4
  import chaiAsPromised from 'chai-as-promised';
5
5
  import MockWebex from '@webex/test-helper-mock-webex';
6
- import Meetings from '@webex/plugin-meetings';
7
6
 
7
+ import Meetings from '@webex/plugin-meetings';
8
8
  import MembersRequest from '@webex/plugin-meetings/src/members/request';
9
+ import membersUtil from '@webex/plugin-meetings/src/members/util';
10
+ import ParameterError from '@webex/plugin-meetings/src/common/errors/parameter';
9
11
 
10
12
  const {assert} = chai;
11
13
 
@@ -118,5 +120,72 @@ describe('plugin-meetings', () => {
118
120
  assert.equal(requestParams.body.hand.raised, true);
119
121
  });
120
122
  });
123
+
124
+ describe('#lowerAllHands', () => {
125
+ const parameterErrorMessage = 'requestingParticipantId must be defined, and the associated locus url for this meeting object must be defined.';
126
+
127
+ const checkInvalid = async (functionParams) => {
128
+ assert.throws(() => membersRequest.lowerAllHandsMember(functionParams), ParameterError, parameterErrorMessage);
129
+ assert(membersRequest.request.notCalled);
130
+ assert(membersUtil.getLowerAllHandsMemberRequestParams.notCalled);
131
+ };
132
+
133
+ it('rejects if no options are passed in', async () => {
134
+ checkInvalid();
135
+ });
136
+
137
+ it('rejects if no locusUrl are passed in', async () => {
138
+ checkInvalid({requestingParticipantId: 'test'});
139
+ });
140
+
141
+ it('rejects if no requestingParticipantId are passed in', async () => {
142
+ checkInvalid({locusUrl: 'test'});
143
+ });
144
+
145
+ it('returns a promise', async () => {
146
+ const locusUrl = url1;
147
+ const memberId = 'test1';
148
+
149
+ const options = {
150
+ requestingParticipantId: memberId,
151
+ locusUrl,
152
+ };
153
+
154
+ assert.strictEqual(membersRequest.lowerAllHandsMember(options), membersRequest.request.getCall(0).returnValue);
155
+ });
156
+
157
+ it('sends a PATCH to the locus endpoint', async () => {
158
+ const locusUrl = url1;
159
+ const memberId = 'test1';
160
+
161
+ const options = {
162
+ requestingParticipantId: memberId,
163
+ locusUrl,
164
+ };
165
+
166
+
167
+ const getRequestParamsSpy = sandbox.spy(membersUtil, 'getLowerAllHandsMemberRequestParams');
168
+
169
+ await membersRequest.lowerAllHandsMember(options);
170
+
171
+ assert.calledOnceWithExactly(getRequestParamsSpy, {
172
+ requestingParticipantId: memberId,
173
+ locusUrl: url1
174
+ });
175
+
176
+ const requestParams = membersRequest.request.getCall(0).args[0];
177
+
178
+ assert.deepEqual(requestParams, {
179
+ method: 'PATCH',
180
+ uri: `${locusUrl}/controls`,
181
+ body: {
182
+ hand: {
183
+ raised: false
184
+ },
185
+ requestingParticipantId: memberId
186
+ }
187
+ });
188
+ });
189
+ });
121
190
  });
122
191
  });
@@ -0,0 +1,39 @@
1
+ import sinon from 'sinon';
2
+ import chai from 'chai';
3
+ import chaiAsPromised from 'chai-as-promised';
4
+
5
+ import MembersUtil from '@webex/plugin-meetings/src/members/util';
6
+
7
+ const {assert} = chai;
8
+
9
+ chai.use(chaiAsPromised);
10
+ sinon.assert.expose(chai.assert, {prefix: ''});
11
+
12
+ describe('plugin-meetings', () => {
13
+ describe('members utils library', () => {
14
+ describe('#generateRaiseHandMemberOptions', () => {
15
+ it('returns the correct options', () => {
16
+ const memberId = 'test';
17
+ const status = true;
18
+ const locusUrl = 'urlTest1';
19
+
20
+ assert.deepEqual(MembersUtil.generateRaiseHandMemberOptions(memberId, status, locusUrl), {
21
+ memberId,
22
+ raised: status,
23
+ locusUrl
24
+ });
25
+ });
26
+ });
27
+ describe('#generateLowerAllHandsMemberOptions', () => {
28
+ it('returns the correct options', () => {
29
+ const requestingParticipantId = 'test';
30
+ const locusUrl = 'urlTest1';
31
+
32
+ assert.deepEqual(MembersUtil.generateLowerAllHandsMemberOptions(requestingParticipantId, locusUrl), {
33
+ requestingParticipantId,
34
+ locusUrl
35
+ });
36
+ });
37
+ });
38
+ });
39
+ });