@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.
- package/dist/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/controls-options-manager/index.js +17 -5
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +44 -13
- package/dist/hashTree/hashTreeParser.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/meeting/util.js +15 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/recording-controller/index.js +1 -3
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/types/controls-options-manager/index.d.ts +10 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +8 -0
- package/dist/types/meeting/util.d.ts +7 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/webinar/index.js +6 -1
- package/dist/webinar/index.js.map +1 -1
- package/package.json +1 -1
- package/src/controls-options-manager/index.ts +22 -6
- package/src/hashTree/hashTreeParser.ts +29 -1
- package/src/interpretation/index.ts +25 -8
- package/src/meeting/util.ts +16 -2
- package/src/metrics/constants.ts +1 -0
- package/src/recording-controller/index.ts +1 -2
- package/src/webinar/index.ts +13 -4
- package/test/unit/spec/controls-options-manager/index.js +35 -32
- package/test/unit/spec/hashTree/hashTreeParser.ts +158 -0
- package/test/unit/spec/interpretation/index.ts +26 -4
- package/test/unit/spec/meeting/muteState.js +3 -0
- package/test/unit/spec/meeting/utils.js +25 -0
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
});
|