Skip to content

Commit 4ed5f2b

Browse files
Add tests for streaming mode
1 parent 4615c32 commit 4ed5f2b

File tree

5 files changed

+114
-10
lines changed

5 files changed

+114
-10
lines changed

src/__tests__/browserSuites/push-synchronization.spec.js

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import unboundedMessage from '../mocks/message.V2.UNBOUNDED.1457552650000.json';
1717
import boundedZlibMessage from '../mocks/message.V2.BOUNDED.ZLIB.1457552651000.json';
1818
import keylistGzipMessage from '../mocks/message.V2.KEYLIST.GZIP.1457552652000.json';
1919
import segmentRemovalMessage from '../mocks/message.V2.SEGMENT_REMOVAL.1457552653000.json';
20+
import unboundedMyLargeSegmentsMessage from '../mocks/message.MY_LARGE_SEGMENTS_UPDATE.UNBOUNDED.1457552650000.json';
21+
import myLargeSegmentRemovalMessage from '../mocks/message.MY_LARGE_SEGMENTS_UPDATE.SEGMENT_REMOVAL.1457552653000.json';
2022

2123
import authPushEnabledNicolas from '../mocks/auth.pushEnabled.nicolas@split.io.json';
2224
import authPushEnabledNicolasAndMarcio from '../mocks/auth.pushEnabled.nicolas@split.io.marcio@split.io.json';
@@ -49,6 +51,9 @@ const config = {
4951
},
5052
urls: baseUrls,
5153
streamingEnabled: true,
54+
sync: {
55+
largeSegmentsEnabled: true
56+
}
5257
};
5358
const settings = settingsFactory(config);
5459

@@ -68,17 +73,19 @@ const MILLIS_KEYLIST_FALLBACK = 1300;
6873
const MILLIS_BOUNDED = 1400;
6974
const MILLIS_KEYLIST = 1500;
7075
const MILLIS_SEGMENT_REMOVAL = 1600;
76+
const MILLIS_UNBOUNDED_FETCH_LS = 1700;
77+
const MILLIS_SEGMENT_REMOVAL_LS = 2000;
7178

7279
/**
7380
* Sequence of calls:
7481
* 0.0 secs: initial SyncAll (/splitChanges, /mySegments/*), auth, SSE connection
75-
* 0.1 secs: SSE connection opened -> syncAll (/splitChanges, /mySegments/*)
82+
* 0.1 secs: SSE connection opened -> syncAll (/splitChanges, /mySegments/*, /myLargeSegments/*)
7683
* 0.2 secs: SPLIT_UPDATE event -> /splitChanges
7784
* 0.3 secs: SPLIT_UPDATE event with old changeNumber
7885
* 0.4 secs: MY_SEGMENTS_UPDATE event -> /mySegments/nicolas@split.io
7986
* 0.5 secs: SPLIT_KILL event -> /splitChanges
8087
* 0.6 secs: creates a new client -> new auth and SSE connection
81-
* 0.7 secs: SSE connection opened -> syncAll (/splitChanges, /mySegments/*)
88+
* 0.7 secs: SSE connection opened -> syncAll (/splitChanges, /mySegments/*, /myLargeSegments/*)
8289
* 0.8 secs: MY_SEGMENTS_UPDATE event for new client (with payload).
8390
* 0.9 secs: MY_SEGMENTS_UPDATE event for new client (with empty payload).
8491
* 1.0 secs: creates more clients
@@ -88,9 +95,12 @@ const MILLIS_SEGMENT_REMOVAL = 1600;
8895
* 1.4 secs: MY_SEGMENTS_UPDATE_V2 BoundedFetchRequest event.
8996
* 1.5 secs: MY_SEGMENTS_UPDATE_V2 KeyList event.
9097
* 1.6 secs: MY_SEGMENTS_UPDATE_V2 SegmentRemoval event.
98+
* 1.7 secs: MY_LARGE_SEGMENTS_UPDATE UnboundedFetchRequest event, with 241 ms delay for 'nicolas@split.io' (hash('nicolas@split.io') % 300)
99+
* 1.941 secs: /myLargeSegments/* fetch due to unbounded MY_LARGE_SEGMENTS_UPDATE event -> SPLIT_UPDATE event
100+
* 2.0 secs: MY_LARGE_SEGMENTS_UPDATE SegmentRemoval event -> SPLIT_UPDATE event
91101
*/
92102
export function testSynchronization(fetchMock, assert) {
93-
assert.plan(38);
103+
assert.plan(44);
94104
fetchMock.reset();
95105

96106
let start, splitio, client, otherClient, keylistAddClient, keylistRemoveClient, bitmapTrueClient, sharedClients = [];
@@ -236,6 +246,31 @@ export function testSynchronization(fetchMock, assert) {
236246
assert.deepEqual(sharedClients.map(c => c.getTreatment('splitters')), ['off', 'on', 'off', 'on'], 'evaluation before segment removal');
237247
bitmapTrueClient.once(bitmapTrueClient.Event.SDK_UPDATE, () => {
238248
assert.deepEqual(sharedClients.map(c => c.getTreatment('splitters')), ['off', 'off', 'off', 'off'], 'evaluation after segment removal');
249+
});
250+
251+
eventSourceInstance.emitMessage(segmentRemovalMessage);
252+
}, MILLIS_SEGMENT_REMOVAL - MILLIS_MORE_CLIENTS);
253+
254+
setTimeout(() => {
255+
assert.equal(client.getTreatment('in_large_segment'), 'no', 'evaluation before myLargeSegment fetch');
256+
257+
const timestampUnboundEvent = Date.now();
258+
const EXPECTED_DELAY = 241;
259+
260+
client.once(client.Event.SDK_UPDATE, () => {
261+
assert.true(nearlyEqual(Date.now() - timestampUnboundEvent, EXPECTED_DELAY), 'SDK_UPDATE after fetching myLargeSegments with a delay');
262+
assert.equal(client.getTreatment('in_large_segment'), 'yes', 'evaluation after myLargeSegment fetch');
263+
});
264+
265+
eventSourceInstance.emitMessage(unboundedMyLargeSegmentsMessage);
266+
}, MILLIS_UNBOUNDED_FETCH_LS - MILLIS_MORE_CLIENTS);
267+
268+
setTimeout(() => {
269+
assert.equal(client.getTreatment('in_large_segment'), 'yes', 'evaluation before large segment removal');
270+
assert.deepEqual(sharedClients.map(c => c.getTreatment('in_large_segment')), ['no', 'no', 'no', 'no'], 'evaluation before segment removal');
271+
272+
client.once(client.Event.SDK_UPDATE, () => {
273+
assert.equal(client.getTreatment('in_large_segment'), 'no', 'evaluation after large segment removal');
239274

240275
// destroy shared clients and then main client
241276
Promise.all(sharedClients.map(c => c.destroy()))
@@ -252,8 +287,8 @@ export function testSynchronization(fetchMock, assert) {
252287
});
253288
});
254289

255-
eventSourceInstance.emitMessage(segmentRemovalMessage);
256-
}, MILLIS_SEGMENT_REMOVAL - MILLIS_MORE_CLIENTS);
290+
eventSourceInstance.emitMessage(myLargeSegmentRemovalMessage);
291+
}, MILLIS_SEGMENT_REMOVAL_LS - MILLIS_MORE_CLIENTS);
257292
});
258293
}, MILLIS_MORE_CLIENTS - MILLIS_NEW_CLIENT);
259294

@@ -283,7 +318,7 @@ export function testSynchronization(fetchMock, assert) {
283318
authParams += `&users=${encodeURIComponent(keylistAddKey)}&users=${encodeURIComponent(keylistRemoveKey)}&users=${encodeURIComponent(bitmapTrueKey)}`;
284319
fetchMock.getOnce(url(settings, `/v2/auth?s=1.1&${authParams}`), { status: 200, body: authPushEnabledNicolasAndMarcio });
285320

286-
// initial split and mySegments sync
321+
// initial sync
287322
fetchMock.getOnce(url(settings, '/splitChanges?s=1.1&since=-1'), function (url, opts) {
288323
const lapse = Date.now() - start;
289324
assert.true(nearlyEqual(lapse, 0), 'initial sync');
@@ -294,6 +329,7 @@ export function testSynchronization(fetchMock, assert) {
294329
if (hasNoCacheHeader(opts)) assert.fail('request must not include `Cache-Control` header');
295330
return { status: 200, body: mySegmentsNicolasMock1 };
296331
});
332+
fetchMock.getOnce(url(settings, '/myLargeSegments/nicolas%40split.io'), { status: 200, body: { myLargeSegments: [] } });
297333

298334
// split and segment sync after SSE opened
299335
fetchMock.getOnce(url(settings, '/splitChanges?s=1.1&since=1457552620999'), function (url, opts) {
@@ -306,6 +342,7 @@ export function testSynchronization(fetchMock, assert) {
306342
if (hasNoCacheHeader(opts)) assert.fail('request must not include `Cache-Control` header');
307343
return { status: 200, body: mySegmentsNicolasMock1 };
308344
});
345+
fetchMock.getOnce(url(settings, '/myLargeSegments/nicolas%40split.io'), { status: 200, body: { myLargeSegments: [] } });
309346

310347
// fetch due to SPLIT_UPDATE event
311348
fetchMock.getOnce(url(settings, '/splitChanges?s=1.1&since=1457552620999'), function (url, opts) {
@@ -326,13 +363,15 @@ export function testSynchronization(fetchMock, assert) {
326363
return { status: 200, body: splitChangesMock4 };
327364
});
328365

329-
// initial fetch of mySegments for new client
366+
// initial fetch of mySegments and myLargeSegments for new client
330367
fetchMock.getOnce(url(settings, '/mySegments/marcio%40split.io'), function (url, opts) {
331368
if (hasNoCacheHeader(opts)) assert.fail('request must not include `Cache-Control` header');
332369
return { status: 200, body: mySegmentsMarcio };
333370
});
371+
fetchMock.getOnce(url(settings, '/myLargeSegments/marcio%40split.io'), { status: 200, body: { myLargeSegments: [] } });
372+
334373

335-
// split and mySegment sync after second SSE opened
374+
// sync after second SSE opened
336375
fetchMock.getOnce(url(settings, '/splitChanges?s=1.1&since=1457552650000'), function (url, opts) {
337376
const lapse = Date.now() - start;
338377
assert.true(nearlyEqual(lapse, MILLIS_SECOND_SSE_OPEN), 'sync after second SSE connection is opened');
@@ -347,6 +386,9 @@ export function testSynchronization(fetchMock, assert) {
347386
if (hasNoCacheHeader(opts)) assert.fail('request must not include `Cache-Control` header');
348387
return { status: 200, body: mySegmentsMarcio };
349388
});
389+
fetchMock.get({ url: url(settings, '/myLargeSegments/nicolas%40split.io'), repeat: 2 }, { status: 200, body: { myLargeSegments: [] } });
390+
fetchMock.get({ url: url(settings, '/myLargeSegments/marcio%40split.io'), repeat: 2 }, { status: 200, body: { myLargeSegments: [] } });
391+
350392
// 3 unbounded fetch requests
351393
fetchMock.get({ url: url(settings, '/mySegments/nicolas%40split.io'), repeat: 3 }, function (url, opts) {
352394
if (!hasNoCacheHeader(opts)) assert.fail('request must not include `Cache-Control` header');
@@ -357,15 +399,25 @@ export function testSynchronization(fetchMock, assert) {
357399
return { status: 200, body: mySegmentsMarcio };
358400
});
359401

360-
// initial fetch of mySegments for other clients + sync after third SSE opened + 3 unbounded fetch requests
402+
// initial fetch of mySegments and myLargeSegments for other clients + sync after third SSE opened + 3 unbounded fetch requests for mySegments
361403
fetchMock.getOnce(url(settings, '/splitChanges?s=1.1&since=1457552650000'), { status: 200, body: { splits: [], since: 1457552650000, till: 1457552650000 } });
362404
fetchMock.get({ url: url(settings, '/mySegments/key1'), repeat: 5 }, { status: 200, body: { mySegments: [] } });
363405
fetchMock.get({ url: url(settings, '/mySegments/key3'), repeat: 5 }, { status: 200, body: { mySegments: [{ name: 'splitters' }] } });
364406
fetchMock.get({ url: url(settings, `/mySegments/${bitmapTrueKey}`), repeat: 5 }, { status: 200, body: { mySegments: [] } });
407+
fetchMock.get({ url: url(settings, '/myLargeSegments/key1'), repeat: 2 }, { status: 200, body: { myLargeSegments: [] } });
408+
fetchMock.get({ url: url(settings, '/myLargeSegments/key3'), repeat: 2 }, { status: 200, body: { myLargeSegments: [] } });
409+
fetchMock.get({ url: url(settings, `/myLargeSegments/${bitmapTrueKey}`), repeat: 2 }, { status: 200, body: { myLargeSegments: [] } });
365410

366411
// bounded fetch request
367412
fetchMock.get(url(settings, `/mySegments/${bitmapTrueKey}`), { status: 200, body: { mySegments: [{ name: 'splitters' }] } });
368413

414+
// unbounded myLargeSegments fetch requests
415+
fetchMock.getOnce(url(settings, '/myLargeSegments/nicolas%40split.io'), { status: 200, body: { myLargeSegments: ['employees', 'splitters'] } });
416+
fetchMock.getOnce(url(settings, '/myLargeSegments/marcio%40split.io'), { status: 200, body: { myLargeSegments: [] } });
417+
fetchMock.getOnce(url(settings, '/myLargeSegments/key1'), { status: 200, body: { myLargeSegments: [] } });
418+
fetchMock.getOnce(url(settings, '/myLargeSegments/key3'), { status: 200, body: { myLargeSegments: [] } });
419+
fetchMock.getOnce(url(settings, `/myLargeSegments/${bitmapTrueKey}`), { status: 200, body: { myLargeSegments: [] } });
420+
369421
fetchMock.get(new RegExp('.*'), function (url) {
370422
assert.fail('unexpected GET request with url: ' + url);
371423
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "message",
3+
"data": "{\"data\":\"{\\\"type\\\":\\\"MY_LARGE_SEGMENTS_UPDATE\\\",\\\"changeNumber\\\":1457552653000,\\\"largeSegments\\\":[\\\"harnessians\\\",\\\"splitters\\\"],\\\"c\\\": 0,\\\"u\\\": 3,\\\"d\\\":\\\"\\\"}\"}"
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "message",
3+
"data": "{\"data\":\"{\\\"type\\\":\\\"MY_LARGE_SEGMENTS_UPDATE\\\",\\\"changeNumber\\\":1457552650000,\\\"largeSegments\\\":[],\\\"c\\\": 0,\\\"u\\\": 0,\\\"d\\\":\\\"\\\",\\\"i\\\":300,\\\"h\\\":0,\\\"s\\\":0}\"}"
4+
}

src/__tests__/mocks/splitchanges.real.withSegments.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,47 @@
11
{
22
"splits": [
3+
{
4+
"orgId": null,
5+
"environment": null,
6+
"trafficTypeId": null,
7+
"trafficTypeName": null,
8+
"name": "in_large_segment",
9+
"seed": -1984784937,
10+
"status": "ACTIVE",
11+
"killed": false,
12+
"defaultTreatment": "no",
13+
"conditions": [
14+
{
15+
"matcherGroup": {
16+
"combiner": "AND",
17+
"matchers": [
18+
{
19+
"keySelector": {
20+
"trafficType": "user",
21+
"attribute": null
22+
},
23+
"matcherType": "IN_LARGE_SEGMENT",
24+
"negate": false,
25+
"userDefinedSegmentMatcherData": {
26+
"segmentName": "harnessians"
27+
},
28+
"whitelistMatcherData": null,
29+
"unaryNumericMatcherData": null,
30+
"betweenMatcherData": null,
31+
"unaryStringMatcherData": null
32+
}
33+
]
34+
},
35+
"partitions": [
36+
{
37+
"treatment": "yes",
38+
"size": 100
39+
}
40+
]
41+
}
42+
],
43+
"configurations": {}
44+
},
345
{
446
"trafficTypeName": "user",
547
"name": "real_split",

src/__tests__/nodeSuites/push-synchronization.spec.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ const config = {
4444
},
4545
urls: baseUrls,
4646
streamingEnabled: true,
47-
// debug: true,
47+
sync: {
48+
largeSegmentsEnabled: true // ignored in node
49+
}
4850
};
4951
const settings = settingsFactory(config);
5052

0 commit comments

Comments
 (0)