@webex/plugin-meetings 3.12.0-mobius-socket.2 → 3.12.0-mobius-socket.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -0
- package/dist/aiEnableRequest/index.js +15 -2
- package/dist/aiEnableRequest/index.js.map +1 -1
- package/dist/breakouts/breakout.js +8 -3
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/index.js +3 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +6 -3
- 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 +38 -24
- 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 +651 -382
- 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/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +10 -1
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +4 -1
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +289 -87
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/types.js +19 -0
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/properties.js +1 -0
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +3 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +848 -582
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +19 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +205 -77
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +6 -1
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/request.js +39 -0
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +67 -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/metrics/constants.js +4 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/receiveSlot.js +9 -0
- package/dist/multistream/receiveSlot.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/recording-controller/index.js +1 -3
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/controls-options-manager/index.d.ts +10 -0
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +83 -16
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/index.d.ts +2 -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 +21 -1
- package/dist/types/media/properties.d.ts +1 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
- package/dist/types/meeting/index.d.ts +65 -1
- package/dist/types/meeting/util.d.ts +8 -0
- package/dist/types/meetings/index.d.ts +20 -2
- package/dist/types/meetings/meetings.types.d.ts +15 -0
- package/dist/types/meetings/request.d.ts +14 -0
- 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/types/metrics/constants.d.ts +3 -0
- package/dist/types/reactions/reactions.type.d.ts +3 -0
- package/dist/webinar/index.js +68 -17
- 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 +3 -1
- package/src/breakouts/index.ts +1 -0
- package/src/config.ts +1 -0
- package/src/constants.ts +5 -1
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +47 -24
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +375 -197
- package/src/hashTree/utils.ts +17 -0
- package/src/index.ts +5 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/interpretation/index.ts +25 -8
- package/src/locus-info/controlsUtils.ts +3 -1
- package/src/locus-info/index.ts +291 -97
- package/src/locus-info/types.ts +25 -1
- package/src/media/properties.ts +1 -0
- package/src/meeting/in-meeting-actions.ts +4 -0
- package/src/meeting/index.ts +260 -23
- package/src/meeting/util.ts +20 -2
- package/src/meetings/index.ts +109 -43
- package/src/meetings/meetings.types.ts +19 -0
- package/src/meetings/request.ts +43 -0
- package/src/meetings/util.ts +80 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/metrics/constants.ts +3 -0
- package/src/multistream/receiveSlot.ts +18 -0
- package/src/reactions/reactions.type.ts +3 -0
- package/src/recording-controller/index.ts +1 -2
- package/src/webinar/index.ts +88 -21
- package/test/unit/spec/aiEnableRequest/index.ts +86 -0
- package/test/unit/spec/breakouts/breakout.ts +9 -3
- package/test/unit/spec/breakouts/index.ts +2 -0
- package/test/unit/spec/controls-options-manager/index.js +140 -29
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1263 -157
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/interpretation/index.ts +26 -4
- package/test/unit/spec/locus-info/controlsUtils.js +172 -57
- package/test/unit/spec/locus-info/index.js +475 -81
- package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
- package/test/unit/spec/meeting/index.js +902 -14
- package/test/unit/spec/meeting/muteState.js +3 -0
- package/test/unit/spec/meeting/utils.js +33 -0
- package/test/unit/spec/meetings/index.js +309 -10
- package/test/unit/spec/meetings/request.js +141 -0
- package/test/unit/spec/meetings/utils.js +161 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/recording-controller/index.js +9 -8
- package/test/unit/spec/webinar/index.ts +81 -16
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import 'jsdom-global/register';
|
|
2
|
+
import sinon from 'sinon';
|
|
3
|
+
import {assert} from '@webex/test-helper-chai';
|
|
4
|
+
import MockWebex from '@webex/test-helper-mock-webex';
|
|
5
|
+
import Meetings from '@webex/plugin-meetings';
|
|
6
|
+
import ParameterError from '@webex/plugin-meetings/src/common/errors/parameter';
|
|
7
|
+
import MeetingRequest from '@webex/plugin-meetings/src/meetings/request';
|
|
8
|
+
import {SitePreferenceSelectOption} from '@webex/plugin-meetings/src/meetings/meetings.types';
|
|
9
|
+
|
|
10
|
+
const multipartSitePrefixList = ['.my.', '.mydmz.', '.mybts.', '.mydev.', '.myats2.', '.myats.'];
|
|
11
|
+
|
|
12
|
+
describe('plugin-meetings/meetings/request', () => {
|
|
13
|
+
let meetingRequest;
|
|
14
|
+
let request;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
const webex = new MockWebex({
|
|
18
|
+
children: {
|
|
19
|
+
meetings: Meetings,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
request = sinon.stub().resolves({
|
|
24
|
+
body: {
|
|
25
|
+
scheduling: {
|
|
26
|
+
supportScheduleWebinar: true,
|
|
27
|
+
webinarWebLink: 'https://go.webex.com/webappng/sites/go/webinar/scheduler',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
meetingRequest = new MeetingRequest(
|
|
33
|
+
{},
|
|
34
|
+
{
|
|
35
|
+
parent: webex,
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
meetingRequest.request = request;
|
|
39
|
+
meetingRequest.config.meetings.multipartSitePrefixList = multipartSitePrefixList;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
sinon.restore();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('#fetchSitePreferencesMeViaSite', () => {
|
|
47
|
+
const assertRequest = (expectedOptions) => {
|
|
48
|
+
assert.calledOnceWithExactly(request, expectedOptions);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
it('throws a parameter error when no Webex site is available', () => {
|
|
52
|
+
assert.throws(
|
|
53
|
+
() => meetingRequest.fetchSitePreferencesMeViaSite(),
|
|
54
|
+
ParameterError,
|
|
55
|
+
'No siteUrl available. Call register() before fetching site preferences or provide options.siteUrl.'
|
|
56
|
+
);
|
|
57
|
+
assert.notCalled(request);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('fetches scheduling preferences by default', async () => {
|
|
61
|
+
const result = await meetingRequest.fetchSitePreferencesMeViaSite({siteUrl: 'go.webex.com'});
|
|
62
|
+
|
|
63
|
+
assert.deepEqual(result, {
|
|
64
|
+
scheduling: {
|
|
65
|
+
supportScheduleWebinar: true,
|
|
66
|
+
webinarWebLink: 'https://go.webex.com/webappng/sites/go/webinar/scheduler',
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
assertRequest({
|
|
70
|
+
method: 'GET',
|
|
71
|
+
uri: 'https://go.webex.com/wbxappapi/v1/users/me/preference?select=scheduling&siteurl=go',
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('derives the site name for my.webex.com sites', async () => {
|
|
76
|
+
await meetingRequest.fetchSitePreferencesMeViaSite({siteUrl: 'go.my.webex.com'});
|
|
77
|
+
|
|
78
|
+
assertRequest({
|
|
79
|
+
method: 'GET',
|
|
80
|
+
uri: 'https://go.my.webex.com/wbxappapi/v1/users/me/preference?select=scheduling&siteurl=go.my',
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('uses the configured multipart site prefix list to derive the site name', async () => {
|
|
85
|
+
meetingRequest.config.meetings.multipartSitePrefixList = ['.custom.'];
|
|
86
|
+
|
|
87
|
+
await meetingRequest.fetchSitePreferencesMeViaSite({siteUrl: 'go.my.webex.com'});
|
|
88
|
+
|
|
89
|
+
assertRequest({
|
|
90
|
+
method: 'GET',
|
|
91
|
+
uri: 'https://go.my.webex.com/wbxappapi/v1/users/me/preference?select=scheduling&siteurl=go',
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('falls back to the first label when no multipart site prefix list is configured', async () => {
|
|
96
|
+
delete meetingRequest.config.meetings.multipartSitePrefixList;
|
|
97
|
+
|
|
98
|
+
await meetingRequest.fetchSitePreferencesMeViaSite({siteUrl: 'go.my.webex.com'});
|
|
99
|
+
|
|
100
|
+
assertRequest({
|
|
101
|
+
method: 'GET',
|
|
102
|
+
uri: 'https://go.my.webex.com/wbxappapi/v1/users/me/preference?select=scheduling&siteurl=go',
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('supports custom site name overrides', async () => {
|
|
107
|
+
await meetingRequest.fetchSitePreferencesMeViaSite({
|
|
108
|
+
siteUrl: 'go.my.webex.com',
|
|
109
|
+
siteName: 'custom-site',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
assertRequest({
|
|
113
|
+
method: 'GET',
|
|
114
|
+
uri: 'https://go.my.webex.com/wbxappapi/v1/users/me/preference?select=scheduling&siteurl=custom-site',
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('supports enum-backed preference sections', async () => {
|
|
119
|
+
await meetingRequest.fetchSitePreferencesMeViaSite({
|
|
120
|
+
siteUrl: 'go.webex.com',
|
|
121
|
+
selectOptions: [SitePreferenceSelectOption.SCHEDULING],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
assertRequest({
|
|
125
|
+
method: 'GET',
|
|
126
|
+
uri: 'https://go.webex.com/wbxappapi/v1/users/me/preference?select=scheduling&siteurl=go',
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('does not suppress request errors', async () => {
|
|
131
|
+
const error = new Error('site preferences failed');
|
|
132
|
+
|
|
133
|
+
request.rejects(error);
|
|
134
|
+
|
|
135
|
+
await assert.isRejected(
|
|
136
|
+
meetingRequest.fetchSitePreferencesMeViaSite({siteUrl: 'go.webex.com'}),
|
|
137
|
+
'site preferences failed'
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -5,6 +5,8 @@ import MeetingsUtil from '@webex/plugin-meetings/src/meetings/util';
|
|
|
5
5
|
import Metrics from '@webex/plugin-meetings/src/metrics';
|
|
6
6
|
import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
|
|
7
7
|
|
|
8
|
+
const multipartSitePrefixList = ['.my.', '.mydmz.', '.mybts.', '.mydev.', '.myats2.', '.myats.'];
|
|
9
|
+
|
|
8
10
|
describe('plugin-meetings', () => {
|
|
9
11
|
beforeEach(() => {
|
|
10
12
|
sinon.stub(Metrics, 'sendBehavioralMetric');
|
|
@@ -75,6 +77,28 @@ describe('plugin-meetings', () => {
|
|
|
75
77
|
});
|
|
76
78
|
});
|
|
77
79
|
|
|
80
|
+
describe('#getSiteName', () => {
|
|
81
|
+
it('gets the site name from a standard Webex site', () => {
|
|
82
|
+
assert.equal(MeetingsUtil.getSiteName('go.webex.com', multipartSitePrefixList), 'go');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('gets the site name from a my Webex site', () => {
|
|
86
|
+
assert.equal(MeetingsUtil.getSiteName('go.my.webex.com', multipartSitePrefixList), 'go.my');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('uses the configured multipart site prefix list', () => {
|
|
90
|
+
assert.equal(MeetingsUtil.getSiteName('go.custom.webex.com', ['.custom.']), 'go.custom');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('falls back to the first label when the multipart site prefix list does not match', () => {
|
|
94
|
+
assert.equal(MeetingsUtil.getSiteName('go.my.webex.com', ['.custom.']), 'go');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('returns null when the site is empty', () => {
|
|
98
|
+
assert.equal(MeetingsUtil.getSiteName('', multipartSitePrefixList), null);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
78
102
|
describe('#getThisDevice', () => {
|
|
79
103
|
it('return null if no devices in self', () => {
|
|
80
104
|
const newLocus = {};
|
|
@@ -128,6 +152,143 @@ describe('plugin-meetings', () => {
|
|
|
128
152
|
};
|
|
129
153
|
assert.equal(MeetingsUtil.isBreakoutLocusDTO(newLocus), false);
|
|
130
154
|
});
|
|
155
|
+
|
|
156
|
+
it('returns true if newLocus.info.isBreakout is true', () => {
|
|
157
|
+
const newLocus = {
|
|
158
|
+
info: {
|
|
159
|
+
isBreakout: true,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
assert.equal(MeetingsUtil.isBreakoutLocusDTO(newLocus), true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('returns false if newLocus.info.isBreakout is false', () => {
|
|
166
|
+
const newLocus = {
|
|
167
|
+
info: {
|
|
168
|
+
isBreakout: false,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
assert.equal(MeetingsUtil.isBreakoutLocusDTO(newLocus), false);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('returns true if both sessionType is BREAKOUT and info.isBreakout is true', () => {
|
|
175
|
+
const newLocus = {
|
|
176
|
+
controls: {
|
|
177
|
+
breakout: {
|
|
178
|
+
sessionType: 'BREAKOUT',
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
info: {
|
|
182
|
+
isBreakout: true,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
assert.equal(MeetingsUtil.isBreakoutLocusDTO(newLocus), true);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('#isMainAssociatedWithBreakout', () => {
|
|
190
|
+
it('returns true when breakout control url matches main locus breakout url', () => {
|
|
191
|
+
const mainLocus = {
|
|
192
|
+
url: 'main-locus-url',
|
|
193
|
+
controls: {
|
|
194
|
+
breakout: {
|
|
195
|
+
url: 'breakout-control-url',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
const breakoutLocus = {
|
|
200
|
+
controls: {
|
|
201
|
+
breakout: {
|
|
202
|
+
url: 'breakout-control-url',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
assert.equal(MeetingsUtil.isMainAssociatedWithBreakout(mainLocus, breakoutLocus), true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('returns true when breakout self device replaces the main locus url', () => {
|
|
211
|
+
const mainLocus = {
|
|
212
|
+
url: 'main-locus-url',
|
|
213
|
+
controls: {},
|
|
214
|
+
};
|
|
215
|
+
const breakoutLocus = {
|
|
216
|
+
controls: {
|
|
217
|
+
breakout: {
|
|
218
|
+
url: 'other-breakout-url',
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
self: {
|
|
222
|
+
deviceUrl: 'device-url-1',
|
|
223
|
+
devices: [
|
|
224
|
+
{
|
|
225
|
+
url: 'device-url-1',
|
|
226
|
+
replaces: [{locusUrl: 'main-locus-url'}],
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
assert.equal(MeetingsUtil.isMainAssociatedWithBreakout(mainLocus, breakoutLocus), true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('returns false when breakout locus is not associated with the main locus', () => {
|
|
236
|
+
const mainLocus = {
|
|
237
|
+
url: 'main-locus-url',
|
|
238
|
+
controls: {
|
|
239
|
+
breakout: {
|
|
240
|
+
url: 'breakout-control-url',
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
const breakoutLocus = {
|
|
245
|
+
controls: {
|
|
246
|
+
breakout: {
|
|
247
|
+
url: 'different-breakout-url',
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
self: {
|
|
251
|
+
deviceUrl: 'device-url-1',
|
|
252
|
+
devices: [
|
|
253
|
+
{
|
|
254
|
+
url: 'device-url-1',
|
|
255
|
+
replaces: [{locusUrl: 'another-main-locus-url'}],
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
assert.equal(MeetingsUtil.isMainAssociatedWithBreakout(mainLocus, breakoutLocus), false);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe('#isWholeMeetingEnded', () => {
|
|
266
|
+
[
|
|
267
|
+
{description: 'state is INACTIVE with no endMeetingReason', fullState: {state: 'INACTIVE'}, expected: true},
|
|
268
|
+
{description: 'state is INACTIVE with endMeetingReason OTHER', fullState: {state: 'INACTIVE', endMeetingReason: 'SOME_OTHER_REASON'}, expected: true},
|
|
269
|
+
{description: 'state is INACTIVE with endMeetingReason BREAKOUT_ENDED', fullState: {state: 'INACTIVE', endMeetingReason: 'BREAKOUT_ENDED'}, expected: false},
|
|
270
|
+
{description: 'state is not INACTIVE', fullState: {state: 'ACTIVE', endMeetingReason: 'SOME_OTHER_REASON'}, expected: false},
|
|
271
|
+
].forEach(({description, fullState, expected}) => {
|
|
272
|
+
it(`returns ${expected} when ${description}`, () => {
|
|
273
|
+
assert.equal(MeetingsUtil.isWholeMeetingEnded(fullState), expected);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('#isSelfMovedOrBreakoutEnded', () => {
|
|
279
|
+
[
|
|
280
|
+
{description: 'locus is undefined', locus: undefined, expected: false},
|
|
281
|
+
{description: 'self state is JOINED', locus: {self: {state: 'JOINED', reason: 'OTHER'}}, expected: false},
|
|
282
|
+
{description: 'self state is LEFT with reason MOVED', locus: {self: {state: 'LEFT', reason: 'MOVED'}}, expected: true},
|
|
283
|
+
{description: 'fullState is INACTIVE with BREAKOUT_ENDED', locus: {self: {state: 'LEFT', reason: 'OTHER'}, fullState: {state: 'INACTIVE', endMeetingReason: 'BREAKOUT_ENDED'}}, expected: true},
|
|
284
|
+
{description: 'fullState is INACTIVE with different endMeetingReason', locus: {self: {state: 'LEFT', reason: 'OTHER'}, fullState: {state: 'INACTIVE', endMeetingReason: 'SOME_OTHER_REASON'}}, expected: false},
|
|
285
|
+
{description: 'fullState is missing', locus: {self: {state: 'LEFT', reason: 'OTHER'}}, expected: false},
|
|
286
|
+
{description: 'endMeetingReason is missing', locus: {self: {state: 'LEFT', reason: 'OTHER'}, fullState: {state: 'INACTIVE'}}, expected: false},
|
|
287
|
+
].forEach(({description, locus, expected}) => {
|
|
288
|
+
it(`returns ${expected} when ${description}`, () => {
|
|
289
|
+
assert.equal(MeetingsUtil.isSelfMovedOrBreakoutEnded(locus), expected);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
131
292
|
});
|
|
132
293
|
|
|
133
294
|
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', () => {
|
|
@@ -35,6 +35,7 @@ describe('plugin-meetings', () => {
|
|
|
35
35
|
beforeEach(() => {
|
|
36
36
|
request = {
|
|
37
37
|
request: sinon.stub().returns(Promise.resolve()),
|
|
38
|
+
locusDeltaRequest: sinon.stub().returns(Promise.resolve()),
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
controller = new RecordingController(request);
|
|
@@ -69,13 +70,13 @@ describe('plugin-meetings', () => {
|
|
|
69
70
|
|
|
70
71
|
const result = controller.startRecording();
|
|
71
72
|
|
|
72
|
-
assert.calledWith(request.
|
|
73
|
+
assert.calledWith(request.locusDeltaRequest, {
|
|
73
74
|
uri: `${locusUrl}/controls`,
|
|
74
75
|
body: {record: {recording: true, paused: false}},
|
|
75
76
|
method: HTTP_VERBS.PATCH,
|
|
76
77
|
});
|
|
77
78
|
|
|
78
|
-
assert.deepEqual(result, request.
|
|
79
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
79
80
|
});
|
|
80
81
|
});
|
|
81
82
|
|
|
@@ -103,13 +104,13 @@ describe('plugin-meetings', () => {
|
|
|
103
104
|
|
|
104
105
|
const result = controller.stopRecording();
|
|
105
106
|
|
|
106
|
-
assert.calledWith(request.
|
|
107
|
+
assert.calledWith(request.locusDeltaRequest, {
|
|
107
108
|
uri: `${locusUrl}/controls`,
|
|
108
109
|
body: {record: {recording: false, paused: false}},
|
|
109
110
|
method: HTTP_VERBS.PATCH,
|
|
110
111
|
});
|
|
111
112
|
|
|
112
|
-
assert.deepEqual(result, request.
|
|
113
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
113
114
|
});
|
|
114
115
|
});
|
|
115
116
|
|
|
@@ -139,13 +140,13 @@ describe('plugin-meetings', () => {
|
|
|
139
140
|
|
|
140
141
|
const result = controller.pauseRecording();
|
|
141
142
|
|
|
142
|
-
assert.calledWith(request.
|
|
143
|
+
assert.calledWith(request.locusDeltaRequest, {
|
|
143
144
|
uri: `${locusUrl}/controls`,
|
|
144
145
|
body: {record: {recording: true, paused: true}},
|
|
145
146
|
method: HTTP_VERBS.PATCH,
|
|
146
147
|
});
|
|
147
148
|
|
|
148
|
-
assert.deepEqual(result, request.
|
|
149
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
149
150
|
});
|
|
150
151
|
});
|
|
151
152
|
|
|
@@ -176,13 +177,13 @@ describe('plugin-meetings', () => {
|
|
|
176
177
|
|
|
177
178
|
const result = controller.resumeRecording();
|
|
178
179
|
|
|
179
|
-
assert.calledWith(request.
|
|
180
|
+
assert.calledWith(request.locusDeltaRequest, {
|
|
180
181
|
uri: `${locusUrl}/controls`,
|
|
181
182
|
body: {record: {recording: true, paused: false}},
|
|
182
183
|
method: HTTP_VERBS.PATCH,
|
|
183
184
|
});
|
|
184
185
|
|
|
185
|
-
assert.deepEqual(result, request.
|
|
186
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
186
187
|
});
|
|
187
188
|
});
|
|
188
189
|
});
|
|
@@ -176,6 +176,42 @@ describe('plugin-meetings', () => {
|
|
|
176
176
|
});
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
+
describe('#getValidatedWebinarMeeting', () => {
|
|
180
|
+
it('returns the meeting when its locusUrl matches the webinar locusUrl', () => {
|
|
181
|
+
const meeting = {locusUrl: 'locusUrl'};
|
|
182
|
+
webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
|
|
183
|
+
webinar.locusUrl = 'locusUrl';
|
|
184
|
+
|
|
185
|
+
assert.equal(webinar.getValidatedWebinarMeeting(), meeting);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('returns undefined and warns when the resolved meeting locusUrl does not match', () => {
|
|
189
|
+
const warnStub = sinon.stub(LoggerProxy.logger, 'warn');
|
|
190
|
+
const meeting = {locusUrl: 'other-locus-url'};
|
|
191
|
+
webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
|
|
192
|
+
webinar.locusUrl = 'locusUrl';
|
|
193
|
+
|
|
194
|
+
assert.isUndefined(webinar.getValidatedWebinarMeeting());
|
|
195
|
+
assert.calledOnce(warnStub);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('returns undefined when no meeting is resolved', () => {
|
|
199
|
+
webex.meetings.getMeetingByType = sinon.stub().returns(undefined);
|
|
200
|
+
|
|
201
|
+
assert.isUndefined(webinar.getValidatedWebinarMeeting());
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('returns undefined and warns when webinar locusUrl is not yet initialized', () => {
|
|
205
|
+
const warnStub = sinon.stub(LoggerProxy.logger, 'warn');
|
|
206
|
+
const meeting = {locusUrl: 'some-url'};
|
|
207
|
+
webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
|
|
208
|
+
webinar.locusUrl = undefined;
|
|
209
|
+
|
|
210
|
+
assert.isUndefined(webinar.getValidatedWebinarMeeting());
|
|
211
|
+
assert.calledOnce(warnStub);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
179
215
|
describe('#cleanUp', () => {
|
|
180
216
|
it('delegates to cleanupPSDataChannel', () => {
|
|
181
217
|
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
@@ -187,17 +223,14 @@ describe('plugin-meetings', () => {
|
|
|
187
223
|
});
|
|
188
224
|
|
|
189
225
|
describe('#cleanupPSDataChannel', () => {
|
|
190
|
-
let
|
|
226
|
+
let relayListener;
|
|
191
227
|
|
|
192
228
|
beforeEach(() => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
|
|
229
|
+
relayListener = sinon.stub();
|
|
230
|
+
webinar._practiceSessionRelayListener = relayListener;
|
|
198
231
|
});
|
|
199
232
|
|
|
200
|
-
it('disconnects the practice session channel and removes the relay listener', async () => {
|
|
233
|
+
it('disconnects the practice session channel and removes the tracked relay listener', async () => {
|
|
201
234
|
await webinar.cleanupPSDataChannel();
|
|
202
235
|
|
|
203
236
|
assert.calledOnceWithExactly(
|
|
@@ -208,8 +241,28 @@ describe('plugin-meetings', () => {
|
|
|
208
241
|
assert.calledOnceWithExactly(
|
|
209
242
|
webex.internal.llm.off,
|
|
210
243
|
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
211
|
-
|
|
244
|
+
relayListener
|
|
212
245
|
);
|
|
246
|
+
assert.isNull(webinar._practiceSessionRelayListener);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('skips relay listener removal when no listener has been tracked', async () => {
|
|
250
|
+
webinar._practiceSessionRelayListener = null;
|
|
251
|
+
|
|
252
|
+
await webinar.cleanupPSDataChannel();
|
|
253
|
+
|
|
254
|
+
const relayOffCalls = webex.internal.llm.off.args.filter(
|
|
255
|
+
([event]) => event === `event:relay.event:${LLM_PRACTICE_SESSION}`
|
|
256
|
+
);
|
|
257
|
+
assert.equal(relayOffCalls.length, 0);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('does not consult the meeting collection during cleanup', async () => {
|
|
261
|
+
webex.meetings.getMeetingByType = sinon.stub();
|
|
262
|
+
|
|
263
|
+
await webinar.cleanupPSDataChannel();
|
|
264
|
+
|
|
265
|
+
assert.notCalled(webex.meetings.getMeetingByType);
|
|
213
266
|
});
|
|
214
267
|
|
|
215
268
|
it('removes a pending online listener if one exists', async () => {
|
|
@@ -240,6 +293,7 @@ describe('plugin-meetings', () => {
|
|
|
240
293
|
beforeEach(() => {
|
|
241
294
|
processRelayEvent = sinon.stub();
|
|
242
295
|
meeting = {
|
|
296
|
+
locusUrl: 'locusUrl',
|
|
243
297
|
isJoined: sinon.stub().returns(true),
|
|
244
298
|
processRelayEvent,
|
|
245
299
|
locusInfo: {
|
|
@@ -418,19 +472,30 @@ describe('plugin-meetings', () => {
|
|
|
418
472
|
assert.calledOnce(webex.internal.llm.registerAndConnect);
|
|
419
473
|
});
|
|
420
474
|
|
|
421
|
-
it('
|
|
475
|
+
it('tracks and binds the relay listener after successful connect', async () => {
|
|
422
476
|
await webinar.updatePSDataChannel();
|
|
423
477
|
|
|
478
|
+
// Stores the exact listener reference for deterministic cleanup
|
|
479
|
+
assert.equal(webinar._practiceSessionRelayListener, processRelayEvent);
|
|
424
480
|
assert.calledWith(
|
|
425
|
-
webex.internal.llm.
|
|
481
|
+
webex.internal.llm.on,
|
|
426
482
|
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
427
483
|
processRelayEvent
|
|
428
484
|
);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('removes a previously tracked relay listener before re-binding on reconnect', async () => {
|
|
488
|
+
const previousListener = sinon.stub();
|
|
489
|
+
webinar._practiceSessionRelayListener = previousListener;
|
|
490
|
+
|
|
491
|
+
await webinar.updatePSDataChannel();
|
|
492
|
+
|
|
429
493
|
assert.calledWith(
|
|
430
|
-
webex.internal.llm.
|
|
494
|
+
webex.internal.llm.off,
|
|
431
495
|
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
432
|
-
|
|
496
|
+
previousListener
|
|
433
497
|
);
|
|
498
|
+
assert.equal(webinar._practiceSessionRelayListener, processRelayEvent);
|
|
434
499
|
});
|
|
435
500
|
|
|
436
501
|
it('subscribes to transcription when caption intent is enabled', async () => {
|
|
@@ -551,7 +616,7 @@ describe('plugin-meetings', () => {
|
|
|
551
616
|
updateMediaShares = sinon.stub()
|
|
552
617
|
webinar.webex.meetings = {
|
|
553
618
|
getMeetingByType: sinon.stub().returns({
|
|
554
|
-
id: 'meeting-id',
|
|
619
|
+
id: 'meeting-id', locusUrl: 'locusUrl',
|
|
555
620
|
isJoined: sinon.stub().returns(false),
|
|
556
621
|
updateLLMConnection: sinon.stub(),
|
|
557
622
|
shareStatus: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE,
|
|
@@ -606,7 +671,7 @@ describe('plugin-meetings', () => {
|
|
|
606
671
|
|
|
607
672
|
webinar.webex.meetings = {
|
|
608
673
|
getMeetingByType: sinon.stub().returns({
|
|
609
|
-
id: 'meeting-id',
|
|
674
|
+
id: 'meeting-id', locusUrl: 'locusUrl',
|
|
610
675
|
isJoined: sinon.stub().returns(false),
|
|
611
676
|
updateLLMConnection: sinon.stub(),
|
|
612
677
|
shareStatus: SHARE_STATUS.REMOTE_SHARE_ACTIVE,
|
|
@@ -1034,7 +1099,7 @@ describe('plugin-meetings', () => {
|
|
|
1034
1099
|
// @ts-ignore
|
|
1035
1100
|
webinar.webex.meetings = {
|
|
1036
1101
|
getMeetingByType: sinon.stub().returns({
|
|
1037
|
-
id: 'meeting-id',
|
|
1102
|
+
id: 'meeting-id', locusUrl: 'locusUrl',
|
|
1038
1103
|
locusInfo: {
|
|
1039
1104
|
links:{
|
|
1040
1105
|
resources: {
|
|
@@ -1051,7 +1116,7 @@ describe('plugin-meetings', () => {
|
|
|
1051
1116
|
it('throws an error if attendeeSearchUrl is not available', async () => {
|
|
1052
1117
|
webinar.webex.meetings = {
|
|
1053
1118
|
getMeetingByType: sinon.stub().returns({
|
|
1054
|
-
id: 'meeting-id',
|
|
1119
|
+
id: 'meeting-id', locusUrl: 'locusUrl',
|
|
1055
1120
|
locusInfo: {
|
|
1056
1121
|
links:{
|
|
1057
1122
|
resources: {
|