@webex/plugin-meetings 3.0.0-beta.185 → 3.0.0-beta.187

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.
@@ -3,6 +3,7 @@ import sinon from 'sinon';
3
3
  import {cloneDeep} from 'lodash';
4
4
  import {assert} from '@webex/test-helper-chai';
5
5
  import MockWebex from '@webex/test-helper-mock-webex';
6
+ import testUtils from '../../../utils/testUtils';
6
7
  import Meetings from '@webex/plugin-meetings';
7
8
  import LocusInfo from '@webex/plugin-meetings/src/locus-info';
8
9
  import SelfUtils from '@webex/plugin-meetings/src/locus-info/selfUtils';
@@ -1547,6 +1548,7 @@ describe('plugin-meetings', () => {
1547
1548
  meeting: true,
1548
1549
  participants: true,
1549
1550
  url: 'newLocusUrl',
1551
+ syncUrl: 'newSyncUrl',
1550
1552
  };
1551
1553
  });
1552
1554
 
@@ -1707,39 +1709,82 @@ describe('plugin-meetings', () => {
1707
1709
  assert.calledWith(meeting.locusInfo.onDeltaLocus, fakeLocus);
1708
1710
  });
1709
1711
 
1710
- it('applyLocusDeltaData gets full locus on DESYNC action', () => {
1712
+ it('applyLocusDeltaData gets delta locus on DESYNC action if we have a syncUrl', () => {
1711
1713
  const {DESYNC} = LocusDeltaParser.loci;
1714
+ const fakeDeltaLocus = {id: 'fake delta locus'};
1712
1715
  const meeting = {
1713
1716
  meetingRequest: {
1714
- getFullLocus: sandbox.stub().resolves(true),
1717
+ getLocusDTO: sandbox.stub().resolves({body: fakeDeltaLocus}),
1715
1718
  },
1716
1719
  locusInfo: {
1720
+ handleLocusDelta: sandbox.stub(),
1721
+ },
1722
+ locusUrl: 'oldLocusUrl',
1723
+ };
1724
+
1725
+ locusInfo.locusParser.workingCopy = {
1726
+ syncUrl: 'oldSyncUrl',
1727
+ };
1728
+
1729
+ // Since we have a promise inside a function we want to test that's not returned,
1730
+ // we will wait and stub it's last function to resolve this waiting promise.
1731
+ // Also ensures .handleLocusDelta() is called before .resume()
1732
+ return new Promise((resolve) => {
1733
+ locusInfo.locusParser.resume = sandbox.stub().callsFake(() => resolve());
1734
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1735
+ }).then(() => {
1736
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, { url: 'oldSyncUrl' });
1737
+
1738
+ assert.calledOnceWithExactly(meeting.locusInfo.handleLocusDelta, fakeDeltaLocus, meeting);
1739
+ assert.calledOnce(locusInfo.locusParser.resume);
1740
+ });
1741
+ });
1742
+
1743
+ it('applyLocusDeltaData gets delta locus on DESYNC action if we have a syncUrl (empty response body)', () => {
1744
+ const {DESYNC} = LocusDeltaParser.loci;
1745
+ const meeting = {
1746
+ meetingRequest: {
1747
+ getLocusDTO: sandbox.stub().resolves({body: {}}),
1748
+ },
1749
+ locusInfo: {
1750
+ handleLocusDelta: sandbox.stub(),
1717
1751
  onFullLocus: sandbox.stub(),
1718
1752
  },
1719
1753
  locusUrl: 'oldLocusUrl',
1720
1754
  };
1721
1755
 
1722
- locusInfo.locusParser.resume = sandbox.stub();
1723
- locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1756
+ locusInfo.locusParser.workingCopy = {
1757
+ syncUrl: 'oldSyncUrl',
1758
+ };
1724
1759
 
1725
- assert.calledOnceWithExactly(meeting.meetingRequest.getFullLocus,
1726
- {
1727
- desync: true,
1728
- locusUrl: 'newLocusUrl',
1729
- }
1730
- );
1760
+ // Since we have a promise inside a function we want to test that's not returned,
1761
+ // we will wait and stub it's last function to resolve this waiting promise.
1762
+ return new Promise((resolve) => {
1763
+ locusInfo.locusParser.resume = sandbox.stub().callsFake(() => resolve());
1764
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1765
+ }).then(() => {
1766
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, { url: 'oldSyncUrl' });
1767
+
1768
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
1769
+ assert.notCalled(meeting.locusInfo.onFullLocus);
1770
+ assert.calledOnce(locusInfo.locusParser.resume);
1771
+ });
1731
1772
  });
1732
1773
 
1733
- it('getFullLocus handles DESYNC action correctly', () => {
1774
+ it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl', () => {
1734
1775
  const {DESYNC} = LocusDeltaParser.loci;
1776
+ const fakeFullLocusDto = {id: 'fake full locus dto'};
1735
1777
  const meeting = {
1736
1778
  meetingRequest: {
1737
- getFullLocus: sandbox.stub().resolves({body: true}),
1779
+ getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
1780
+ },
1781
+ locusInfo: {
1782
+ onFullLocus: sandbox.stub(),
1738
1783
  },
1739
- locusInfo,
1784
+ locusUrl: 'oldLocusUrl',
1740
1785
  };
1741
1786
 
1742
- locusInfo.onFullLocus = sandbox.stub();
1787
+ locusInfo.locusParser.workingCopy = {}; // no syncUrl
1743
1788
 
1744
1789
  // Since we have a promise inside a function we want to test that's not returned,
1745
1790
  // we will wait and stub it's last function to resolve this waiting promise.
@@ -1748,7 +1793,9 @@ describe('plugin-meetings', () => {
1748
1793
  locusInfo.locusParser.resume = sandbox.stub().callsFake(() => resolve());
1749
1794
  locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1750
1795
  }).then(() => {
1751
- assert.calledOnce(meeting.locusInfo.onFullLocus);
1796
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, { url: 'oldLocusUrl' });
1797
+
1798
+ assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
1752
1799
  assert.calledOnce(locusInfo.locusParser.resume);
1753
1800
  });
1754
1801
  });
@@ -2083,5 +2130,295 @@ describe('plugin-meetings', () => {
2083
2130
  });
2084
2131
  });
2085
2132
  });
2133
+
2134
+ // semi-integration tests that use real LocusInfo with real Parser
2135
+ // and test various scenarios related to handling out-of-order Locus delta events
2136
+ describe('handling of out-of-order Locus delta events', () => {
2137
+ let clock;
2138
+
2139
+ const generateDeltaEvent = (base, sequence) => {
2140
+ return {
2141
+ baseSequence: {
2142
+ rangeStart: 0,
2143
+ rangeEnd: 0,
2144
+ entries: [base]
2145
+ },
2146
+ sequence: {
2147
+ rangeStart: 0,
2148
+ rangeEnd: 0,
2149
+ entries: [sequence]
2150
+ },
2151
+ syncUrl: `fake sync url for sequence ${sequence}`,
2152
+ self: {
2153
+ person: {
2154
+ id: 'test person id'
2155
+ }
2156
+ },
2157
+ }
2158
+ };
2159
+
2160
+ // a list of example delta events, sorted by time and each event is based on the previous one
2161
+ const deltaEvents = [
2162
+ generateDeltaEvent(10, 20), // 0
2163
+ generateDeltaEvent(20, 30), // 1
2164
+ generateDeltaEvent(30, 40), // 2
2165
+ generateDeltaEvent(40, 50), // 3
2166
+ generateDeltaEvent(50, 60), // 4
2167
+ generateDeltaEvent(60, 70), // 5
2168
+ generateDeltaEvent(70, 80), // 6
2169
+ generateDeltaEvent(80, 90), // 7
2170
+ generateDeltaEvent(90, 100), // 8
2171
+ ];
2172
+
2173
+ let updateLocusInfoStub; // we use this stub to verify that an event has been fully processed
2174
+ let syncRequestStub;
2175
+
2176
+ beforeEach(() => {
2177
+ clock = sinon.useFakeTimers();
2178
+
2179
+ sinon.stub(locusInfo, 'updateParticipantDeltas');
2180
+ sinon.stub(locusInfo, 'updateParticipants');
2181
+ sinon.stub(locusInfo, 'isMeetingActive'),
2182
+ sinon.stub(locusInfo, 'handleOneOnOneEvent'),
2183
+
2184
+ updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo');
2185
+ syncRequestStub = sinon.stub().resolves({body: {}});
2186
+
2187
+ mockMeeting.locusInfo = locusInfo;
2188
+ mockMeeting.locusUrl = 'fake locus url';
2189
+ mockMeeting.meetingRequest = {
2190
+ getLocusDTO: syncRequestStub,
2191
+ };
2192
+
2193
+ locusInfo.onFullLocus({
2194
+ sequence: {
2195
+ rangeStart: 0,
2196
+ rangeEnd: 0,
2197
+ entries: [10]
2198
+ },
2199
+ self: {
2200
+ person: {
2201
+ id: 'test person id'
2202
+ }
2203
+ },
2204
+ });
2205
+
2206
+ updateLocusInfoStub.resetHistory();
2207
+ });
2208
+
2209
+ afterEach(() => {
2210
+ clock.restore();
2211
+ });
2212
+
2213
+ it('queues out-of-order deltas until it receives a correct delta', () => {
2214
+ // send some out-of-order deltas
2215
+ locusInfo.handleLocusDelta(deltaEvents[1], mockMeeting);
2216
+ locusInfo.handleLocusDelta(deltaEvents[4], mockMeeting);
2217
+
2218
+ // they should be queued and not processed
2219
+ assert.notCalled(updateLocusInfoStub);
2220
+
2221
+ // now one of the missing ones, but not the one SDK is really waiting for
2222
+ locusInfo.handleLocusDelta(deltaEvents[2], mockMeeting);
2223
+
2224
+ // still nothing should be processed
2225
+ assert.notCalled(updateLocusInfoStub);
2226
+
2227
+ // now send the one SDK is waiting for
2228
+ locusInfo.handleLocusDelta(deltaEvents[0], mockMeeting);
2229
+
2230
+ // so deltaEvents with indexes 1,2,3 can be processed, but 5 still not, because 4 is missing
2231
+ assert.callCount(updateLocusInfoStub, 3);
2232
+ assert.calledWith(updateLocusInfoStub.getCall(0), deltaEvents[0]);
2233
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[1]);
2234
+ assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[2]);
2235
+
2236
+ updateLocusInfoStub.resetHistory();
2237
+
2238
+ // now send deltaEvents[4]
2239
+ locusInfo.handleLocusDelta(deltaEvents[3], mockMeeting);
2240
+
2241
+ // and verify deltaEvents[4] and deltaEvents[5] have been processed
2242
+ assert.callCount(updateLocusInfoStub, 2);
2243
+ assert.calledWith(updateLocusInfoStub.getCall(0), deltaEvents[3]);
2244
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[4]);
2245
+ });
2246
+
2247
+ it('handles out-of-order deltas correctly even if all arrive in reverse order', () => {
2248
+ // send a bunch deltas in reverse order
2249
+ for(let i = 4; i >= 0; i--) {
2250
+ locusInfo.handleLocusDelta(deltaEvents[i], mockMeeting);
2251
+ }
2252
+
2253
+ // they should be queued and then processed in correct order
2254
+ assert.callCount(updateLocusInfoStub, 5);
2255
+ assert.calledWith(updateLocusInfoStub.getCall(0), deltaEvents[0]);
2256
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[1]);
2257
+ assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[2]);
2258
+ assert.calledWith(updateLocusInfoStub.getCall(3), deltaEvents[3]);
2259
+ assert.calledWith(updateLocusInfoStub.getCall(4), deltaEvents[4]);
2260
+ });
2261
+
2262
+ it('sends a sync request using syncUrl if it receives at least 1 delta event and processes later deltas after sync correctly', async () => {
2263
+ // the test first sends an initial "good" delta
2264
+ const initialDeltaIdx = 0;
2265
+ const initialDelta = deltaEvents[initialDeltaIdx];
2266
+
2267
+ // then it sends a bunch of out-of-order deltas (at least 6 to trigger a sync), last one being lastOooDelta
2268
+ const firstOooDeltaIdx = 2;
2269
+ const lastOooDeltaIdx = 7;
2270
+ const lastOooDelta = deltaEvents[lastOooDeltaIdx];
2271
+
2272
+ // and finally, after the sync it sends another "good" delta
2273
+ const goodDeltaAfterSync = deltaEvents[8];
2274
+
2275
+ const deltaLocusFromSyncResponse = {
2276
+ baseSequence: {
2277
+ rangeStart: 0,
2278
+ rangeEnd: 0,
2279
+ entries: [initialDelta.sequence.entries[0]]
2280
+ },
2281
+ sequence: {
2282
+ rangeStart: 0,
2283
+ rangeEnd: 0,
2284
+ entries: [lastOooDelta.sequence.entries[0]]
2285
+ },
2286
+ syncUrl: `fake sync url for sequence ${lastOooDelta.sequence.entries[0]}`,
2287
+ self: {
2288
+ person: {
2289
+ id: 'test person id'
2290
+ }
2291
+ },
2292
+ };
2293
+
2294
+ syncRequestStub.resolves({
2295
+ body: deltaLocusFromSyncResponse
2296
+ });
2297
+
2298
+ // send one correct delta so that SDK has the syncUrl
2299
+ locusInfo.handleLocusDelta(initialDelta, mockMeeting);
2300
+
2301
+ updateLocusInfoStub.resetHistory();
2302
+
2303
+ // send 6 out-of-order deltas to trigger a sync (we're skipping deltaEvents[1])
2304
+ for(let i = firstOooDeltaIdx; i <= lastOooDeltaIdx; i++) {
2305
+ locusInfo.handleLocusDelta(deltaEvents[i], mockMeeting);
2306
+ }
2307
+
2308
+ await testUtils.flushPromises();
2309
+
2310
+ // check that sync was done using the correct syncUrl
2311
+ assert.calledOnceWithExactly(syncRequestStub, {url: initialDelta.syncUrl});
2312
+ assert.calledOnceWithExactly(updateLocusInfoStub, deltaLocusFromSyncResponse);
2313
+
2314
+ updateLocusInfoStub.resetHistory();
2315
+
2316
+ // now send another delta - a good one, it should be processed as normal
2317
+ locusInfo.handleLocusDelta(goodDeltaAfterSync, mockMeeting);
2318
+
2319
+ assert.calledOnceWithExactly(updateLocusInfoStub, goodDeltaAfterSync);
2320
+ });
2321
+
2322
+ it('does a sync if blocked on out-of-order deltas for too long', async () => {
2323
+ // stub random so that the timer fires after 12500 ms
2324
+ sinon.stub(Math, 'random').returns(0.5);
2325
+
2326
+ const oooDelta = deltaEvents[3];
2327
+
2328
+ // setup the stubs so that the sync request receives a full DTO with the sequence equal to the out-of-order delta we simulate
2329
+ const fullLocus = {
2330
+ sequence: oooDelta.sequence
2331
+ };
2332
+ syncRequestStub.resolves({
2333
+ body: fullLocus
2334
+ });
2335
+
2336
+ // send an out-of-order delta
2337
+ locusInfo.handleLocusDelta(oooDelta, mockMeeting);
2338
+
2339
+ await clock.tickAsync(12499);
2340
+ await testUtils.flushPromises();
2341
+ assert.notCalled(syncRequestStub);
2342
+ assert.notCalled(updateLocusInfoStub);
2343
+
2344
+ await clock.tickAsync(1);
2345
+ await testUtils.flushPromises();
2346
+
2347
+ assert.calledOnceWithExactly(syncRequestStub, {url: mockMeeting.locusUrl});
2348
+ assert.calledOnceWithExactly(updateLocusInfoStub, fullLocus);
2349
+ });
2350
+
2351
+ it('does a sync if out-of-order deltas queue becomes too big', async () => {
2352
+ // setup the stubs so that the sync request receives a full DTO with the sequence equal to the out-of-order delta we simulate
2353
+ const fullLocus = {
2354
+ sequence: deltaEvents[6].sequence
2355
+ };
2356
+ syncRequestStub.resolves({
2357
+ body: fullLocus
2358
+ });
2359
+
2360
+ // send 5 deltas, starting from deltaEvents[1] so that SDK is blocked waiting for deltaEvents[0]
2361
+ for(let i = 0; i < 5; i++) {
2362
+ locusInfo.handleLocusDelta(deltaEvents[i + 1], mockMeeting);
2363
+ }
2364
+
2365
+ // nothing should happen, SDK should still be waiting for deltaEvents[0]
2366
+ assert.notCalled(syncRequestStub);
2367
+ assert.notCalled(updateLocusInfoStub);
2368
+
2369
+ // now send one more out-of-order delta to trigger a sync request
2370
+ locusInfo.handleLocusDelta(deltaEvents[6], mockMeeting);
2371
+
2372
+ await testUtils.flushPromises();
2373
+
2374
+ // check sync was done
2375
+ assert.calledOnceWithExactly(syncRequestStub, {url: mockMeeting.locusUrl});
2376
+ assert.calledOnceWithExactly(updateLocusInfoStub, fullLocus);
2377
+ });
2378
+
2379
+ it('processes delta events that are not included in sync response', async () => {
2380
+ // this test sends a bunch of out-of-order deltas, this triggers a sync
2381
+ // but the full locus response doesn't include the last 2 deltas received, so
2382
+ // we check that these 2 deltas are also processed after sync response
2383
+ const fullLocusFromSyncResponse = {
2384
+ baseSequence: {
2385
+ rangeStart: 0,
2386
+ rangeEnd: 0,
2387
+ entries: [deltaEvents[0].sequence.entries[0]]
2388
+ },
2389
+ sequence: {
2390
+ rangeStart: 0,
2391
+ rangeEnd: 0,
2392
+ entries: [deltaEvents[5].sequence.entries[0]]
2393
+ },
2394
+ syncUrl: `fake sync url for sequence ${deltaEvents[5].sequence.entries[0]}`,
2395
+ self: {
2396
+ person: {
2397
+ id: 'test person id'
2398
+ }
2399
+ },
2400
+ };
2401
+
2402
+ syncRequestStub.resolves({
2403
+ body: fullLocusFromSyncResponse
2404
+ });
2405
+
2406
+ // send at least 6 out-of-order deltas to trigger a sync (we're skipping deltaEvents[0])
2407
+ for(let i = 1; i <= 7; i++) {
2408
+ locusInfo.handleLocusDelta(deltaEvents[i], mockMeeting);
2409
+ }
2410
+
2411
+ await testUtils.flushPromises();
2412
+
2413
+ // check that sync was done
2414
+ assert.calledOnceWithExactly(syncRequestStub, {url: mockMeeting.locusUrl});
2415
+
2416
+ // and that remaining deltas from the queue that were not included in full Locus were also processed
2417
+ assert.callCount(updateLocusInfoStub, 3);
2418
+ assert.calledWith(updateLocusInfoStub.getCall(0), fullLocusFromSyncResponse);
2419
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[6]);
2420
+ assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[7]);
2421
+ });
2422
+ });
2086
2423
  });
2087
2424
  });
@@ -334,27 +334,5 @@ describe('locus-info/parser', () => {
334
334
 
335
335
  assert.isFalse(result);
336
336
  });
337
-
338
- it('sets parser status to IDLE if workingCopy is invalid', () => {
339
- const {IDLE, WORKING} = LocusDeltaParser.status;
340
-
341
- parser.workingCopy = null;
342
- parser.status = WORKING;
343
-
344
- parser.isValidLocus(loci);
345
-
346
- assert.equal(parser.status, IDLE);
347
- });
348
-
349
- it('sets parser status to IDLE if new loci is invalid', () => {
350
- const {IDLE, WORKING} = LocusDeltaParser.status;
351
-
352
- parser.workingCopy = loci;
353
- parser.status = WORKING;
354
-
355
- parser.isValidLocus(null);
356
-
357
- assert.equal(parser.status, IDLE);
358
- });
359
337
  });
360
338
  });
@@ -37,7 +37,7 @@ describe('plugin-meetings', () => {
37
37
  unmuteVideoAllowed: true,
38
38
 
39
39
  locusInfo: {
40
- onDeltaLocus: sinon.stub(),
40
+ handleLocusDelta: sinon.stub(),
41
41
  },
42
42
  members: {
43
43
  selfId: 'fake self id',
@@ -179,10 +179,10 @@ describe('plugin-meetings', () => {
179
179
  });
180
180
 
181
181
  describe('updateLocusWithDelta', () => {
182
- it('should call onDeltaLocus with the new delta locus', () => {
182
+ it('should call handleLocusDelta with the new delta locus', () => {
183
183
  const meeting = {
184
184
  locusInfo: {
185
- onDeltaLocus: sinon.stub()
185
+ handleLocusDelta: sinon.stub()
186
186
  },
187
187
  };
188
188
 
@@ -195,13 +195,13 @@ describe('plugin-meetings', () => {
195
195
  const response = MeetingUtil.updateLocusWithDelta(meeting, originalResponse);
196
196
 
197
197
  assert.deepEqual(response, originalResponse);
198
- assert.calledOnceWithExactly(meeting.locusInfo.onDeltaLocus, 'locus');
198
+ assert.calledOnceWithExactly(meeting.locusInfo.handleLocusDelta, 'locus', meeting);
199
199
  });
200
200
 
201
201
  it('should handle locus being missing from the response', () => {
202
202
  const meeting = {
203
203
  locusInfo: {
204
- onDeltaLocus: sinon.stub(),
204
+ handleLocusDelta: sinon.stub(),
205
205
  },
206
206
  };
207
207
 
@@ -212,7 +212,7 @@ describe('plugin-meetings', () => {
212
212
  const response = MeetingUtil.updateLocusWithDelta(meeting, originalResponse);
213
213
 
214
214
  assert.deepEqual(response, originalResponse);
215
- assert.notCalled(meeting.locusInfo.onDeltaLocus);
215
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
216
216
  });
217
217
 
218
218
  it('should work with an undefined meeting', () => {