@webex/plugin-meetings 3.12.0-next.45 → 3.12.0-next.47

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.
Files changed (36) hide show
  1. package/dist/aiEnableRequest/index.js +1 -1
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/controls-options-manager/index.js +17 -5
  5. package/dist/controls-options-manager/index.js.map +1 -1
  6. package/dist/hashTree/hashTreeParser.js +44 -13
  7. package/dist/hashTree/hashTreeParser.js.map +1 -1
  8. package/dist/interpretation/index.js +10 -1
  9. package/dist/interpretation/index.js.map +1 -1
  10. package/dist/interpretation/siLanguage.js +1 -1
  11. package/dist/meeting/util.js +15 -2
  12. package/dist/meeting/util.js.map +1 -1
  13. package/dist/metrics/constants.js +2 -1
  14. package/dist/metrics/constants.js.map +1 -1
  15. package/dist/recording-controller/index.js +1 -3
  16. package/dist/recording-controller/index.js.map +1 -1
  17. package/dist/types/controls-options-manager/index.d.ts +10 -0
  18. package/dist/types/hashTree/hashTreeParser.d.ts +8 -0
  19. package/dist/types/meeting/util.d.ts +7 -0
  20. package/dist/types/metrics/constants.d.ts +1 -0
  21. package/dist/webinar/index.js +6 -1
  22. package/dist/webinar/index.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/controls-options-manager/index.ts +22 -6
  25. package/src/hashTree/hashTreeParser.ts +29 -1
  26. package/src/interpretation/index.ts +25 -8
  27. package/src/meeting/util.ts +16 -2
  28. package/src/metrics/constants.ts +1 -0
  29. package/src/recording-controller/index.ts +1 -2
  30. package/src/webinar/index.ts +13 -4
  31. package/test/unit/spec/controls-options-manager/index.js +35 -32
  32. package/test/unit/spec/hashTree/hashTreeParser.ts +158 -0
  33. package/test/unit/spec/interpretation/index.ts +26 -4
  34. package/test/unit/spec/meeting/muteState.js +3 -0
  35. package/test/unit/spec/meeting/utils.js +25 -0
  36. package/test/unit/spec/recording-controller/index.js +9 -8
@@ -27,6 +27,7 @@ describe('plugin-meetings', () => {
27
27
  beforeEach(() => {
28
28
  request = {
29
29
  request: sinon.stub().returns(Promise.resolve()),
30
+ locusDeltaRequest: sinon.stub().returns(Promise.resolve()),
30
31
  };
31
32
 
32
33
  manager = new ControlsOptionsManager(request);
@@ -59,11 +60,11 @@ describe('plugin-meetings', () => {
59
60
 
60
61
  const result = manager.setMuteOnEntry(true);
61
62
 
62
- assert.calledWith(request.request, { uri: 'test/id/controls',
63
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
63
64
  body: { muteOnEntry: { enabled: true } },
64
65
  method: HTTP_VERBS.PATCH});
65
66
 
66
- assert.deepEqual(result, request.request.firstCall.returnValue);
67
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
67
68
  });
68
69
 
69
70
  it('can set mute on entry when the display hint is available enabled=false', () => {
@@ -71,11 +72,11 @@ describe('plugin-meetings', () => {
71
72
 
72
73
  const result = manager.setMuteOnEntry(false);
73
74
 
74
- assert.calledWith(request.request, { uri: 'test/id/controls',
75
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
75
76
  body: { muteOnEntry: { enabled: false } },
76
77
  method: HTTP_VERBS.PATCH});
77
78
 
78
- assert.deepEqual(result, request.request.firstCall.returnValue);
79
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
79
80
  });
80
81
 
81
82
  it('should send setMuteOnEntry to locusUrl without authorizingLocusUrl when in breakout', () => {
@@ -84,11 +85,11 @@ describe('plugin-meetings', () => {
84
85
 
85
86
  const result = manager.setMuteOnEntry(true);
86
87
 
87
- assert.calledWith(request.request, { uri: 'test/id/controls',
88
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
88
89
  body: { muteOnEntry: { enabled: true } },
89
90
  method: HTTP_VERBS.PATCH});
90
91
 
91
- assert.deepEqual(result, request.request.firstCall.returnValue);
92
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
92
93
  });
93
94
  });
94
95
 
@@ -114,11 +115,11 @@ describe('plugin-meetings', () => {
114
115
 
115
116
  const result = manager.setDisallowUnmute(true);
116
117
 
117
- assert.calledWith(request.request, { uri: 'test/id/controls',
118
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
118
119
  body: { disallowUnmute: { enabled: true } },
119
120
  method: HTTP_VERBS.PATCH});
120
121
 
121
- assert.deepEqual(result, request.request.firstCall.returnValue);
122
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
122
123
  });
123
124
 
124
125
  it('can set allow unmute when DISABLE_HARD_MUTE display hint is available', () => {
@@ -126,11 +127,11 @@ describe('plugin-meetings', () => {
126
127
 
127
128
  const result = manager.setDisallowUnmute(false);
128
129
 
129
- assert.calledWith(request.request, { uri: 'test/id/controls',
130
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
130
131
  body: { disallowUnmute: { enabled: false } },
131
132
  method: HTTP_VERBS.PATCH});
132
133
 
133
- assert.deepEqual(result, request.request.firstCall.returnValue);
134
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
134
135
  });
135
136
 
136
137
  it('should send setDisallowUnmute to locusUrl without authorizingLocusUrl when in breakout', () => {
@@ -139,11 +140,11 @@ describe('plugin-meetings', () => {
139
140
 
140
141
  const result = manager.setDisallowUnmute(true);
141
142
 
142
- assert.calledWith(request.request, { uri: 'test/id/controls',
143
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
143
144
  body: { disallowUnmute: { enabled: true } },
144
145
  method: HTTP_VERBS.PATCH});
145
146
 
146
- assert.deepEqual(result, request.request.firstCall.returnValue);
147
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
147
148
  });
148
149
  });
149
150
  });
@@ -154,6 +155,7 @@ describe('plugin-meetings', () => {
154
155
  beforeEach(() => {
155
156
  request = {
156
157
  request: sinon.stub().resolves(),
158
+ locusDeltaRequest: sinon.stub().resolves(),
157
159
  };
158
160
 
159
161
  manager = new ControlsOptionsManager(request);
@@ -202,7 +204,7 @@ describe('plugin-meetings', () => {
202
204
 
203
205
  return manager.update(audio, reactions)
204
206
  .then(() => {
205
- assert.calledWith(request.request, {
207
+ assert.calledWith(request.locusDeltaRequest, {
206
208
  uri: 'test/id/controls',
207
209
  body: {
208
210
  audio: audio.properties,
@@ -210,7 +212,7 @@ describe('plugin-meetings', () => {
210
212
  method: HTTP_VERBS.PATCH,
211
213
  });
212
214
 
213
- assert.calledWith(request.request, {
215
+ assert.calledWith(request.locusDeltaRequest, {
214
216
  uri: 'test/id/controls',
215
217
  body: {
216
218
  reactions: reactions.properties,
@@ -253,7 +255,7 @@ describe('plugin-meetings', () => {
253
255
  return manager.update(audio, reactions)
254
256
  .then(() => {
255
257
  // Audio controls go directly to current locusUrl (no cross-locus authorization)
256
- assert.calledWith(request.request, {
258
+ assert.calledWith(request.locusDeltaRequest, {
257
259
  uri: 'test/id/controls',
258
260
  body: {
259
261
  audio: audio.properties,
@@ -284,7 +286,7 @@ describe('plugin-meetings', () => {
284
286
 
285
287
  return manager.update(audio)
286
288
  .then(() => {
287
- assert.calledWith(request.request, {
289
+ assert.calledWith(request.locusDeltaRequest, {
288
290
  uri: 'test/id/controls',
289
291
  body: {
290
292
  audio: audio.properties,
@@ -324,6 +326,7 @@ describe('plugin-meetings', () => {
324
326
  beforeEach(() => {
325
327
  request = {
326
328
  request: sinon.stub().returns(Promise.resolve()),
329
+ locusDeltaRequest: sinon.stub().returns(Promise.resolve()),
327
330
  };
328
331
 
329
332
  manager = new ControlsOptionsManager(request);
@@ -368,11 +371,11 @@ describe('plugin-meetings', () => {
368
371
 
369
372
  const result = manager.setMuteAll(true, true, true);
370
373
 
371
- assert.calledWith(request.request, { uri: 'test/id/controls',
374
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
372
375
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true } },
373
376
  method: HTTP_VERBS.PATCH});
374
377
 
375
- assert.deepEqual(result, request.request.firstCall.returnValue);
378
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
376
379
  });
377
380
 
378
381
  it('can set mute all when the display hint is available mutedEnabled=true', () => {
@@ -380,11 +383,11 @@ describe('plugin-meetings', () => {
380
383
 
381
384
  const result = manager.setMuteAll(true, true, true);
382
385
 
383
- assert.calledWith(request.request, { uri: 'test/id/controls',
386
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
384
387
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true } },
385
388
  method: HTTP_VERBS.PATCH});
386
389
 
387
- assert.deepEqual(result, request.request.firstCall.returnValue);
390
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
388
391
  });
389
392
 
390
393
  it('can set mute all when the display hint is available mutedEnabled=true', () => {
@@ -392,11 +395,11 @@ describe('plugin-meetings', () => {
392
395
 
393
396
  const result = manager.setMuteAll(true, true, true);
394
397
 
395
- assert.calledWith(request.request, { uri: 'test/id/controls',
398
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
396
399
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true } },
397
400
  method: HTTP_VERBS.PATCH});
398
401
 
399
- assert.deepEqual(result, request.request.firstCall.returnValue);
402
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
400
403
  });
401
404
 
402
405
  it('can set mute all when the display hint is available mutedEnabled=false', () => {
@@ -404,11 +407,11 @@ describe('plugin-meetings', () => {
404
407
 
405
408
  const result = manager.setMuteAll(false, false, false);
406
409
 
407
- assert.calledWith(request.request, { uri: 'test/id/controls',
410
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
408
411
  body: { audio: { muted: false, disallowUnmute: false, muteOnEntry: false } },
409
412
  method: HTTP_VERBS.PATCH});
410
413
 
411
- assert.deepEqual(result, request.request.firstCall.returnValue);
414
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
412
415
  });
413
416
 
414
417
  it('can set mute all panelists when the display hint is available mutedEnabled=true', () => {
@@ -416,11 +419,11 @@ describe('plugin-meetings', () => {
416
419
 
417
420
  const result = manager.setMuteAll(true, true, true, ['panelist']);
418
421
 
419
- assert.calledWith(request.request, { uri: 'test/id/controls',
422
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
420
423
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['panelist'] } },
421
424
  method: HTTP_VERBS.PATCH});
422
425
 
423
- assert.deepEqual(result, request.request.firstCall.returnValue);
426
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
424
427
  });
425
428
 
426
429
  it('can set mute all attendees when the display hint is available mutedEnabled=true', () => {
@@ -428,11 +431,11 @@ describe('plugin-meetings', () => {
428
431
 
429
432
  const result = manager.setMuteAll(true, true, true, ['attendee']);
430
433
 
431
- assert.calledWith(request.request, { uri: 'test/id/controls',
434
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
432
435
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['attendee'] } },
433
436
  method: HTTP_VERBS.PATCH});
434
437
 
435
- assert.deepEqual(result, request.request.firstCall.returnValue);
438
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
436
439
  });
437
440
 
438
441
  it('should send setMuteAll to locusUrl without authorizingLocusUrl when in breakout', () => {
@@ -441,11 +444,11 @@ describe('plugin-meetings', () => {
441
444
 
442
445
  const result = manager.setMuteAll(true, true, true, ['attendee']);
443
446
 
444
- assert.calledWith(request.request, { uri: 'test/id/controls',
447
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
445
448
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['attendee'] } },
446
449
  method: HTTP_VERBS.PATCH});
447
450
 
448
- assert.deepEqual(result, request.request.firstCall.returnValue);
451
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
449
452
  });
450
453
 
451
454
  it('should send setMuteAll with PANELIST role to locusUrl without authorizingLocusUrl when in breakout', () => {
@@ -454,11 +457,11 @@ describe('plugin-meetings', () => {
454
457
 
455
458
  const result = manager.setMuteAll(true, true, true, ['PANELIST']);
456
459
 
457
- assert.calledWith(request.request, { uri: 'test/id/controls',
460
+ assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
458
461
  body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['PANELIST'] } },
459
462
  method: HTTP_VERBS.PATCH});
460
463
 
461
- assert.deepEqual(result, request.request.firstCall.returnValue);
464
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
462
465
  });
463
466
  });
464
467
  });
@@ -8,6 +8,8 @@ import sinon from 'sinon';
8
8
  import {assert} from '@webex/test-helper-chai';
9
9
  import {EMPTY_HASH} from '@webex/plugin-meetings/src/hashTree/constants';
10
10
  import { some } from 'lodash';
11
+ import Metrics from '@webex/plugin-meetings/src/metrics';
12
+ import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
11
13
 
12
14
  const visibleDataSetsUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/visibleDataSets';
13
15
 
@@ -152,16 +154,19 @@ describe('HashTreeParser', () => {
152
154
  let webexRequest: sinon.SinonStub;
153
155
  let callback: sinon.SinonStub;
154
156
  let mathRandomStub: sinon.SinonStub;
157
+ let metricsStub: sinon.SinonStub;
155
158
 
156
159
  beforeEach(() => {
157
160
  clock = sinon.useFakeTimers();
158
161
  webexRequest = sinon.stub();
159
162
  callback = sinon.stub();
160
163
  mathRandomStub = sinon.stub(Math, 'random').returns(0);
164
+ metricsStub = sinon.stub(Metrics, 'sendBehavioralMetric');
161
165
  });
162
166
  afterEach(() => {
163
167
  clock.restore();
164
168
  mathRandomStub.restore();
169
+ metricsStub.restore();
165
170
  });
166
171
 
167
172
  // Helper to create a HashTreeParser instance with common defaults
@@ -1817,6 +1822,9 @@ describe('HashTreeParser', () => {
1817
1822
  assert.isUndefined(ds.timer);
1818
1823
  assert.isUndefined(ds.heartbeatWatchdogTimer);
1819
1824
  });
1825
+
1826
+ // Verify no sync failure metric was sent for end-meeting sentinel
1827
+ assert.notCalled(metricsStub);
1820
1828
  });
1821
1829
 
1822
1830
  it(`when /sync returns ${statusCode}`, async () => {
@@ -1880,6 +1888,9 @@ describe('HashTreeParser', () => {
1880
1888
  assert.isUndefined(ds.timer);
1881
1889
  assert.isUndefined(ds.heartbeatWatchdogTimer);
1882
1890
  });
1891
+
1892
+ // Verify no sync failure metric was sent for end-meeting sentinel
1893
+ assert.notCalled(metricsStub);
1883
1894
  });
1884
1895
  });
1885
1896
  });
@@ -2125,6 +2136,153 @@ describe('HashTreeParser', () => {
2125
2136
  })
2126
2137
  );
2127
2138
  });
2139
+
2140
+ it('updates dataSet.leafCount when hash tree is resized during sync so that the sync request has the correct leafCount', async () => {
2141
+ const parser = createHashTreeParser();
2142
+
2143
+ // Send a heartbeat with a mismatched root hash to trigger runSyncAlgorithm
2144
+ const heartbeatMessage = {
2145
+ dataSets: [
2146
+ {
2147
+ ...createDataSet('main', 16, 1100),
2148
+ root: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1', // different from ours
2149
+ },
2150
+ ],
2151
+ visibleDataSetsUrl,
2152
+ locusUrl,
2153
+ };
2154
+
2155
+ parser.handleMessage(heartbeatMessage, 'heartbeat with mismatch');
2156
+
2157
+ // The sync timer should be set
2158
+ expect(parser.dataSets.main.timer).to.not.be.undefined;
2159
+
2160
+ const mainDataSetUrl = parser.dataSets.main.url;
2161
+ const newLeafCount = 32;
2162
+
2163
+ // Mock getHashesFromLocus response with a DIFFERENT leafCount (32 instead of 16)
2164
+ mockGetHashesFromLocusResponse(
2165
+ mainDataSetUrl,
2166
+ new Array(newLeafCount).fill('00000000000000000000000000000000'),
2167
+ createDataSet('main', newLeafCount, 1101)
2168
+ );
2169
+
2170
+ // Mock the sync request - use matching root hash
2171
+ const syncResponseDataSet = createDataSet('main', newLeafCount, 1102);
2172
+ syncResponseDataSet.root = parser.dataSets.main.hashTree.getRootHash();
2173
+ mockSendSyncRequestResponse(mainDataSetUrl, {
2174
+ dataSets: [syncResponseDataSet],
2175
+ visibleDataSetsUrl,
2176
+ locusUrl,
2177
+ locusStateElements: [],
2178
+ });
2179
+
2180
+ // Advance time to fire the sync timer (idleMs=1000 + backoff=0)
2181
+ await clock.tickAsync(1000);
2182
+
2183
+ // Verify the sync request was sent with the NEW leafCount (32), not the old one (16)
2184
+ assert.calledWith(
2185
+ webexRequest,
2186
+ sinon.match({
2187
+ method: 'POST',
2188
+ uri: `${mainDataSetUrl}/sync`,
2189
+ body: sinon.match({
2190
+ leafCount: newLeafCount,
2191
+ }),
2192
+ })
2193
+ );
2194
+ });
2195
+
2196
+ it('sends HASH_TREE_SYNC_FAILURE metric when GET /hashtree request fails', async () => {
2197
+ const parser = createHashTreeParser();
2198
+
2199
+ // Send a heartbeat with a mismatched root hash to trigger runSyncAlgorithm
2200
+ const heartbeatMessage = {
2201
+ dataSets: [
2202
+ {
2203
+ ...createDataSet('main', 16, 1100),
2204
+ root: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1',
2205
+ },
2206
+ ],
2207
+ visibleDataSetsUrl,
2208
+ locusUrl,
2209
+ };
2210
+
2211
+ parser.handleMessage(heartbeatMessage, 'heartbeat with mismatch');
2212
+
2213
+ const mainDataSetUrl = parser.dataSets.main.url;
2214
+ const hashTreeError = new Error('server error') as any;
2215
+ hashTreeError.statusCode = 500;
2216
+
2217
+ webexRequest
2218
+ .withArgs(
2219
+ sinon.match({
2220
+ method: 'GET',
2221
+ uri: `${mainDataSetUrl}/hashtree`,
2222
+ })
2223
+ )
2224
+ .rejects(hashTreeError);
2225
+
2226
+ await clock.tickAsync(1000);
2227
+
2228
+ assert.calledOnceWithExactly(metricsStub, BEHAVIORAL_METRICS.HASH_TREE_SYNC_FAILURE, {
2229
+ debugId: 'test',
2230
+ dataSetName: 'main',
2231
+ request: 'GET /hashtree',
2232
+ statusCode: 500,
2233
+ reason: 'server error',
2234
+ });
2235
+ });
2236
+
2237
+ it('sends HASH_TREE_SYNC_FAILURE metric when POST /sync request fails', async () => {
2238
+ const parser = createHashTreeParser();
2239
+
2240
+ // Send a heartbeat with a mismatched root hash to trigger runSyncAlgorithm
2241
+ const heartbeatMessage = {
2242
+ dataSets: [
2243
+ {
2244
+ ...createDataSet('main', 16, 1100),
2245
+ root: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1',
2246
+ },
2247
+ ],
2248
+ visibleDataSetsUrl,
2249
+ locusUrl,
2250
+ };
2251
+
2252
+ parser.handleMessage(heartbeatMessage, 'heartbeat with mismatch');
2253
+
2254
+ const mainDataSetUrl = parser.dataSets.main.url;
2255
+
2256
+ // Mock getHashesFromLocus to succeed
2257
+ mockGetHashesFromLocusResponse(
2258
+ mainDataSetUrl,
2259
+ new Array(16).fill('00000000000000000000000000000000'),
2260
+ createDataSet('main', 16, 1101)
2261
+ );
2262
+
2263
+ // Mock sendSyncRequestToLocus to fail
2264
+ const syncError = new Error('sync failed') as any;
2265
+ syncError.statusCode = 500;
2266
+
2267
+ webexRequest
2268
+ .withArgs(
2269
+ sinon.match({
2270
+ method: 'POST',
2271
+ uri: `${mainDataSetUrl}/sync`,
2272
+ })
2273
+ )
2274
+ .rejects(syncError);
2275
+
2276
+ await clock.tickAsync(1000);
2277
+
2278
+ assert.calledOnceWithExactly(metricsStub, BEHAVIORAL_METRICS.HASH_TREE_SYNC_FAILURE, {
2279
+ debugId: 'test',
2280
+ dataSetName: 'main',
2281
+ request: 'POST /sync',
2282
+ statusCode: 500,
2283
+ reason: 'sync failed',
2284
+ });
2285
+ });
2128
2286
  });
2129
2287
 
2130
2288
  describe('handles visible data sets changes correctly', () => {
@@ -9,6 +9,7 @@ describe('plugin-meetings', () => {
9
9
  describe('SimultaneousInterpretation', () => {
10
10
  let webex;
11
11
  let interpretation;
12
+ let mockMeeting;
12
13
 
13
14
  beforeEach(() => {
14
15
  // @ts-ignore
@@ -17,8 +18,17 @@ describe('plugin-meetings', () => {
17
18
  interpretation = new SimultaneousInterpretation({}, {parent: webex});
18
19
  interpretation.locusUrl = 'locusUrl';
19
20
  webex.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
20
- webex.meetings = {};
21
- webex.meetings.getMeetingByType = sinon.stub();
21
+ mockMeeting = {
22
+ locusInfo: {
23
+ handleLocusAPIResponse: sinon.stub(),
24
+ },
25
+ };
26
+ webex.meetings = {
27
+ getMeetingByType: sinon.stub(),
28
+ meetingCollection: {
29
+ getByKey: sinon.stub().returns(mockMeeting),
30
+ },
31
+ };
22
32
  });
23
33
 
24
34
  describe('#initialize', () => {
@@ -316,7 +326,8 @@ describe('plugin-meetings', () => {
316
326
  order : 0,
317
327
  isActive : true
318
328
  },];
319
- webex.request.returns(Promise.resolve({}));
329
+ const mockResponse = {body: {locus: {url: 'locusUrl'}}};
330
+ webex.request.returns(Promise.resolve(mockResponse));
320
331
 
321
332
  await interpretation.updateInterpreters(sampleData);
322
333
  assert.calledOnceWithExactly(webex.request, {
@@ -328,6 +339,11 @@ describe('plugin-meetings', () => {
328
339
  },
329
340
  },
330
341
  });
342
+ assert.calledOnceWithExactly(
343
+ mockMeeting.locusInfo.handleLocusAPIResponse,
344
+ mockMeeting,
345
+ mockResponse.body
346
+ );
331
347
  });
332
348
 
333
349
  it('rejects with error', async () => {
@@ -354,7 +370,8 @@ describe('plugin-meetings', () => {
354
370
  order: 0,
355
371
  selfParticipantId: '123',
356
372
  });
357
- webex.request.returns(Promise.resolve({}));
373
+ const mockResponse = {body: {locus: {url: 'locusUrl'}}};
374
+ webex.request.returns(Promise.resolve(mockResponse));
358
375
 
359
376
  await interpretation.changeDirection();
360
377
  assert.calledOnceWithExactly(webex.request, {
@@ -369,6 +386,11 @@ describe('plugin-meetings', () => {
369
386
  },
370
387
  },
371
388
  });
389
+ assert.calledOnceWithExactly(
390
+ mockMeeting.locusInfo.handleLocusAPIResponse,
391
+ mockMeeting,
392
+ mockResponse.body
393
+ );
372
394
  });
373
395
 
374
396
  it('request rejects with error', async () => {
@@ -11,6 +11,7 @@ describe('plugin-meetings', () => {
11
11
  let audio;
12
12
  let video;
13
13
  let originalRemoteUpdateAudioVideo;
14
+ let originalUpdateLocusFromApiResponse;
14
15
 
15
16
  const fakeLocusResponse = {body: {locus: {info: 'this is a fake locus'}}};
16
17
 
@@ -45,6 +46,7 @@ describe('plugin-meetings', () => {
45
46
  };
46
47
 
47
48
  originalRemoteUpdateAudioVideo = MeetingUtil.remoteUpdateAudioVideo;
49
+ originalUpdateLocusFromApiResponse = MeetingUtil.updateLocusFromApiResponse;
48
50
 
49
51
  MeetingUtil.remoteUpdateAudioVideo = sinon.stub().resolves(fakeLocusResponse);
50
52
  MeetingUtil.updateLocusFromApiResponse = sinon.stub();
@@ -57,6 +59,7 @@ describe('plugin-meetings', () => {
57
59
 
58
60
  afterEach(() => {
59
61
  MeetingUtil.remoteUpdateAudioVideo = originalRemoteUpdateAudioVideo;
62
+ MeetingUtil.updateLocusFromApiResponse = originalUpdateLocusFromApiResponse;
60
63
  });
61
64
 
62
65
  describe('mute state library', () => {
@@ -276,6 +276,31 @@ describe('plugin-meetings', () => {
276
276
  assert.notCalled(meeting.locusInfo.handleLocusAPIResponse);
277
277
  });
278
278
 
279
+ it('should call handleLocusAPIResponse when response body is an unwrapped LocusDTO', () => {
280
+ const meeting = {
281
+ locusInfo: {
282
+ handleLocusAPIResponse: sinon.stub(),
283
+ },
284
+ };
285
+
286
+ const originalResponse = {
287
+ body: {
288
+ url: 'https://locus-a.wbx2.com/locus/api/v1/loci/some-id',
289
+ participants: [],
290
+ self: {},
291
+ },
292
+ };
293
+
294
+ const response = MeetingUtil.updateLocusFromApiResponse(meeting, originalResponse);
295
+
296
+ assert.deepEqual(response, originalResponse);
297
+ assert.calledOnceWithExactly(
298
+ meeting.locusInfo.handleLocusAPIResponse,
299
+ meeting,
300
+ originalResponse.body
301
+ );
302
+ });
303
+
279
304
  it('should work with an undefined meeting', () => {
280
305
  const originalResponse = {
281
306
  body: {
@@ -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.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.request.firstCall.returnValue);
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.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.request.firstCall.returnValue);
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.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.request.firstCall.returnValue);
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.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.request.firstCall.returnValue);
186
+ assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
186
187
  });
187
188
  });
188
189
  });