@webex/plugin-meetings 3.12.0-next.4 → 3.12.0-next.40
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/AGENTS.md +9 -0
- package/dist/aiEnableRequest/index.js +15 -2
- package/dist/aiEnableRequest/index.js.map +1 -1
- package/dist/breakouts/breakout.js +6 -2
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +23 -21
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +10 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +554 -350
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +22 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +274 -85
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/types.js +16 -0
- package/dist/locus-info/types.js.map +1 -1
- package/dist/meeting/index.js +710 -499
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +174 -77
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +49 -5
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +3 -0
- package/dist/member/util.js.map +1 -1
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +53 -15
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/locus-info/index.d.ts +46 -6
- package/dist/types/locus-info/types.d.ts +17 -1
- package/dist/types/meeting/index.d.ts +64 -1
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/member/util.d.ts +1 -0
- package/dist/webinar/index.js +301 -226
- package/dist/webinar/index.js.map +1 -1
- package/package.json +22 -22
- package/src/aiEnableRequest/index.ts +16 -0
- package/src/breakouts/breakout.ts +2 -1
- package/src/constants.ts +1 -1
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +26 -19
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +278 -160
- package/src/hashTree/utils.ts +17 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/locus-info/index.ts +274 -93
- package/src/locus-info/types.ts +19 -1
- package/src/meeting/index.ts +206 -22
- package/src/meeting/util.ts +1 -0
- package/src/meetings/index.ts +77 -43
- package/src/meetings/util.ts +56 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/webinar/index.ts +75 -1
- package/test/unit/spec/aiEnableRequest/index.ts +86 -0
- package/test/unit/spec/breakouts/breakout.ts +7 -3
- package/test/unit/spec/controls-options-manager/index.js +114 -6
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +996 -51
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/locus-info/index.js +397 -81
- package/test/unit/spec/meeting/index.js +271 -44
- package/test/unit/spec/meeting/utils.js +4 -0
- package/test/unit/spec/meetings/index.js +195 -13
- package/test/unit/spec/meetings/utils.js +137 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/webinar/index.ts +60 -0
|
@@ -91,6 +91,7 @@ describe('plugin-meetings', () => {
|
|
|
91
91
|
locusInfo = {
|
|
92
92
|
parse: sinon.stub().returns(true),
|
|
93
93
|
updateMainSessionLocusCache: sinon.stub(),
|
|
94
|
+
syncAllHashTreeDatasets: sinon.stub(),
|
|
94
95
|
};
|
|
95
96
|
webex = new MockWebex({
|
|
96
97
|
children: {
|
|
@@ -1285,10 +1286,10 @@ describe('plugin-meetings', () => {
|
|
|
1285
1286
|
assert.exists(result.dispose);
|
|
1286
1287
|
});
|
|
1287
1288
|
|
|
1288
|
-
it('creates noise reduction effect with
|
|
1289
|
+
it('creates noise reduction effect with OFMV model', async () => {
|
|
1289
1290
|
const result = await webex.meetings.createNoiseReductionEffect({
|
|
1290
1291
|
audioContext: {},
|
|
1291
|
-
model: '
|
|
1292
|
+
model: 'ofmv',
|
|
1292
1293
|
});
|
|
1293
1294
|
|
|
1294
1295
|
assert.exists(result);
|
|
@@ -1300,7 +1301,7 @@ describe('plugin-meetings', () => {
|
|
|
1300
1301
|
authToken: 'fake_token',
|
|
1301
1302
|
mode: 'WORKLET',
|
|
1302
1303
|
avoidSimd: false,
|
|
1303
|
-
model: '
|
|
1304
|
+
model: 'ofmv',
|
|
1304
1305
|
});
|
|
1305
1306
|
assert.exists(result.enable);
|
|
1306
1307
|
assert.exists(result.disable);
|
|
@@ -1391,7 +1392,7 @@ describe('plugin-meetings', () => {
|
|
|
1391
1392
|
it('should have #syncMeetings', () => {
|
|
1392
1393
|
assert.exists(webex.meetings.syncMeetings);
|
|
1393
1394
|
});
|
|
1394
|
-
it('should
|
|
1395
|
+
it('should skip getActiveMeetings but still call syncAllHashTreeDatasets if unverified guest', async () => {
|
|
1395
1396
|
webex.meetings.request.getActiveMeetings = sinon.stub().returns(
|
|
1396
1397
|
Promise.resolve({
|
|
1397
1398
|
loci: [
|
|
@@ -1404,13 +1405,23 @@ describe('plugin-meetings', () => {
|
|
|
1404
1405
|
webex.credentials.isUnverifiedGuest = true;
|
|
1405
1406
|
LoggerProxy.logger.info = sinon.stub();
|
|
1406
1407
|
|
|
1408
|
+
const mockLocusInfo = {
|
|
1409
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1410
|
+
};
|
|
1411
|
+
webex.meetings.meetingCollection.getAll = sinon.stub().returns({
|
|
1412
|
+
meeting1: {locusInfo: mockLocusInfo},
|
|
1413
|
+
meeting2: {locusInfo: undefined},
|
|
1414
|
+
meeting3: {},
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1407
1417
|
await webex.meetings.syncMeetings();
|
|
1408
1418
|
|
|
1409
1419
|
assert.notCalled(webex.meetings.request.getActiveMeetings);
|
|
1410
1420
|
assert.calledWith(
|
|
1411
1421
|
LoggerProxy.logger.info,
|
|
1412
|
-
'Meetings:index#syncMeetings --> skipping
|
|
1422
|
+
'Meetings:index#syncMeetings --> user is unverified guest, skipping calling Locus for meeting sync'
|
|
1413
1423
|
);
|
|
1424
|
+
assert.calledOnce(mockLocusInfo.syncAllHashTreeDatasets);
|
|
1414
1425
|
});
|
|
1415
1426
|
describe('succesful requests', () => {
|
|
1416
1427
|
beforeEach(() => {
|
|
@@ -1429,6 +1440,9 @@ describe('plugin-meetings', () => {
|
|
|
1429
1440
|
webex.meetings.meetingCollection.getByKey = sinon.stub().returns({
|
|
1430
1441
|
locusInfo,
|
|
1431
1442
|
});
|
|
1443
|
+
webex.meetings.meetingCollection.getAll = sinon.stub().returns({
|
|
1444
|
+
meeting1: {locusInfo, locusUrl: url1},
|
|
1445
|
+
});
|
|
1432
1446
|
});
|
|
1433
1447
|
it('tests the sync meeting calls for existing meeting', async () => {
|
|
1434
1448
|
await webex.meetings.syncMeetings();
|
|
@@ -1436,6 +1450,7 @@ describe('plugin-meetings', () => {
|
|
|
1436
1450
|
assert.calledOnce(webex.meetings.meetingCollection.getByKey);
|
|
1437
1451
|
assert.calledOnce(locusInfo.parse);
|
|
1438
1452
|
assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
|
|
1453
|
+
assert.calledOnce(locusInfo.syncAllHashTreeDatasets);
|
|
1439
1454
|
});
|
|
1440
1455
|
});
|
|
1441
1456
|
describe('when meeting is not returned', () => {
|
|
@@ -1474,7 +1489,7 @@ describe('plugin-meetings', () => {
|
|
|
1474
1489
|
url: url1,
|
|
1475
1490
|
},
|
|
1476
1491
|
hashTreeMessage: undefined,
|
|
1477
|
-
});
|
|
1492
|
+
}, sinon.match.func);
|
|
1478
1493
|
});
|
|
1479
1494
|
});
|
|
1480
1495
|
describe('when destroying meeting is needed', () => {
|
|
@@ -1520,7 +1535,7 @@ describe('plugin-meetings', () => {
|
|
|
1520
1535
|
it('destroy any meeting that has no active locus url if keepOnlyLocusMeetings is not defined', async () => {
|
|
1521
1536
|
await webex.meetings.syncMeetings();
|
|
1522
1537
|
assert.calledOnce(webex.meetings.request.getActiveMeetings);
|
|
1523
|
-
assert.
|
|
1538
|
+
assert.calledTwice(webex.meetings.meetingCollection.getAll);
|
|
1524
1539
|
assert.calledWith(destroySpy, meetingCollectionMeetings.noLongerValidLocusMeeting);
|
|
1525
1540
|
assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting1);
|
|
1526
1541
|
assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting2);
|
|
@@ -1532,7 +1547,7 @@ describe('plugin-meetings', () => {
|
|
|
1532
1547
|
it('destroy any meeting that has no active locus url if keepOnlyLocusMeetings === true', async () => {
|
|
1533
1548
|
await webex.meetings.syncMeetings({keepOnlyLocusMeetings: true});
|
|
1534
1549
|
assert.calledOnce(webex.meetings.request.getActiveMeetings);
|
|
1535
|
-
assert.
|
|
1550
|
+
assert.calledTwice(webex.meetings.meetingCollection.getAll);
|
|
1536
1551
|
assert.calledWith(destroySpy, meetingCollectionMeetings.noLongerValidLocusMeeting);
|
|
1537
1552
|
assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting1);
|
|
1538
1553
|
assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting2);
|
|
@@ -1544,7 +1559,7 @@ describe('plugin-meetings', () => {
|
|
|
1544
1559
|
it('destroy any LOCUS meetings that have no active locus url if keepOnlyLocusMeetings === false', async () => {
|
|
1545
1560
|
await webex.meetings.syncMeetings({keepOnlyLocusMeetings: false});
|
|
1546
1561
|
assert.calledOnce(webex.meetings.request.getActiveMeetings);
|
|
1547
|
-
assert.
|
|
1562
|
+
assert.calledTwice(webex.meetings.meetingCollection.getAll);
|
|
1548
1563
|
assert.calledWith(destroySpy, meetingCollectionMeetings.noLongerValidLocusMeeting);
|
|
1549
1564
|
assert.callCount(destroySpy, 1);
|
|
1550
1565
|
|
|
@@ -1552,6 +1567,113 @@ describe('plugin-meetings', () => {
|
|
|
1552
1567
|
});
|
|
1553
1568
|
});
|
|
1554
1569
|
});
|
|
1570
|
+
|
|
1571
|
+
describe('when globalMeetingId preserves breakout meetings', () => {
|
|
1572
|
+
let destroySpy;
|
|
1573
|
+
let cleanUpSpy;
|
|
1574
|
+
|
|
1575
|
+
beforeEach(() => {
|
|
1576
|
+
destroySpy = sinon.spy(webex.meetings, 'destroy');
|
|
1577
|
+
cleanUpSpy = sinon.stub(MeetingUtil, 'cleanUp').returns(Promise.resolve());
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
afterEach(() => {
|
|
1581
|
+
cleanUpSpy.restore();
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
it('should not destroy a meeting whose globalMeetingId matches an active locus', async () => {
|
|
1585
|
+
const meetingCollectionMeetings = {
|
|
1586
|
+
breakoutMeeting: {
|
|
1587
|
+
locusUrl: 'breakout-url',
|
|
1588
|
+
locusInfo: {
|
|
1589
|
+
info: {globalMeetingId: 'gmid-123'},
|
|
1590
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1591
|
+
},
|
|
1592
|
+
sendCallAnalyzerMetrics: sinon.stub(),
|
|
1593
|
+
},
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1596
|
+
webex.meetings.meetingCollection.getAll = sinon
|
|
1597
|
+
.stub()
|
|
1598
|
+
.returns(meetingCollectionMeetings);
|
|
1599
|
+
webex.meetings.request.getActiveMeetings = sinon.stub().resolves({
|
|
1600
|
+
loci: [{url: 'main-url', info: {globalMeetingId: 'gmid-123'}}],
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
await webex.meetings.syncMeetings();
|
|
1604
|
+
|
|
1605
|
+
assert.notCalled(destroySpy);
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
it('should destroy a meeting whose globalMeetingId does NOT match any active locus', async () => {
|
|
1609
|
+
const meetingCollectionMeetings = {
|
|
1610
|
+
breakoutMeeting: {
|
|
1611
|
+
locusUrl: 'breakout-url',
|
|
1612
|
+
locusInfo: {
|
|
1613
|
+
info: {globalMeetingId: 'gmid-other'},
|
|
1614
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1615
|
+
},
|
|
1616
|
+
sendCallAnalyzerMetrics: sinon.stub(),
|
|
1617
|
+
},
|
|
1618
|
+
};
|
|
1619
|
+
|
|
1620
|
+
webex.meetings.meetingCollection.getAll = sinon
|
|
1621
|
+
.stub()
|
|
1622
|
+
.returns(meetingCollectionMeetings);
|
|
1623
|
+
webex.meetings.request.getActiveMeetings = sinon.stub().resolves({
|
|
1624
|
+
loci: [{url: 'main-url', info: {globalMeetingId: 'gmid-123'}}],
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1627
|
+
await webex.meetings.syncMeetings();
|
|
1628
|
+
|
|
1629
|
+
assert.calledOnce(destroySpy);
|
|
1630
|
+
assert.calledWith(destroySpy, meetingCollectionMeetings.breakoutMeeting);
|
|
1631
|
+
});
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
describe('syncAllHashTreeDatasets in syncMeetings', () => {
|
|
1635
|
+
it('should call syncAllHashTreeDatasets for multiple meetings, skipping those without locusInfo', async () => {
|
|
1636
|
+
const mockLocusInfo1 = {
|
|
1637
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1638
|
+
};
|
|
1639
|
+
const mockLocusInfo2 = {
|
|
1640
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1641
|
+
};
|
|
1642
|
+
|
|
1643
|
+
webex.meetings.request.getActiveMeetings = sinon.stub().resolves({loci: []});
|
|
1644
|
+
webex.meetings.meetingCollection.getAll = sinon.stub().returns({
|
|
1645
|
+
meeting1: {locusInfo: mockLocusInfo1},
|
|
1646
|
+
meeting2: {locusInfo: undefined},
|
|
1647
|
+
meeting3: {locusInfo: mockLocusInfo2},
|
|
1648
|
+
meeting4: {},
|
|
1649
|
+
});
|
|
1650
|
+
|
|
1651
|
+
await webex.meetings.syncMeetings({keepOnlyLocusMeetings: false});
|
|
1652
|
+
|
|
1653
|
+
assert.calledOnce(mockLocusInfo1.syncAllHashTreeDatasets);
|
|
1654
|
+
assert.calledOnce(mockLocusInfo2.syncAllHashTreeDatasets);
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
it('should not call syncAllHashTreeDatasets when getActiveMeetings throws an error', async () => {
|
|
1658
|
+
const mockLocusInfo = {
|
|
1659
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
webex.meetings.request.getActiveMeetings = sinon.stub().rejects(new Error('network error'));
|
|
1663
|
+
webex.meetings.meetingCollection.getAll = sinon.stub().returns({
|
|
1664
|
+
meeting1: {locusInfo: mockLocusInfo},
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
try {
|
|
1668
|
+
await webex.meetings.syncMeetings();
|
|
1669
|
+
assert.fail('should have thrown');
|
|
1670
|
+
} catch (err) {
|
|
1671
|
+
assert.equal(err.message, 'network error');
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
assert.notCalled(mockLocusInfo.syncAllHashTreeDatasets);
|
|
1675
|
+
});
|
|
1676
|
+
});
|
|
1555
1677
|
});
|
|
1556
1678
|
describe('#fetchStaticMeetingLink', () => {
|
|
1557
1679
|
const conversationUrl = 'conv.fakeconversationurl.com';
|
|
@@ -2015,7 +2137,7 @@ describe('plugin-meetings', () => {
|
|
|
2015
2137
|
},
|
|
2016
2138
|
},
|
|
2017
2139
|
hashTreeMessage: undefined,
|
|
2018
|
-
});
|
|
2140
|
+
}, sinon.match.func);
|
|
2019
2141
|
});
|
|
2020
2142
|
it('should setup the meeting from a hash tree event', async () => {
|
|
2021
2143
|
const selfData = {};
|
|
@@ -2049,7 +2171,7 @@ describe('plugin-meetings', () => {
|
|
|
2049
2171
|
info: infoData,
|
|
2050
2172
|
},
|
|
2051
2173
|
hashTreeMessage,
|
|
2052
|
-
});
|
|
2174
|
+
}, sinon.match.func);
|
|
2053
2175
|
});
|
|
2054
2176
|
|
|
2055
2177
|
it('should ignore hash tree event when created locus has INACTIVE fullState', async () => {
|
|
@@ -2129,7 +2251,7 @@ describe('plugin-meetings', () => {
|
|
|
2129
2251
|
},
|
|
2130
2252
|
},
|
|
2131
2253
|
hashTreeMessage: undefined,
|
|
2132
|
-
});
|
|
2254
|
+
}, sinon.match.func);
|
|
2133
2255
|
});
|
|
2134
2256
|
|
|
2135
2257
|
it('sends client event correctly on finally', async () => {
|
|
@@ -2205,7 +2327,7 @@ describe('plugin-meetings', () => {
|
|
|
2205
2327
|
},
|
|
2206
2328
|
},
|
|
2207
2329
|
hashTreeMessage: undefined,
|
|
2208
|
-
});
|
|
2330
|
+
}, sinon.match.func);
|
|
2209
2331
|
});
|
|
2210
2332
|
|
|
2211
2333
|
const generateFakeLocusData = (isUnifiedSpaceMeeting) => ({
|
|
@@ -2833,6 +2955,39 @@ describe('plugin-meetings', () => {
|
|
|
2833
2955
|
checkCreateMeetingWithNoMeetingInfo(true, true);
|
|
2834
2956
|
});
|
|
2835
2957
|
|
|
2958
|
+
it('does not emit meeting:added when meeting is destroyed due to missing meeting info', async () => {
|
|
2959
|
+
// Make destroy actually remove the meeting from the collection
|
|
2960
|
+
// so that getMeetingByType returns null in the finally block
|
|
2961
|
+
webex.meetings.destroy = sinon.stub().callsFake((meeting) => {
|
|
2962
|
+
webex.meetings.meetingCollection.delete(meeting.id);
|
|
2963
|
+
});
|
|
2964
|
+
|
|
2965
|
+
try {
|
|
2966
|
+
await webex.meetings.createMeeting(
|
|
2967
|
+
'test destination',
|
|
2968
|
+
'test type',
|
|
2969
|
+
undefined,
|
|
2970
|
+
undefined,
|
|
2971
|
+
undefined,
|
|
2972
|
+
true
|
|
2973
|
+
);
|
|
2974
|
+
assert.fail('should have thrown NoMeetingInfoError');
|
|
2975
|
+
} catch (err) {
|
|
2976
|
+
assert.instanceOf(err, NoMeetingInfoError);
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
assert.calledOnce(webex.meetings.destroy);
|
|
2980
|
+
|
|
2981
|
+
// meeting:added should NOT have been triggered since the meeting was destroyed
|
|
2982
|
+
assert.neverCalledWith(
|
|
2983
|
+
TriggerProxy.trigger,
|
|
2984
|
+
sinon.match.any,
|
|
2985
|
+
sinon.match({function: 'createMeeting'}),
|
|
2986
|
+
'meeting:added',
|
|
2987
|
+
sinon.match.any
|
|
2988
|
+
);
|
|
2989
|
+
});
|
|
2990
|
+
|
|
2836
2991
|
it('creates the meeting avoiding meeting info fetch by passing type as DESTINATION_TYPE.ONE_ON_ONE_CALL', async () => {
|
|
2837
2992
|
const meeting = await webex.meetings.createMeeting(
|
|
2838
2993
|
'test destination',
|
|
@@ -3426,6 +3581,21 @@ describe('plugin-meetings', () => {
|
|
|
3426
3581
|
'Meetings:index#isNeedHandleMainLocus --> self device left&moved in main locus with self joined status, not need to handle'
|
|
3427
3582
|
);
|
|
3428
3583
|
});
|
|
3584
|
+
|
|
3585
|
+
it('check breakout ended with self removed, return false', () => {
|
|
3586
|
+
webex.meetings.meetingCollection.getActiveBreakoutLocus = sinon.stub().returns(null);
|
|
3587
|
+
newLocus.self.state = 'LEFT';
|
|
3588
|
+
newLocus.self.reason = 'OTHER';
|
|
3589
|
+
newLocus.self.removed = true;
|
|
3590
|
+
newLocus.fullState = {state: 'INACTIVE', endMeetingReason: 'BREAKOUT_ENDED'};
|
|
3591
|
+
LoggerProxy.logger.log = sinon.stub();
|
|
3592
|
+
const result = webex.meetings.isNeedHandleMainLocus(meeting, newLocus);
|
|
3593
|
+
assert.equal(result, false);
|
|
3594
|
+
assert.calledWith(
|
|
3595
|
+
LoggerProxy.logger.log,
|
|
3596
|
+
'Meetings:index#isNeedHandleMainLocus --> self moved main locus with self removed status or with device resource moved, not need to handle'
|
|
3597
|
+
);
|
|
3598
|
+
});
|
|
3429
3599
|
});
|
|
3430
3600
|
|
|
3431
3601
|
describe('#isNeedHandleLocusDTO', () => {
|
|
@@ -3486,6 +3656,18 @@ describe('plugin-meetings', () => {
|
|
|
3486
3656
|
const result = webex.meetings.isNeedHandleLocusDTO(meeting, newLocus);
|
|
3487
3657
|
assert.equal(result, false);
|
|
3488
3658
|
});
|
|
3659
|
+
it('breakout session with breakout ended, return false', () => {
|
|
3660
|
+
newLocus.controls.breakout = {
|
|
3661
|
+
sessionType: 'BREAKOUT',
|
|
3662
|
+
};
|
|
3663
|
+
newLocus.self.state = 'LEFT';
|
|
3664
|
+
newLocus.self.reason = 'OTHER';
|
|
3665
|
+
newLocus.self.devices = [];
|
|
3666
|
+
newLocus.fullState = {state: 'INACTIVE', endMeetingReason: 'BREAKOUT_ENDED'};
|
|
3667
|
+
LoggerProxy.logger.log = sinon.stub();
|
|
3668
|
+
const result = webex.meetings.isNeedHandleLocusDTO(meeting, newLocus);
|
|
3669
|
+
assert.equal(result, false);
|
|
3670
|
+
});
|
|
3489
3671
|
it('moved to lobby, return true', () => {
|
|
3490
3672
|
newLocus.controls.breakout = {
|
|
3491
3673
|
sessionType: 'MAIN',
|
|
@@ -128,6 +128,143 @@ describe('plugin-meetings', () => {
|
|
|
128
128
|
};
|
|
129
129
|
assert.equal(MeetingsUtil.isBreakoutLocusDTO(newLocus), false);
|
|
130
130
|
});
|
|
131
|
+
|
|
132
|
+
it('returns true if newLocus.info.isBreakout is true', () => {
|
|
133
|
+
const newLocus = {
|
|
134
|
+
info: {
|
|
135
|
+
isBreakout: true,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
assert.equal(MeetingsUtil.isBreakoutLocusDTO(newLocus), true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('returns false if newLocus.info.isBreakout is false', () => {
|
|
142
|
+
const newLocus = {
|
|
143
|
+
info: {
|
|
144
|
+
isBreakout: false,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
assert.equal(MeetingsUtil.isBreakoutLocusDTO(newLocus), false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('returns true if both sessionType is BREAKOUT and info.isBreakout is true', () => {
|
|
151
|
+
const newLocus = {
|
|
152
|
+
controls: {
|
|
153
|
+
breakout: {
|
|
154
|
+
sessionType: 'BREAKOUT',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
info: {
|
|
158
|
+
isBreakout: true,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
assert.equal(MeetingsUtil.isBreakoutLocusDTO(newLocus), true);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('#isMainAssociatedWithBreakout', () => {
|
|
166
|
+
it('returns true when breakout control url matches main locus breakout url', () => {
|
|
167
|
+
const mainLocus = {
|
|
168
|
+
url: 'main-locus-url',
|
|
169
|
+
controls: {
|
|
170
|
+
breakout: {
|
|
171
|
+
url: 'breakout-control-url',
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
const breakoutLocus = {
|
|
176
|
+
controls: {
|
|
177
|
+
breakout: {
|
|
178
|
+
url: 'breakout-control-url',
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
assert.equal(MeetingsUtil.isMainAssociatedWithBreakout(mainLocus, breakoutLocus), true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('returns true when breakout self device replaces the main locus url', () => {
|
|
187
|
+
const mainLocus = {
|
|
188
|
+
url: 'main-locus-url',
|
|
189
|
+
controls: {},
|
|
190
|
+
};
|
|
191
|
+
const breakoutLocus = {
|
|
192
|
+
controls: {
|
|
193
|
+
breakout: {
|
|
194
|
+
url: 'other-breakout-url',
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
self: {
|
|
198
|
+
deviceUrl: 'device-url-1',
|
|
199
|
+
devices: [
|
|
200
|
+
{
|
|
201
|
+
url: 'device-url-1',
|
|
202
|
+
replaces: [{locusUrl: 'main-locus-url'}],
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
assert.equal(MeetingsUtil.isMainAssociatedWithBreakout(mainLocus, breakoutLocus), true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('returns false when breakout locus is not associated with the main locus', () => {
|
|
212
|
+
const mainLocus = {
|
|
213
|
+
url: 'main-locus-url',
|
|
214
|
+
controls: {
|
|
215
|
+
breakout: {
|
|
216
|
+
url: 'breakout-control-url',
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
const breakoutLocus = {
|
|
221
|
+
controls: {
|
|
222
|
+
breakout: {
|
|
223
|
+
url: 'different-breakout-url',
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
self: {
|
|
227
|
+
deviceUrl: 'device-url-1',
|
|
228
|
+
devices: [
|
|
229
|
+
{
|
|
230
|
+
url: 'device-url-1',
|
|
231
|
+
replaces: [{locusUrl: 'another-main-locus-url'}],
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
assert.equal(MeetingsUtil.isMainAssociatedWithBreakout(mainLocus, breakoutLocus), false);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('#isWholeMeetingEnded', () => {
|
|
242
|
+
[
|
|
243
|
+
{description: 'state is INACTIVE with no endMeetingReason', fullState: {state: 'INACTIVE'}, expected: true},
|
|
244
|
+
{description: 'state is INACTIVE with endMeetingReason OTHER', fullState: {state: 'INACTIVE', endMeetingReason: 'SOME_OTHER_REASON'}, expected: true},
|
|
245
|
+
{description: 'state is INACTIVE with endMeetingReason BREAKOUT_ENDED', fullState: {state: 'INACTIVE', endMeetingReason: 'BREAKOUT_ENDED'}, expected: false},
|
|
246
|
+
{description: 'state is not INACTIVE', fullState: {state: 'ACTIVE', endMeetingReason: 'SOME_OTHER_REASON'}, expected: false},
|
|
247
|
+
].forEach(({description, fullState, expected}) => {
|
|
248
|
+
it(`returns ${expected} when ${description}`, () => {
|
|
249
|
+
assert.equal(MeetingsUtil.isWholeMeetingEnded(fullState), expected);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('#isSelfMovedOrBreakoutEnded', () => {
|
|
255
|
+
[
|
|
256
|
+
{description: 'locus is undefined', locus: undefined, expected: false},
|
|
257
|
+
{description: 'self state is JOINED', locus: {self: {state: 'JOINED', reason: 'OTHER'}}, expected: false},
|
|
258
|
+
{description: 'self state is LEFT with reason MOVED', locus: {self: {state: 'LEFT', reason: 'MOVED'}}, expected: true},
|
|
259
|
+
{description: 'fullState is INACTIVE with BREAKOUT_ENDED', locus: {self: {state: 'LEFT', reason: 'OTHER'}, fullState: {state: 'INACTIVE', endMeetingReason: 'BREAKOUT_ENDED'}}, expected: true},
|
|
260
|
+
{description: 'fullState is INACTIVE with different endMeetingReason', locus: {self: {state: 'LEFT', reason: 'OTHER'}, fullState: {state: 'INACTIVE', endMeetingReason: 'SOME_OTHER_REASON'}}, expected: false},
|
|
261
|
+
{description: 'fullState is missing', locus: {self: {state: 'LEFT', reason: 'OTHER'}}, expected: false},
|
|
262
|
+
{description: 'endMeetingReason is missing', locus: {self: {state: 'LEFT', reason: 'OTHER'}, fullState: {state: 'INACTIVE'}}, expected: false},
|
|
263
|
+
].forEach(({description, locus, expected}) => {
|
|
264
|
+
it(`returns ${expected} when ${description}`, () => {
|
|
265
|
+
assert.equal(MeetingsUtil.isSelfMovedOrBreakoutEnded(locus), expected);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
131
268
|
});
|
|
132
269
|
|
|
133
270
|
describe('#joinedOnThisDevice', () => {
|
|
@@ -59,6 +59,13 @@ describe('member', () => {
|
|
|
59
59
|
assert.calledOnceWithExactly(MemberUtil.isPresenterAssignmentProhibited, participant);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
it('checks that processParticipant calls isAttendeeAssignmentProhibited', () => {
|
|
63
|
+
sinon.spy(MemberUtil, 'isAttendeeAssignmentProhibited');
|
|
64
|
+
member.processParticipant(participant);
|
|
65
|
+
|
|
66
|
+
assert.calledOnceWithExactly(MemberUtil.isAttendeeAssignmentProhibited, participant);
|
|
67
|
+
});
|
|
68
|
+
|
|
62
69
|
it('checks that processParticipant calls canApproveAIEnablement', () => {
|
|
63
70
|
sinon.spy(MemberUtil, 'canApproveAIEnablement');
|
|
64
71
|
member.processParticipant(participant);
|
|
@@ -643,6 +643,30 @@ describe('plugin-meetings', () => {
|
|
|
643
643
|
assert.isUndefined(MemberUtil.isPresenterAssignmentProhibited(participant));
|
|
644
644
|
});
|
|
645
645
|
});
|
|
646
|
+
|
|
647
|
+
describe('MemberUtil.isAttendeeAssignmentProhibited', () => {
|
|
648
|
+
it('returns true when attendeeAssignmentNotAllowed is true', () => {
|
|
649
|
+
const participant = {
|
|
650
|
+
attendeeAssignmentNotAllowed: true,
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
assert.isTrue(MemberUtil.isAttendeeAssignmentProhibited(participant));
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it('returns false when attendeeAssignmentNotAllowed is false', () => {
|
|
657
|
+
const participant = {
|
|
658
|
+
attendeeAssignmentNotAllowed: false,
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
assert.isFalse(MemberUtil.isAttendeeAssignmentProhibited(participant));
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it('returns false when attendeeAssignmentNotAllowed is undefined', () => {
|
|
665
|
+
const participant = {};
|
|
666
|
+
|
|
667
|
+
assert.isFalse(MemberUtil.isAttendeeAssignmentProhibited(participant));
|
|
668
|
+
});
|
|
669
|
+
});
|
|
646
670
|
});
|
|
647
671
|
|
|
648
672
|
describe('extractMediaStatus', () => {
|
|
@@ -33,6 +33,7 @@ describe('plugin-meetings', () => {
|
|
|
33
33
|
webex.internal.llm = {
|
|
34
34
|
getDatachannelToken: sinon.stub().returns(undefined),
|
|
35
35
|
setDatachannelToken: sinon.stub(),
|
|
36
|
+
isDataChannelTokenEnabled: sinon.stub().resolves(false),
|
|
36
37
|
isConnected: sinon.stub().returns(false),
|
|
37
38
|
disconnectLLM: sinon.stub().resolves(),
|
|
38
39
|
off: sinon.stub(),
|
|
@@ -267,6 +268,65 @@ describe('plugin-meetings', () => {
|
|
|
267
268
|
webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
|
|
268
269
|
});
|
|
269
270
|
|
|
271
|
+
it('refreshes practice-session token before register when cached token is missing', async () => {
|
|
272
|
+
webex.internal.llm.isDataChannelTokenEnabled.resolves(true);
|
|
273
|
+
webex.internal.llm.getDatachannelToken = sinon.stub().callsFake((tokenType) => {
|
|
274
|
+
if (tokenType === DataChannelTokenType.PracticeSession) return undefined;
|
|
275
|
+
|
|
276
|
+
return undefined;
|
|
277
|
+
});
|
|
278
|
+
meeting.refreshDataChannelToken = sinon.stub().resolves({
|
|
279
|
+
body: {
|
|
280
|
+
datachannelToken: 'ps-token-from-refresh',
|
|
281
|
+
dataChannelTokenType: DataChannelTokenType.PracticeSession,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
await webinar.updatePSDataChannel();
|
|
286
|
+
|
|
287
|
+
assert.calledOnceWithExactly(meeting.refreshDataChannelToken);
|
|
288
|
+
assert.calledWithExactly(
|
|
289
|
+
webex.internal.llm.setDatachannelToken,
|
|
290
|
+
'ps-token-from-refresh',
|
|
291
|
+
DataChannelTokenType.PracticeSession
|
|
292
|
+
);
|
|
293
|
+
assert.calledWith(
|
|
294
|
+
webex.internal.llm.registerAndConnect,
|
|
295
|
+
'locus-url',
|
|
296
|
+
'dc-url',
|
|
297
|
+
'ps-token-from-refresh',
|
|
298
|
+
LLM_PRACTICE_SESSION
|
|
299
|
+
);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('does not reconnect if practice-session eligibility changes during async token refresh', async () => {
|
|
303
|
+
webex.internal.llm.isDataChannelTokenEnabled.resolves(true);
|
|
304
|
+
webex.internal.llm.getDatachannelToken = sinon.stub().returns(undefined);
|
|
305
|
+
|
|
306
|
+
let resolveRefresh;
|
|
307
|
+
meeting.refreshDataChannelToken = sinon.stub().returns(
|
|
308
|
+
new Promise((resolve) => {
|
|
309
|
+
resolveRefresh = resolve;
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const updatePromise = webinar.updatePSDataChannel();
|
|
314
|
+
|
|
315
|
+
webinar.practiceSessionEnabled = false;
|
|
316
|
+
|
|
317
|
+
resolveRefresh({
|
|
318
|
+
body: {
|
|
319
|
+
datachannelToken: 'stale-ps-token',
|
|
320
|
+
dataChannelTokenType: DataChannelTokenType.PracticeSession,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const result = await updatePromise;
|
|
325
|
+
|
|
326
|
+
assert.isUndefined(result);
|
|
327
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
328
|
+
});
|
|
329
|
+
|
|
270
330
|
it('no-ops when practice session join eligibility is false', async () => {
|
|
271
331
|
webinar.practiceSessionEnabled = false;
|
|
272
332
|
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|