From 9241e6deb76d6d3876d862057239721cc0416775 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 12 Mar 2025 12:40:29 -0300 Subject: [PATCH 1/7] Add rbs in /splitChanges payload --- .../ignore-ip-addresses-setting.spec.js | 1 - .../browserSuites/impressions.debug.spec.js | 2 - .../browserSuites/impressions.none.spec.js | 2 - .../browserSuites/impressions.spec.js | 2 +- .../push-initialization-nopush.spec.js | 4 +- .../push-initialization-retries.spec.js | 14 +- .../browserSuites/push-refresh-token.spec.js | 6 +- .../push-synchronization-retries.spec.js | 14 +- .../push-synchronization.spec.js | 10 +- src/__tests__/browserSuites/readiness.spec.js | 9 +- .../browserSuites/ready-promise.spec.js | 2 +- src/__tests__/browserSuites/telemetry.spec.js | 4 +- .../mocks/message.RB_SEGMENT_UPDATE.C0.json | 4 + .../mocks/message.RB_SEGMENT_UPDATE.C1.json | 4 + .../mocks/message.RB_SEGMENT_UPDATE.C2.json | 4 + .../mocks/splitchanges.since.-1.json | 181 ++++++++++++++++++ .../nodeSuites/expected-treatments.spec.js | 3 +- .../nodeSuites/impressions.debug.spec.js | 2 - .../nodeSuites/impressions.none.spec.js | 2 - src/__tests__/nodeSuites/impressions.spec.js | 2 +- .../nodeSuites/ip-addresses-setting.spec.js | 1 - .../push-initialization-nopush.spec.js | 4 +- .../push-initialization-retries.spec.js | 14 +- .../nodeSuites/push-refresh-token.spec.js | 6 +- .../push-synchronization-retries.spec.js | 14 +- .../nodeSuites/push-synchronization.spec.js | 16 +- src/__tests__/nodeSuites/telemetry.spec.js | 4 +- src/__tests__/online/browser.spec.js | 2 +- src/__tests__/online/node.spec.js | 2 +- 29 files changed, 260 insertions(+), 75 deletions(-) create mode 100644 src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C0.json create mode 100644 src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C1.json create mode 100644 src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C2.json diff --git a/src/__tests__/browserSuites/ignore-ip-addresses-setting.spec.js b/src/__tests__/browserSuites/ignore-ip-addresses-setting.spec.js index 2d262cb14..599a95707 100644 --- a/src/__tests__/browserSuites/ignore-ip-addresses-setting.spec.js +++ b/src/__tests__/browserSuites/ignore-ip-addresses-setting.spec.js @@ -102,7 +102,6 @@ export default function (fetchMock, assert) { // Mock GET endpoints before creating the client const settings = settingsFactory(config); fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }); fetchMock.getOnce(url(settings, `/memberships/${encodeURIComponent(config.core.key)}`), { status: 200, body: { ms: {} } }); // Init Split client diff --git a/src/__tests__/browserSuites/impressions.debug.spec.js b/src/__tests__/browserSuites/impressions.debug.spec.js index 533ab767c..1539e947b 100644 --- a/src/__tests__/browserSuites/impressions.debug.spec.js +++ b/src/__tests__/browserSuites/impressions.debug.spec.js @@ -1,7 +1,6 @@ import { SplitFactory } from '../../'; import { settingsFactory } from '../../settings'; import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; -import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; import membershipsFacundo from '../mocks/memberships.facundo@split.io.json'; import { DEBUG } from '@splitsoftware/splitio-commons/src/utils/constants'; import { truncateTimeFrame } from '@splitsoftware/splitio-commons/src/utils/time'; @@ -25,7 +24,6 @@ let truncatedTimeFrame; export default function (fetchMock, assert) { // Mocking this specific route to make sure we only get the items we want to test from the handlers. fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); fetchMock.get(url(settings, '/memberships/facundo%40split.io'), { status: 200, body: membershipsFacundo }); const splitio = SplitFactory({ diff --git a/src/__tests__/browserSuites/impressions.none.spec.js b/src/__tests__/browserSuites/impressions.none.spec.js index ac0aada23..8b24bf8d3 100644 --- a/src/__tests__/browserSuites/impressions.none.spec.js +++ b/src/__tests__/browserSuites/impressions.none.spec.js @@ -1,7 +1,6 @@ import { SplitFactory } from '../..'; import { settingsFactory } from '../../settings/node'; import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; -import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; import membershipsFacundo from '../mocks/memberships.facundo@split.io.json'; import { NONE } from '@splitsoftware/splitio-commons/src/utils/constants'; import { truncateTimeFrame } from '@splitsoftware/splitio-commons/src/utils/time'; @@ -42,7 +41,6 @@ const config = { export default async function (fetchMock, assert) { // Mocking this specific route to make sure we only get the items we want to test from the handlers. fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); fetchMock.get(url(settings, '/memberships/facundo%40split.io'), { status: 200, body: membershipsFacundo }); fetchMock.get(url(settings, '/memberships/emma%40split.io'), { status: 200, body: membershipsFacundo }); diff --git a/src/__tests__/browserSuites/impressions.spec.js b/src/__tests__/browserSuites/impressions.spec.js index 762657dc4..35d58a912 100644 --- a/src/__tests__/browserSuites/impressions.spec.js +++ b/src/__tests__/browserSuites/impressions.spec.js @@ -25,7 +25,7 @@ let truncatedTimeFrame; export default function (fetchMock, assert) { // Mocking this specific route to make sure we only get the items we want to test from the handlers. fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); + fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }); fetchMock.get(url(settings, '/memberships/facundo%40split.io'), { status: 200, body: membershipsFacundo }); const splitio = SplitFactory({ diff --git a/src/__tests__/browserSuites/push-initialization-nopush.spec.js b/src/__tests__/browserSuites/push-initialization-nopush.spec.js index cb76abd2d..40007b3e0 100644 --- a/src/__tests__/browserSuites/push-initialization-nopush.spec.js +++ b/src/__tests__/browserSuites/push-initialization-nopush.spec.js @@ -53,7 +53,7 @@ function testInitializationFail(fetchMock, assert, fallbackToPolling) { }); if (fallbackToPolling) { - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { assert.true(ready, 'client ready'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, 0), 'polling (first fetch)'); @@ -61,7 +61,7 @@ function testInitializationFail(fetchMock, assert, fallbackToPolling) { }); } - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { assert.true(ready, 'client ready'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, settings.scheduler.featuresRefreshRate), 'polling (second fetch)'); diff --git a/src/__tests__/browserSuites/push-initialization-retries.spec.js b/src/__tests__/browserSuites/push-initialization-retries.spec.js index ebe12112f..77e0ad7ac 100644 --- a/src/__tests__/browserSuites/push-initialization-retries.spec.js +++ b/src/__tests__/browserSuites/push-initialization-retries.spec.js @@ -71,18 +71,18 @@ export function testPushRetriesDueToAuthErrors(fetchMock, assert) { assert.true(nearlyEqual(lapse, 0), 'initial sync'); return { status: 200, body: splitChangesMock1 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { assert.true(ready, 'client ready before first polling fetch'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, 0), 'fallback to polling'); return { status: 200, body: splitChangesMock2 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, settings.scheduler.featuresRefreshRate), 'polling'); return { status: 200, body: splitChangesMock2 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, settings.scheduler.featuresRefreshRate * 2), 'keep polling since auth success buth with push disabled'); client.destroy().then(() => { @@ -142,18 +142,18 @@ export function testPushRetriesDueToSseErrors(fetchMock, assert) { assert.true(nearlyEqual(lapse, 0), 'initial sync'); return { status: 200, body: splitChangesMock1 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { assert.true(ready, 'client ready before first polling fetch'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, 0), 'fallback to polling'); return { status: 200, body: splitChangesMock2 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, settings.scheduler.featuresRefreshRate), 'polling'); return { status: 200, body: splitChangesMock2 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, expectedTimeToSSEsuccess), 'sync due to success SSE connection'); client.destroy().then(() => { @@ -260,7 +260,7 @@ export function testSdkDestroyWhileAuthRetries(fetchMock, assert) { fetchMock.get({ url: url(settings, '/memberships/nicolas%40split.io'), repeat: 2 }, { status: 200, body: membershipsNicolasMock }); fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }); fetchMock.get(new RegExp('.*'), function (url) { assert.fail('unexpected GET request with url: ' + url); diff --git a/src/__tests__/browserSuites/push-refresh-token.spec.js b/src/__tests__/browserSuites/push-refresh-token.spec.js index 24634a49e..6acf48d41 100644 --- a/src/__tests__/browserSuites/push-refresh-token.spec.js +++ b/src/__tests__/browserSuites/push-refresh-token.spec.js @@ -88,7 +88,7 @@ export function testRefreshToken(fetchMock, assert) { }); // sync after SSE opened - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }); fetchMock.getOnce(url(settings, '/memberships/nicolas%40split.io'), { status: 200, body: membershipsNicolasMock1 }); // re-auth due to refresh token, with connDelay of 0.5 seconds @@ -100,7 +100,7 @@ export function testRefreshToken(fetchMock, assert) { }); // sync after SSE reopened - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_REFRESH_TOKEN + MILLIS_CONNDELAY), 'sync after SSE connection is reopened'); return { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }; @@ -116,7 +116,7 @@ export function testRefreshToken(fetchMock, assert) { }); // split sync after SSE closed due to push disabled - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_REFRESH_TOKEN * 2), 'sync after SSE connection is reopened a second time'); setTimeout(() => { diff --git a/src/__tests__/browserSuites/push-synchronization-retries.spec.js b/src/__tests__/browserSuites/push-synchronization-retries.spec.js index 88472f643..c88f65e23 100644 --- a/src/__tests__/browserSuites/push-synchronization-retries.spec.js +++ b/src/__tests__/browserSuites/push-synchronization-retries.spec.js @@ -147,7 +147,7 @@ export function testSynchronizationRetries(fetchMock, assert) { fetchMock.get({ url: url(settings, '/memberships/marcio%40split.io'), repeat: 3 }, { status: 200, body: membershipsMarcio }); // split and segment sync after SSE opened - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_SSE_OPEN), 'sync after SSE connection is opened'); return { status: 200, body: splitChangesMock2 }; @@ -155,9 +155,9 @@ export function testSynchronizationRetries(fetchMock, assert) { fetchMock.getOnce(url(settings, '/memberships/nicolas%40split.io'), { status: 200, body: membershipsNicolasMock1 }); // fetch due to SPLIT_UPDATE event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }); // fetch retry for SPLIT_UPDATE event, due to previous unexpected response (response till minor than SPLIT_UPDATE changeNumber) - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_RETRY_FOR_FIRST_SPLIT_UPDATE_EVENT), 'fetch retry due to SPLIT_UPDATE event'); return { status: 200, body: splitChangesMock3 }; @@ -177,18 +177,18 @@ export function testSynchronizationRetries(fetchMock, assert) { }); // fetch due to SPLIT_KILL event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=100'), function () { assert.equal(client.getTreatment('whitelist'), 'not_allowed', 'evaluation with split killed immediately, before fetch is done'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_SPLIT_KILL_EVENT), 'sync due to SPLIT_KILL event'); return { status: 200, body: { ff: { d: [], s: 1457552649999, t: 1457552649999 } } }; // returning old state }); // first fetch retry for SPLIT_KILL event, due to previous unexpected response (response till minor than SPLIT_KILL changeNumber) - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), { throws: new TypeError('Network error') }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=100'), { throws: new TypeError('Network error') }); // second fetch retry for SPLIT_KILL event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), { status: 200, body: '{ "since": 1457552620999, "til' }); // invalid JSON response + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=100'), { status: 200, body: '{ "since": 1457552620999, "til' }); // invalid JSON response // third fetch retry for SPLIT_KILL event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_THIRD_RETRY_FOR_SPLIT_KILL_EVENT), 'third fetch retry due to SPLIT_KILL event'); diff --git a/src/__tests__/browserSuites/push-synchronization.spec.js b/src/__tests__/browserSuites/push-synchronization.spec.js index 6ae6fca48..d9db7ff79 100644 --- a/src/__tests__/browserSuites/push-synchronization.spec.js +++ b/src/__tests__/browserSuites/push-synchronization.spec.js @@ -278,7 +278,7 @@ export function testSynchronization(fetchMock, assert) { }); // sync all after SSE opened - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function (url, opts) { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_SSE_OPEN), 'sync after SSE connection is opened'); if (hasNoCacheHeader(opts)) assert.fail('request must not include `Cache-Control` header'); @@ -290,13 +290,13 @@ export function testSynchronization(fetchMock, assert) { }); // fetch due to SPLIT_UPDATE event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function (url, opts) { if (!hasNoCacheHeader(opts)) assert.fail('request must include `Cache-Control` header'); return { status: 200, body: splitChangesMock3 }; }); // fetch due to SPLIT_KILL event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=100'), function (url, opts) { if (!hasNoCacheHeader(opts)) assert.fail('request must include `Cache-Control` header'); assert.equal(client.getTreatment('whitelist'), 'not_allowed', 'evaluation with split killed immediately, before fetch is done'); return { status: 200, body: splitChangesMock4 }; @@ -309,7 +309,7 @@ export function testSynchronization(fetchMock, assert) { }); // sync all after second SSE opened - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552650000&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552650000&rbSince=100'), function (url, opts) { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_SECOND_SSE_OPEN), 'sync after second SSE connection is opened'); if (hasNoCacheHeader(opts)) assert.fail('request must not include `Cache-Control` header'); @@ -337,7 +337,7 @@ export function testSynchronization(fetchMock, assert) { fetchMock.getOnce(url(settings, '/memberships/nicolas%40split.io'), { status: 200, body: { ms: { k: [{ n: 'developers' }, { n: 'engineers' }] }, ls: { k: [{ n: 'employees' }, { n: 'splitters' }], cn: 1457552650000 } } }); // target changeNumber // initial fetch of memberships for other clients + sync all after third SSE opened + 3 unbounded fetch for MEMBERSHIPS_MS_UPDATE + 1 unbounded fetch for MEMBERSHIPS_LS_UPDATE - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552650000&rbSince=-1'), { status: 200, body: { ff: { d: [], s: 1457552650000, t: 1457552650000 } } }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552650000&rbSince=100'), { status: 200, body: { ff: { d: [], s: 1457552650000, t: 1457552650000 } } }); fetchMock.get({ url: url(settings, '/memberships/key1'), repeat: 6 }, { status: 200, body: { ms: {} } }); fetchMock.get({ url: url(settings, '/memberships/key3'), repeat: 6 }, { status: 200, body: { ms: { k: [{ n: 'splitters' }] } } }); fetchMock.get({ url: url(settings, `/memberships/${bitmapTrueKey}`), repeat: 5 }, { status: 200, body: { ms: { k: [] } } }); diff --git a/src/__tests__/browserSuites/readiness.spec.js b/src/__tests__/browserSuites/readiness.spec.js index fa7d7ad85..c6246d3f1 100644 --- a/src/__tests__/browserSuites/readiness.spec.js +++ b/src/__tests__/browserSuites/readiness.spec.js @@ -122,7 +122,7 @@ export default function (fetchMock, assert) { /************** Now we will validate the intelligent memberships pausing, which requires lots of code. Related code below. **************/ localStorage.clear(); const membershipsEndpointDelay = 450; - function mockForSegmentsPauseTest(testUrls, startWithSegments = false) { + function mockForSegmentsPauseTest(testUrls, startWithSegments) { let membershipsHits = 0; fetchMock.get(new RegExp(`${testUrls.sdk}/memberships/nicolas\\d?%40split.io`), function () { // Mock any memberships call, so we can test with multiple clients. @@ -130,7 +130,6 @@ export default function (fetchMock, assert) { return new Promise((res) => { setTimeout(() => { res({ status: 200, body: { ms: {} } }); }, membershipsEndpointDelay); }); }); // Now mock the no more updates state - fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552669999&rbSince=-1', { status: 200, body: { ff: { d: [], s: 1457552669999, t: 1457552669999 } } }); if (startWithSegments) { @@ -138,10 +137,12 @@ export default function (fetchMock, assert) { fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesStartWithSegmentsMock }); fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: { ff: { ...splitChangesUpdateWithoutSegmentsMock.ff, s: 1457552620999, t: 1457552649999 } } }); fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552649999&rbSince=-1', { status: 200, body: { ff: { ...splitChangesUpdateWithSegmentsMock.ff, s: 1457552649999, t: 1457552669999 } } }); + fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552669999&rbSince=-1', { status: 200, body: { ff: { d: [], s: 1457552669999, t: 1457552669999 } } }); } else { fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesStartWithoutSegmentsMock }); - fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesUpdateWithSegmentsMock }); - fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552649999&rbSince=-1', { status: 200, body: splitChangesUpdateWithoutSegmentsMock }); + fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: { ff: splitChangesUpdateWithSegmentsMock.ff } }); + fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552649999&rbSince=-1', { status: 200, body: { ff: splitChangesUpdateWithoutSegmentsMock.ff } }); + fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552669999&rbSince=-1', { status: 200, body: { ff: { d: [], s: 1457552669999, t: 1457552669999 } } }); } return () => membershipsHits; diff --git a/src/__tests__/browserSuites/ready-promise.spec.js b/src/__tests__/browserSuites/ready-promise.spec.js index a8176e628..3371d1618 100644 --- a/src/__tests__/browserSuites/ready-promise.spec.js +++ b/src/__tests__/browserSuites/ready-promise.spec.js @@ -231,7 +231,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { fetchMock.getOnce(config.urls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', splitChangesMock1, { delay: refreshTimeMillis }); // main client endpoint configured to fetch segments before request timeout fetchMock.get(config.urls.sdk + '/memberships/facundo%40split.io', membershipsFacundo, { delay: fromSecondsToMillis(config.startup.requestTimeoutBeforeReady) - 20 }); - fetchMock.get(config.urls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { ff: { d: [], s: 1457552620999, t: 1457552620999 } }); + fetchMock.get(config.urls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=100', { ff: { d: [], s: 1457552620999, t: 1457552620999 } }); // shared client endpoint configured to fetch segments immediately, in order to emit SDK_READY as soon as splits arrives fetchMock.get(config.urls.sdk + '/memberships/nicolas%40split.io', membershipsFacundo); // shared client endpoint configured to emit SDK_READY_TIMED_OUT diff --git a/src/__tests__/browserSuites/telemetry.spec.js b/src/__tests__/browserSuites/telemetry.spec.js index f9995f5ff..82e031f2d 100644 --- a/src/__tests__/browserSuites/telemetry.spec.js +++ b/src/__tests__/browserSuites/telemetry.spec.js @@ -76,7 +76,7 @@ export default async function telemetryBrowserSuite(fetchMock, t) { // @TODO check if iDe value is correct assert.deepEqual(data, { - mE: {}, hE: { sp: { 500: 1 }, ms: { 500: 1 } }, tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 33, seC: 1, skC: 1, eQ: 1, eD: 0, sE: [], t: [], ufs: {} + mE: {}, hE: { sp: { 500: 1 }, ms: { 500: 1 } }, tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 35, seC: 1, skC: 1, eQ: 1, eD: 0, sE: [], t: [], ufs: {} }, 'metrics/usage JSON payload should be the expected'); finish.next(); @@ -96,7 +96,7 @@ export default async function telemetryBrowserSuite(fetchMock, t) { // @TODO check if iDe value is correct assert.deepEqual(data, { mL: {}, mE: {}, hE: {}, hL: {}, // errors and latencies were popped - tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 33, seC: 1, skC: 1, eQ: 1, eD: 0, sE: [], t: [], ufs: {} + tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 35, seC: 1, skC: 1, eQ: 1, eD: 0, sE: [], t: [], ufs: {} }, '2nd metrics/usage JSON payload should be the expected'); return 200; }); diff --git a/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C0.json b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C0.json new file mode 100644 index 000000000..fcc45c379 --- /dev/null +++ b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C0.json @@ -0,0 +1,4 @@ +{ + "type": "message", + "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694505,\\\"pcn\\\":111,\\\"c\\\":0,\\\"d\\\":\\\"eyJuYW1lIjoicmJzX3Rlc3QiLCJzdGF0dXMiOiJBQ1RJVkUiLCJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiZXhjbHVkZWQiOnsia2V5cyI6W10sInNlZ21lbnRzIjpbXX0sImNvbmRpdGlvbnMiOlt7Im1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6InVzZXIifSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2V9XX19XX0=\\\"}\"}" +} diff --git a/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C1.json b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C1.json new file mode 100644 index 000000000..e3181f524 --- /dev/null +++ b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C1.json @@ -0,0 +1,4 @@ +{ + "type": "message", + "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694505,\\\"pcn\\\":111,\\\"c\\\":1,\\\"d\\\":\\\"H4sIAAAAAAAA/3SSQYucQBCF/0udPeTsbYhLCJl4cQiEsCxl+3SabbuluppFFv976HGiE3Bu3b73PnxV/Unmyn5AncYWQuWXglS47625zBNqHkElpQihgvx6kza+KaLSZj05FwyrDf4RsH9tgO6mxO2grClSSaevl++/Xqigd+tcFnt2EQV16Dk5vQhYR3ilkqggdkO45U3wnc3oSOWfz/2af3q1jqzmCvkmIU1UZsvYWp8r0qmudsMKeMfcwMFokGx+GMFen1XFtklBpU/OLRvibjudz28/Xn43eVIYOPvuZTKgQm89ugZDrvNzTVasvNIK+rhahbPxSEueZa7TCLHmQG6hH4B/rjQq1g8HeocJvoM381E4BAc+wsZnvIemZ5YBz+our0tBE4v+W+Iad9zC5f0tr7cd93ZIwv9ZInQ723ESxJjlykZu9we0/A0AAP//PlHpp9gCAAA=\\\"}\"}" +} diff --git a/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C2.json b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C2.json new file mode 100644 index 000000000..4e1b78068 --- /dev/null +++ b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C2.json @@ -0,0 +1,4 @@ +{ + "type": "message", + "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694505,\\\"pcn\\\":111,\\\"c\\\":2,\\\"d\\\":\\\"eJx0kkGLnEAQhf9LnT3k7G2ISwiZeHEIhLAsZft0mm27pbqaRRb/e+hxohNwbt2+9z58Vf1J5sp+QJ3GFkLll4JUuO+tucwTah5BJaUIoYL8epM2vimi0mY9ORcMqw3+EbB/bYDupsTtoKwpUkmnr5fvv16ooHfrXBZ7dhEFdeg5Ob0IWEd4pZKoIHZDuOVN8J3N6Ejln8/9mn96tY6s5gr5JiFNVGbL2FqfK9KprnbDCnjH3MDBaJBsfhjBXp9VxbZJQaVPzi0b4m47nc9vP15+N3lSGDj77mUyoEJvPboGQ67zc01WrLzSCvq4WoWz8UhLnmWu0wix5kBuoR+Af640KtYPB3qHCb6DN/NROAQHPsLGZ7yHpmeWAc/qLq9LQROL/lviGnfcwuX9La+3Hfd2SML/WSJ0O9txEsSY5cpGbvcHtPwNAAD//9u9Atc=\\\"}\"}" +} diff --git a/src/__tests__/mocks/splitchanges.since.-1.json b/src/__tests__/mocks/splitchanges.since.-1.json index 4f8195b27..193b9fd36 100644 --- a/src/__tests__/mocks/splitchanges.since.-1.json +++ b/src/__tests__/mocks/splitchanges.since.-1.json @@ -1,4 +1,41 @@ { + "rbs": { + "s": -1, + "t": 100, + "d": [ + { + "changeNumber": 5, + "name": "test_rule_based_segment", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded": { + "keys": [ "mauro@split.io", "gaston@split.io" ], + "segments": [ "segment_test" ] + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "ENDS_WITH", + "negate": false, + "whitelistMatcherData": { + "whitelist": [ + "@split.io" + ] + } + } + ] + } + } + ] + } + ] + }, "ff": { "d": [ { @@ -1549,6 +1586,150 @@ "configurations": { "o.n": "{\"color\":\"brown\",\"dimensions\":{\"height\":12,\"width\":14},\"text\":{\"inner\":\"click me\"}}" } + }, + { + "changeNumber": 10, + "trafficTypeName": "user", + "name": "rbs_test_flag", + "trafficAllocation": 100, + "trafficAllocationSeed": 1828377380, + "seed": -286617921, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "algo": 2, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "IN_RULE_BASED_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "test_rule_based_segment" + } + } + ] + }, + "partitions": [ + { + "treatment": "v1", + "size": 100 + }, + { + "treatment": "v2", + "size": 0 + } + ], + "label": "in rule based segment test_rule_based_segment" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "ALL_KEYS", + "negate": false + } + ] + }, + "partitions": [ + { + "treatment": "v1", + "size": 0 + }, + { + "treatment": "v2", + "size": 100 + } + ], + "label": "default rule" + } + ], + "configurations": {}, + "sets": [], + "impressionsDisabled": false + }, + { + "changeNumber": 10, + "trafficTypeName": "user", + "name": "rbs_test_flag_negated", + "trafficAllocation": 100, + "trafficAllocationSeed": 1828377380, + "seed": -286617921, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "algo": 2, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "IN_RULE_BASED_SEGMENT", + "negate": true, + "userDefinedSegmentMatcherData": { + "segmentName": "test_rule_based_segment" + } + } + ] + }, + "partitions": [ + { + "treatment": "v1", + "size": 100 + }, + { + "treatment": "v2", + "size": 0 + } + ], + "label": "not in rule based segment test_rule_based_segment" + }, + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "ALL_KEYS", + "negate": false + } + ] + }, + "partitions": [ + { + "treatment": "v1", + "size": 0 + }, + { + "treatment": "v2", + "size": 100 + } + ], + "label": "default rule" + } + ], + "configurations": {}, + "sets": [], + "impressionsDisabled": false } ], "s": -1, diff --git a/src/__tests__/nodeSuites/expected-treatments.spec.js b/src/__tests__/nodeSuites/expected-treatments.spec.js index b4c2f6a18..0173777f1 100644 --- a/src/__tests__/nodeSuites/expected-treatments.spec.js +++ b/src/__tests__/nodeSuites/expected-treatments.spec.js @@ -6,7 +6,8 @@ import { url } from '../testUtils'; import splitChangesMockReal from '../mocks/splitchanges.real.json'; export default async function (config, settings, fetchMock, assert) { - fetchMock.get({ url: url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), overwriteRoutes: true }, { status: 200, body: splitChangesMockReal }); + fetchMock.getOnce({ url: url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), overwriteRoutes: true }, { status: 200, body: splitChangesMockReal }); + fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }); const splitio = SplitFactory({ ...config, diff --git a/src/__tests__/nodeSuites/impressions.debug.spec.js b/src/__tests__/nodeSuites/impressions.debug.spec.js index 975d43edb..8d68e2125 100644 --- a/src/__tests__/nodeSuites/impressions.debug.spec.js +++ b/src/__tests__/nodeSuites/impressions.debug.spec.js @@ -1,7 +1,6 @@ import { SplitFactory } from '../../'; import { settingsFactory } from '../../settings'; import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; -import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; import { DEBUG } from '@splitsoftware/splitio-commons/src/utils/constants'; import { truncateTimeFrame } from '@splitsoftware/splitio-commons/src/utils/time'; import { url } from '../testUtils'; @@ -44,7 +43,6 @@ let truncatedTimeFrame; export default async function (key, fetchMock, assert) { // Mocking this specific route to make sure we only get the items we want to test from the handlers. fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); fetchMock.get(new RegExp(`${url(settings, '/segmentChanges/')}.*`), { status: 200, body: { since: 10, till: 10, name: 'segmentName', added: [], removed: [] } }); const splitio = SplitFactory(config); diff --git a/src/__tests__/nodeSuites/impressions.none.spec.js b/src/__tests__/nodeSuites/impressions.none.spec.js index 145c47339..dfc831082 100644 --- a/src/__tests__/nodeSuites/impressions.none.spec.js +++ b/src/__tests__/nodeSuites/impressions.none.spec.js @@ -1,7 +1,6 @@ import { SplitFactory } from '../../'; import { settingsFactory } from '../../settings'; import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; -import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; import { NONE } from '@splitsoftware/splitio-commons/src/utils/constants'; import { truncateTimeFrame } from '@splitsoftware/splitio-commons/src/utils/time'; import { url } from '../testUtils'; @@ -40,7 +39,6 @@ const config = { export default async function (key, fetchMock, assert) { // Mocking this specific route to make sure we only get the items we want to test from the handlers. fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); fetchMock.get(new RegExp(`${url(settings, '/segmentChanges/')}.*`), { status: 200, body: { since: 10, till: 10, name: 'segmentName', added: [], removed: [] } }); const splitio = SplitFactory(config); diff --git a/src/__tests__/nodeSuites/impressions.spec.js b/src/__tests__/nodeSuites/impressions.spec.js index e7addea85..4500abf3c 100644 --- a/src/__tests__/nodeSuites/impressions.spec.js +++ b/src/__tests__/nodeSuites/impressions.spec.js @@ -38,7 +38,7 @@ let truncatedTimeFrame; export default async function (key, fetchMock, assert) { // Mocking this specific route to make sure we only get the items we want to test from the handlers. fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); + fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }); fetchMock.get(new RegExp(`${url(settings, '/segmentChanges/')}.*`), { status: 200, body: { since: 10, till: 10, name: 'segmentName', added: [], removed: [] } }); const splitio = SplitFactory(config); diff --git a/src/__tests__/nodeSuites/ip-addresses-setting.spec.js b/src/__tests__/nodeSuites/ip-addresses-setting.spec.js index 99aea2916..18038de82 100644 --- a/src/__tests__/nodeSuites/ip-addresses-setting.spec.js +++ b/src/__tests__/nodeSuites/ip-addresses-setting.spec.js @@ -122,7 +122,6 @@ export default function ipAddressesSettingAssertions(fetchMock, assert) { // Mock GET endpoints to run client normally fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }); fetchMock.get(new RegExp(`${url(settings, '/segmentChanges/')}.*`), { status: 200, body: { since: 10, till: 10, name: 'segmentName', added: [], removed: [] } }); // Mock and assert POST endpoints diff --git a/src/__tests__/nodeSuites/push-initialization-nopush.spec.js b/src/__tests__/nodeSuites/push-initialization-nopush.spec.js index d273279cd..e06397592 100644 --- a/src/__tests__/nodeSuites/push-initialization-nopush.spec.js +++ b/src/__tests__/nodeSuites/push-initialization-nopush.spec.js @@ -50,7 +50,7 @@ function testInitializationFail(fetchMock, assert, fallbackToPolling) { }); if (fallbackToPolling) { - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { assert.true(ready, 'client ready'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, 0, 100), 'polling (first fetch)'); @@ -58,7 +58,7 @@ function testInitializationFail(fetchMock, assert, fallbackToPolling) { }); } - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { assert.true(ready, 'client ready'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, settings.scheduler.featuresRefreshRate, 100), 'polling (second fetch)'); diff --git a/src/__tests__/nodeSuites/push-initialization-retries.spec.js b/src/__tests__/nodeSuites/push-initialization-retries.spec.js index e81d7d050..94fc9d7c0 100644 --- a/src/__tests__/nodeSuites/push-initialization-retries.spec.js +++ b/src/__tests__/nodeSuites/push-initialization-retries.spec.js @@ -65,18 +65,18 @@ export function testPushRetriesDueToAuthErrors(fetchMock, assert) { assert.true(nearlyEqual(lapse, 0), 'initial sync'); return { status: 200, body: splitChangesMock1 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { assert.true(ready, 'client ready before first polling fetch'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, 0), 'fallback to polling'); return { status: 200, body: splitChangesMock2 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, settings.scheduler.featuresRefreshRate), 'polling'); return { status: 200, body: splitChangesMock2 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, settings.scheduler.featuresRefreshRate * 2), 'keep polling since auth success buth with push disabled'); client.destroy().then(() => { @@ -134,18 +134,18 @@ export function testPushRetriesDueToSseErrors(fetchMock, assert) { assert.true(nearlyEqual(lapse, 0), 'initial sync'); return { status: 200, body: splitChangesMock1 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { assert.true(ready, 'client ready before first polling fetch'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, 0), 'fallback to polling'); return { status: 200, body: splitChangesMock2 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, settings.scheduler.featuresRefreshRate), 'polling'); return { status: 200, body: splitChangesMock2 }; }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, expectedTimeToSSEsuccess), 'sync due to success SSE connection'); client.destroy().then(() => { @@ -224,7 +224,7 @@ export function testSdkDestroyWhileAuthRetries(fetchMock, assert) { fetchMock.get(new RegExp(`${url(settings, '/segmentChanges/')}.*`), { status: 200, body: { since: 10, till: 10, name: 'segmentName', added: [], removed: [] } }); fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }); fetchMock.get(new RegExp('.*'), function (url) { assert.fail('unexpected GET request with url: ' + url); diff --git a/src/__tests__/nodeSuites/push-refresh-token.spec.js b/src/__tests__/nodeSuites/push-refresh-token.spec.js index 9ff9bfa5c..148bc45b7 100644 --- a/src/__tests__/nodeSuites/push-refresh-token.spec.js +++ b/src/__tests__/nodeSuites/push-refresh-token.spec.js @@ -87,7 +87,7 @@ export function testRefreshToken(fetchMock, assert) { }); // split sync after SSE opened - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }); // re-auth due to refresh token, with connDelay of 0.5 seconds fetchMock.getOnce(url(settings, '/v2/auth?s=1.3'), function (url, opts) { @@ -98,7 +98,7 @@ export function testRefreshToken(fetchMock, assert) { }); // split sync after SSE reopened - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_REFRESH_TOKEN + MILLIS_CONNDELAY), 'sync after SSE connection is reopened'); return { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }; @@ -113,7 +113,7 @@ export function testRefreshToken(fetchMock, assert) { }); // split sync after SSE closed due to push disabled - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_REFRESH_TOKEN * 2), 'sync after SSE connection is reopened a second time'); setTimeout(() => { diff --git a/src/__tests__/nodeSuites/push-synchronization-retries.spec.js b/src/__tests__/nodeSuites/push-synchronization-retries.spec.js index 2a741dd6b..08ba0acf0 100644 --- a/src/__tests__/nodeSuites/push-synchronization-retries.spec.js +++ b/src/__tests__/nodeSuites/push-synchronization-retries.spec.js @@ -143,7 +143,7 @@ export function testSynchronizationRetries(fetchMock, assert) { ); // split and segment sync after SSE opened - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_SSE_OPEN), 'sync after SSE connection is opened'); return { status: 200, body: splitChangesMock2 }; @@ -153,9 +153,9 @@ export function testSynchronizationRetries(fetchMock, assert) { ); // fetch due to SPLIT_UPDATE event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { throws: new TypeError('Network error') }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { throws: new TypeError('Network error') }); // fetch retry for SPLIT_UPDATE event, due to previous fail - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_RETRY_FOR_FIRST_SPLIT_UPDATE_EVENT), 'fetch retry due to SPLIT_UPDATE event'); return { status: 200, body: splitChangesMock3 }; @@ -182,18 +182,18 @@ export function testSynchronizationRetries(fetchMock, assert) { fetchMock.getOnce(url(settings, '/segmentChanges/splitters?since=1457552640000'), { status: 200, body: { since: 1457552640000, till: 1457552640000, name: 'splitters', added: [], removed: [] } }); // fetch due to SPLIT_KILL event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=100'), function () { assert.equal(client.getTreatment(key, 'whitelist'), 'not_allowed', 'evaluation with split killed immediately, before fetch is done'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_SPLIT_KILL_EVENT), 'sync due to SPLIT_KILL event'); return { status: 200, body: { ff: { d: [], s: 1457552649999, t: 1457552649999 } } }; // returning old state }); // first fetch retry for SPLIT_KILL event, due to previous unexpected response (response till minor than SPLIT_KILL changeNumber) - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), { status: 200, body: '{ "since": 1457552620999, "til' }); // invalid JSON + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=100'), { status: 200, body: '{ "since": 1457552620999, "til' }); // invalid JSON // second fetch retry for SPLIT_KILL event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), { throws: new TypeError('Network error') }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=100'), { throws: new TypeError('Network error') }); // third fetch retry for SPLIT_KILL event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), function () { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=100'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_THIRD_RETRY_FOR_SPLIT_KILL_EVENT), 'third fetch retry due to SPLIT_KILL event'); diff --git a/src/__tests__/nodeSuites/push-synchronization.spec.js b/src/__tests__/nodeSuites/push-synchronization.spec.js index d14bf6bae..f48f0b7da 100644 --- a/src/__tests__/nodeSuites/push-synchronization.spec.js +++ b/src/__tests__/nodeSuites/push-synchronization.spec.js @@ -239,7 +239,7 @@ export function testSynchronization(fetchMock, assert) { }); // split and segment sync after SSE opened - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function (url, opts) { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_SSE_OPEN), 'sync after SSE connection is opened'); if (hasNoCacheHeader(opts)) assert.fail('request must not include `Cache-Control` header'); @@ -251,7 +251,7 @@ export function testSynchronization(fetchMock, assert) { }); // fetch due to SPLIT_UPDATE event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function (url, opts) { if (!hasNoCacheHeader(opts)) assert.fail('request must include `Cache-Control` header'); return { status: 200, body: splitChangesMock3 }; }); @@ -268,14 +268,14 @@ export function testSynchronization(fetchMock, assert) { }); // fetch due to SPLIT_KILL event - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=100'), function (url, opts) { if (!hasNoCacheHeader(opts)) assert.fail('request must include `Cache-Control` header'); assert.equal(client.getTreatment(key, 'whitelist'), 'not_allowed', 'evaluation with split killed immediately, before fetch is done'); return { status: 200, body: splitChangesMock4 }; }); // fetch due to SPLIT_UPDATE event, with an update that involves a new segment - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552650000&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552650000&rbSince=100'), function (url, opts) { if (!hasNoCacheHeader(opts)) assert.fail('request must include `Cache-Control` header'); return { status: 200, body: splitChangesMock5 }; }); @@ -286,25 +286,25 @@ export function testSynchronization(fetchMock, assert) { }); // fetch feature flags due to IFFU SPLIT_UPDATE event with wrong compress code - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1684265694505&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1684265694505&rbSince=100'), function (url, opts) { if (!hasNoCacheHeader(opts)) assert.fail('request must include `Cache-Control` header'); return { status: 200, body: splitChangesMock6 }; }); // fetch feature flags due to IFFU SPLIT_UPDATE event with previous change number = 0 - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1684265694506&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1684265694506&rbSince=100'), function (url, opts) { if (!hasNoCacheHeader(opts)) assert.fail('request must include `Cache-Control` header'); return { status: 200, body: splitChangesMock7 }; }); // fetch feature flags due to IFFU SPLIT_UPDATE event with previous change number !== current change number - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1684265694526&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1684265694526&rbSince=100'), function (url, opts) { if (!hasNoCacheHeader(opts)) assert.fail('request must include `Cache-Control` header'); return { status: 200, body: splitChangesMock8 }; }); // fetch feature flags due to IFFU SPLIT_UPDATE event with ARCHIVED feature flag - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1684265694546&rbSince=-1'), function (url, opts) { + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1684265694546&rbSince=100'), function (url, opts) { if (!hasNoCacheHeader(opts)) assert.fail('request must include `Cache-Control` header'); return { status: 200, body: splitChangesMock9 }; }); diff --git a/src/__tests__/nodeSuites/telemetry.spec.js b/src/__tests__/nodeSuites/telemetry.spec.js index d27611150..0549560c3 100644 --- a/src/__tests__/nodeSuites/telemetry.spec.js +++ b/src/__tests__/nodeSuites/telemetry.spec.js @@ -66,7 +66,7 @@ export default async function telemetryNodejsSuite(key, fetchMock, assert) { // @TODO check if iDe value is correct assert.deepEqual(data, { - mE: {}, hE: { sp: { 500: 1 } }, tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 33, seC: 3, skC: 3, eQ: 1, eD: 0, sE: [], t: [], ufs: {} + mE: {}, hE: { sp: { 500: 1 } }, tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 35, seC: 3, skC: 3, eQ: 1, eD: 0, sE: [], t: [], ufs: {} }, 'metrics/usage JSON payload should be the expected'); finish.next(); @@ -85,7 +85,7 @@ export default async function telemetryNodejsSuite(key, fetchMock, assert) { // @TODO check if iDe value is correct assert.deepEqual(data, { mL: {}, mE: {}, hE: {}, hL: {}, // errors and latencies were popped - tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 33, seC: 3, skC: 3, eQ: 1, eD: 0, sE: [], t: [], ufs: {} + tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 35, seC: 3, skC: 3, eQ: 1, eD: 0, sE: [], t: [], ufs: {} }, '2nd metrics/usage JSON payload should be the expected'); return 200; }); diff --git a/src/__tests__/online/browser.spec.js b/src/__tests__/online/browser.spec.js index 51911c28b..3f2051374 100644 --- a/src/__tests__/online/browser.spec.js +++ b/src/__tests__/online/browser.spec.js @@ -88,7 +88,7 @@ tape('## E2E CI Tests ##', function (assert) { localStorage.clear(); fetchMock.get(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); + fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }); fetchMock.get(url(settings, '/memberships/facundo%40split.io'), { status: 200, body: membershipsFacundo }); fetchMock.get(url(settings, '/memberships/nicolas%40split.io'), { status: 200, body: membershipsNicolas }); fetchMock.get(url(settings, '/memberships/marcio%40split.io'), { status: 200, body: membershipsMarcio }); diff --git a/src/__tests__/online/node.spec.js b/src/__tests__/online/node.spec.js index 18b2c6283..0bd7dc567 100644 --- a/src/__tests__/online/node.spec.js +++ b/src/__tests__/online/node.spec.js @@ -40,7 +40,7 @@ const settings = settingsFactory(config); const key = 'facundo@split.io'; fetchMock.get(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); -fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: splitChangesMock2 }); +fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }); fetchMock.get(new RegExp(`${url(settings, '/segmentChanges')}/*`), { status: 200, body: { 'name': 'segment', From 12eb78fef71acfb4edfd335c1bfedba6b9b4cb78 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 17 Mar 2025 16:01:46 -0300 Subject: [PATCH 2/7] Evaluation tests --- .../browserSuites/evaluations.spec.js | 46 ++++++++++++++++--- .../mocks/splitchanges.since.-1.json | 2 +- src/__tests__/nodeSuites/evaluations.spec.js | 11 +++++ src/__tests__/nodeSuites/telemetry.spec.js | 6 +-- src/__tests__/online/node.spec.js | 17 ++++--- 5 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/__tests__/browserSuites/evaluations.spec.js b/src/__tests__/browserSuites/evaluations.spec.js index b40b0b1d3..e4c5bf8d8 100644 --- a/src/__tests__/browserSuites/evaluations.spec.js +++ b/src/__tests__/browserSuites/evaluations.spec.js @@ -362,6 +362,37 @@ export default function (config, fetchMock, assert) { }; + const evaluationsWithRuleBasedSegments = async (splitio) => { + fetchMock.getOnce('https://sdk.split.io/api/memberships/emi%40split.io', { status: 200, body: { ms: { k: [{ n: 'segment_excluded_by_rbs' }] } } }); + fetchMock.getOnce('https://sdk.split.io/api/memberships/mauro%40split.io', { status: 200, body: { ms: {} } }); + fetchMock.getOnce('https://sdk.split.io/api/memberships/bilal%40split.io', { status: 200, body: { ms: {} } }); + fetchMock.getOnce('https://sdk.split.io/api/memberships/other_key', { status: 200, body: { ms: {} } }); + + const client1 = splitio.client('emi@split.io'); + await client1.ready(); + assert.equal(client1.getTreatment('rbs_test_flag'), 'v2', 'key in excluded segment'); + assert.equal(client1.getTreatment('rbs_test_flag_negated'), 'v1', 'key in excluded segment'); + await client1.destroy(); + + const client2 = splitio.client('mauro@split.io'); + await client2.ready(); + assert.equal(client2.getTreatment('rbs_test_flag'), 'v2', 'excluded key'); + assert.equal(client2.getTreatment('rbs_test_flag_negated'), 'v1', 'excluded key'); + await client2.destroy(); + + const client3 = splitio.client('bilal@split.io'); + await client3.ready(); + assert.equal(client3.getTreatment('rbs_test_flag'), 'v1', 'key satisfies the rbs condition'); + assert.equal(client3.getTreatment('rbs_test_flag_negated'), 'v2', 'key satisfies the rbs condition'); + await client3.destroy(); + + const client4 = splitio.client('other_key'); + await client4.ready(); + assert.equal(client4.getTreatment('rbs_test_flag'), 'v2', 'key not in segment'); + assert.equal(client4.getTreatment('rbs_test_flag_negated'), 'v1', 'key not in segment'); + await client4.destroy(); + }; + for (i; i < SDK_INSTANCES_TO_TEST; i++) { let splitio = SplitFactory(config); @@ -376,13 +407,16 @@ export default function (config, fetchMock, assert) { getTreatmentsTests(client); getTreatmentsWithConfigTests(client); getTreatmentsWithInMemoryAttributes(client); - clientTABucket1.destroy(); - client.destroy(); - tested++; - if (tested === SDK_INSTANCES_TO_TEST) { - assert.end(); - } + evaluationsWithRuleBasedSegments(splitio).then(() => { + clientTABucket1.destroy(); + client.destroy(); + tested++; + + if (tested === SDK_INSTANCES_TO_TEST) { + assert.end(); + } + }); }); } } diff --git a/src/__tests__/mocks/splitchanges.since.-1.json b/src/__tests__/mocks/splitchanges.since.-1.json index 193b9fd36..8589565e8 100644 --- a/src/__tests__/mocks/splitchanges.since.-1.json +++ b/src/__tests__/mocks/splitchanges.since.-1.json @@ -10,7 +10,7 @@ "trafficTypeName": "user", "excluded": { "keys": [ "mauro@split.io", "gaston@split.io" ], - "segments": [ "segment_test" ] + "segments": [ "segment_excluded_by_rbs" ] }, "conditions": [ { diff --git a/src/__tests__/nodeSuites/evaluations.spec.js b/src/__tests__/nodeSuites/evaluations.spec.js index f5cf6aa53..6c7208b09 100644 --- a/src/__tests__/nodeSuites/evaluations.spec.js +++ b/src/__tests__/nodeSuites/evaluations.spec.js @@ -164,6 +164,17 @@ export default async function (config, key, assert) { assert.equal(client.getTreatment('aaaaaaklmnbv', 'ta_bucket1_test'), 'rollout_treatment'); // With a higher bucket it's ok to get default treatment assert.equal(client.getTreatment('nico_test', 'ta_bucket1_test'), 'default_treatment'); + + // Rule-based segments + assert.equal(client.getTreatment('emi@split.io', 'rbs_test_flag'), 'v2', 'key in excluded segment'); + assert.equal(client.getTreatment('mauro@split.io', 'rbs_test_flag'), 'v2', 'excluded key'); + assert.equal(client.getTreatment('bilal@split.io', 'rbs_test_flag'), 'v1', 'key satisfies the rbs condition'); + assert.equal(client.getTreatment('other_key', 'rbs_test_flag'), 'v2', 'key not in segment'); + + assert.equal(client.getTreatment('emi@split.io', 'rbs_test_flag_negated'), 'v1', 'key in excluded segment'); + assert.equal(client.getTreatment('mauro@split.io', 'rbs_test_flag_negated'), 'v1', 'excluded key'); + assert.equal(client.getTreatment('bilal@split.io', 'rbs_test_flag_negated'), 'v2', 'key satisfies the rbs condition'); + assert.equal(client.getTreatment('other_key', 'rbs_test_flag_negated'), 'v1', 'key not in segment'); }; const getTreatmentsTests = (client, sdkInstance) => { diff --git a/src/__tests__/nodeSuites/telemetry.spec.js b/src/__tests__/nodeSuites/telemetry.spec.js index 0549560c3..adbcfc311 100644 --- a/src/__tests__/nodeSuites/telemetry.spec.js +++ b/src/__tests__/nodeSuites/telemetry.spec.js @@ -55,7 +55,7 @@ export default async function telemetryNodejsSuite(key, fetchMock, assert) { // Validate http and method latencies const getLatencyCount = buckets => buckets ? buckets.reduce((accum, entry) => accum + entry, 0) : 0; assert.equal(getLatencyCount(data.hL.sp), 2, 'Two latency metrics for splitChanges GET request'); - assert.equal(getLatencyCount(data.hL.se), 6, 'Six latency metrics for segmentChanges GET request'); + assert.equal(getLatencyCount(data.hL.se), 8, 'Six latency metrics for segmentChanges GET request'); assert.equal(getLatencyCount(data.hL.te), 1, 'One latency metric for telemetry config POST request'); assert.equal(getLatencyCount(data.mL.t), 2, 'Two latency metrics for getTreatment (one not ready usage'); assert.equal(getLatencyCount(data.mL.ts), 1, 'One latency metric for getTreatments'); @@ -66,7 +66,7 @@ export default async function telemetryNodejsSuite(key, fetchMock, assert) { // @TODO check if iDe value is correct assert.deepEqual(data, { - mE: {}, hE: { sp: { 500: 1 } }, tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 35, seC: 3, skC: 3, eQ: 1, eD: 0, sE: [], t: [], ufs: {} + mE: {}, hE: { sp: { 500: 1 } }, tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 35, seC: 4, skC: 4, eQ: 1, eD: 0, sE: [], t: [], ufs: {} }, 'metrics/usage JSON payload should be the expected'); finish.next(); @@ -85,7 +85,7 @@ export default async function telemetryNodejsSuite(key, fetchMock, assert) { // @TODO check if iDe value is correct assert.deepEqual(data, { mL: {}, mE: {}, hE: {}, hL: {}, // errors and latencies were popped - tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 35, seC: 3, skC: 3, eQ: 1, eD: 0, sE: [], t: [], ufs: {} + tR: 0, aR: 0, iQ: 4, iDe: 1, iDr: 0, spC: 35, seC: 4, skC: 4, eQ: 1, eD: 0, sE: [], t: [], ufs: {} }, '2nd metrics/usage JSON payload should be the expected'); return 200; }); diff --git a/src/__tests__/online/node.spec.js b/src/__tests__/online/node.spec.js index 0bd7dc567..8b2eb34e2 100644 --- a/src/__tests__/online/node.spec.js +++ b/src/__tests__/online/node.spec.js @@ -41,15 +41,14 @@ const key = 'facundo@split.io'; fetchMock.get(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }); -fetchMock.get(new RegExp(`${url(settings, '/segmentChanges')}/*`), { - status: 200, body: { - 'name': 'segment', - 'added': [], - 'removed': [], - 'since': 1, - 'till': 1 - } -}); + +const emptySegmentResponse = { added: [], removed: [], since: 1, till: 1 }; +fetchMock.get(new RegExp(`${url(settings, '/segmentChanges/employees')}*`), { status: 200, body: emptySegmentResponse }); +fetchMock.get(new RegExp(`${url(settings, '/segmentChanges/splitters')}*`), { status: 200, body: emptySegmentResponse }); +fetchMock.get(new RegExp(`${url(settings, '/segmentChanges/developers')}*`), { status: 200, body: emptySegmentResponse }); +fetchMock.get(url(settings, '/segmentChanges/segment_excluded_by_rbs?since=-1'), { status: 200, body: { added: ['emi@split.io'], removed: [], since: -1, till: 1 } }); +fetchMock.get(url(settings, '/segmentChanges/segment_excluded_by_rbs?since=1'), { status: 200, body: { added: [], removed: [], since: 1, till: 1 } }); + fetchMock.post(url(settings, '/testImpressions/bulk'), 200); fetchMock.post(url(settings, '/testImpressions/count'), 200); fetchMock.post(url(settings, '/v1/metrics/config'), 200); From e1192c96996942797f7932fd9bce590b8645e03b Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 17 Mar 2025 16:45:12 -0300 Subject: [PATCH 3/7] Evaluation tests in Redis --- src/__tests__/consumer/node_redis.spec.js | 17 ++++++++++++++--- src/__tests__/mocks/redis-commands.txt | 5 +++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/__tests__/consumer/node_redis.spec.js b/src/__tests__/consumer/node_redis.spec.js index a2317ce48..62ce973b3 100644 --- a/src/__tests__/consumer/node_redis.spec.js +++ b/src/__tests__/consumer/node_redis.spec.js @@ -157,6 +157,17 @@ tape('Node.js Redis', function (t) { assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_on_negated'), 'off', 'Evaluations using Redis storage should be correct.'); assert.equal(await client.getTreatment('other_key', 'always-on-impressions-disabled-true'), 'on', 'Evaluations using Redis storage should be correct.'); + // Evaluations with rule-based segments + assert.equal(await client.getTreatment('emi@split.io', 'rbs_test_flag'), 'v2', 'key in excluded segment'); + assert.equal(await client.getTreatment('mauro@split.io', 'rbs_test_flag'), 'v2', 'excluded key'); + assert.equal(await client.getTreatment('bilal@split.io', 'rbs_test_flag'), 'v1', 'key satisfies the rbs condition'); + assert.equal(await client.getTreatment('other_key', 'rbs_test_flag'), 'v2', 'key not in segment'); + + assert.equal(await client.getTreatment('emi@split.io', 'rbs_test_flag_negated'), 'v1', 'key in excluded segment'); + assert.equal(await client.getTreatment('mauro@split.io', 'rbs_test_flag_negated'), 'v1', 'excluded key'); + assert.equal(await client.getTreatment('bilal@split.io', 'rbs_test_flag_negated'), 'v2', 'key satisfies the rbs condition'); + assert.equal(await client.getTreatment('other_key', 'rbs_test_flag_negated'), 'v1', 'key not in segment'); + assert.equal(typeof client.track().then, 'function', 'Track calls should always return a promise on Redis mode, even when parameters are incorrect.'); assert.true(await client.track('nicolas@split.io', 'user', 'test.redis.event', 18), 'If the event was successfully queued the promise will resolve to true'); @@ -164,11 +175,11 @@ tape('Node.js Redis', function (t) { // Manager methods const splitNames = await manager.names(); - assert.equal(splitNames.length, 26, 'manager `names` method returns the list of split names asynchronously'); + assert.equal(splitNames.length, 28, 'manager `names` method returns the list of split names asynchronously'); assert.equal(splitNames.indexOf(expectedSplitName) > -1, true, 'list of split names should contain expected splits'); assert.deepEqual(await manager.split(expectedSplitName), expectedSplitView, 'manager `split` method returns the split view of the given split name asynchronously'); const splitViews = await manager.splits(); - assert.equal(splitViews.length, 26, 'manager `splits` method returns the list of split views asynchronously'); + assert.equal(splitViews.length, 28, 'manager `splits` method returns the list of split views asynchronously'); assert.deepEqual(splitViews.find(splitView => splitView.name === expectedSplitName), expectedSplitView, 'manager `split` method returns the split view of the given split name asynchronously'); await client.ready(); // promise already resolved @@ -188,7 +199,7 @@ tape('Node.js Redis', function (t) { if (error) assert.fail('Redis server should be reachable'); const trackedImpressionsAndEvents = stdout.split('\n').filter(line => line !== '').map(line => parseInt(line)); - assert.deepEqual(trackedImpressionsAndEvents, [TOTAL_RAW_IMPRESSIONS, TOTAL_EVENTS], 'Tracked impressions and events should be stored in Redis'); + assert.deepEqual(trackedImpressionsAndEvents, [TOTAL_RAW_IMPRESSIONS + 8 /* evaluations with rule-based segments */, TOTAL_EVENTS], 'Tracked impressions and events should be stored in Redis'); // Validate stored telemetry exec(`echo "HLEN ${config.storage.prefix}.SPLITIO.telemetry.latencies \n HLEN ${config.storage.prefix}.SPLITIO.telemetry.exceptions \n HGET ${config.storage.prefix}.SPLITIO.telemetry.init 'nodejs-${version}/${HOSTNAME_VALUE}/${IP_VALUE}'" | redis-cli -p ${redisPort}`, (error, stdout) => { diff --git a/src/__tests__/mocks/redis-commands.txt b/src/__tests__/mocks/redis-commands.txt index e3ad7c35a..946b045ab 100644 --- a/src/__tests__/mocks/redis-commands.txt +++ b/src/__tests__/mocks/redis-commands.txt @@ -40,3 +40,8 @@ SET 'REDIS_NODE_UT.SPLITIO.split.testing_traffic_types' '{"changeNumber":149 SET 'REDIS_NODE_UT.SPLITIO.split.traffic_allocation_testing' '{"changeNumber":1490974123779,"trafficTypeName":"user","name":"traffic_allocation_testing","seed":1716284102,"status":"ACTIVE","killed":false,"defaultTreatment":"off","conditions":[{"matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":""},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":{"segmentName":""},"unaryNumericMatcherData":{"dataType":"","value":0},"whitelistMatcherData":{"whitelist":null},"betweenMatcherData":{"dataType":"","start":0,"end":0}}]},"partitions":[{"treatment":"on","size":100},{"treatment":"off","size":0}],"label":"in segment all"}]}' SET 'REDIS_NODE_UT.SPLITIO.splits.till' 1492723024413 SET 'REDIS_NODE_UT.SPLITIO.split.always-on-impressions-disabled-true' '{"changeNumber":1487277320548,"trafficTypeName":"user","name":"always-on-impressions-disabled-true","impressionsDisabled":true,"seed":1684183541,"status":"ACTIVE","killed":false,"defaultTreatment":"off","conditions":[{"matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":""},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":{"segmentName":""},"unaryNumericMatcherData":{"dataType":"","value":0},"whitelistMatcherData":{"whitelist":null},"betweenMatcherData":{"dataType":"","start":0,"end":0}}]},"partitions":[{"treatment":"on","size":100},{"treatment":"off","size":0}],"label":"in segment all"}]}' +SET 'REDIS_NODE_UT.SPLITIO.split.rbs_test_flag' '{"changeNumber":10,"trafficTypeName":"user","name":"rbs_test_flag","trafficAllocation":100,"trafficAllocationSeed":1828377380,"seed":-286617921,"status":"ACTIVE","killed":false,"defaultTreatment":"off","algo":2,"conditions":[{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user"},"matcherType":"IN_RULE_BASED_SEGMENT","negate":false,"userDefinedSegmentMatcherData":{"segmentName":"test_rule_based_segment"}}]},"partitions":[{"treatment":"v1","size":100},{"treatment":"v2","size":0}],"label":"in rule based segment test_rule_based_segment"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user"},"matcherType":"ALL_KEYS","negate":false}]},"partitions":[{"treatment":"v1","size":0},{"treatment":"v2","size":100}],"label":"default rule"}],"configurations":{},"sets":[],"impressionsDisabled":false}' +SET 'REDIS_NODE_UT.SPLITIO.split.rbs_test_flag_negated' '{"changeNumber":10,"trafficTypeName":"user","name":"rbs_test_flag_negated","trafficAllocation":100,"trafficAllocationSeed":1828377380,"seed":-286617921,"status":"ACTIVE","killed":false,"defaultTreatment":"off","algo":2,"conditions":[{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user"},"matcherType":"IN_RULE_BASED_SEGMENT","negate":true,"userDefinedSegmentMatcherData":{"segmentName":"test_rule_based_segment"}}]},"partitions":[{"treatment":"v1","size":100},{"treatment":"v2","size":0}],"label":"not in rule based segment test_rule_based_segment"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user"},"matcherType":"ALL_KEYS","negate":false}]},"partitions":[{"treatment":"v1","size":0},{"treatment":"v2","size":100}],"label":"default rule"}],"configurations":{},"sets":[],"impressionsDisabled":false}' +SET 'REDIS_NODE_UT.SPLITIO.rbsegment.test_rule_based_segment' '{"changeNumber":5,"name":"test_rule_based_segment","status":"ACTIVE","trafficTypeName":"user","excluded":{"keys":["mauro@split.io","gaston@split.io"],"segments":["segment_excluded_by_rbs"]},"conditions":[{"matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user"},"matcherType":"ENDS_WITH","negate":false,"whitelistMatcherData":{"whitelist":["@split.io"]}}]}}]}' +SET 'REDIS_NODE_UT.SPLITIO.rbsegments.till' 1492723024413 +SADD 'REDIS_NODE_UT.SPLITIO.segment.segment_excluded_by_rbs' emi@split.io From 6cc08c1c4bf4c12d899122a88fb08a672a77613f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 18 Mar 2025 16:47:28 -0300 Subject: [PATCH 4/7] Streaming tests --- ...ssage.RB_SEGMENT_UPDATE.1457552649999.json | 4 ++ .../mocks/message.RB_SEGMENT_UPDATE.C0.json | 2 +- .../mocks/message.RB_SEGMENT_UPDATE.C1.json | 2 +- .../mocks/message.RB_SEGMENT_UPDATE.C2.json | 2 +- ....till.1457552649999.RB_SEGMENT_UPDATE.json | 39 ++++++++++++ .../push-synchronization-retries.spec.js | 2 +- .../nodeSuites/push-synchronization.spec.js | 60 +++++++++++++++++-- 7 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 src/__tests__/mocks/message.RB_SEGMENT_UPDATE.1457552649999.json create mode 100644 src/__tests__/mocks/splitchanges.since.100.till.1457552649999.RB_SEGMENT_UPDATE.json diff --git a/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.1457552649999.json b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.1457552649999.json new file mode 100644 index 000000000..acad9194d --- /dev/null +++ b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.1457552649999.json @@ -0,0 +1,4 @@ +{ + "type": "message", + "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\": \"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1457552649999}\"}" +} \ No newline at end of file diff --git a/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C0.json b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C0.json index fcc45c379..e1696abd2 100644 --- a/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C0.json +++ b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C0.json @@ -1,4 +1,4 @@ { "type": "message", - "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694505,\\\"pcn\\\":111,\\\"c\\\":0,\\\"d\\\":\\\"eyJuYW1lIjoicmJzX3Rlc3QiLCJzdGF0dXMiOiJBQ1RJVkUiLCJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiZXhjbHVkZWQiOnsia2V5cyI6W10sInNlZ21lbnRzIjpbXX0sImNvbmRpdGlvbnMiOlt7Im1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6InVzZXIifSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2V9XX19XX0=\\\"}\"}" + "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694505,\\\"pcn\\\":1457552649999,\\\"c\\\":0,\\\"d\\\":\\\"eyJuYW1lIjoicmJzX3Rlc3QiLCJzdGF0dXMiOiJBQ1RJVkUiLCJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiZXhjbHVkZWQiOnsia2V5cyI6W10sInNlZ21lbnRzIjpbXX0sImNvbmRpdGlvbnMiOlt7Im1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6InVzZXIifSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2V9XX19XX0=\\\"}\"}" } diff --git a/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C1.json b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C1.json index e3181f524..2b09cfaa0 100644 --- a/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C1.json +++ b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C1.json @@ -1,4 +1,4 @@ { "type": "message", - "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694505,\\\"pcn\\\":111,\\\"c\\\":1,\\\"d\\\":\\\"H4sIAAAAAAAA/3SSQYucQBCF/0udPeTsbYhLCJl4cQiEsCxl+3SabbuluppFFv976HGiE3Bu3b73PnxV/Unmyn5AncYWQuWXglS47625zBNqHkElpQihgvx6kza+KaLSZj05FwyrDf4RsH9tgO6mxO2grClSSaevl++/Xqigd+tcFnt2EQV16Dk5vQhYR3ilkqggdkO45U3wnc3oSOWfz/2af3q1jqzmCvkmIU1UZsvYWp8r0qmudsMKeMfcwMFokGx+GMFen1XFtklBpU/OLRvibjudz28/Xn43eVIYOPvuZTKgQm89ugZDrvNzTVasvNIK+rhahbPxSEueZa7TCLHmQG6hH4B/rjQq1g8HeocJvoM381E4BAc+wsZnvIemZ5YBz+our0tBE4v+W+Iad9zC5f0tr7cd93ZIwv9ZInQ723ESxJjlykZu9we0/A0AAP//PlHpp9gCAAA=\\\"}\"}" + "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694506,\\\"pcn\\\":1684265694505,\\\"c\\\":1,\\\"d\\\":\\\"H4sIAAAAAAAA/0zOwWrDMBAE0H+Zs75At9CGUhpySSiUYoIij1MTSwraFdQY/XtRU5ccd3jDzoLoAmGRz3JSisJA1GkRWGyejq/vWxhodsMw+uN84/7OizDDgN9+Kj172AVXzgL72RkIL4FRf69q4FPsRx1TbMGC4NR/Mb/kVG6t51M4j5G5Pdw/w6zgrq+cD5zoNeWGH5asK+p/4y/d7Hant+3HAQaRF6eEHdwkrF2tXf0JAAD//9JucZnyAAAA\\\"}\"}" } diff --git a/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C2.json b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C2.json index 4e1b78068..0addfbc45 100644 --- a/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C2.json +++ b/src/__tests__/mocks/message.RB_SEGMENT_UPDATE.C2.json @@ -1,4 +1,4 @@ { "type": "message", - "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694505,\\\"pcn\\\":111,\\\"c\\\":2,\\\"d\\\":\\\"eJx0kkGLnEAQhf9LnT3k7G2ISwiZeHEIhLAsZft0mm27pbqaRRb/e+hxohNwbt2+9z58Vf1J5sp+QJ3GFkLll4JUuO+tucwTah5BJaUIoYL8epM2vimi0mY9ORcMqw3+EbB/bYDupsTtoKwpUkmnr5fvv16ooHfrXBZ7dhEFdeg5Ob0IWEd4pZKoIHZDuOVN8J3N6Ejln8/9mn96tY6s5gr5JiFNVGbL2FqfK9KprnbDCnjH3MDBaJBsfhjBXp9VxbZJQaVPzi0b4m47nc9vP15+N3lSGDj77mUyoEJvPboGQ67zc01WrLzSCvq4WoWz8UhLnmWu0wix5kBuoR+Af640KtYPB3qHCb6DN/NROAQHPsLGZ7yHpmeWAc/qLq9LQROL/lviGnfcwuX9La+3Hfd2SML/WSJ0O9txEsSY5cpGbvcHtPwNAAD//9u9Atc=\\\"}\"}" + "data": "{\"id\":\"1111\",\"clientId\":\"pri:ODc1NjQyNzY1\",\"timestamp\":1588254699236,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_flags\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1684265694507,\\\"pcn\\\":1684265694506,\\\"c\\\":2,\\\"d\\\":\\\"eJxMzsFqwzAQBNB/mbO+QLfQhlIackkolGKCIo9TE0sK2hXUGP17UVOXHHd4w86C6AJhkc9yUorCQNRpEVhsno6v71sYaHbDMPrjfOP+zosww4Dffio9e9gFV84C+9kZCC+BUX+vauBT7EcdU2zBguDUfzG/5FRuredTOI+RuT3cP8Os4K6vnA+c6DXlhh+WrCvqf+Mv3ex2p7ftxwEGkRenhB3cJKxdrV39CQAA//8FrVMM\\\"}\"}" } diff --git a/src/__tests__/mocks/splitchanges.since.100.till.1457552649999.RB_SEGMENT_UPDATE.json b/src/__tests__/mocks/splitchanges.since.100.till.1457552649999.RB_SEGMENT_UPDATE.json new file mode 100644 index 000000000..7e7f42e89 --- /dev/null +++ b/src/__tests__/mocks/splitchanges.since.100.till.1457552649999.RB_SEGMENT_UPDATE.json @@ -0,0 +1,39 @@ +{ + "rbs": { + "s": 100, + "t": 1457552649999, + "d": [ + { + "changeNumber": 1457552649999, + "name": "test_rule_based_segment", + "status": "ACTIVE", + "trafficTypeName": "user", + "excluded": { + "keys": [ "mauro@split.io" ], + "segments": [ "segment_excluded_by_rbs" ] + }, + "conditions": [ + { + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user" + }, + "matcherType": "ENDS_WITH", + "negate": false, + "whitelistMatcherData": { + "whitelist": [ + "@split.io" + ] + } + } + ] + } + } + ] + } + ] + } +} diff --git a/src/__tests__/nodeSuites/push-synchronization-retries.spec.js b/src/__tests__/nodeSuites/push-synchronization-retries.spec.js index 08ba0acf0..227b7fdde 100644 --- a/src/__tests__/nodeSuites/push-synchronization-retries.spec.js +++ b/src/__tests__/nodeSuites/push-synchronization-retries.spec.js @@ -208,7 +208,7 @@ export function testSynchronizationRetries(fetchMock, assert) { return { status: 408, body: 'request timeout' }; }); - mockSegmentChanges(fetchMock, new RegExp(`${url(settings, '/segmentChanges')}/(employees|developers)`), [key]); + mockSegmentChanges(fetchMock, new RegExp(`${url(settings, '/segmentChanges')}/(employees|developers|segment_excluded_by_rbs)`), [key]); fetchMock.get(new RegExp('.*'), function (url) { assert.fail('unexpected GET request with url: ' + url); diff --git a/src/__tests__/nodeSuites/push-synchronization.spec.js b/src/__tests__/nodeSuites/push-synchronization.spec.js index f48f0b7da..a97ad5937 100644 --- a/src/__tests__/nodeSuites/push-synchronization.spec.js +++ b/src/__tests__/nodeSuites/push-synchronization.spec.js @@ -7,6 +7,7 @@ import splitChangesMock6 from '../mocks/splitchanges.since.1684265694505.till.16 import splitChangesMock7 from '../mocks/splitchanges.since.1684265694506.till.1684265694526.SPLIT_UPDATE.json'; import splitChangesMock8 from '../mocks/splitchanges.since.1684265694526.till.1684265694546.SPLIT_UPDATE.json'; import splitChangesMock9 from '../mocks/splitchanges.since.1684265694546.till.1684265694556.SPLIT_UPDATE.json'; +import splitChangesMock10 from '../mocks/splitchanges.since.100.till.1457552649999.RB_SEGMENT_UPDATE.json'; import splitUpdateMessage from '../mocks/message.SPLIT_UPDATE.1457552649999.json'; import oldSplitUpdateMessage from '../mocks/message.SPLIT_UPDATE.1457552620999.json'; @@ -19,6 +20,11 @@ import iffuSplitUpdateMessageZeroPCN from '../mocks/message.SPLIT_UPDATE.IFFU.16 import iffuSplitUpdateMessageMissingPCN from '../mocks/message.SPLIT_UPDATE.IFFU.1684265694545.json'; import iffuSplitUpdateMessageArchivedFF from '../mocks/message.SPLIT_UPDATE.IFFU.1684265694555.json'; +import rbsUpdateMessage from '../mocks/message.RB_SEGMENT_UPDATE.1457552649999.json'; +import iffuRbsUpdateNoCompressionMessage from '../mocks/message.RB_SEGMENT_UPDATE.C0.json'; +import iffuRbsUpdateGZipMessage from '../mocks/message.RB_SEGMENT_UPDATE.C1.json'; +import iffuRbsUpdateZLibMessage from '../mocks/message.RB_SEGMENT_UPDATE.C2.json'; + import authPushEnabled from '../mocks/auth.pushEnabled.node.json'; import { nearlyEqual, mockSegmentChanges, url, hasNoCacheHeader } from '../testUtils'; @@ -59,7 +65,11 @@ const MILLIS_IFFU_UPDATE_EVENT_WITH_OLD_CHANGENUMBER = 900; const MILLIS_IFFU_UPDATE_EVENT_WITH_ZERO_PCN = 1000; const MILLIS_IFFU_UPDATE_EVENT_WITH_MISSING_PCN = 1100; const MILLIS_IFFU_UPDATE_EVENT_WITH_ARCHIVED = 1200; -const MILLIS_DESTROY = 1300; +const MILLIS_FIRST_RB_SEGMENT_UPDATE_EVENT = 1300; +const MILLIS_IFFU_RB_SEGMENT_UPDATE_C0_EVENT = 1400; +const MILLIS_IFFU_RB_SEGMENT_UPDATE_C1_EVENT = 1500; +const MILLIS_IFFU_RB_SEGMENT_UPDATE_C2_EVENT = 1600; +const MILLIS_DESTROY = 1700; /** * Sequence of calls: @@ -76,9 +86,13 @@ const MILLIS_DESTROY = 1300; * 1.0 secs: SPLIT_UPDATE IFFU event with pcn = 0 -> /splitChanges * 1.1 secs: SPLIT_UPDATE IFFU event with previous change number !== current change number -> /splitChanges * 1.2 secs: SPLIT_UPDATE IFFU event with ARCHIVED feature flag in notification and Base64 encoded + zLib (c==2) -> /splitChanges + * 1.3 secs: RB_SEGMENT_UPDATE event -> /splitChanges + * 1.4 secs: RB_SEGMENT_UPDATE IFFU event with no compression + * 1.5 secs: RB_SEGMENT_UPDATE IFFU event with Gzip compression + * 1.6 secs: RB_SEGMENT_UPDATE IFFU event with ZLib compression */ export function testSynchronization(fetchMock, assert) { - assert.plan(49); + assert.plan(53); fetchMock.reset(); __setEventSource(EventSourceMock); @@ -204,14 +218,46 @@ export function testSynchronization(fetchMock, assert) { eventSourceInstance.emitMessage(iffuSplitUpdateMessageArchivedFF); }, MILLIS_IFFU_UPDATE_EVENT_WITH_ARCHIVED); // send a SPLIT_UPDATE event with pcn = 0 after 1.1 seconds + setTimeout(() => { + client.once(client.Event.SDK_UPDATE, () => { + const lapse = Date.now() - start; + assert.true(nearlyEqual(lapse, MILLIS_FIRST_RB_SEGMENT_UPDATE_EVENT), 'SDK_UPDATE due to RB_SEGMENT_UPDATE event'); + }); + eventSourceInstance.emitMessage(rbsUpdateMessage); + }, MILLIS_FIRST_RB_SEGMENT_UPDATE_EVENT); // send a RB_SEGMENT_UPDATE event with a new changeNumber + + setTimeout(() => { + client.once(client.Event.SDK_UPDATE, () => { + const lapse = Date.now() - start; + assert.true(nearlyEqual(lapse, MILLIS_IFFU_RB_SEGMENT_UPDATE_C0_EVENT), 'SDK_UPDATE due to RB_SEGMENT_UPDATE IFFU event with no compression'); + }); + eventSourceInstance.emitMessage(iffuRbsUpdateNoCompressionMessage); + }, MILLIS_IFFU_RB_SEGMENT_UPDATE_C0_EVENT); // send a IFFU RB_SEGMENT_UPDATE event + + setTimeout(() => { + client.once(client.Event.SDK_UPDATE, () => { + const lapse = Date.now() - start; + assert.true(nearlyEqual(lapse, MILLIS_IFFU_RB_SEGMENT_UPDATE_C1_EVENT), 'SDK_UPDATE due to RB_SEGMENT_UPDATE IFFU event with GZip compression'); + }); + eventSourceInstance.emitMessage(iffuRbsUpdateGZipMessage); + }, MILLIS_IFFU_RB_SEGMENT_UPDATE_C1_EVENT); // send a IFFU RB_SEGMENT_UPDATE event + + setTimeout(() => { + client.once(client.Event.SDK_UPDATE, () => { + const lapse = Date.now() - start; + assert.true(nearlyEqual(lapse, MILLIS_IFFU_RB_SEGMENT_UPDATE_C2_EVENT), 'SDK_UPDATE due to RB_SEGMENT_UPDATE IFFU event with ZLib compression'); + }); + eventSourceInstance.emitMessage(iffuRbsUpdateZLibMessage); + }, MILLIS_IFFU_RB_SEGMENT_UPDATE_C2_EVENT); // send a IFFU RB_SEGMENT_UPDATE event + setTimeout(() => { client.destroy().then(() => { assert.equal(client.getTreatment(key, 'whitelist'), 'control', 'evaluation returns control if client is destroyed'); - // @TODO SDK_UPDATE should be emitted 9 times, but currently it is being emitted twice on SPLIT_KILL - assert.equal(sdkUpdateCount, 10, 'SDK_UPDATE should be emitted 10 times'); + // @TODO SDK_UPDATE should be emitted 13 times, but currently it is being emitted twice on SPLIT_KILL + assert.equal(sdkUpdateCount, 14, 'SDK_UPDATE should be emitted 14 times'); assert.end(); }); - }, MILLIS_DESTROY); // destroy client after 1.3 second + }, MILLIS_DESTROY); // destroy client }); // initial auth @@ -309,7 +355,11 @@ export function testSynchronization(fetchMock, assert) { return { status: 200, body: splitChangesMock9 }; }); + // fetch due to RB_SEGMENTS_UPDATE event + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1684265694556&rbSince=100'), { status: 200, body: splitChangesMock10 }); + mockSegmentChanges(fetchMock, new RegExp(`${url(settings, '/segmentChanges')}/employees`), [key]); + mockSegmentChanges(fetchMock, new RegExp(`${url(settings, '/segmentChanges')}/segment_excluded_by_rbs`), []); // Special case: empty segment with -1 till mockSegmentChanges(fetchMock, new RegExp(`${url(settings, '/segmentChanges')}/developers`), [], -1); mockSegmentChanges(fetchMock, { url: new RegExp(`${url(settings, '/segmentChanges')}/new_segment`), repeat: 2 }, [otherUserKey]); From 8ea167af8119dac08a6c645ada5ea0f5b7ae557e Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 3 Apr 2025 17:03:46 -0300 Subject: [PATCH 5/7] Fix tests --- .../browserSuites/evaluations-semver.spec.js | 1 - .../browserSuites/push-corner-cases.spec.js | 3 ++- src/__tests__/browserSuites/push-fallback.spec.js | 12 ++++++------ src/__tests__/browserSuites/readiness.spec.js | 6 ------ src/__tests__/browserSuites/ready-from-cache.spec.js | 12 ++++-------- .../browserSuites/use-beacon-api.debug.spec.js | 1 - src/__tests__/browserSuites/use-beacon-api.spec.js | 1 - .../nodeSuites/ip-addresses-setting.debug.spec.js | 1 - src/__tests__/nodeSuites/push-fallback.spec.js | 12 ++++++------ 9 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/__tests__/browserSuites/evaluations-semver.spec.js b/src/__tests__/browserSuites/evaluations-semver.spec.js index d049ddbf4..ce2831409 100644 --- a/src/__tests__/browserSuites/evaluations-semver.spec.js +++ b/src/__tests__/browserSuites/evaluations-semver.spec.js @@ -26,7 +26,6 @@ const config = { export default async function (fetchMock, assert) { fetchMock.getOnce(config.urls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 }); - fetchMock.getOnce(config.urls.sdk + '/splitChanges?s=1.3&since=1675259356568&rbSince=-1', { status: 200, body: { ff: { d: [], s: 1675259356568, t: 1675259356568 } } }); fetchMock.getOnce(config.urls.sdk + '/memberships/emi%40split.io', { status: 200, body: { ms: {} } }); fetchMock.getOnce(config.urls.sdk + '/memberships/2nd', { status: 200, body: { ms: {} } }); diff --git a/src/__tests__/browserSuites/push-corner-cases.spec.js b/src/__tests__/browserSuites/push-corner-cases.spec.js index d25170307..37edd1587 100644 --- a/src/__tests__/browserSuites/push-corner-cases.spec.js +++ b/src/__tests__/browserSuites/push-corner-cases.spec.js @@ -1,5 +1,6 @@ import { getStorageHash } from '@splitsoftware/splitio-commons/src/storages/KeyBuilder'; import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; +import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; import splitKillMessage from '../mocks/message.SPLIT_KILL.1457552650000.json'; import authPushEnabledNicolas from '../mocks/auth.pushEnabled.nicolas@split.io.json'; import { nearlyEqual, url } from '../testUtils'; @@ -77,7 +78,7 @@ export function testSplitKillOnReadyFromCache(fetchMock, assert) { // 2 splitChanges request: initial sync and after SSE opened. Sync after SPLIT_KILL is not performed because SplitsSyncTask is "executing" fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=25&rbSince=-1'), { status: 200, body: splitChangesMock1 }, { delay: MILLIS_SPLIT_CHANGES_RESPONSE, /* delay response */ }); - fetchMock.getOnce(url(settings, `/splitChanges?s=1.3&since=${splitChangesMock1.ff.t}&rbSince=-1`), { status: 200, body: { ff: { d: [], s: splitChangesMock1.ff.t, t: splitChangesMock1.ff.t } } }, { delay: MILLIS_SPLIT_CHANGES_RESPONSE - 100, /* delay response */ }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), { status: 200, body: splitChangesMock2 }, { delay: MILLIS_SPLIT_CHANGES_RESPONSE - 100, /* delay response */ }); fetchMock.get(new RegExp('.*'), function (url) { assert.fail('unexpected GET request with url: ' + url); diff --git a/src/__tests__/browserSuites/push-fallback.spec.js b/src/__tests__/browserSuites/push-fallback.spec.js index 5c3e0a4de..1066c69e6 100644 --- a/src/__tests__/browserSuites/push-fallback.spec.js +++ b/src/__tests__/browserSuites/push-fallback.spec.js @@ -2,9 +2,9 @@ * Validate the handling of OCCUPANCY and CONTROL events */ -import splitChangesMock1 from '../mocks/splitchanges.real.withSegments.json'; // since: -1, till: 1457552620999 (for initial fetch) -import splitChangesMock2 from '../mocks/splitchanges.real.updateWithSegments.json'; // since: 1457552620999, till: 1457552649999 (for SPLIT_UPDATE event) -import splitChangesMock3 from '../mocks/splitchanges.real.updateWithoutSegments.json'; // since: 1457552649999, till: 1457552669999 (for second polling fetch) +import splitChangesMockReal1 from '../mocks/splitchanges.real.withSegments.json'; // since: -1, till: 1457552620999 (for initial fetch) +import splitChangesMockReal2 from '../mocks/splitchanges.real.updateWithSegments.json'; // since: 1457552620999, till: 1457552649999 (for SPLIT_UPDATE event) +import splitChangesMockReal3 from '../mocks/splitchanges.real.updateWithoutSegments.json'; // since: 1457552649999, till: 1457552669999 (for second polling fetch) import membershipsNicolasMock1 from '../mocks/memberships.nicolas@split.io.json'; import membershipsNicolasMock2 from '../mocks/memberships.nicolas@split.io.mock2.json'; import membershipsMarcio from '../mocks/memberships.marcio@split.io.json'; @@ -213,7 +213,7 @@ export function testFallback(fetchMock, assert) { }); // initial split and memberships sync - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMockReal1 }); fetchMock.getOnce(url(settings, '/memberships/nicolas%40split.io'), { status: 200, body: membershipsNicolasMock1 }); // split and segment sync after SSE opened @@ -249,7 +249,7 @@ export function testFallback(fetchMock, assert) { fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_SPLIT_UPDATE_EVENT_DURING_PUSH), 'sync due to SPLIT_UPDATE event'); - return { status: 200, body: splitChangesMock2 }; + return { status: 200, body: splitChangesMockReal2 }; }); // fetches due to second fallback to polling @@ -288,7 +288,7 @@ export function testFallback(fetchMock, assert) { fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_STREAMING_DISABLED_CONTROL + settings.scheduler.featuresRefreshRate, 100), 'fetch due to fourth fallback to polling'); - return { status: 200, body: splitChangesMock3 }; + return { status: 200, body: splitChangesMockReal3 }; }); fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552669999&rbSince=-1'), function () { const lapse = Date.now() - start; diff --git a/src/__tests__/browserSuites/readiness.spec.js b/src/__tests__/browserSuites/readiness.spec.js index c6246d3f1..d25c0dae1 100644 --- a/src/__tests__/browserSuites/readiness.spec.js +++ b/src/__tests__/browserSuites/readiness.spec.js @@ -1,7 +1,6 @@ import { SplitFactory } from '../../'; import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; -import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; import membershipsNicolas from '../mocks/memberships.nicolas@split.io.json'; // mocks for memberships readiness tests @@ -44,7 +43,6 @@ export default function (fetchMock, assert) { fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', function () { return new Promise((res) => { setTimeout(() => { res({ status: 200, body: membershipsNicolas, headers: {} }); }, requestTimeoutBeforeReady * 1000 - 50); }); }); - fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); const splitio = SplitFactory({ ...baseConfig, urls: testUrls @@ -73,7 +71,6 @@ export default function (fetchMock, assert) { fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', function () { return new Promise((res) => { setTimeout(() => { res({ status: 200, body: membershipsNicolas, headers: {} }); }, requestTimeoutBeforeReady * 1000 + 50); }); }); - fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); const splitio = SplitFactory({ ...baseConfig, urls: testUrls }); const client = splitio.client(); @@ -104,7 +101,6 @@ export default function (fetchMock, assert) { fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', function () { return new Promise((res) => { setTimeout(() => { res({ status: 200, body: membershipsNicolas, headers: {} }); }, requestTimeoutBeforeReady * 1000 - 50); }); }); - fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); const splitio = SplitFactory({ ...baseConfig, urls: testUrls }); const client = splitio.client(); @@ -129,8 +125,6 @@ export default function (fetchMock, assert) { membershipsHits++; return new Promise((res) => { setTimeout(() => { res({ status: 200, body: { ms: {} } }); }, membershipsEndpointDelay); }); }); - // Now mock the no more updates state - if (startWithSegments) { // Adjust since and till so the order is inverted. diff --git a/src/__tests__/browserSuites/ready-from-cache.spec.js b/src/__tests__/browserSuites/ready-from-cache.spec.js index ef1decb6a..970a78df3 100644 --- a/src/__tests__/browserSuites/ready-from-cache.spec.js +++ b/src/__tests__/browserSuites/ready-from-cache.spec.js @@ -98,7 +98,6 @@ export default function (fetchMock, assert) { t.plan(4); fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 }); - fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas }); fetchMock.get(testUrls.sdk + '/memberships/nicolas2%40split.io', { status: 200, body: { 'ms': {} } }); fetchMock.get(testUrls.sdk + '/memberships/nicolas3%40split.io', { status: 200, body: { 'ms': {} } }); @@ -150,7 +149,6 @@ export default function (fetchMock, assert) { fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=25&rbSince=-1', () => { return new Promise(res => { setTimeout(() => res({ status: 200, body: { ff: { ...splitChangesMock1.ff, s: 25 } }, headers: {} }), 200); }); // 400ms is how long it'll take to reply with Splits, no SDK_READY should be emitted before that. }); - fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', () => { return new Promise(res => { setTimeout(() => res({ status: 200, body: membershipsNicolas, headers: {} }), 400); }); // First client gets segments before splits. No segment cache loading (yet) }); @@ -258,7 +256,6 @@ export default function (fetchMock, assert) { t.equal(localStorage.getItem('readyFromCache_3.SPLITIO.split.always_on'), alwaysOnSplitInverted, 'feature flags must not be cleaned from cache'); return new Promise(res => { setTimeout(() => res({ status: 200, body: { ff: { ...splitChangesMock1.ff, s: 25 } }, headers: {} }), 200); }); // 400ms is how long it'll take to reply with Splits, no SDK_READY should be emitted before that. }); - fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', () => { return new Promise(res => { setTimeout(() => res({ status: 200, body: membershipsNicolas, headers: {} }), 400); }); // First client gets segments before splits. No segment cache loading (yet) }); @@ -379,7 +376,6 @@ export default function (fetchMock, assert) { t.equal(localStorage.length, 3, 'feature flags cache data must be cleaned from localStorage'); return { status: 200, body: splitChangesMock1 }; }); - fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', () => { return new Promise(res => { setTimeout(() => res({ status: 200, body: membershipsNicolas, headers: {} }), CLIENT_READY_MS); }); // First client gets segments before splits. No segment cache loading (yet) }); @@ -808,7 +804,7 @@ export default function (fetchMock, assert) { localStorage.setItem('readyFromCache_10.SPLITIO.hash', expectedHashNullFilter); fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 }); - fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); + fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=100', { status: 200, body: splitChangesMock2 }); fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: { ms: {} } }); let splitio = SplitFactory(clearOnInitConfig); @@ -822,7 +818,7 @@ export default function (fetchMock, assert) { }); await client.ready(); - t.equal(manager.names().sort().length, 33, 'active splits should be present for evaluation'); + t.equal(manager.names().sort().length, 35, 'active splits should be present for evaluation'); await splitio.destroy(); t.equal(localStorage.getItem('readyFromCache_10.SPLITIO.splits.till'), '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits'); @@ -839,7 +835,7 @@ export default function (fetchMock, assert) { await new Promise(res => client.once(client.Event.SDK_READY_FROM_CACHE, res)); - t.equal(manager.names().sort().length, 33, 'active splits should be present for evaluation'); + t.equal(manager.names().sort().length, 35, 'active splits should be present for evaluation'); t.false(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); await splitio.destroy(); @@ -860,7 +856,7 @@ export default function (fetchMock, assert) { await new Promise(res => client.once(client.Event.SDK_READY, res)); - t.equal(manager.names().sort().length, 33, 'active splits should be present for evaluation'); + t.equal(manager.names().sort().length, 35, 'active splits should be present for evaluation'); t.true(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); await splitio.destroy(); diff --git a/src/__tests__/browserSuites/use-beacon-api.debug.spec.js b/src/__tests__/browserSuites/use-beacon-api.debug.spec.js index b82d5a733..bb0d1f4f5 100644 --- a/src/__tests__/browserSuites/use-beacon-api.debug.spec.js +++ b/src/__tests__/browserSuites/use-beacon-api.debug.spec.js @@ -67,7 +67,6 @@ function beaconApiNotSendTestDebug(fetchMock, assert) { // Mocking this specific route to make sure we only get the items we want to test from the handlers. fetchMock.get(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }); fetchMock.get(url(settings, '/memberships/facundo%40split.io'), { status: 200, body: membershipsFacundo }); // Init and run Split client diff --git a/src/__tests__/browserSuites/use-beacon-api.spec.js b/src/__tests__/browserSuites/use-beacon-api.spec.js index 84d573f1a..d8e51d164 100644 --- a/src/__tests__/browserSuites/use-beacon-api.spec.js +++ b/src/__tests__/browserSuites/use-beacon-api.spec.js @@ -79,7 +79,6 @@ function beaconApiNotSendTest(fetchMock, assert) { // Mocking this specific route to make sure we only get the items we want to test from the handlers. fetchMock.get(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.get(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }); fetchMock.get(url(settings, '/memberships/facundo%40split.io'), { status: 200, body: membershipsFacundo }); // Init and run Split client diff --git a/src/__tests__/nodeSuites/ip-addresses-setting.debug.spec.js b/src/__tests__/nodeSuites/ip-addresses-setting.debug.spec.js index 2e9599e78..a93e6865e 100644 --- a/src/__tests__/nodeSuites/ip-addresses-setting.debug.spec.js +++ b/src/__tests__/nodeSuites/ip-addresses-setting.debug.spec.js @@ -78,7 +78,6 @@ export default function ipAddressesSettingAssertions(fetchMock, assert) { // Mock GET endpoints to run client normally fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), { status: 200, body: { ff: { d: [], s: 1457552620999, t: 1457552620999 } } }); fetchMock.get(new RegExp(`${url(settings, '/segmentChanges/')}.*`), { status: 200, body: { since: 10, till: 10, name: 'segmentName', added: [], removed: [] } }); // Mock and assert POST endpoints diff --git a/src/__tests__/nodeSuites/push-fallback.spec.js b/src/__tests__/nodeSuites/push-fallback.spec.js index f534ea14f..61065c7d3 100644 --- a/src/__tests__/nodeSuites/push-fallback.spec.js +++ b/src/__tests__/nodeSuites/push-fallback.spec.js @@ -2,9 +2,9 @@ * Validate the handling of OCCUPANCY and CONTROL events */ -import splitChangesMock1 from '../mocks/splitchanges.real.withSegments.json'; // since: -1, till: 1457552620999 (for initial fetch) -import splitChangesMock2 from '../mocks/splitchanges.real.updateWithSegments.json'; // since: 1457552620999, till: 1457552649999 (for SPLIT_UPDATE event) -import splitChangesMock3 from '../mocks/splitchanges.real.updateWithoutSegments.json'; // since: 1457552649999, till: 1457552669999 (for second polling fetch) +import splitChangesMockReal1 from '../mocks/splitchanges.real.withSegments.json'; // since: -1, till: 1457552620999 (for initial fetch) +import splitChangesMockReal2 from '../mocks/splitchanges.real.updateWithSegments.json'; // since: 1457552620999, till: 1457552649999 (for SPLIT_UPDATE event) +import splitChangesMockReal3 from '../mocks/splitchanges.real.updateWithoutSegments.json'; // since: 1457552649999, till: 1457552669999 (for second polling fetch) import occupancy0ControlPriMessage from '../mocks/message.OCCUPANCY.0.control_pri.1586987434550.json'; import occupancy1ControlPriMessage from '../mocks/message.OCCUPANCY.1.control_pri.1586987434450.json'; @@ -197,7 +197,7 @@ export function testFallback(fetchMock, assert) { }); // initial split and segment sync - fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); + fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMockReal1 }); fetchMock.getOnce(url(settings, '/segmentChanges/employees?since=-1'), { status: 200, body: { since: -1, till: 1457552620999, name: 'employees', added: [key], removed: [] } }); // extra retry due to double request (greedy fetch until since === till) fetchMock.getOnce(url(settings, '/segmentChanges/employees?since=1457552620999'), { status: 200, body: { since: 1457552620999, till: 1457552620999, name: 'employees', added: [], removed: [] } }); @@ -226,7 +226,7 @@ export function testFallback(fetchMock, assert) { fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_SPLIT_UPDATE_EVENT_DURING_PUSH), 'sync due to SPLIT_UPDATE event'); - return { status: 200, body: splitChangesMock2 }; + return { status: 200, body: splitChangesMockReal2 }; }); // fetches due to second fallback to polling @@ -258,7 +258,7 @@ export function testFallback(fetchMock, assert) { fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552649999&rbSince=-1'), function () { const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_STREAMING_DISABLED_CONTROL + settings.scheduler.featuresRefreshRate), 'fetch due to fourth fallback to polling'); - return { status: 200, body: splitChangesMock3 }; + return { status: 200, body: splitChangesMockReal3 }; }); fetchMock.getOnce(url(settings, '/segmentChanges/employees?since=1457552650000'), { status: 200, body: { since: 1457552650000, till: 1457552650000, name: 'employees', added: [], removed: [] } }); fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552669999&rbSince=-1'), function () { From 9b356dbc89c23ae6919f3f1aa27c0d8def5dd91b Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 15 Apr 2025 14:01:35 -0300 Subject: [PATCH 6/7] Add proxy fallback test --- .../nodeSuites/proxy-fallback.spec.js | 135 ++++++++++++++++++ src/__tests__/online/node.spec.js | 4 + 2 files changed, 139 insertions(+) create mode 100644 src/__tests__/nodeSuites/proxy-fallback.spec.js diff --git a/src/__tests__/nodeSuites/proxy-fallback.spec.js b/src/__tests__/nodeSuites/proxy-fallback.spec.js new file mode 100644 index 000000000..de2616a0d --- /dev/null +++ b/src/__tests__/nodeSuites/proxy-fallback.spec.js @@ -0,0 +1,135 @@ +import { SplitFactory } from '../../'; +import splitChangesMockReal from '../mocks/splitchanges.real.json'; +import authPushDisabled from '../mocks/auth.pushDisabled.json'; +import { nearlyEqual, url } from '../testUtils'; + +const config = { + core: { + authorizationKey: '' + }, + scheduler: { + featuresRefreshRate: 0.5, + }, + urls: { + sdk: 'https://outdated-proxy/api', + auth: 'https://outdated-proxy/api', + } +}; + +// Response mocks +const outdatedProxyFailResponse = { + status: 400 +}; +const outdatedProxyInitialResponse = { + status: 200, body: { + splits: splitChangesMockReal.ff.d, + since: splitChangesMockReal.ff.s, + till: splitChangesMockReal.ff.t + } +}; +const outdatedProxyNextResponse = { + status: 200, body: { + splits: [], + since: 1457552620999, + till: 1457552620999 + } +}; +const fixedProxyInitialResponse = { + status: 200, body: splitChangesMockReal +}; +const fixedProxyNextResponse = { + status: 200, body: { + ff: { + d: [], + s: 1457552620999, + t: 1457552620999 + } + } +}; + +export async function proxyFallbackSuite(fetchMock, assert) { + const originalDateNow = Date.now; + const start = originalDateNow(); + + fetchMock.getOnce(url(config, '/v2/auth?s=1.2'), { status: 200, body: authPushDisabled }); + + // Outdated Proxy responds with 400 when spec 1.3 is provided + fetchMock.getOnce(url(config, '/splitChanges?s=1.3&since=-1&rbSince=-1'), outdatedProxyFailResponse); + + // Fallback to spec 1.2 + fetchMock.getOnce(url(config, '/splitChanges?s=1.2&since=-1'), () => { + assert.true(nearlyEqual(originalDateNow(), start), 'Initial fallback to spec 1.2'); + return outdatedProxyInitialResponse; + }); + fetchMock.getOnce(url(config, '/splitChanges?s=1.2&since=1457552620999'), () => { + assert.true(nearlyEqual(originalDateNow(), start), 'Initial fallback to spec 1.2'); + return outdatedProxyNextResponse; + }); + + // Polling with fallback to spec 1.2 + fetchMock.getOnce(url(config, '/splitChanges?s=1.2&since=1457552620999'), () => { + assert.true(nearlyEqual(originalDateNow() - start, config.scheduler.featuresRefreshRate * 1000), 'First polling with fallback to spec 1.2'); + + // Mock Date.now() to return a +24h timestamp to force a proxy recheck + let lastTimestamp = originalDateNow(); + Date.now = () => lastTimestamp += 25 * 60 * 60 * 1000; + + return outdatedProxyNextResponse; + }); + + // Polling with proxy recheck using spec 1.3, but fail again + fetchMock.getOnce(url(config, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), () => { + assert.true(nearlyEqual(originalDateNow() - start, config.scheduler.featuresRefreshRate * 2000), 'Second polling with recheck'); + + return outdatedProxyFailResponse; + }); + // Fallback to spec 1.2 + fetchMock.getOnce(url(config, '/splitChanges?s=1.2&since=1457552620999'), () => { + assert.true(nearlyEqual(originalDateNow() - start, config.scheduler.featuresRefreshRate * 2000), 'Second polling with fallback to spec 1.2'); + + return outdatedProxyNextResponse; + }); + + // Polling with proxy recheck using spec 1.3. This time succeeds + fetchMock.getOnce(url(config, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), () => { + assert.true(nearlyEqual(originalDateNow() - start, config.scheduler.featuresRefreshRate * 3000), 'Third polling with recheck'); + + return fixedProxyNextResponse; + }); + // Proxy recovery: refetch with clear cache + fetchMock.getOnce(url(config, '/splitChanges?s=1.3&since=-1&rbSince=-1'), () => { + assert.true(nearlyEqual(originalDateNow() - start, config.scheduler.featuresRefreshRate * 3000), 'Proxy recovery: refetch with clear cache'); + + return fixedProxyInitialResponse; + }); + + // Polling with spec 1.3 + fetchMock.getOnce(url(config, '/splitChanges?s=1.3&since=1457552620999&rbSince=-1'), () => { + assert.true(nearlyEqual(originalDateNow() - start, config.scheduler.featuresRefreshRate * 4000), 'Fourth polling with spec 1.3'); + + return fixedProxyNextResponse; + }); + + const splitio = SplitFactory(config); + const client = splitio.client(); + const manager = splitio.manager(); + + client.once(client.Event.SDK_READY, () => { + assert.equal(manager.splits().length, splitChangesMockReal.ff.d.length, 'SDK IS READY as it should. The manager.splits() method should return all feature flags.'); + }); + + client.once(client.Event.SDK_READY_TIMED_OUT, () => { + assert.fail('SDK TIMED OUT - It should not in this scenario'); + assert.end(); + }); + + client.once(client.Event.SDK_UPDATE, () => { + assert.true(nearlyEqual(originalDateNow() - start, config.scheduler.featuresRefreshRate * 3000), 'Proxy recovery: refetch with clear cache and spec 1.3 trigger an SDK_UPDATE event'); + assert.equal(manager.splits().length, splitChangesMockReal.ff.d.length, 'Validate that the SDK is operational after proxy recovery'); + }); + + // Wait for 4 feature refreshes + await new Promise(resolve => setTimeout(resolve, config.scheduler.featuresRefreshRate * 1000 * 4 + 200)); + await client.destroy(); + assert.end(); +} diff --git a/src/__tests__/online/node.spec.js b/src/__tests__/online/node.spec.js index 8b2eb34e2..303ab4dd1 100644 --- a/src/__tests__/online/node.spec.js +++ b/src/__tests__/online/node.spec.js @@ -23,6 +23,7 @@ import readyPromiseSuite from '../nodeSuites/ready-promise.spec'; import { fetchSpecificSplits, fetchSpecificSplitsForFlagSets } from '../nodeSuites/fetch-specific-splits.spec'; import flagSets from '../nodeSuites/flag-sets.spec'; import lazyInitSuite from '../nodeSuites/lazy-init.spec'; +import { proxyFallbackSuite } from '../nodeSuites/proxy-fallback.spec'; const config = { core: { @@ -96,5 +97,8 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { assert.test('E2E / SplitFactory with lazy init', lazyInitSuite.bind(null, settings, fetchMock)); + // @TODO remove when dropping support for Split Proxy v5.10.0 or below + assert.test('E2E / Proxy fallback', proxyFallbackSuite.bind(null, fetchMock, assert)); + assert.end(); }); From 3dab06baf543797a36b6b48f7ff6148055991dfe Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 15 Apr 2025 14:09:11 -0300 Subject: [PATCH 7/7] Fix test --- src/__tests__/online/node.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/online/node.spec.js b/src/__tests__/online/node.spec.js index 303ab4dd1..c0f64b939 100644 --- a/src/__tests__/online/node.spec.js +++ b/src/__tests__/online/node.spec.js @@ -98,7 +98,7 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { assert.test('E2E / SplitFactory with lazy init', lazyInitSuite.bind(null, settings, fetchMock)); // @TODO remove when dropping support for Split Proxy v5.10.0 or below - assert.test('E2E / Proxy fallback', proxyFallbackSuite.bind(null, fetchMock, assert)); + assert.test('E2E / Proxy fallback', proxyFallbackSuite.bind(null, fetchMock)); assert.end(); });