@webex/plugin-meetings 3.11.0 → 3.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/dist/aiEnableRequest/index.js +184 -0
- package/dist/aiEnableRequest/index.js.map +1 -0
- package/dist/aiEnableRequest/utils.js +36 -0
- package/dist/aiEnableRequest/utils.js.map +1 -0
- package/dist/annotation/index.js +14 -5
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +28 -6
- package/dist/constants.js.map +1 -1
- package/dist/hashTree/constants.js +3 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTree.js +18 -0
- package/dist/hashTree/hashTree.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +709 -380
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/types.js +4 -2
- package/dist/hashTree/types.js.map +1 -1
- package/dist/hashTree/utils.js +10 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/interceptors/constant.js +12 -0
- package/dist/interceptors/constant.js.map +1 -0
- package/dist/interceptors/dataChannelAuthToken.js +290 -0
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
- package/dist/interceptors/index.js +7 -0
- package/dist/interceptors/index.js.map +1 -1
- package/dist/interceptors/utils.js +27 -0
- package/dist/interceptors/utils.js.map +1 -0
- package/dist/interpretation/index.js +2 -2
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +5 -3
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +217 -79
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +1 -0
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +57 -1
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/properties.js +4 -2
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +7 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1082 -861
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +50 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +133 -3
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +100 -45
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +10 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +9 -60
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +11 -0
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/reachability/index.js +18 -10
- package/dist/reachability/index.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/reconnection-manager/index.js +0 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/types/aiEnableRequest/index.d.ts +5 -0
- package/dist/types/aiEnableRequest/utils.d.ts +2 -0
- package/dist/types/config.d.ts +3 -0
- package/dist/types/constants.d.ts +23 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTree.d.ts +7 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +99 -14
- package/dist/types/hashTree/types.d.ts +3 -0
- package/dist/types/hashTree/utils.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/interceptors/constant.d.ts +5 -0
- package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
- package/dist/types/interceptors/index.d.ts +2 -1
- package/dist/types/interceptors/utils.d.ts +1 -0
- package/dist/types/locus-info/index.d.ts +21 -2
- package/dist/types/locus-info/types.d.ts +1 -0
- package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
- package/dist/types/media/properties.d.ts +2 -1
- package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
- package/dist/types/meeting/index.d.ts +38 -6
- package/dist/types/meeting/request.d.ts +16 -1
- package/dist/types/meeting/request.type.d.ts +5 -0
- package/dist/types/meeting/util.d.ts +31 -0
- package/dist/types/meetings/index.d.ts +4 -2
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/util.d.ts +5 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
- package/dist/types/reactions/reactions.type.d.ts +1 -0
- package/dist/types/webinar/utils.d.ts +6 -0
- package/dist/webinar/index.js +260 -90
- package/dist/webinar/index.js.map +1 -1
- package/dist/webinar/utils.js +25 -0
- package/dist/webinar/utils.js.map +1 -0
- package/package.json +24 -23
- package/src/aiEnableRequest/README.md +84 -0
- package/src/aiEnableRequest/index.ts +170 -0
- package/src/aiEnableRequest/utils.ts +25 -0
- package/src/annotation/index.ts +27 -7
- package/src/config.ts +3 -0
- package/src/constants.ts +29 -1
- package/src/hashTree/constants.ts +1 -0
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +627 -249
- package/src/hashTree/types.ts +4 -0
- package/src/hashTree/utils.ts +9 -0
- package/src/index.ts +8 -1
- package/src/interceptors/constant.ts +6 -0
- package/src/interceptors/dataChannelAuthToken.ts +170 -0
- package/src/interceptors/index.ts +2 -1
- package/src/interceptors/utils.ts +16 -0
- package/src/interpretation/index.ts +2 -2
- package/src/locus-info/controlsUtils.ts +11 -0
- package/src/locus-info/index.ts +231 -61
- package/src/locus-info/selfUtils.ts +1 -0
- package/src/locus-info/types.ts +1 -0
- package/src/media/MediaConnectionAwaiter.ts +41 -1
- package/src/media/properties.ts +3 -1
- package/src/meeting/in-meeting-actions.ts +12 -0
- package/src/meeting/index.ts +205 -44
- package/src/meeting/request.ts +42 -0
- package/src/meeting/request.type.ts +6 -0
- package/src/meeting/util.ts +160 -2
- package/src/meetings/index.ts +135 -41
- package/src/member/index.ts +10 -0
- package/src/member/util.ts +12 -0
- package/src/metrics/constants.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +4 -54
- package/src/multistream/remoteMediaManager.ts +13 -0
- package/src/reachability/index.ts +9 -0
- package/src/reactions/reactions.type.ts +1 -0
- package/src/reconnection-manager/index.ts +0 -1
- package/src/webinar/index.ts +162 -5
- package/src/webinar/utils.ts +16 -0
- package/test/unit/spec/aiEnableRequest/index.ts +981 -0
- package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
- package/test/unit/spec/annotation/index.ts +69 -7
- package/test/unit/spec/hashTree/hashTree.ts +66 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1869 -189
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
- package/test/unit/spec/interceptors/utils.ts +75 -0
- package/test/unit/spec/locus-info/controlsUtils.js +29 -0
- package/test/unit/spec/locus-info/index.js +383 -46
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
- package/test/unit/spec/media/properties.ts +12 -3
- package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
- package/test/unit/spec/meeting/index.js +716 -115
- package/test/unit/spec/meeting/request.js +70 -0
- package/test/unit/spec/meeting/utils.js +438 -26
- package/test/unit/spec/meetings/index.js +652 -31
- package/test/unit/spec/member/index.js +28 -4
- package/test/unit/spec/member/util.js +65 -27
- package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
- package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
- package/test/unit/spec/reachability/index.ts +23 -0
- package/test/unit/spec/reconnection-manager/index.js +4 -8
- package/test/unit/spec/webinar/index.ts +348 -36
- package/test/unit/spec/webinar/utils.ts +39 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {assert
|
|
1
|
+
import {assert} from '@webex/test-helper-chai';
|
|
2
2
|
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
|
3
3
|
import Webinar from '@webex/plugin-meetings/src/webinar';
|
|
4
4
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
5
5
|
import uuid from 'uuid';
|
|
6
6
|
import sinon from 'sinon';
|
|
7
|
+
import {DataChannelTokenType} from '@webex/internal-plugin-llm';
|
|
8
|
+
import {LLM_PRACTICE_SESSION, SHARE_STATUS} from '@webex/plugin-meetings/src/constants';
|
|
7
9
|
|
|
8
10
|
describe('plugin-meetings', () => {
|
|
9
11
|
describe('Webinar', () => {
|
|
@@ -26,7 +28,19 @@ describe('plugin-meetings', () => {
|
|
|
26
28
|
webex.meetings = {};
|
|
27
29
|
webex.credentials.getUserToken = getUserTokenStub;
|
|
28
30
|
webex.meetings.getMeetingByType = sinon.stub();
|
|
31
|
+
webex.internal.voicea.announce = sinon.stub();
|
|
29
32
|
|
|
33
|
+
webex.internal.llm = {
|
|
34
|
+
getDatachannelToken: sinon.stub().returns(undefined),
|
|
35
|
+
setDatachannelToken: sinon.stub(),
|
|
36
|
+
isConnected: sinon.stub().returns(false),
|
|
37
|
+
disconnectLLM: sinon.stub().resolves(),
|
|
38
|
+
off: sinon.stub(),
|
|
39
|
+
on: sinon.stub(),
|
|
40
|
+
getLocusUrl: sinon.stub().returns('old-locus-url'),
|
|
41
|
+
getDatachannelUrl: sinon.stub().returns('old-dc-url'),
|
|
42
|
+
registerAndConnect: sinon.stub().resolves('REGISTER_AND_CONNECT_RESULT'),
|
|
43
|
+
};
|
|
30
44
|
});
|
|
31
45
|
|
|
32
46
|
afterEach(() => {
|
|
@@ -147,20 +161,216 @@ describe('plugin-meetings', () => {
|
|
|
147
161
|
assert.equal(result.isPromoted, false, 'should not indicate promotion');
|
|
148
162
|
assert.equal(result.isDemoted, false, 'should not indicate demotion');
|
|
149
163
|
});
|
|
164
|
+
|
|
165
|
+
it('handles missing role payload safely', () => {
|
|
166
|
+
const updateStatusByRoleStub = sinon.stub(webinar, 'updateStatusByRole');
|
|
167
|
+
|
|
168
|
+
const result = webinar.updateRoleChanged(undefined);
|
|
169
|
+
|
|
170
|
+
assert.equal(webinar.selfIsPanelist, false);
|
|
171
|
+
assert.equal(webinar.selfIsAttendee, false);
|
|
172
|
+
assert.equal(webinar.canManageWebcast, false);
|
|
173
|
+
assert.deepEqual(result, {isPromoted: false, isDemoted: false});
|
|
174
|
+
assert.calledOnceWithExactly(updateStatusByRoleStub, {isPromoted: false, isDemoted: false});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('#cleanUp', () => {
|
|
179
|
+
it('delegates to cleanupPSDataChannel', () => {
|
|
180
|
+
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
181
|
+
|
|
182
|
+
webinar.cleanUp();
|
|
183
|
+
|
|
184
|
+
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('#cleanupPSDataChannel', () => {
|
|
189
|
+
let meeting;
|
|
190
|
+
|
|
191
|
+
beforeEach(() => {
|
|
192
|
+
meeting = {
|
|
193
|
+
processRelayEvent: sinon.stub(),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('disconnects the practice session channel and removes the relay listener', async () => {
|
|
200
|
+
await webinar.cleanupPSDataChannel();
|
|
201
|
+
|
|
202
|
+
assert.calledOnceWithExactly(
|
|
203
|
+
webex.internal.llm.disconnectLLM,
|
|
204
|
+
{code: 3050, reason: 'done (permanent)'},
|
|
205
|
+
LLM_PRACTICE_SESSION
|
|
206
|
+
);
|
|
207
|
+
assert.calledOnceWithExactly(
|
|
208
|
+
webex.internal.llm.off,
|
|
209
|
+
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
210
|
+
meeting.processRelayEvent
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('#updatePSDataChannel', () => {
|
|
216
|
+
let meeting;
|
|
217
|
+
let processRelayEvent;
|
|
218
|
+
|
|
219
|
+
beforeEach(() => {
|
|
220
|
+
processRelayEvent = sinon.stub();
|
|
221
|
+
meeting = {
|
|
222
|
+
isJoined: sinon.stub().returns(true),
|
|
223
|
+
processRelayEvent,
|
|
224
|
+
locusInfo: {
|
|
225
|
+
url: 'locus-url',
|
|
226
|
+
info: {practiceSessionDatachannelUrl: 'dc-url'},
|
|
227
|
+
self: {practiceSessionDatachannelToken: 'ps-token'},
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
|
|
232
|
+
|
|
233
|
+
// Ensure connect path is eligible
|
|
234
|
+
webinar.selfIsPanelist = true;
|
|
235
|
+
webinar.practiceSessionEnabled = true;
|
|
236
|
+
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
|
|
237
|
+
webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('no-ops when practice session join eligibility is false', async () => {
|
|
241
|
+
webinar.practiceSessionEnabled = false;
|
|
242
|
+
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
243
|
+
|
|
244
|
+
const result = await webinar.updatePSDataChannel();
|
|
245
|
+
|
|
246
|
+
assert.isUndefined(result);
|
|
247
|
+
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
248
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('no-ops when meeting is not joined', async () => {
|
|
252
|
+
meeting.isJoined.returns(false);
|
|
253
|
+
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
254
|
+
|
|
255
|
+
const result = await webinar.updatePSDataChannel();
|
|
256
|
+
|
|
257
|
+
assert.isUndefined(result);
|
|
258
|
+
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
259
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('no-ops when practiceSessionDatachannelUrl is missing', async () => {
|
|
263
|
+
meeting.locusInfo.info.practiceSessionDatachannelUrl = undefined;
|
|
264
|
+
|
|
265
|
+
const result = await webinar.updatePSDataChannel();
|
|
266
|
+
|
|
267
|
+
assert.isUndefined(result);
|
|
268
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('no-ops when already connected to the same endpoints', async () => {
|
|
272
|
+
webex.internal.llm.isConnected.returns(true);
|
|
273
|
+
webex.internal.llm.getLocusUrl.returns('locus-url');
|
|
274
|
+
webex.internal.llm.getDatachannelUrl.returns('dc-url');
|
|
275
|
+
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
276
|
+
|
|
277
|
+
const result = await webinar.updatePSDataChannel();
|
|
278
|
+
|
|
279
|
+
assert.isUndefined(result);
|
|
280
|
+
assert.notCalled(cleanupPSDataChannelStub);
|
|
281
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('connects when eligible', async () => {
|
|
285
|
+
const result = await webinar.updatePSDataChannel();
|
|
286
|
+
|
|
287
|
+
assert.calledOnceWithExactly(
|
|
288
|
+
webex.internal.llm.setDatachannelToken,
|
|
289
|
+
'ps-token',
|
|
290
|
+
DataChannelTokenType.PracticeSession
|
|
291
|
+
);
|
|
292
|
+
assert.calledOnce(webex.internal.llm.registerAndConnect);
|
|
293
|
+
assert.calledWith(
|
|
294
|
+
webex.internal.llm.registerAndConnect,
|
|
295
|
+
'locus-url',
|
|
296
|
+
'dc-url',
|
|
297
|
+
'ps-token',
|
|
298
|
+
LLM_PRACTICE_SESSION
|
|
299
|
+
);
|
|
300
|
+
assert.calledOnceWithExactly(webex.internal.voicea.announce);
|
|
301
|
+
assert.equal(result, 'REGISTER_AND_CONNECT_RESULT');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('uses cached token when available', async () => {
|
|
305
|
+
webex.internal.llm.getDatachannelToken.returns('cached-token');
|
|
306
|
+
|
|
307
|
+
await webinar.updatePSDataChannel();
|
|
308
|
+
|
|
309
|
+
assert.calledWithExactly(
|
|
310
|
+
webex.internal.llm.getDatachannelToken,
|
|
311
|
+
DataChannelTokenType.PracticeSession
|
|
312
|
+
);
|
|
313
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
314
|
+
assert.calledWith(
|
|
315
|
+
webex.internal.llm.registerAndConnect,
|
|
316
|
+
'locus-url',
|
|
317
|
+
'dc-url',
|
|
318
|
+
'cached-token',
|
|
319
|
+
LLM_PRACTICE_SESSION
|
|
320
|
+
);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('cleans up the existing practice session channel before reconnecting to new endpoints', async () => {
|
|
324
|
+
webex.internal.llm.isConnected.returns(true);
|
|
325
|
+
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
326
|
+
|
|
327
|
+
await webinar.updatePSDataChannel();
|
|
328
|
+
|
|
329
|
+
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
330
|
+
assert.calledOnce(webex.internal.llm.registerAndConnect);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('rebinds relay listener after successful connect', async () => {
|
|
334
|
+
await webinar.updatePSDataChannel();
|
|
335
|
+
|
|
336
|
+
assert.calledWith(
|
|
337
|
+
webex.internal.llm.off,
|
|
338
|
+
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
339
|
+
processRelayEvent
|
|
340
|
+
);
|
|
341
|
+
assert.calledWith(
|
|
342
|
+
webex.internal.llm.on,
|
|
343
|
+
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
344
|
+
processRelayEvent
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('subscribes to transcription when caption intent is enabled', async () => {
|
|
349
|
+
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(true);
|
|
350
|
+
|
|
351
|
+
await webinar.updatePSDataChannel();
|
|
352
|
+
|
|
353
|
+
assert.calledOnceWithExactly(webex.internal.voicea.updateSubchannelSubscriptions, { subscribe: ['transcription'] });
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('does not subscribe to transcription when caption intent is disabled', async () => {
|
|
357
|
+
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
|
|
358
|
+
|
|
359
|
+
await webinar.updatePSDataChannel();
|
|
360
|
+
|
|
361
|
+
assert.notCalled(webex.internal.voicea.updateSubchannelSubscriptions);
|
|
362
|
+
});
|
|
150
363
|
});
|
|
151
364
|
|
|
152
365
|
describe('#updateStatusByRole', () => {
|
|
153
|
-
let updateLLMConnection;
|
|
154
366
|
let updateMediaShares;
|
|
155
367
|
beforeEach(() => {
|
|
156
|
-
// @ts-ignore
|
|
157
|
-
updateLLMConnection = sinon.stub();
|
|
158
368
|
updateMediaShares = sinon.stub()
|
|
159
369
|
webinar.webex.meetings = {
|
|
160
370
|
getMeetingByType: sinon.stub().returns({
|
|
161
371
|
id: 'meeting-id',
|
|
162
|
-
updateLLMConnection:
|
|
163
|
-
shareStatus:
|
|
372
|
+
updateLLMConnection: sinon.stub(),
|
|
373
|
+
shareStatus: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE,
|
|
164
374
|
locusInfo: {
|
|
165
375
|
mediaShares: 'mediaShares',
|
|
166
376
|
updateMediaShares: updateMediaShares
|
|
@@ -173,40 +383,20 @@ describe('plugin-meetings', () => {
|
|
|
173
383
|
sinon.restore();
|
|
174
384
|
});
|
|
175
385
|
|
|
176
|
-
it('trigger updateLLMConnection if PS started', () => {
|
|
177
|
-
|
|
178
|
-
webinar.practiceSessionEnabled = true;
|
|
179
|
-
const roleChange = {isPromoted: true, isDemoted: false};
|
|
180
|
-
|
|
181
|
-
const result = webinar.updateStatusByRole(roleChange);
|
|
182
|
-
|
|
183
|
-
assert.calledOnce(updateLLMConnection);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('Not trigger updateLLMConnection if PS not started', () => {
|
|
187
|
-
|
|
188
|
-
webinar.practiceSessionEnabled = false;
|
|
189
|
-
const roleChange = {isPromoted: true, isDemoted: false};
|
|
190
|
-
|
|
191
|
-
const result = webinar.updateStatusByRole(roleChange);
|
|
192
|
-
|
|
193
|
-
assert.notCalled(updateLLMConnection);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
386
|
it('trigger updateMediaShares if promoted', () => {
|
|
197
387
|
|
|
198
388
|
const roleChange = {isPromoted: true, isDemoted: false};
|
|
199
389
|
|
|
200
|
-
|
|
390
|
+
webinar.updateStatusByRole(roleChange);
|
|
201
391
|
|
|
202
|
-
assert.
|
|
392
|
+
assert.calledOnceWithExactly(updateMediaShares, 'mediaShares', true);
|
|
203
393
|
});
|
|
204
394
|
|
|
205
395
|
it('Not trigger updateMediaShares if no role change', () => {
|
|
206
396
|
|
|
207
397
|
const roleChange = {isPromoted: false, isDemoted: false};
|
|
208
398
|
|
|
209
|
-
|
|
399
|
+
webinar.updateStatusByRole(roleChange);
|
|
210
400
|
|
|
211
401
|
assert.notCalled(updateMediaShares);
|
|
212
402
|
});
|
|
@@ -214,18 +404,18 @@ describe('plugin-meetings', () => {
|
|
|
214
404
|
|
|
215
405
|
const roleChange = {isPromoted: true, isDemoted: false};
|
|
216
406
|
|
|
217
|
-
|
|
407
|
+
webinar.updateStatusByRole(roleChange);
|
|
218
408
|
|
|
219
|
-
assert.
|
|
409
|
+
assert.calledOnceWithExactly(updateMediaShares, 'mediaShares', true);
|
|
220
410
|
});
|
|
221
411
|
|
|
222
412
|
it('trigger updateMediaShares if is attendee with whiteboard share', () => {
|
|
223
413
|
|
|
224
414
|
const roleChange = {isPromoted: false, isDemoted: true};
|
|
225
415
|
|
|
226
|
-
|
|
416
|
+
webinar.updateStatusByRole(roleChange);
|
|
227
417
|
|
|
228
|
-
assert.
|
|
418
|
+
assert.calledOnceWithExactly(updateMediaShares, 'mediaShares', true);
|
|
229
419
|
});
|
|
230
420
|
|
|
231
421
|
it('Not trigger updateMediaShares if is attendee with screen share', () => {
|
|
@@ -233,8 +423,8 @@ describe('plugin-meetings', () => {
|
|
|
233
423
|
webinar.webex.meetings = {
|
|
234
424
|
getMeetingByType: sinon.stub().returns({
|
|
235
425
|
id: 'meeting-id',
|
|
236
|
-
updateLLMConnection:
|
|
237
|
-
shareStatus:
|
|
426
|
+
updateLLMConnection: sinon.stub(),
|
|
427
|
+
shareStatus: SHARE_STATUS.REMOTE_SHARE_ACTIVE,
|
|
238
428
|
locusInfo: {
|
|
239
429
|
mediaShares: 'mediaShares',
|
|
240
430
|
updateMediaShares: updateMediaShares
|
|
@@ -244,10 +434,18 @@ describe('plugin-meetings', () => {
|
|
|
244
434
|
|
|
245
435
|
const roleChange = {isPromoted: false, isDemoted: true};
|
|
246
436
|
|
|
247
|
-
|
|
437
|
+
webinar.updateStatusByRole(roleChange);
|
|
248
438
|
|
|
249
439
|
assert.notCalled(updateMediaShares);
|
|
250
440
|
});
|
|
441
|
+
|
|
442
|
+
it('updates PS data channel based on join eligibility', () => {
|
|
443
|
+
const updatePSDataChannelStub = sinon.stub(webinar, 'updatePSDataChannel').resolves();
|
|
444
|
+
|
|
445
|
+
webinar.updateStatusByRole({isPromoted: false, isDemoted: false});
|
|
446
|
+
|
|
447
|
+
assert.calledOnceWithExactly(updatePSDataChannelStub);
|
|
448
|
+
});
|
|
251
449
|
});
|
|
252
450
|
|
|
253
451
|
describe("#setPracticeSessionState", () => {
|
|
@@ -323,6 +521,14 @@ describe('plugin-meetings', () => {
|
|
|
323
521
|
|
|
324
522
|
assert.equal(webinar.practiceSessionEnabled, false);
|
|
325
523
|
});
|
|
524
|
+
it('triggers PS data channel update using computed eligibility', () => {
|
|
525
|
+
webinar.selfIsPanelist = true;
|
|
526
|
+
const updatePSDataChannelStub = sinon.stub(webinar, 'updatePSDataChannel').resolves();
|
|
527
|
+
|
|
528
|
+
webinar.updatePracticeSessionStatus({enabled: true});
|
|
529
|
+
|
|
530
|
+
assert.calledOnceWithExactly(updatePSDataChannelStub);
|
|
531
|
+
});
|
|
326
532
|
});
|
|
327
533
|
|
|
328
534
|
describe("#startWebcast", () => {
|
|
@@ -631,5 +837,111 @@ describe('plugin-meetings', () => {
|
|
|
631
837
|
}
|
|
632
838
|
});
|
|
633
839
|
});
|
|
840
|
+
|
|
841
|
+
describe("#searchLargeScaleWebinarAttendees", () => {
|
|
842
|
+
const attendeeSearchUrl = 'https://locusUrl/attendees/search';
|
|
843
|
+
const params = {
|
|
844
|
+
queryString: 'queryString',
|
|
845
|
+
limit: 50,
|
|
846
|
+
next: null,
|
|
847
|
+
};
|
|
848
|
+
beforeEach(() => {
|
|
849
|
+
// @ts-ignore
|
|
850
|
+
webinar.webex.meetings = {
|
|
851
|
+
getMeetingByType: sinon.stub().returns({
|
|
852
|
+
id: 'meeting-id',
|
|
853
|
+
locusInfo: {
|
|
854
|
+
links:{
|
|
855
|
+
resources: {
|
|
856
|
+
attendeeSearch: {
|
|
857
|
+
url: attendeeSearchUrl
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
})
|
|
863
|
+
};
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
it('throws an error if attendeeSearchUrl is not available', async () => {
|
|
867
|
+
webinar.webex.meetings = {
|
|
868
|
+
getMeetingByType: sinon.stub().returns({
|
|
869
|
+
id: 'meeting-id',
|
|
870
|
+
locusInfo: {
|
|
871
|
+
links:{
|
|
872
|
+
resources: {
|
|
873
|
+
attendeeSearch: {
|
|
874
|
+
url: null
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
})
|
|
880
|
+
};
|
|
881
|
+
try {
|
|
882
|
+
await webinar.searchLargeScaleWebinarAttendees(params);
|
|
883
|
+
assert.fail('searchLargeScaleWebinarAttendees should throw an error');
|
|
884
|
+
} catch (error) {
|
|
885
|
+
assert.equal(error.message,'Meeting:webinar5k#Attendee search url is not available', 'should throw the correct error');
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
it('sends a GET request to search the large scale webinar attendees', async () => {
|
|
890
|
+
const result = await webinar.searchLargeScaleWebinarAttendees(params);
|
|
891
|
+
assert.calledOnce(webex.request);
|
|
892
|
+
assert.calledWith(webex.request, {
|
|
893
|
+
method: 'GET',
|
|
894
|
+
uri: `${attendeeSearchUrl}?search_text=${encodeURIComponent(params.queryString)}&limit=50`,
|
|
895
|
+
headers: {
|
|
896
|
+
authorization: 'test-token',
|
|
897
|
+
trackingId: 'webex-js-sdk_test-uuid',
|
|
898
|
+
},
|
|
899
|
+
});
|
|
900
|
+
assert.equal(
|
|
901
|
+
result,
|
|
902
|
+
'REQUEST_RETURN_VALUE',
|
|
903
|
+
'should return the resolved value from the request'
|
|
904
|
+
);
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
it('queryString is empty string', async () => {
|
|
908
|
+
params.queryString = '';
|
|
909
|
+
const result = await webinar.searchLargeScaleWebinarAttendees(params);
|
|
910
|
+
assert.calledOnce(webex.request);
|
|
911
|
+
assert.calledWith(webex.request, {
|
|
912
|
+
method: 'GET',
|
|
913
|
+
uri: `${attendeeSearchUrl}?limit=50`,
|
|
914
|
+
headers: {
|
|
915
|
+
authorization: 'test-token',
|
|
916
|
+
trackingId: 'webex-js-sdk_test-uuid',
|
|
917
|
+
},
|
|
918
|
+
});
|
|
919
|
+
assert.equal(
|
|
920
|
+
result,
|
|
921
|
+
'REQUEST_RETURN_VALUE',
|
|
922
|
+
'should return the resolved value from the request'
|
|
923
|
+
);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
it('handles API call failures gracefully', async () => {
|
|
927
|
+
webex.request.rejects(new Error('API_ERROR'));
|
|
928
|
+
const errorLogger = sinon.stub(LoggerProxy.logger, 'error');
|
|
929
|
+
|
|
930
|
+
try {
|
|
931
|
+
await webinar.searchLargeScaleWebinarAttendees(params);
|
|
932
|
+
assert.fail('searchLargeScaleWebinarAttendees should throw an error');
|
|
933
|
+
} catch (error) {
|
|
934
|
+
assert.equal(error.message, 'API_ERROR', 'should throw the correct error');
|
|
935
|
+
assert.calledOnce(errorLogger);
|
|
936
|
+
assert.calledWith(
|
|
937
|
+
errorLogger,
|
|
938
|
+
'Meeting:webinar5k#searchLargeScaleWebinarAttendees failed',
|
|
939
|
+
sinon.match.instanceOf(Error)
|
|
940
|
+
);
|
|
941
|
+
} finally {
|
|
942
|
+
errorLogger.restore();
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
});
|
|
634
946
|
})
|
|
635
947
|
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import chai from 'chai';
|
|
2
|
+
import {sanitizeParams} from '@webex/plugin-meetings/src/webinar/utils';
|
|
3
|
+
|
|
4
|
+
const {assert} = chai;
|
|
5
|
+
|
|
6
|
+
describe('plugin-meetings', () => {
|
|
7
|
+
describe('webinar utils', () => {
|
|
8
|
+
describe('#sanitizeParams', () => {
|
|
9
|
+
it('sanitizes params by removing undefined, "", or null values', () => {
|
|
10
|
+
const input = {
|
|
11
|
+
a: 1,
|
|
12
|
+
b: undefined,
|
|
13
|
+
c: null,
|
|
14
|
+
d: 'test',
|
|
15
|
+
e: false,
|
|
16
|
+
f: '',
|
|
17
|
+
};
|
|
18
|
+
const expectedOutput = {
|
|
19
|
+
a: 1,
|
|
20
|
+
d: 'test',
|
|
21
|
+
e: false,
|
|
22
|
+
};
|
|
23
|
+
const result = sanitizeParams(input);
|
|
24
|
+
assert.deepEqual(result, expectedOutput);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns an empty object when all values are invalid', () => {
|
|
28
|
+
const input = {
|
|
29
|
+
a: undefined,
|
|
30
|
+
b: null,
|
|
31
|
+
c: '',
|
|
32
|
+
};
|
|
33
|
+
const expectedOutput = {};
|
|
34
|
+
const result = sanitizeParams(input);
|
|
35
|
+
assert.deepEqual(result, expectedOutput);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|