From 3923d91d6ae2da5d6229c50bed47b1dc25c6393b Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Fri, 24 Oct 2025 14:13:02 -0300 Subject: [PATCH 01/21] [FME-10568] update js commons and add fallback treatment e2e tests --- CHANGES.txt | 4 + karma/e2e.online.karma.conf.js | 2 +- package-lock.json | 18 +- package.json | 4 +- .../nodeSuites/evaluations-fallback.spec.js | 261 ++++++++++++++++++ src/__tests__/online/node.spec.js | 2 + src/settings/defaults/version.js | 2 +- 7 files changed, 280 insertions(+), 13 deletions(-) create mode 100644 src/__tests__/nodeSuites/evaluations-fallback.spec.js diff --git a/CHANGES.txt b/CHANGES.txt index 61cd21f1c..1b62a0574 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +11.8.0 (October 24, 2025) + - Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs. + - Updated @splitsoftware/splitio-commons package to version 2.8.0. + 11.7.0 (October 7, 2025) - Added support for custom loggers: added `logger` configuration option and `factory.Logger.setLogger` method to allow the SDK to use a custom logger. - Updated @splitsoftware/splitio-commons package to version 2.7.0. diff --git a/karma/e2e.online.karma.conf.js b/karma/e2e.online.karma.conf.js index a81d35a7f..3321b9e3d 100644 --- a/karma/e2e.online.karma.conf.js +++ b/karma/e2e.online.karma.conf.js @@ -1,6 +1,6 @@ const assign = require('lodash/assign'); -module.exports = function(config) { +module.exports = function (config) { 'use strict'; config.set(assign({}, require('./config'), { diff --git a/package-lock.json b/package-lock.json index 23598221e..7552bb236 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.0", + "version": "11.7.9-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio", - "version": "11.7.0", + "version": "11.7.9-rc.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.7.0", + "@splitsoftware/splitio-commons": "2.7.9-rc.0", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", @@ -351,9 +351,9 @@ "dev": true }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.0.tgz", - "integrity": "sha512-w2aemu5HNVQXX/tbmSuFjpWa/AjS+EBiH6ltHMqfg2MZMWayTFJbfjjQcudAVLR+vLjDw2DuCTp/xj3kKlcf5g==", + "version": "2.7.9-rc.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.0.tgz", + "integrity": "sha512-qP/kXBPXVwcGjvYE5980SXOxLbooHz8hvIcgPQFKITiMiewbjZMuNa+LUxbwRC6SC2rmVcXHjxWmvMymDA4Gow==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -7740,9 +7740,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.0.tgz", - "integrity": "sha512-w2aemu5HNVQXX/tbmSuFjpWa/AjS+EBiH6ltHMqfg2MZMWayTFJbfjjQcudAVLR+vLjDw2DuCTp/xj3kKlcf5g==", + "version": "2.7.9-rc.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.0.tgz", + "integrity": "sha512-qP/kXBPXVwcGjvYE5980SXOxLbooHz8hvIcgPQFKITiMiewbjZMuNa+LUxbwRC6SC2rmVcXHjxWmvMymDA4Gow==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" diff --git a/package.json b/package.json index 8a88d9769..c489eefe5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.0", + "version": "11.7.9-rc.0", "description": "Split SDK", "files": [ "README.md", @@ -38,7 +38,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@splitsoftware/splitio-commons": "2.7.0", + "@splitsoftware/splitio-commons": "2.7.9-rc.0", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", diff --git a/src/__tests__/nodeSuites/evaluations-fallback.spec.js b/src/__tests__/nodeSuites/evaluations-fallback.spec.js new file mode 100644 index 000000000..6b26ee19d --- /dev/null +++ b/src/__tests__/nodeSuites/evaluations-fallback.spec.js @@ -0,0 +1,261 @@ +import sinon from 'sinon'; +import { SplitFactory } from '../../'; + +const listener = { + logImpression: sinon.stub() +}; + +const baseConfig = { + core: { + authorizationKey: '' + }, + sync: { + impressionsMode: 'DEBUG' + }, + streamingEnabled: false +}; + +export default async function (fetchMock, assert) { + + assert.test('FallbackTreatment / Split factory with no fallbackTreatment defined', async t => { + + const splitio = SplitFactory(baseConfig); + const client = splitio.client(); + + await client.ready(); + + t.equal(client.getTreatment('emi@harness.io', 'non_existent_flag'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined'); + t.equal(client.getTreatment('emma@harness.io', 'non_existent_flag_2'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined'); + + await client.destroy(); + t.end(); + + }); + + assert.test('FallbackTreatment / Split factory with global fallbackTreatment defined', async t => { + + const config = Object.assign({}, baseConfig); + config.fallbackTreatments = { + global: 'FALLBACK_TREATMENT' + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.ready(); + + + t.equal(client.getTreatment('emi@harness.io', 'non_existent_flag'), 'FALLBACK_TREATMENT', 'The evaluation will return `FALLBACK_TREATMENT` if the flag does not exist and no fallbackTreatment is defined'); + t.equal(client.getTreatment('emma@harness.io', 'non_existent_flag_2'), 'FALLBACK_TREATMENT', 'The evaluation will return `FALLBACK_TREATMENT` if the flag does not exist and no fallbackTreatment is defined'); + + await client.destroy(); + t.end(); + + }); + + assert.test('FallbackTreatment / Split factory with specific fallbackTreatment defined', async t => { + + const config = Object.assign({}, baseConfig); + config.fallbackTreatments = { + byFlag: { + 'non_existent_flag': 'FALLBACK_TREATMENT', + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.ready(); + + t.equal(client.getTreatment('emi@harness.io', 'non_existent_flag'), 'FALLBACK_TREATMENT', 'The evaluation will return `FALLBACK_TREATMENT` if the flag does not exist and no fallbackTreatment is defined'); + t.equal(client.getTreatment('emi@harness.io', 'non_existent_flag_2'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined'); + + t.equal(client.getTreatment('emma@harness.io', 'non_existent_flag'), 'FALLBACK_TREATMENT', 'The evaluation will return `FALLBACK_TREATMENT` if the flag does not exist and no fallbackTreatment is defined'); + t.equal(client.getTreatment('emma@harness.io', 'non_existent_flag_2'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined'); + + await client.destroy(); + t.end(); + + }); + + + assert.test('FallbackTreatment / flag override beats global fallbackTreatment', async t => { + + const config = Object.assign({}, baseConfig); + config.fallbackTreatments = { + global: 'OFF_FALLBACK', + byFlag: { + 'my_flag': 'ON_FALLBACK', + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.ready(); + + t.equal(client.getTreatment('emi@harness.io', 'my_flag'), 'ON_FALLBACK', 'The evaluation will return `ON_FALLBACK` if the flag does not exist and no fallbackTreatment is defined'); + t.equal(client.getTreatment('emi@harness.io', 'non_existent_flag_2'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist and no fallbackTreatment is defined'); + + t.equal(client.getTreatment('emma@harness.io', 'my_flag'), 'ON_FALLBACK', 'The evaluation will return `ON_FALLBACK` if the flag does not exist and no fallbackTreatment is defined'); + t.equal(client.getTreatment('emma@harness.io', 'non_existent_flag_2'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist and no fallbackTreatment is defined'); + + await client.destroy(); + t.end(); + + }); + + assert.test('FallbackTreatment / override applies only when original is control', async t => { + + const config = Object.assign({}, baseConfig); + config.fallbackTreatments = { + global: 'OFF_FALLBACK' + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.ready(); + + t.equal(client.getTreatment('emma@harness.io', 'user_account_in_whitelist'), 'off', 'The evaluation will return the treatment defined in the flag if it exists'); + t.equal(client.getTreatment('emma@harness.io', 'non_existent_flag'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist and no fallbackTreatment is defined'); + + await client.destroy(); + t.end(); + + }); + + assert.test('FallbackTreatment / Impressions correctness with fallback when client is not ready', async t => { + + const config = Object.assign({}, baseConfig); + config.urls = { + events: 'https://events.fallbacktreatment/api' + }; + config.fallbackTreatments = { + byFlag: { + 'any_flag': 'OFF_FALLBACK' + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + t.equal(client.getTreatment('emi@harness.io', 'any_flag'), 'OFF_FALLBACK', 'The evaluation will return the fallbackTreatment if the client is not ready yet'); + t.equal(client.getTreatment('emma@harness.io', 'user_account_in_whitelist'), 'control', 'The evaluation will return the fallbackTreatment if the client is not ready yet'); + + await client.ready(); + + fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', (_, opts) => { + + const payload = JSON.parse(opts.body); + + function validateImpressionData(featureFlagName, expectedLabel) { + const impressions = payload.find(e => e.f === featureFlagName).i; + + t.equal(impressions[0].r, expectedLabel, `${featureFlagName} impression with label ${expectedLabel}`); + } + + validateImpressionData('any_flag', 'fallback - not ready'); + validateImpressionData('user_account_in_whitelist', 'not ready'); + t.end(); + + return 200; + }); + + await client.destroy(); + + }); + + assert.test('FallbackTreatment / Fallback dynamic config propagation', async t => { + + const config = Object.assign({}, baseConfig); + config.fallbackTreatments = { + global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } + } + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.ready(); + + t.deepEqual(client.getTreatmentWithConfig('emma@harness.io', 'my_flag'), { treatment: 'ON_FALLBACK', config: '{"flag": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment'); + t.deepEqual(client.getTreatmentWithConfig('emma@harness.io', 'non_existent_flag'), { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment'); + + await client.destroy(); + t.end(); + + }); + + assert.test('FallbackTreatment / Evaluations non existing flags with fallback do not generate impressions', async t => { + + const config = Object.assign({}, baseConfig); + config.urls = { + events: 'https://events.fallbacktreatment/api' + }; + config.fallbackTreatments = { + global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } + } + }; + config.impressionListener = listener; + + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.ready(); + + t.deepEqual(client.getTreatmentWithConfig('emma@harness.io', 'my_flag'), { treatment: 'ON_FALLBACK', config: '{"flag": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment'); + t.deepEqual(client.getTreatmentWithConfig('emma@harness.io', 'non_existent_flag'), { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment'); + + let POSTED_IMPRESSIONS_COUNT = 0; + + fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', (_, opts) => { + + const payload = JSON.parse(opts.body); + t.equal(payload.length, 1, 'We should have just one impression for the two evaluated flags'); + + function validateImpressionData(featureFlagName, expectedLength) { + + const impressions = payload.find(e => e.f === featureFlagName).i; + t.equal(impressions.length, expectedLength, `${featureFlagName} has ${expectedLength} impressions`); + } + + validateImpressionData('my_flag', 1); + validateImpressionData('non_existent_flag', 0); + POSTED_IMPRESSIONS_COUNT = payload.reduce((acc, curr) => acc + curr.i.length, 0); + t.equal(POSTED_IMPRESSIONS_COUNT, 1, 'We should have just one impression in total.'); + + return 200; + }); + + setTimeout(() => { + t.equal(listener.logImpression.callCount, POSTED_IMPRESSIONS_COUNT, 'Impression listener should be called once per each impression generated.'); + + t.end(); + }, 0); + await client.destroy(); + + + }); + + assert.test('FallbackTreatment / LocalhostMode', async t => { + + const config = Object.assign({}, baseConfig); + config.core.authorizationKey = 'localhost'; + config.fallbackTreatments = { + global: 'OFF_FALLBACK' + }; + const splitio = SplitFactory(config); + const client = splitio.client(); + + await client.ready(); + + t.deepEqual(client.getTreatment('emma@harness.io', 'workspaces_v1'), 'on', 'The evaluation should return the treatment defined in localhost mode'); + t.deepEqual(client.getTreatment('emma@harness.io', 'non_existent_flag'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist'); + + await client.destroy(); + + t.end(); + }); + + + assert.end(); +} diff --git a/src/__tests__/online/node.spec.js b/src/__tests__/online/node.spec.js index c0f64b939..a48cbb309 100644 --- a/src/__tests__/online/node.spec.js +++ b/src/__tests__/online/node.spec.js @@ -8,6 +8,7 @@ import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; import evaluationsSuite from '../nodeSuites/evaluations.spec'; import evaluationsSemverSuite from '../nodeSuites/evaluations-semver.spec'; +import evaluationsFallbackSuite from '../nodeSuites/evaluations-fallback.spec'; import eventsSuite from '../nodeSuites/events.spec'; import impressionsSuite from '../nodeSuites/impressions.spec'; import impressionsSuiteDebug from '../nodeSuites/impressions.debug.spec'; @@ -59,6 +60,7 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { /* Check client evaluations. */ assert.test('E2E / In Memory', evaluationsSuite.bind(null, config, key)); assert.test('E2E / In Memory - Semver', evaluationsSemverSuite.bind(null, fetchMock)); + assert.test('E2E / In Memory - Fallback treatment', evaluationsFallbackSuite.bind(null, fetchMock)); /* Check impressions */ assert.test('E2E / Impressions', impressionsSuite.bind(null, key, fetchMock)); diff --git a/src/settings/defaults/version.js b/src/settings/defaults/version.js index d51e26fdc..5e675a540 100644 --- a/src/settings/defaults/version.js +++ b/src/settings/defaults/version.js @@ -1 +1 @@ -export const packageVersion = '11.7.0'; +export const packageVersion = '11.7.9-rc.0'; From cb8214a23ba917c937e1b6c926076acdd6983811 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Fri, 24 Oct 2025 15:11:34 -0300 Subject: [PATCH 02/21] fix version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7552bb236..8a168d2f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.9-rc.0", + "version": "11.7.2-rc.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio", - "version": "11.7.9-rc.0", + "version": "11.7.2-rc.2", "license": "Apache-2.0", "dependencies": { "@splitsoftware/splitio-commons": "2.7.9-rc.0", diff --git a/package.json b/package.json index c489eefe5..95dceaf9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.9-rc.0", + "version": "11.7.2-rc.2", "description": "Split SDK", "files": [ "README.md", From 3fe6120ee22fdce2fee61ace62cd697f992a2fcf Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Fri, 24 Oct 2025 15:54:22 -0300 Subject: [PATCH 03/21] fix version test --- src/settings/defaults/version.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/defaults/version.js b/src/settings/defaults/version.js index 5e675a540..24979a7c0 100644 --- a/src/settings/defaults/version.js +++ b/src/settings/defaults/version.js @@ -1 +1 @@ -export const packageVersion = '11.7.9-rc.0'; +export const packageVersion = '11.7.2-rc.2'; From b28c3f472ffece90fcaaf9b91b2eaf8c3d847af2 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Fri, 24 Oct 2025 18:25:38 -0300 Subject: [PATCH 04/21] remove timeout --- src/__tests__/nodeSuites/evaluations-fallback.spec.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/__tests__/nodeSuites/evaluations-fallback.spec.js b/src/__tests__/nodeSuites/evaluations-fallback.spec.js index 6b26ee19d..c0c7f73ce 100644 --- a/src/__tests__/nodeSuites/evaluations-fallback.spec.js +++ b/src/__tests__/nodeSuites/evaluations-fallback.spec.js @@ -152,12 +152,12 @@ export default async function (fetchMock, assert) { validateImpressionData('any_flag', 'fallback - not ready'); validateImpressionData('user_account_in_whitelist', 'not ready'); - t.end(); return 200; }); await client.destroy(); + t.end(); }); @@ -225,15 +225,10 @@ export default async function (fetchMock, assert) { return 200; }); - - setTimeout(() => { - t.equal(listener.logImpression.callCount, POSTED_IMPRESSIONS_COUNT, 'Impression listener should be called once per each impression generated.'); - - t.end(); - }, 0); + t.equal(listener.logImpression.callCount, POSTED_IMPRESSIONS_COUNT, 'Impression listener should be called once per each impression generated.'); await client.destroy(); - + t.end(); }); assert.test('FallbackTreatment / LocalhostMode', async t => { From 355cb94ef14f32542a82af768bc217263258e7cd Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 15 Oct 2025 15:42:01 -0300 Subject: [PATCH 05/21] Update tests --- .../ready-from-cache-async-wrapper.spec.js | 18 ++-- .../browserSuites/ready-from-cache.spec.js | 56 +++++++++---- .../browserSuites/ready-promise.spec.js | 82 +++++++++---------- src/__tests__/browserSuites/telemetry.spec.js | 4 +- src/__tests__/nodeSuites/lazy-init.spec.js | 12 +-- .../nodeSuites/ready-promise.spec.js | 65 +++++++-------- src/__tests__/nodeSuites/telemetry.spec.js | 4 +- src/__tests__/offline/browser.spec.js | 16 ++-- 8 files changed, 135 insertions(+), 122 deletions(-) diff --git a/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js b/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js index 73350b3c6..d4f37eed5 100644 --- a/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js +++ b/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js @@ -168,7 +168,7 @@ export default function (fetchMock, assert) { t.true(Date.now() - startTime >= 400, 'It should emit SDK_READY too but after syncing with the cloud.'); t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); - client.ready().then(() => { + client.whenReady().then(() => { t.true(Date.now() - startTime >= 400, 'It should resolve ready promise after syncing with the cloud.'); t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); @@ -176,12 +176,12 @@ export default function (fetchMock, assert) { t.true(Date.now() - startTime >= 700, 'It should emit SDK_READY too but after syncing with the cloud.'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); - client2.ready().then(() => { + client2.whenReady().then(() => { t.true(Date.now() - startTime >= 700, 'It should resolve ready promise after syncing with the cloud.'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); client3.on(client3.Event.SDK_READY, () => { - client3.ready().then(() => { + client3.whenReady().then(() => { t.true(Date.now() - startTime >= 1000, 'It should resolve ready promise after syncing with the cloud.'); t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); @@ -196,7 +196,7 @@ export default function (fetchMock, assert) { t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); client3.on(client3.Event.SDK_READY_TIMED_OUT, () => { - client3.ready().catch(() => { + client3.whenReady().catch(() => { t.true(Date.now() - startTime >= 850, 'It should reject ready promise before syncing memberships data with the cloud.'); t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with memberships data from cache.'); }); @@ -280,7 +280,7 @@ export default function (fetchMock, assert) { t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should emit SDK_READY after syncing with the cloud.'); t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); - client.ready().then(() => { + client.whenReady().then(() => { t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); @@ -288,12 +288,12 @@ export default function (fetchMock, assert) { t.true(nearlyEqual(Date.now() - startTime, CLIENT2_READY_MS), 'It should emit SDK_READY after syncing with the cloud.'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); - client2.ready().then(() => { + client2.whenReady().then(() => { t.true(nearlyEqual(Date.now() - startTime, CLIENT2_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); client3.on(client3.Event.SDK_READY, () => { - client3.ready().then(() => { + client3.whenReady().then(() => { t.true(nearlyEqual(Date.now() - startTime, CLIENT3_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); @@ -310,7 +310,7 @@ export default function (fetchMock, assert) { t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); client3.on(client3.Event.SDK_READY_TIMED_OUT, () => { - client3.ready().catch(() => { + client3.whenReady().catch(() => { t.true(Date.now() - startTime >= 850, 'It should reject ready promise before syncing memberships data with the cloud.'); t.equal(client3.getTreatment('always_on'), 'control', 'It should not evaluate treatments with memberships data from cache.'); }); @@ -359,7 +359,7 @@ export default function (fetchMock, assert) { t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); - await client.ready(); + await client.whenReady(); 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'); diff --git a/src/__tests__/browserSuites/ready-from-cache.spec.js b/src/__tests__/browserSuites/ready-from-cache.spec.js index 23053b239..2ff9cc183 100644 --- a/src/__tests__/browserSuites/ready-from-cache.spec.js +++ b/src/__tests__/browserSuites/ready-from-cache.spec.js @@ -226,7 +226,7 @@ export default function (fetchMock, assert) { t.true(Date.now() - startTime >= 400, 'It should emit SDK_READY too but after syncing with the cloud.'); t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); - client.ready().then(() => { + client.whenReady().then(() => { t.true(Date.now() - startTime >= 400, 'It should resolve ready promise after syncing with the cloud.'); t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); @@ -234,12 +234,12 @@ export default function (fetchMock, assert) { t.true(Date.now() - startTime >= 700, 'It should emit SDK_READY too but after syncing with the cloud.'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); - client2.ready().then(() => { + client2.whenReady().then(() => { t.true(Date.now() - startTime >= 700, 'It should resolve ready promise after syncing with the cloud.'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); client3.on(client3.Event.SDK_READY, () => { - client3.ready().then(() => { + client3.whenReady().then(() => { t.true(Date.now() - startTime >= 1000, 'It should resolve ready promise after syncing with the cloud.'); t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); @@ -254,7 +254,7 @@ export default function (fetchMock, assert) { t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); client3.on(client3.Event.SDK_READY_TIMED_OUT, () => { - client3.ready().catch(() => { + client3.whenReady().catch(() => { t.true(Date.now() - startTime >= 850, 'It should reject ready promise before syncing memberships data with the cloud.'); t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with memberships data from cache.'); }); @@ -269,7 +269,7 @@ export default function (fetchMock, assert) { events: 'https://events.baseurl/readyFromCacheWithData3' }; localStorage.clear(); - t.plan(12 * 2 + 5); + t.plan(12 * 2 + 14); fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=25&rbSince=-1', () => { t.equal(localStorage.getItem('readyFromCache_3.SPLITIO.split.always_on'), alwaysOnSplitInverted, 'feature flags must not be cleaned from cache'); @@ -285,8 +285,16 @@ export default function (fetchMock, assert) { return new Promise(res => { setTimeout(() => res({ status: 200, body: { 'ms': {} }, headers: {} }), 1000); }); // Third client memberships will come after 1s }); fetchMock.get(testUrls.sdk + '/memberships/nicolas4%40split.io', { 'ms': {} }); - fetchMock.postOnce(testUrls.events + '/testImpressions/bulk', 200); - fetchMock.postOnce(testUrls.events + '/testImpressions/count', 200); + fetchMock.postOnce(testUrls.events + '/testImpressions/bulk', (_, opts) => { + const payload = JSON.parse(opts.body); + t.equal(payload.length, 1, 'Only one flag was evaluated'); + t.equal(payload[0].i.length, 14, '14 impressions were queued, one per getTreatment call'); + t.equal(payload[0].i.filter((imp) => imp.t === 'control').length, 2, '2 impressions were queued for control (not ready from cache)'); + t.equal(payload[0].i.filter((imp) => imp.t === 'off').length, 4, '4 impressions were queued for off (ready from cache)'); + t.equal(payload[0].i.filter((imp) => imp.t === 'on').length, 8, '8 impressions were queued for on (ready)'); + + return 200; + }); localStorage.setItem('some_user_item', 'user_item'); localStorage.setItem('readyFromCache_3.SPLITIO.splits.till', 25); @@ -301,6 +309,9 @@ export default function (fetchMock, assert) { type: 'LOCALSTORAGE', prefix: 'readyFromCache_3' }, + sync: { + impressionsMode: 'DEBUG' + }, startup: { readyTimeout: 0.85 }, @@ -320,6 +331,8 @@ export default function (fetchMock, assert) { client.on(client.Event.SDK_READY_FROM_CACHE, () => { t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.'); + t.false(client.__getStatus().isReady, 'It should not be ready yet'); + t.equal(client.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation'); const client4 = splitio.client('nicolas4@split.io'); @@ -342,7 +355,11 @@ export default function (fetchMock, assert) { t.true(Date.now() - startTime >= 400, 'It should emit SDK_READY too but after syncing with the cloud.'); t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); - client.ready().then(() => { + client.whenReadyFromCache().then((isReady) => { + t.false(isReady, 'It should be ready from cache before ready (syncing with the cloud).'); + t.true(Date.now() - startTime < 50, 'It should resolve ready from cache promise almost immediately.'); + }); + client.whenReady().then(() => { t.true(Date.now() - startTime >= 400, 'It should resolve ready promise after syncing with the cloud.'); t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); @@ -350,12 +367,15 @@ export default function (fetchMock, assert) { t.true(Date.now() - startTime >= 700, 'It should emit SDK_READY too but after syncing with the cloud.'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); - client2.ready().then(() => { + client2.whenReadyFromCache().then((isReady) => { + t.false(isReady, 'It should be ready from cache before ready (syncing with the cloud).'); + }); + client2.whenReady().then(() => { t.true(Date.now() - startTime >= 700, 'It should resolve ready promise after syncing with the cloud.'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); client3.on(client3.Event.SDK_READY, () => { - client3.ready().then(() => { + client3.whenReady().then(() => { t.true(Date.now() - startTime >= 1000, 'It should resolve ready promise after syncing with the cloud.'); t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); @@ -370,7 +390,7 @@ export default function (fetchMock, assert) { t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); client3.on(client3.Event.SDK_READY_TIMED_OUT, () => { - client3.ready().catch(() => { + client3.whenReady().catch(() => { t.true(Date.now() - startTime >= 850, 'It should reject ready promise before syncing memberships data with the cloud.'); t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with memberships data from cache.'); }); @@ -452,7 +472,11 @@ export default function (fetchMock, assert) { t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should emit SDK_READY after syncing with the cloud.'); t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); - client.ready().then(() => { + client.whenReadyFromCache().then((isReady) => { + t.true(isReady, 'It should be ready from cache and ready.'); + t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should resolve ready from cache promise after syncing with the cloud.'); + }); + client.whenReady().then(() => { t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); @@ -460,12 +484,12 @@ export default function (fetchMock, assert) { t.true(nearlyEqual(Date.now() - startTime, CLIENT2_READY_MS), 'It should emit SDK_READY after syncing with the cloud.'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); - client2.ready().then(() => { + client2.whenReady().then(() => { t.true(nearlyEqual(Date.now() - startTime, CLIENT2_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); client3.on(client3.Event.SDK_READY, () => { - client3.ready().then(() => { + client3.whenReady().then(() => { t.true(nearlyEqual(Date.now() - startTime, CLIENT3_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); @@ -482,7 +506,7 @@ export default function (fetchMock, assert) { t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); }); client3.on(client3.Event.SDK_READY_TIMED_OUT, () => { - client3.ready().catch(() => { + client3.whenReady().catch(() => { t.true(Date.now() - startTime >= 850, 'It should reject ready promise before syncing memberships data with the cloud.'); t.equal(client3.getTreatment('always_on'), 'control', 'It should not evaluate treatments with memberships data from cache.'); }); @@ -898,7 +922,7 @@ export default function (fetchMock, assert) { t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); - await client.ready(); + await client.whenReady(); 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'); diff --git a/src/__tests__/browserSuites/ready-promise.spec.js b/src/__tests__/browserSuites/ready-promise.spec.js index 3371d1618..f632a177c 100644 --- a/src/__tests__/browserSuites/ready-promise.spec.js +++ b/src/__tests__/browserSuites/ready-promise.spec.js @@ -31,7 +31,7 @@ function assertGetTreatmentWhenReady(assert, client) { function assertGetTreatmentControlNotReady(assert, client) { consoleSpy.log.resetHistory(); assert.equal(client.getTreatment('hierarchical_splits_test'), 'control', 'We should get control if client is not ready.'); - assert.true(consoleSpy.log.calledWithExactly('[WARN] splitio => getTreatment: the SDK is not ready, results may be incorrect for feature flag hierarchical_splits_test. Make sure to wait for SDK readiness before using this method.'), 'Telling us that calling getTreatment would return CONTROL since SDK is not ready at this point.'); + assert.true(consoleSpy.log.calledWithExactly('[WARN] splitio => getTreatment: the SDK is not ready to evaluate. Results may be incorrect for feature flag hierarchical_splits_test. Make sure to wait for SDK readiness before using this method.'), 'Telling us that calling getTreatment would return CONTROL since SDK is not ready at this point.'); } function assertGetTreatmentControlNotReadyOnDestroy(assert, client) { @@ -68,7 +68,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -77,7 +77,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { assertGetTreatmentControlNotReady(t, client); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - It should not in this scenario.'); t.end(); @@ -118,13 +118,13 @@ export default function readyPromiseAssertions(fetchMock, assert) { // In this case, we use the manager instead of the client to get the ready promise const manager = splitio.manager(); - manager.ready() + manager.whenReady() .then(() => { t.pass('### SDK IS READY - the retry request is under the limits.'); assertGetTreatmentWhenReady(t, client); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -166,7 +166,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -175,13 +175,13 @@ export default function readyPromiseAssertions(fetchMock, assert) { assertGetTreatmentControlNotReady(t, client); setTimeout(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - retry attempt finishes before the requestTimeoutBeforeReady limit'); assertGetTreatmentWhenReady(t, client); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -244,7 +244,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { const client = splitio.client(); const nicolasClient = splitio.client('nicolas@split.io'); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -253,25 +253,25 @@ export default function readyPromiseAssertions(fetchMock, assert) { assertGetTreatmentControlNotReady(t, client); client.on(client.Event.SDK_READY, () => { - client.ready().then(() => { + client.whenReady().then(() => { t.pass('### SDK IS READY - the scheduled refresh changes the client state into "ready"'); assertGetTreatmentWhenReady(t, client); const tStart = Date.now(); - nicolasClient.ready().then(() => { + nicolasClient.whenReady().then(() => { const delta = Date.now() - tStart; t.true(nearlyEqual(delta, 0), 'shared client is ready as soon as main client is ready (i.e., splits have arrived)'); const timeoutClient = splitio.client('emiliano@split.io'); - timeoutClient.ready().then(undefined, () => { // setting onRejected handler via `then` method + timeoutClient.whenReady().then(undefined, () => { // setting onRejected handler via `then` method t.pass('### Shared client TIMED OUT - promise rejected since memberships fetch took more time than readyTimeout'); - timeoutClient.ready().catch(() => { // setting onRejected handler via `catch` method + timeoutClient.whenReady().catch(() => { // setting onRejected handler via `catch` method t.pass('### Shared client TIMED OUT - promise keeps being rejected'); timeoutClient.on(timeoutClient.Event.SDK_READY, () => { - timeoutClient.ready().then(() => { + timeoutClient.whenReady().then(() => { t.pass('### Shared client READY - `ready` returns a new resolved promise'); Promise.all([timeoutClient.destroy(), nicolasClient.destroy(), client.destroy()]).then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -318,7 +318,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); client.destroy().then(() => { t.end(); }); @@ -330,7 +330,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { .catch((error) => { t.equal(error, 'error', '### Handled thrown exception on onRejected callback.'); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - It should not in this scenario.'); t.end(); @@ -368,7 +368,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY as it should, request is under the limits.'); assertGetTreatmentWhenReady(t, client); @@ -377,7 +377,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { .catch((error) => { t.equal(error, 'error', '### Handled thrown exception on onRejected callback.'); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -421,7 +421,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { // `ready` is called immediately. Thus, the 'reject' callback is expected to be called in 0.15 seconds aprox. setTimeout(() => { const tStart = Date.now(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -436,7 +436,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { // `ready` is called in 0.15 seconds, when the promise is just rejected. Thus, the 'reject' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const tStart = Date.now(); - manager.ready() + manager.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -451,7 +451,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { // `ready` is called in 0.25 seconds, right after the promise is resolved (0.2 secs). Thus, the 'resolve' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const tStart = Date.now(); - manager.ready() + manager.whenReady() .then(() => { t.pass('### SDK IS READY - retry attempt finishes before the requestTimeoutBeforeReady limit'); assertGetTreatmentWhenReady(t, client); @@ -465,7 +465,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { }) .then(() => { client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -506,26 +506,24 @@ export default function readyPromiseAssertions(fetchMock, assert) { const splitio = SplitFactory(config); - const onReadycallback = function () { }; + const onReadyCallback = function () { }; // We invoke the ready method and also add and remove SDK_READY event listeners using the client and manager instances const client = splitio.client(); - client.ready(); - client.on(client.Event.SDK_READY, onReadycallback); - client.off(client.Event.SDK_READY, onReadycallback); + client.whenReady().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); + client.on(client.Event.SDK_READY, onReadyCallback); + client.off(client.Event.SDK_READY, onReadyCallback); const manager = splitio.manager(); - manager.ready(); - manager.on(manager.Event.SDK_READY, onReadycallback); - manager.off(manager.Event.SDK_READY, onReadycallback); + client.whenReadyFromCache().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); + manager.on(manager.Event.SDK_READY, onReadyCallback); + manager.off(manager.Event.SDK_READY, onReadyCallback); consoleSpy.log.resetHistory(); setTimeout(() => { - client.ready(); + client.whenReadyFromCache().then((isReady) => t.true(isReady, 'SDK IS READY (& READY FROM CACHE) - Should resolve')).catch(() => t.fail('SDK TIMED OUT - Should not reject')); assertGetTreatmentWhenReady(t, client); - t.true(consoleSpy.log.calledWithExactly('[WARN] splitio => No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'), - 'Warning that there are not listeners for SDK_READY event'); // assert error messages when adding event listeners after SDK has already triggered them consoleSpy.log.resetHistory(); @@ -540,15 +538,15 @@ export default function readyPromiseAssertions(fetchMock, assert) { consoleSpy.log.resetHistory(); const sharedClientWithCb = splitio.client('nicolas@split.io'); sharedClientWithCb.on(client.Event.SDK_READY, () => { - t.false(consoleSpy.log.calledWithExactly('[WARN] splitio => No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'), + t.false(consoleSpy.log.calledWithExactly('[WARN] splitio => No listeners for SDK_READY event detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet synchronized with the backend.'), 'No warning logged'); const sharedClientWithoutCb = splitio.client('emiliano@split.io'); setTimeout(() => { - t.true(consoleSpy.log.calledWithExactly('[WARN] splitio => No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'), + t.true(consoleSpy.log.calledWithExactly('[WARN] splitio => No listeners for SDK_READY event detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet synchronized with the backend.'), 'Warning logged'); Promise.all([sharedClientWithoutCb.destroy(), client.destroy()]).then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client); @@ -594,18 +592,12 @@ export default function readyPromiseAssertions(fetchMock, assert) { // Assert getTreatment return CONTROL and trigger warning when SDK is not ready yet assertGetTreatmentControlNotReady(t, client); - client.ready() - .then(() => { - t.fail('### SDK IS READY - not TIMED OUT when it should.'); - }); - otherClient.ready() - .then(() => { - t.fail('### SDK IS READY - not TIMED OUT when it should.'); - }); + client.whenReady().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); + otherClient.whenReady().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); setTimeout(() => { Promise.all([client.destroy(), otherClient.destroy()]).then(() => { - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - It should not in this scenario.'); t.end(); diff --git a/src/__tests__/browserSuites/telemetry.spec.js b/src/__tests__/browserSuites/telemetry.spec.js index 29facccf4..a1a64b4fd 100644 --- a/src/__tests__/browserSuites/telemetry.spec.js +++ b/src/__tests__/browserSuites/telemetry.spec.js @@ -105,14 +105,14 @@ export default async function telemetryBrowserSuite(fetchMock, t) { const data = JSON.parse(opts.body); assert.true(data.tR > 0, 'timeUntilReady is larger than 0'); - delete data.tR; // delete to validate other properties + assert.true(data.tC > 0, 'timeUntilReadyFromCache is larger than 0'); assert.deepEqual(data, { oM: 0, st: 'memory', aF: 1, rF: 0, sE: false, rR: { sp: 99999, ms: 60, im: 300, ev: 60, te: 1 } /* override featuresRefreshRate */, uO: { s: true, e: true, a: false, st: false, t: true } /* override sdk, events and telemetry URLs */, iQ: 30000, eQ: 500, iM: 0, iL: false, hP: false, nR: 1 /* 1 non ready usage */, t: [], uC: 2 /* Default GRANTED */, - fsT: 0, fsI: 0 + fsT: 0, fsI: 0, tR: data.tR, tC: data.tC }, 'metrics/config JSON payload should be the expected'); finish.next(); diff --git a/src/__tests__/nodeSuites/lazy-init.spec.js b/src/__tests__/nodeSuites/lazy-init.spec.js index c4d94c1d2..44bfacaa6 100644 --- a/src/__tests__/nodeSuites/lazy-init.spec.js +++ b/src/__tests__/nodeSuites/lazy-init.spec.js @@ -41,14 +41,14 @@ export default function (settings, fetchMock, t) { splitio.init(); await splitio.client().ready(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); await splitio.destroy(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: true, isOperational: false, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: true, isOperational: false, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); splitio.init(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); await splitio.destroy(); @@ -99,14 +99,14 @@ export default function (settings, fetchMock, t) { splitio.init(); await splitio.client().ready(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); await splitio.destroy(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: true, isOperational: false, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: true, isOperational: false, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); splitio.init(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); await splitio.destroy(); diff --git a/src/__tests__/nodeSuites/ready-promise.spec.js b/src/__tests__/nodeSuites/ready-promise.spec.js index 7d0f35619..b6b76c93e 100644 --- a/src/__tests__/nodeSuites/ready-promise.spec.js +++ b/src/__tests__/nodeSuites/ready-promise.spec.js @@ -28,7 +28,7 @@ function assertGetTreatmentWhenReady(assert, client, key) { function assertGetTreatmentControlNotReady(assert, client, key) { consoleSpy.log.resetHistory(); assert.equal(client.getTreatment(key, 'hierarchical_splits_test'), 'control', 'We should get control if client is not ready.'); - assert.true(consoleSpy.log.calledWithExactly('[WARN] splitio => getTreatment: the SDK is not ready, results may be incorrect for feature flag hierarchical_splits_test. Make sure to wait for SDK readiness before using this method.'), 'Telling us that calling getTreatment would return CONTROL since SDK is not ready at this point.'); + assert.true(consoleSpy.log.calledWithExactly('[WARN] splitio => getTreatment: the SDK is not ready to evaluate. Results may be incorrect for feature flag hierarchical_splits_test. Make sure to wait for SDK readiness before using this method.'), 'Telling us that calling getTreatment would return CONTROL since SDK is not ready at this point.'); } function assertGetTreatmentControlNotReadyOnDestroy(assert, client, key) { @@ -64,7 +64,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -73,7 +73,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { assertGetTreatmentControlNotReady(t, client, key); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - It should not in this scenario.'); t.end(); @@ -114,13 +114,13 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { // In this case, we use the manager instead of the client to get the ready promise const manager = splitio.manager(); - manager.ready() + manager.whenReady() .then(() => { t.pass('### SDK IS READY - the retry request is under the limits.'); assertGetTreatmentWhenReady(t, client, key); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client, key); @@ -162,7 +162,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -171,13 +171,13 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { assertGetTreatmentControlNotReady(t, client, key); setTimeout(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - retry attempt finishes before the requestTimeoutBeforeReady limit'); assertGetTreatmentWhenReady(t, client, key); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client, key); @@ -232,7 +232,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -241,13 +241,13 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { assertGetTreatmentControlNotReady(t, client, key); setTimeout(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the scheduled refresh changes the client state into "is ready"'); assertGetTreatmentWhenReady(t, client, key); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client, key); @@ -289,7 +289,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); client.destroy().then(() => { t.end(); }); @@ -301,7 +301,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { .catch((error) => { t.equal(error, 'error', '### Handled thrown exception on onRejected callback.'); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - It should not in this scenario.'); t.end(); @@ -339,7 +339,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { const splitio = SplitFactory(config); const client = splitio.client(); - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY as it should, request is under the limits.'); assertGetTreatmentWhenReady(t, client, key); @@ -348,7 +348,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { .catch((error) => { t.equal(error, 'error', '### Handled thrown exception on onRejected callback.'); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client, key); @@ -392,7 +392,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { // `ready` is called immediately. Thus, the 'reject' callback is expected to be called in 0.15 seconds aprox. setTimeout(() => { const tStart = Date.now(); - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -407,7 +407,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { // `ready` is called in 0.15 seconds, when the promise is just rejected. Thus, the 'reject' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const tStart = Date.now(); - manager.ready() + manager.whenReady() .then(() => { t.fail('### SDK IS READY - not TIMED OUT when it should.'); }) @@ -422,7 +422,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { // `ready` is called in 0.25 seconds, right after the promise is resolved (0.2 secs). Thus, the 'resolve' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const tStart = Date.now(); - manager.ready() + manager.whenReady() .then(() => { t.pass('### SDK IS READY - retry attempt finishes before the requestTimeoutBeforeReady limit'); assertGetTreatmentWhenReady(t, client, key); @@ -436,7 +436,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { }) .then(() => { client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client, key); @@ -475,26 +475,24 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { const splitio = SplitFactory(config); - const onReadycallback = function () { }; + const onReadyCallback = function () { }; // We invoke the ready method and also add and remove SDK_READY event listeners using the client and manager instances const client = splitio.client(); - client.ready(); - client.on(client.Event.SDK_READY, onReadycallback); - client.off(client.Event.SDK_READY, onReadycallback); + client.whenReady().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); + client.on(client.Event.SDK_READY, onReadyCallback); + client.off(client.Event.SDK_READY, onReadyCallback); const manager = splitio.manager(); - manager.ready(); - manager.on(manager.Event.SDK_READY, onReadycallback); - manager.off(manager.Event.SDK_READY, onReadycallback); + client.whenReadyFromCache().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); + manager.on(manager.Event.SDK_READY, onReadyCallback); + manager.off(manager.Event.SDK_READY, onReadyCallback); consoleSpy.log.resetHistory(); setTimeout(() => { - client.ready(); + client.whenReadyFromCache().then((isReady) => t.true(isReady, 'SDK IS READY (& READY FROM CACHE) - Should resolve')).catch(() => t.fail('SDK TIMED OUT - Should not reject')); assertGetTreatmentWhenReady(t, client, key); - t.true(consoleSpy.log.calledWithExactly('[WARN] splitio => No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'), - 'Warning that there are not listeners for SDK_READY event'); // assert error messages when adding event listeners after SDK has already triggered them consoleSpy.log.resetHistory(); @@ -506,7 +504,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { 'Logging error that a listeners for SDK_READY_TIMED_OUT event was added after triggered'); client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.pass('### SDK IS READY - the promise remains resolved after client destruction.'); assertGetTreatmentControlNotReadyOnDestroy(t, client, key); @@ -549,14 +547,11 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { // Assert getTreatment return CONTROL and trigger warning when SDK is not ready yet assertGetTreatmentControlNotReady(t, client, key); - client.ready() - .then(() => { - t.fail('### SDK IS READY - not TIMED OUT when it should.'); - }); + client.whenReady().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); setTimeout(() => { client.destroy().then(() => { - client.ready() + client.whenReady() .then(() => { t.fail('### SDK IS READY - It should not in this scenario.'); t.end(); diff --git a/src/__tests__/nodeSuites/telemetry.spec.js b/src/__tests__/nodeSuites/telemetry.spec.js index 85f7cdf67..e73f8c4f1 100644 --- a/src/__tests__/nodeSuites/telemetry.spec.js +++ b/src/__tests__/nodeSuites/telemetry.spec.js @@ -94,14 +94,14 @@ export default async function telemetryNodejsSuite(key, fetchMock, assert) { const data = JSON.parse(opts.body); assert.true(data.tR > 0, 'timeUntilReady is larger than 0'); - delete data.tR; // delete to validate other properties + assert.true(data.tC > 0, 'timeUntilReadyFromCache is larger than 0'); assert.deepEqual(data, { oM: 0, st: 'memory', aF: 1, rF: 0, sE: false, rR: { sp: 99999, se: 60, im: 300, ev: 60, te: 1 } /* override featuresRefreshRate */, uO: { s: true, e: true, a: false, st: false, t: true } /* override sdk, events and telemetry URLs */, iQ: 30000, eQ: 500, iM: 0, iL: false, hP: false, nR: 1 /* 1 non ready usage */, t: [], uC: 0 /* NA */, - fsT: 0, fsI: 0 + fsT: 0, fsI: 0, tR: data.tR, tC: data.tC }, 'metrics/config JSON payload should be the expected'); finish.next(); diff --git a/src/__tests__/offline/browser.spec.js b/src/__tests__/offline/browser.spec.js index 8507e080b..f2cb8dac0 100644 --- a/src/__tests__/offline/browser.spec.js +++ b/src/__tests__/offline/browser.spec.js @@ -116,21 +116,23 @@ tape('Browser offline mode', function (assert) { const clientStatus = client.__getStatus(); assert.equal(clientStatus.isReadyFromCache, true, 'If ready from cache, READY_FROM_CACHE status must be true'); - assert.equal(clientStatus.isReady, false, 'READY status must not be set before READY_FROM_CACHE'); + assert.equal(clientStatus.isReady, configs[i].storage && configs[i].storage.type === 'LOCALSTORAGE' ? false : true, 'When not using LOCALSTORAGE, READY status is set together with READY_FROM_CACHE'); + if (!clientStatus.isReady) readyFromCacheCount++; assert.deepEqual(manager.names(), ['testing_split', 'testing_split_with_config']); assert.equal(client.getTreatment('testing_split_with_config'), 'off'); - readyFromCacheCount++; client.on(client.Event.SDK_READY_FROM_CACHE, () => { assert.fail('It should not emit SDK_READY_FROM_CACHE again'); }); - const newClient = factory.client('another'); - assert.equal(newClient.getTreatment('testing_split_with_config'), 'off', 'It should evaluate treatments with data from cache instead of control'); - newClient.on(newClient.Event.SDK_READY_FROM_CACHE, () => { - assert.fail('It should not emit SDK_READY_FROM_CACHE if already done.'); - }); + if (configs[i].storage && configs[i].storage.type === 'LOCALSTORAGE') { + const newClient = factory.client('another'); + assert.equal(newClient.getTreatment('testing_split_with_config'), 'off', 'It should evaluate treatments with data from cache instead of control'); + newClient.on(newClient.Event.SDK_READY_FROM_CACHE, () => { + assert.fail('It should not emit SDK_READY_FROM_CACHE if already done.'); + }); + } }; client.on(client.Event.SDK_READY_FROM_CACHE, sdkReadyFromCache(client)); From 5857e2d7be2c5e0e87d39e39fb1bb2adc8580de5 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Mon, 27 Oct 2025 16:02:14 -0300 Subject: [PATCH 06/21] Move test to the end --- src/__tests__/nodeSuites/evaluations-fallback.spec.js | 1 - src/__tests__/online/node.spec.js | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/__tests__/nodeSuites/evaluations-fallback.spec.js b/src/__tests__/nodeSuites/evaluations-fallback.spec.js index c0c7f73ce..2d99a8e4f 100644 --- a/src/__tests__/nodeSuites/evaluations-fallback.spec.js +++ b/src/__tests__/nodeSuites/evaluations-fallback.spec.js @@ -251,6 +251,5 @@ export default async function (fetchMock, assert) { t.end(); }); - assert.end(); } diff --git a/src/__tests__/online/node.spec.js b/src/__tests__/online/node.spec.js index a48cbb309..b4644d718 100644 --- a/src/__tests__/online/node.spec.js +++ b/src/__tests__/online/node.spec.js @@ -60,7 +60,6 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { /* Check client evaluations. */ assert.test('E2E / In Memory', evaluationsSuite.bind(null, config, key)); assert.test('E2E / In Memory - Semver', evaluationsSemverSuite.bind(null, fetchMock)); - assert.test('E2E / In Memory - Fallback treatment', evaluationsFallbackSuite.bind(null, fetchMock)); /* Check impressions */ assert.test('E2E / Impressions', impressionsSuite.bind(null, key, fetchMock)); @@ -102,5 +101,8 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { // @TODO remove when dropping support for Split Proxy v5.10.0 or below assert.test('E2E / Proxy fallback', proxyFallbackSuite.bind(null, fetchMock)); + /* Check evaluations fallback */ + assert.test('E2E / In Memory - Fallback treatment', evaluationsFallbackSuite.bind(null, fetchMock)); + assert.end(); }); From ea6bff700fc7aa25682f83858e3b88c150cbecd8 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Mon, 27 Oct 2025 16:14:43 -0300 Subject: [PATCH 07/21] Move tests to the top --- src/__tests__/online/node.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/__tests__/online/node.spec.js b/src/__tests__/online/node.spec.js index b4644d718..cc999cd7d 100644 --- a/src/__tests__/online/node.spec.js +++ b/src/__tests__/online/node.spec.js @@ -57,6 +57,9 @@ fetchMock.post(url(settings, '/v1/metrics/config'), 200); fetchMock.post(url(settings, '/v1/metrics/usage'), 200); tape('## Node.js - E2E CI Tests ##', async function (assert) { + /* Check evaluations fallback */ + assert.test('E2E / In Memory - Fallback treatment', evaluationsFallbackSuite.bind(null, fetchMock)); + /* Check client evaluations. */ assert.test('E2E / In Memory', evaluationsSuite.bind(null, config, key)); assert.test('E2E / In Memory - Semver', evaluationsSemverSuite.bind(null, fetchMock)); @@ -101,8 +104,5 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { // @TODO remove when dropping support for Split Proxy v5.10.0 or below assert.test('E2E / Proxy fallback', proxyFallbackSuite.bind(null, fetchMock)); - /* Check evaluations fallback */ - assert.test('E2E / In Memory - Fallback treatment', evaluationsFallbackSuite.bind(null, fetchMock)); - assert.end(); }); From 23521c447f8605b4fe70b1666ceee05f1ac05e10 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Mon, 27 Oct 2025 16:20:00 -0300 Subject: [PATCH 08/21] Move test --- src/__tests__/online/node.spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/__tests__/online/node.spec.js b/src/__tests__/online/node.spec.js index cc999cd7d..aae279c1d 100644 --- a/src/__tests__/online/node.spec.js +++ b/src/__tests__/online/node.spec.js @@ -57,8 +57,6 @@ fetchMock.post(url(settings, '/v1/metrics/config'), 200); fetchMock.post(url(settings, '/v1/metrics/usage'), 200); tape('## Node.js - E2E CI Tests ##', async function (assert) { - /* Check evaluations fallback */ - assert.test('E2E / In Memory - Fallback treatment', evaluationsFallbackSuite.bind(null, fetchMock)); /* Check client evaluations. */ assert.test('E2E / In Memory', evaluationsSuite.bind(null, config, key)); @@ -92,6 +90,9 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { /* Validate readiness with ready promises */ assert.test('E2E / Ready promise', readyPromiseSuite.bind(null, key, fetchMock)); + /* Check evaluations fallback */ + assert.test('E2E / In Memory - Fallback treatment', evaluationsFallbackSuite.bind(null, fetchMock)); + /* Validate fetching specific splits */ assert.test('E2E / Fetch specific splits', fetchSpecificSplits.bind(null, fetchMock)); assert.test('E2E / Fetch specific splits for flag sets', fetchSpecificSplitsForFlagSets.bind(null, fetchMock)); From 2b26ceddfbc7565b2cb95625869b30d47c2a325e Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Mon, 27 Oct 2025 16:33:55 -0300 Subject: [PATCH 09/21] Fix test --- src/__tests__/nodeSuites/evaluations-fallback.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/nodeSuites/evaluations-fallback.spec.js b/src/__tests__/nodeSuites/evaluations-fallback.spec.js index 2d99a8e4f..f649fb727 100644 --- a/src/__tests__/nodeSuites/evaluations-fallback.spec.js +++ b/src/__tests__/nodeSuites/evaluations-fallback.spec.js @@ -113,7 +113,7 @@ export default async function (fetchMock, assert) { await client.ready(); - t.equal(client.getTreatment('emma@harness.io', 'user_account_in_whitelist'), 'off', 'The evaluation will return the treatment defined in the flag if it exists'); + t.equal(client.getTreatment('emma@harness.io', 'real_split'), 'on', 'The evaluation will return the treatment defined in the flag if it exists'); t.equal(client.getTreatment('emma@harness.io', 'non_existent_flag'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist and no fallbackTreatment is defined'); await client.destroy(); From d842d6c75dccadcd0574997620e7e00baac8e171 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Mon, 27 Oct 2025 16:49:35 -0300 Subject: [PATCH 10/21] Move telemery tests to bottom --- src/__tests__/nodeSuites/evaluations-fallback.spec.js | 2 +- src/__tests__/online/node.spec.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/__tests__/nodeSuites/evaluations-fallback.spec.js b/src/__tests__/nodeSuites/evaluations-fallback.spec.js index f649fb727..2d99a8e4f 100644 --- a/src/__tests__/nodeSuites/evaluations-fallback.spec.js +++ b/src/__tests__/nodeSuites/evaluations-fallback.spec.js @@ -113,7 +113,7 @@ export default async function (fetchMock, assert) { await client.ready(); - t.equal(client.getTreatment('emma@harness.io', 'real_split'), 'on', 'The evaluation will return the treatment defined in the flag if it exists'); + t.equal(client.getTreatment('emma@harness.io', 'user_account_in_whitelist'), 'off', 'The evaluation will return the treatment defined in the flag if it exists'); t.equal(client.getTreatment('emma@harness.io', 'non_existent_flag'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist and no fallbackTreatment is defined'); await client.destroy(); diff --git a/src/__tests__/online/node.spec.js b/src/__tests__/online/node.spec.js index aae279c1d..4ba9279a3 100644 --- a/src/__tests__/online/node.spec.js +++ b/src/__tests__/online/node.spec.js @@ -61,6 +61,7 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { /* Check client evaluations. */ assert.test('E2E / In Memory', evaluationsSuite.bind(null, config, key)); assert.test('E2E / In Memory - Semver', evaluationsSemverSuite.bind(null, fetchMock)); + assert.test('E2E / In Memory - Fallback treatment', evaluationsFallbackSuite.bind(null, fetchMock)); /* Check impressions */ assert.test('E2E / Impressions', impressionsSuite.bind(null, key, fetchMock)); @@ -68,8 +69,6 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { assert.test('E2E / Impressions None Mode', impressionsSuiteNone.bind(null, key, fetchMock)); assert.test('E2E / Impressions listener', impressionsListenerSuite); - /* Check telemetry */ - assert.test('E2E / Telemetry', telemetrySuite.bind(null, key, fetchMock)); /* Check events in memory */ assert.test('E2E / Events', eventsSuite.bind(null, fetchMock)); @@ -90,9 +89,6 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { /* Validate readiness with ready promises */ assert.test('E2E / Ready promise', readyPromiseSuite.bind(null, key, fetchMock)); - /* Check evaluations fallback */ - assert.test('E2E / In Memory - Fallback treatment', evaluationsFallbackSuite.bind(null, fetchMock)); - /* Validate fetching specific splits */ assert.test('E2E / Fetch specific splits', fetchSpecificSplits.bind(null, fetchMock)); assert.test('E2E / Fetch specific splits for flag sets', fetchSpecificSplitsForFlagSets.bind(null, fetchMock)); @@ -105,5 +101,9 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { // @TODO remove when dropping support for Split Proxy v5.10.0 or below assert.test('E2E / Proxy fallback', proxyFallbackSuite.bind(null, fetchMock)); + + /* Check telemetry */ + assert.test('E2E / Telemetry', telemetrySuite.bind(null, key, fetchMock)); + assert.end(); }); From 2acd976dbba1542052a99643206f9bdd39088149 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Mon, 27 Oct 2025 17:20:55 -0300 Subject: [PATCH 11/21] Move telemetry test --- src/__tests__/online/node.spec.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/__tests__/online/node.spec.js b/src/__tests__/online/node.spec.js index 4ba9279a3..04413bb14 100644 --- a/src/__tests__/online/node.spec.js +++ b/src/__tests__/online/node.spec.js @@ -69,6 +69,8 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { assert.test('E2E / Impressions None Mode', impressionsSuiteNone.bind(null, key, fetchMock)); assert.test('E2E / Impressions listener', impressionsListenerSuite); + // /* Check telemetry */ + assert.test('E2E / Telemetry', telemetrySuite.bind(null, key, fetchMock)); /* Check events in memory */ assert.test('E2E / Events', eventsSuite.bind(null, fetchMock)); @@ -101,9 +103,5 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { // @TODO remove when dropping support for Split Proxy v5.10.0 or below assert.test('E2E / Proxy fallback', proxyFallbackSuite.bind(null, fetchMock)); - - /* Check telemetry */ - assert.test('E2E / Telemetry', telemetrySuite.bind(null, key, fetchMock)); - assert.end(); }); From 1d63122abaddc1f699e5e52958ecb16d125e7e20 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 27 Oct 2025 17:26:31 -0300 Subject: [PATCH 12/21] Fix fallback treatment localhost test --- src/__tests__/nodeSuites/evaluations-fallback.spec.js | 5 +++-- src/__tests__/online/node.spec.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/__tests__/nodeSuites/evaluations-fallback.spec.js b/src/__tests__/nodeSuites/evaluations-fallback.spec.js index 2d99a8e4f..53c226fea 100644 --- a/src/__tests__/nodeSuites/evaluations-fallback.spec.js +++ b/src/__tests__/nodeSuites/evaluations-fallback.spec.js @@ -1,3 +1,4 @@ +import path from 'path'; import sinon from 'sinon'; import { SplitFactory } from '../../'; @@ -238,12 +239,13 @@ export default async function (fetchMock, assert) { config.fallbackTreatments = { global: 'OFF_FALLBACK' }; + config.features = path.join(__dirname, '../offline/split.yaml'); const splitio = SplitFactory(config); const client = splitio.client(); await client.ready(); - t.deepEqual(client.getTreatment('emma@harness.io', 'workspaces_v1'), 'on', 'The evaluation should return the treatment defined in localhost mode'); + t.deepEqual(client.getTreatment('emma@harness.io', 'testing_split_on'), 'on', 'The evaluation should return the treatment defined in localhost mode'); t.deepEqual(client.getTreatment('emma@harness.io', 'non_existent_flag'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist'); await client.destroy(); @@ -251,5 +253,4 @@ export default async function (fetchMock, assert) { t.end(); }); - assert.end(); } diff --git a/src/__tests__/online/node.spec.js b/src/__tests__/online/node.spec.js index 04413bb14..35d87a3c3 100644 --- a/src/__tests__/online/node.spec.js +++ b/src/__tests__/online/node.spec.js @@ -69,7 +69,7 @@ tape('## Node.js - E2E CI Tests ##', async function (assert) { assert.test('E2E / Impressions None Mode', impressionsSuiteNone.bind(null, key, fetchMock)); assert.test('E2E / Impressions listener', impressionsListenerSuite); - // /* Check telemetry */ + /* Check telemetry */ assert.test('E2E / Telemetry', telemetrySuite.bind(null, key, fetchMock)); /* Check events in memory */ From 5d621b1a00afd1306b65d65db8f66b97f7c4760a Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 27 Oct 2025 19:07:50 -0300 Subject: [PATCH 13/21] Update JS-commons --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2cc7b9b9a..02fc53b90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "11.7.1", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.7.1", + "@splitsoftware/splitio-commons": "2.7.9-rc.1", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", @@ -351,9 +351,9 @@ "dev": true }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.1.tgz", - "integrity": "sha512-7a4VVMczh0YKVRi35EhD0FOAEwzqfJRcCiKqLLhZCxAvrZBpE2khpGn8pOP+y6TefdPVtblW8GIku4O4r0KRdQ==", + "version": "2.7.9-rc.1", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.1.tgz", + "integrity": "sha512-hmcekZebItAc67+AF3xGgKXIvnLybQhpaEtQKsqU3WbyRittUI9hN9HqA6qUiaLxFPwrsCxioihTYsnxxej/Rg==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -7740,9 +7740,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.1.tgz", - "integrity": "sha512-7a4VVMczh0YKVRi35EhD0FOAEwzqfJRcCiKqLLhZCxAvrZBpE2khpGn8pOP+y6TefdPVtblW8GIku4O4r0KRdQ==", + "version": "2.7.9-rc.1", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.1.tgz", + "integrity": "sha512-hmcekZebItAc67+AF3xGgKXIvnLybQhpaEtQKsqU3WbyRittUI9hN9HqA6qUiaLxFPwrsCxioihTYsnxxej/Rg==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" diff --git a/package.json b/package.json index ea3ad14b7..f39ff2e23 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@splitsoftware/splitio-commons": "2.7.1", + "@splitsoftware/splitio-commons": "2.7.9-rc.1", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", From 8fb634eb5ae5fbec7a8ccc37d9894ee1af971c87 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 27 Oct 2025 21:59:14 -0300 Subject: [PATCH 14/21] rc --- package-lock.json | 4 ++-- package.json | 2 +- src/settings/defaults/version.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index e174c2f15..538bfbfc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.2", + "version": "11.7.2-rc.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.2", + "version": "11.7.2-rc.3", "license": "Apache-2.0", "dependencies": { "@splitsoftware/splitio-commons": "2.7.9-rc.1", diff --git a/package.json b/package.json index d342a6d0b..b1981d99c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.2", + "version": "11.7.2-rc.3", "description": "Split SDK", "files": [ "README.md", diff --git a/src/settings/defaults/version.js b/src/settings/defaults/version.js index 24979a7c0..645b9899d 100644 --- a/src/settings/defaults/version.js +++ b/src/settings/defaults/version.js @@ -1 +1 @@ -export const packageVersion = '11.7.2-rc.2'; +export const packageVersion = '11.7.2-rc.3'; From 910e0ca265f4d2e1f161eae15b786f7a14063da2 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 27 Oct 2025 22:41:44 -0300 Subject: [PATCH 15/21] Test update --- src/__tests__/nodeSuites/ready-promise.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/nodeSuites/ready-promise.spec.js b/src/__tests__/nodeSuites/ready-promise.spec.js index b6b76c93e..6e2ee9c91 100644 --- a/src/__tests__/nodeSuites/ready-promise.spec.js +++ b/src/__tests__/nodeSuites/ready-promise.spec.js @@ -484,7 +484,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { client.off(client.Event.SDK_READY, onReadyCallback); const manager = splitio.manager(); - client.whenReadyFromCache().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); + manager.whenReadyFromCache().then(() => t.fail('SDK TIMED OUT - Should not resolve')).catch(() => t.pass('SDK TIMED OUT - Should reject')); manager.on(manager.Event.SDK_READY, onReadyCallback); manager.off(manager.Event.SDK_READY, onReadyCallback); From 39c9d8a57b959aad92141294aff7102dd2ead8fa Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 28 Oct 2025 16:13:33 -0300 Subject: [PATCH 16/21] Use getStatus --- .github/workflows/ci-cd.yml | 4 +-- CHANGES.txt | 1 + package-lock.json | 18 +++++------ package.json | 4 +-- .../ready-from-cache-async-wrapper.spec.js | 12 ++++---- .../browserSuites/ready-from-cache.spec.js | 30 +++++++++++-------- src/__tests__/errorCatching/browser.spec.js | 6 ++-- src/__tests__/nodeSuites/lazy-init.spec.js | 12 ++++---- src/__tests__/nodeSuites/readiness.spec.js | 2 +- src/__tests__/offline/browser.spec.js | 2 +- src/settings/defaults/version.js | 2 +- 11 files changed, 49 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f94fb108b..692ee45f3 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -57,7 +57,7 @@ jobs: run: BUILD_BRANCH=$(echo "${GITHUB_REF#refs/heads/}") npm run build - name: Store assets - if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/development' || github.ref == 'refs/heads/main') }} + if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/fme-10568' || github.ref == 'refs/heads/main') }} uses: actions/upload-artifact@v4 with: name: assets @@ -68,7 +68,7 @@ jobs: name: Upload assets runs-on: ubuntu-latest needs: build - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/development' }} + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/fme-10568' }} strategy: matrix: environment: diff --git a/CHANGES.txt b/CHANGES.txt index fb1ee4a83..b1a126457 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,6 @@ 11.8.0 (October 28, 2025) - Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs. + - Added `client.getStatus()` method to retrieve the client readiness status properties (`isReady`, `isReadyFromCache`, etc). - Added `client.whenReady()` and `client.whenReadyFromCache()` methods to replace the deprecated `client.ready()` method, which has an issue causing the returned promise to hang when using async/await syntax if it was rejected. - Updated the SDK_READY_FROM_CACHE event to be emitted alongside the SDK_READY event if it hasn’t already been emitted. - Updated @splitsoftware/splitio-commons package to version 2.8.0. diff --git a/package-lock.json b/package-lock.json index 538bfbfc3..4bc186fd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.3", + "version": "11.7.2-rc.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.3", + "version": "11.7.2-rc.4", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.7.9-rc.1", + "@splitsoftware/splitio-commons": "2.7.9-rc.2", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", @@ -351,9 +351,9 @@ "dev": true }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.7.9-rc.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.1.tgz", - "integrity": "sha512-hmcekZebItAc67+AF3xGgKXIvnLybQhpaEtQKsqU3WbyRittUI9hN9HqA6qUiaLxFPwrsCxioihTYsnxxej/Rg==", + "version": "2.7.9-rc.2", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.2.tgz", + "integrity": "sha512-t8YVwDe4UBvD95w+mvKq7Z2khozZXDrIuOWt3ixxtmyeyoZp5L0L9x9E3DWOcQ0EVxfpQv+tAErHG3bw3LkbNg==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -7740,9 +7740,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "2.7.9-rc.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.1.tgz", - "integrity": "sha512-hmcekZebItAc67+AF3xGgKXIvnLybQhpaEtQKsqU3WbyRittUI9hN9HqA6qUiaLxFPwrsCxioihTYsnxxej/Rg==", + "version": "2.7.9-rc.2", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.2.tgz", + "integrity": "sha512-t8YVwDe4UBvD95w+mvKq7Z2khozZXDrIuOWt3ixxtmyeyoZp5L0L9x9E3DWOcQ0EVxfpQv+tAErHG3bw3LkbNg==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" diff --git a/package.json b/package.json index b1981d99c..01a530f39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.3", + "version": "11.7.2-rc.4", "description": "Split SDK", "files": [ "README.md", @@ -38,7 +38,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@splitsoftware/splitio-commons": "2.7.9-rc.1", + "@splitsoftware/splitio-commons": "2.7.9-rc.2", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", diff --git a/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js b/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js index d4f37eed5..d66b2993b 100644 --- a/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js +++ b/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js @@ -70,17 +70,17 @@ export default function (fetchMock, assert) { t.end(); }); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.on(client.Event.SDK_READY, () => { - t.true(client.__getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); + t.true(client.getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); }); client2.on(client.Event.SDK_READY, () => { - t.true(client2.__getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); + t.true(client2.getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); }); client3.on(client.Event.SDK_READY, () => { - t.true(client2.__getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); + t.true(client2.getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); }); }); @@ -356,7 +356,7 @@ export default function (fetchMock, assert) { let manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); await client.whenReady(); @@ -400,7 +400,7 @@ export default function (fetchMock, assert) { manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); await new Promise(res => client.once(client.Event.SDK_READY, res)); diff --git a/src/__tests__/browserSuites/ready-from-cache.spec.js b/src/__tests__/browserSuites/ready-from-cache.spec.js index 2ff9cc183..4650af9b0 100644 --- a/src/__tests__/browserSuites/ready-from-cache.spec.js +++ b/src/__tests__/browserSuites/ready-from-cache.spec.js @@ -142,17 +142,17 @@ export default function (fetchMock, assert) { t.end(); }); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.on(client.Event.SDK_READY, () => { - t.true(client.__getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); + t.true(client.getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); }); client2.on(client.Event.SDK_READY, () => { - t.true(client2.__getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); + t.true(client2.getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); }); client3.on(client.Event.SDK_READY, () => { - t.true(client2.__getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); + t.true(client2.getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); }); }); @@ -331,7 +331,7 @@ export default function (fetchMock, assert) { client.on(client.Event.SDK_READY_FROM_CACHE, () => { t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.'); - t.false(client.__getStatus().isReady, 'It should not be ready yet'); + t.false(client.getStatus().isReady, 'It should not be ready yet'); t.equal(client.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation'); @@ -515,7 +515,7 @@ export default function (fetchMock, assert) { }); }); - assert.test(t => { // Testing when we start with initial rollout plan data and sync storage type (is ready from cache immediately) + assert.test(async t => { // Testing when we start with initial rollout plan data and sync storage type (is ready from cache immediately) const testUrls = { sdk: 'https://sdk.baseurl/readyFromCacheWithInitialRolloutPlan', events: 'https://events.baseurl/readyFromCacheWithInitialRolloutPlan' @@ -552,7 +552,7 @@ export default function (fetchMock, assert) { const client = splitio.client(); const client2 = splitio.client('emi@split.io'); - t.equal(client.__getStatus().isReadyFromCache, true, 'Client is ready from cache'); + t.equal(client.getStatus().isReadyFromCache, true, 'Client is ready from cache'); t.equal(client.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache. Key without memberships'); t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with data from cache. Key with memberships'); @@ -577,6 +577,10 @@ export default function (fetchMock, assert) { t.end(); }); }); + + const startTime = Date.now(); + await client.whenReadyFromCache(); + t.true(nearlyEqual(Date.now() - startTime, 0), 'whenReadyFromCache should be resolved immediately'); }); /** Fetch specific splits **/ @@ -613,7 +617,7 @@ export default function (fetchMock, assert) { const manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.once(client.Event.SDK_READY, () => { @@ -657,7 +661,7 @@ export default function (fetchMock, assert) { const manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.once(client.Event.SDK_READY, () => { @@ -758,7 +762,7 @@ export default function (fetchMock, assert) { const manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.once(client.Event.SDK_READY, () => { @@ -819,7 +823,7 @@ export default function (fetchMock, assert) { const manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); }); client.once(client.Event.SDK_READY, () => { @@ -919,7 +923,7 @@ export default function (fetchMock, assert) { let manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); await client.whenReady(); @@ -959,7 +963,7 @@ export default function (fetchMock, assert) { manager = splitio.manager(); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + t.true(client.getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); await new Promise(res => client.once(client.Event.SDK_READY, res)); diff --git a/src/__tests__/errorCatching/browser.spec.js b/src/__tests__/errorCatching/browser.spec.js index 4e422dc10..ef8d7ffdd 100644 --- a/src/__tests__/errorCatching/browser.spec.js +++ b/src/__tests__/errorCatching/browser.spec.js @@ -91,13 +91,13 @@ tape('Error catching on callbacks - Browsers', assert => { } client.on(client.Event.SDK_READY_TIMED_OUT, () => { - assert.true(client.__getStatus().hasTimedout); // SDK status should be already updated + assert.true(client.getStatus().hasTimedout); // SDK status should be already updated attachErrorHandlerIfApplicable(); null.willThrowForTimedOut(); }); client.once(client.Event.SDK_READY, () => { - assert.true(client.__getStatus().isReady); // SDK status should be already updated + assert.true(client.getStatus().isReady); // SDK status should be already updated attachErrorHandlerIfApplicable(); null.willThrowForReady(); }); @@ -108,7 +108,7 @@ tape('Error catching on callbacks - Browsers', assert => { }); client.once(client.Event.SDK_READY_FROM_CACHE, () => { - assert.true(client.__getStatus().isReadyFromCache); // SDK status should be already updated + assert.true(client.getStatus().isReadyFromCache); // SDK status should be already updated attachErrorHandlerIfApplicable(); null.willThrowForReadyFromCache(); }); diff --git a/src/__tests__/nodeSuites/lazy-init.spec.js b/src/__tests__/nodeSuites/lazy-init.spec.js index 44bfacaa6..7194ebb7a 100644 --- a/src/__tests__/nodeSuites/lazy-init.spec.js +++ b/src/__tests__/nodeSuites/lazy-init.spec.js @@ -41,14 +41,14 @@ export default function (settings, fetchMock, t) { splitio.init(); await splitio.client().ready(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().getStatus().lastUpdate }, 'Status'); await splitio.destroy(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: true, isOperational: false, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: true, isOperational: false, lastUpdate: splitio.client().getStatus().lastUpdate }, 'Status'); splitio.init(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().getStatus().lastUpdate }, 'Status'); await splitio.destroy(); @@ -99,14 +99,14 @@ export default function (settings, fetchMock, t) { splitio.init(); await splitio.client().ready(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().getStatus().lastUpdate }, 'Status'); await splitio.destroy(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: true, isOperational: false, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: true, isOperational: false, lastUpdate: splitio.client().getStatus().lastUpdate }, 'Status'); splitio.init(); - assert.deepEqual(splitio.client().__getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().__getStatus().lastUpdate }, 'Status'); + assert.deepEqual(splitio.client().getStatus(), { isReady: true, isReadyFromCache: true, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: true, lastUpdate: splitio.client().getStatus().lastUpdate }, 'Status'); await splitio.destroy(); diff --git a/src/__tests__/nodeSuites/readiness.spec.js b/src/__tests__/nodeSuites/readiness.spec.js index ebce11039..531cb0ad0 100644 --- a/src/__tests__/nodeSuites/readiness.spec.js +++ b/src/__tests__/nodeSuites/readiness.spec.js @@ -89,7 +89,7 @@ export default function (fetchMock, assert) { const client = splitio.client(); - t.equal(client.__getStatus().isReadyFromCache, true, 'Client is ready from cache'); + t.equal(client.getStatus().isReadyFromCache, true, 'Client is ready from cache'); t.equal(client.getTreatment('nicolas@split.io', 'always_on'), 'off', 'It should evaluate treatments with data from cache. Key not in segment'); t.equal(client.getTreatment('emi@split.io', 'always_on'), 'on', 'It should evaluate treatments with data from cache. Key in segment'); diff --git a/src/__tests__/offline/browser.spec.js b/src/__tests__/offline/browser.spec.js index f2cb8dac0..8edd7d50c 100644 --- a/src/__tests__/offline/browser.spec.js +++ b/src/__tests__/offline/browser.spec.js @@ -114,7 +114,7 @@ tape('Browser offline mode', function (assert) { const sdkReadyFromCache = (client) => () => { assert.equal(factory.settings.storage.type, 'MEMORY', 'In localhost mode, storage must fallback to memory storage'); - const clientStatus = client.__getStatus(); + const clientStatus = client.getStatus(); assert.equal(clientStatus.isReadyFromCache, true, 'If ready from cache, READY_FROM_CACHE status must be true'); assert.equal(clientStatus.isReady, configs[i].storage && configs[i].storage.type === 'LOCALSTORAGE' ? false : true, 'When not using LOCALSTORAGE, READY status is set together with READY_FROM_CACHE'); if (!clientStatus.isReady) readyFromCacheCount++; diff --git a/src/settings/defaults/version.js b/src/settings/defaults/version.js index 645b9899d..500d24409 100644 --- a/src/settings/defaults/version.js +++ b/src/settings/defaults/version.js @@ -1 +1 @@ -export const packageVersion = '11.7.2-rc.3'; +export const packageVersion = '11.7.2-rc.4'; From 18235b71a75d19439eecbae564b23bd937753885 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 28 Oct 2025 16:22:33 -0300 Subject: [PATCH 17/21] fix test --- src/__tests__/browserSuites/ready-from-cache.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/browserSuites/ready-from-cache.spec.js b/src/__tests__/browserSuites/ready-from-cache.spec.js index 4650af9b0..419728fca 100644 --- a/src/__tests__/browserSuites/ready-from-cache.spec.js +++ b/src/__tests__/browserSuites/ready-from-cache.spec.js @@ -521,7 +521,7 @@ export default function (fetchMock, assert) { events: 'https://events.baseurl/readyFromCacheWithInitialRolloutPlan' }; - t.plan(5); + t.plan(6); fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=25&rbSince=-1', { status: 200, body: { ff: { ...splitChangesMock1.ff, s: 25 } } }); fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas }); From c1bdce1002c0e9c48907e69ea8cfa1ed9d272364 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 29 Oct 2025 11:51:13 -0300 Subject: [PATCH 18/21] Update ts-test --- .github/workflows/ci-cd.yml | 4 ++-- ts-tests/index.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 692ee45f3..f94fb108b 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -57,7 +57,7 @@ jobs: run: BUILD_BRANCH=$(echo "${GITHUB_REF#refs/heads/}") npm run build - name: Store assets - if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/fme-10568' || github.ref == 'refs/heads/main') }} + if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/development' || github.ref == 'refs/heads/main') }} uses: actions/upload-artifact@v4 with: name: assets @@ -68,7 +68,7 @@ jobs: name: Upload assets runs-on: ubuntu-latest needs: build - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/fme-10568' }} + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/development' }} strategy: matrix: environment: diff --git a/ts-tests/index.ts b/ts-tests/index.ts index 271a437ea..bd764176a 100644 --- a/ts-tests/index.ts +++ b/ts-tests/index.ts @@ -258,10 +258,24 @@ let nodeEventEmitter: NodeJS.EventEmitter = client; // Ready, destroy and flush let promise: Promise = client.ready(); +promise = client.whenReady(); promise = client.destroy(); promise = SDK.destroy(); // @TODO not public yet // promise = client.flush(); +const promiseWhenReadyFromCache: Promise = client.whenReadyFromCache(); + +// Get readiness status +let status: SplitIO.ReadinessStatus = client.getStatus(); +status = { + isReady: false, + isReadyFromCache: false, + isTimedout: false, + isDestroyed: false, + isOperational: false, + hasTimedout: false, + lastUpdate: 0 +} // We can call getTreatment with or without a key. treatment = client.getTreatment(splitKey, 'mySplit'); From d40d0817d8d791bc12606d47f2095d54af3fb267 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Wed, 29 Oct 2025 13:38:56 -0300 Subject: [PATCH 19/21] add ts-tests, update commons to 2.7.9-rc.3 --- package-lock.json | 14 +- package.json | 2 +- .../nodeSuites/evaluations-fallback.spec.js | 121 ++++++++++-------- ts-tests/index.ts | 23 +++- 4 files changed, 100 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4bc186fd3..0faf40dec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "11.7.2-rc.4", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.7.9-rc.2", + "@splitsoftware/splitio-commons": "2.7.9-rc.3", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", @@ -351,9 +351,9 @@ "dev": true }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.7.9-rc.2", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.2.tgz", - "integrity": "sha512-t8YVwDe4UBvD95w+mvKq7Z2khozZXDrIuOWt3ixxtmyeyoZp5L0L9x9E3DWOcQ0EVxfpQv+tAErHG3bw3LkbNg==", + "version": "2.7.9-rc.3", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.3.tgz", + "integrity": "sha512-momlpLuBt0yQXzo7blDWbNIs+H0fIPcxWukZVXMIKHiLiZtfu608diLT8EB/PNtA245OUMIRzachk5If4BBOWw==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -7740,9 +7740,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "2.7.9-rc.2", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.2.tgz", - "integrity": "sha512-t8YVwDe4UBvD95w+mvKq7Z2khozZXDrIuOWt3ixxtmyeyoZp5L0L9x9E3DWOcQ0EVxfpQv+tAErHG3bw3LkbNg==", + "version": "2.7.9-rc.3", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.3.tgz", + "integrity": "sha512-momlpLuBt0yQXzo7blDWbNIs+H0fIPcxWukZVXMIKHiLiZtfu608diLT8EB/PNtA245OUMIRzachk5If4BBOWw==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" diff --git a/package.json b/package.json index 01a530f39..67633080f 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@splitsoftware/splitio-commons": "2.7.9-rc.2", + "@splitsoftware/splitio-commons": "2.7.9-rc.3", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", diff --git a/src/__tests__/nodeSuites/evaluations-fallback.spec.js b/src/__tests__/nodeSuites/evaluations-fallback.spec.js index 53c226fea..610d646eb 100644 --- a/src/__tests__/nodeSuites/evaluations-fallback.spec.js +++ b/src/__tests__/nodeSuites/evaluations-fallback.spec.js @@ -23,7 +23,7 @@ export default async function (fetchMock, assert) { const splitio = SplitFactory(baseConfig); const client = splitio.client(); - await client.ready(); + await client.whenReady(); t.equal(client.getTreatment('emi@harness.io', 'non_existent_flag'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined'); t.equal(client.getTreatment('emma@harness.io', 'non_existent_flag_2'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined'); @@ -35,14 +35,16 @@ export default async function (fetchMock, assert) { assert.test('FallbackTreatment / Split factory with global fallbackTreatment defined', async t => { - const config = Object.assign({}, baseConfig); - config.fallbackTreatments = { - global: 'FALLBACK_TREATMENT' + const config = { + ...baseConfig, + fallbackTreatments: { + global: 'FALLBACK_TREATMENT' + } }; const splitio = SplitFactory(config); const client = splitio.client(); - await client.ready(); + await client.whenReady(); t.equal(client.getTreatment('emi@harness.io', 'non_existent_flag'), 'FALLBACK_TREATMENT', 'The evaluation will return `FALLBACK_TREATMENT` if the flag does not exist and no fallbackTreatment is defined'); @@ -55,16 +57,18 @@ export default async function (fetchMock, assert) { assert.test('FallbackTreatment / Split factory with specific fallbackTreatment defined', async t => { - const config = Object.assign({}, baseConfig); - config.fallbackTreatments = { - byFlag: { - 'non_existent_flag': 'FALLBACK_TREATMENT', + const config = { + ...baseConfig, + fallbackTreatments: { + byFlag: { + 'non_existent_flag': 'FALLBACK_TREATMENT', + } } }; const splitio = SplitFactory(config); const client = splitio.client(); - await client.ready(); + await client.whenReady(); t.equal(client.getTreatment('emi@harness.io', 'non_existent_flag'), 'FALLBACK_TREATMENT', 'The evaluation will return `FALLBACK_TREATMENT` if the flag does not exist and no fallbackTreatment is defined'); t.equal(client.getTreatment('emi@harness.io', 'non_existent_flag_2'), 'control', 'The evaluation will return `control` if the flag does not exist and no fallbackTreatment is defined'); @@ -80,17 +84,19 @@ export default async function (fetchMock, assert) { assert.test('FallbackTreatment / flag override beats global fallbackTreatment', async t => { - const config = Object.assign({}, baseConfig); - config.fallbackTreatments = { - global: 'OFF_FALLBACK', - byFlag: { - 'my_flag': 'ON_FALLBACK', + const config = { + ...baseConfig, + fallbackTreatments: { + global: 'OFF_FALLBACK', + byFlag: { + 'my_flag': 'ON_FALLBACK', + } } }; const splitio = SplitFactory(config); const client = splitio.client(); - await client.ready(); + await client.whenReady(); t.equal(client.getTreatment('emi@harness.io', 'my_flag'), 'ON_FALLBACK', 'The evaluation will return `ON_FALLBACK` if the flag does not exist and no fallbackTreatment is defined'); t.equal(client.getTreatment('emi@harness.io', 'non_existent_flag_2'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist and no fallbackTreatment is defined'); @@ -105,14 +111,16 @@ export default async function (fetchMock, assert) { assert.test('FallbackTreatment / override applies only when original is control', async t => { - const config = Object.assign({}, baseConfig); - config.fallbackTreatments = { - global: 'OFF_FALLBACK' + const config = { + ...baseConfig, + fallbackTreatments: { + global: 'OFF_FALLBACK' + } }; const splitio = SplitFactory(config); const client = splitio.client(); - await client.ready(); + await client.whenReady(); t.equal(client.getTreatment('emma@harness.io', 'user_account_in_whitelist'), 'off', 'The evaluation will return the treatment defined in the flag if it exists'); t.equal(client.getTreatment('emma@harness.io', 'non_existent_flag'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist and no fallbackTreatment is defined'); @@ -124,13 +132,15 @@ export default async function (fetchMock, assert) { assert.test('FallbackTreatment / Impressions correctness with fallback when client is not ready', async t => { - const config = Object.assign({}, baseConfig); - config.urls = { - events: 'https://events.fallbacktreatment/api' - }; - config.fallbackTreatments = { - byFlag: { - 'any_flag': 'OFF_FALLBACK' + const config = { + ...baseConfig, + urls: { + events: 'https://events.fallbacktreatment/api' + }, + fallbackTreatments: { + byFlag: { + 'any_flag': 'OFF_FALLBACK' + } } }; const splitio = SplitFactory(config); @@ -139,7 +149,7 @@ export default async function (fetchMock, assert) { t.equal(client.getTreatment('emi@harness.io', 'any_flag'), 'OFF_FALLBACK', 'The evaluation will return the fallbackTreatment if the client is not ready yet'); t.equal(client.getTreatment('emma@harness.io', 'user_account_in_whitelist'), 'control', 'The evaluation will return the fallbackTreatment if the client is not ready yet'); - await client.ready(); + await client.whenReady(); fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', (_, opts) => { @@ -164,17 +174,19 @@ export default async function (fetchMock, assert) { assert.test('FallbackTreatment / Fallback dynamic config propagation', async t => { - const config = Object.assign({}, baseConfig); - config.fallbackTreatments = { - global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, - byFlag: { - 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } + const config = { + ...baseConfig, + fallbackTreatments: { + global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } + } } }; const splitio = SplitFactory(config); const client = splitio.client(); - await client.ready(); + await client.whenReady(); t.deepEqual(client.getTreatmentWithConfig('emma@harness.io', 'my_flag'), { treatment: 'ON_FALLBACK', config: '{"flag": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment'); t.deepEqual(client.getTreatmentWithConfig('emma@harness.io', 'non_existent_flag'), { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment'); @@ -186,22 +198,24 @@ export default async function (fetchMock, assert) { assert.test('FallbackTreatment / Evaluations non existing flags with fallback do not generate impressions', async t => { - const config = Object.assign({}, baseConfig); - config.urls = { - events: 'https://events.fallbacktreatment/api' - }; - config.fallbackTreatments = { - global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, - byFlag: { - 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } - } + const config = { + ...baseConfig, + urls: { + events: 'https://events.fallbacktreatment/api' + }, + fallbackTreatments: { + global: { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'ON_FALLBACK', config: '{"flag": true}' } + } + }, + impressionListener: listener }; - config.impressionListener = listener; const splitio = SplitFactory(config); const client = splitio.client(); - await client.ready(); + await client.whenReady(); t.deepEqual(client.getTreatmentWithConfig('emma@harness.io', 'my_flag'), { treatment: 'ON_FALLBACK', config: '{"flag": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment'); t.deepEqual(client.getTreatmentWithConfig('emma@harness.io', 'non_existent_flag'), { treatment: 'OFF_FALLBACK', config: '{"global": true}' }, 'The evaluation will propagate the config along with the treatment from the fallbackTreatment'); @@ -234,16 +248,21 @@ export default async function (fetchMock, assert) { assert.test('FallbackTreatment / LocalhostMode', async t => { - const config = Object.assign({}, baseConfig); - config.core.authorizationKey = 'localhost'; - config.fallbackTreatments = { - global: 'OFF_FALLBACK' + const config = { + ...baseConfig, + core: { + ...baseConfig.core, + authorizationKey: 'localhost' + }, + fallbackTreatments: { + global: 'OFF_FALLBACK' + }, + features: path.join(__dirname, '../offline/split.yaml') }; - config.features = path.join(__dirname, '../offline/split.yaml'); const splitio = SplitFactory(config); const client = splitio.client(); - await client.ready(); + await client.whenReady(); t.deepEqual(client.getTreatment('emma@harness.io', 'testing_split_on'), 'on', 'The evaluation should return the treatment defined in localhost mode'); t.deepEqual(client.getTreatment('emma@harness.io', 'non_existent_flag'), 'OFF_FALLBACK', 'The evaluation will return `OFF_FALLBACK` if the flag does not exist'); diff --git a/ts-tests/index.ts b/ts-tests/index.ts index bd764176a..c1cf06960 100644 --- a/ts-tests/index.ts +++ b/ts-tests/index.ts @@ -598,7 +598,14 @@ let fullBrowserSettings: SplitIO.IBrowserSettings = { getHeaderOverrides(context) { return { ...context.headers, 'header': 'value' } }, } }, - userConsent: 'GRANTED' + userConsent: 'GRANTED', + fallbackTreatments: { + global: { treatment: 'global-treatment', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'flag-treatment', config: '{"flag": true}' }, + 'my_other_flag': 'other-flag-treatment' + } + } }; fullBrowserSettings.storage.type = 'MEMORY'; fullBrowserSettings.userConsent = 'DECLINED'; @@ -658,6 +665,13 @@ let fullNodeSettings: SplitIO.INodeSettings = { getHeaderOverrides(context) { return { ...context.headers, 'header': 'value' } }, agent: new (require('https')).Agent(), } + }, + fallbackTreatments: { + global: { treatment: 'global-treatment', config: '{"global": true}' }, + byFlag: { + 'my_flag': { treatment: 'flag-treatment', config: '{"flag": true}' }, + 'my_other_flag': 'other-flag-treatment' + } } }; fullNodeSettings.storage.type = 'MEMORY'; @@ -706,6 +720,13 @@ let fullAsyncSettings: SplitIO.INodeAsyncSettings = { sync: { splitFilters: splitFilters, impressionsMode: 'DEBUG', + }, + fallbackTreatments: { + global: 'global-treatment', + byFlag: { + 'my_flag': { treatment: 'flag-treatment', config: '{"flag": true}' }, + 'my_other_flag': 'other-flag-treatment' + } } }; From 1787f07603f6adcad5c03851396aa65cf1fc4db3 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 30 Oct 2025 13:10:06 -0300 Subject: [PATCH 20/21] rc --- .github/workflows/ci-cd.yml | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- src/settings/defaults/version.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f94fb108b..2a4095f63 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -57,7 +57,7 @@ jobs: run: BUILD_BRANCH=$(echo "${GITHUB_REF#refs/heads/}") npm run build - name: Store assets - if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/development' || github.ref == 'refs/heads/main') }} + if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/fallback-treatment' || github.ref == 'refs/heads/main') }} uses: actions/upload-artifact@v4 with: name: assets @@ -68,7 +68,7 @@ jobs: name: Upload assets runs-on: ubuntu-latest needs: build - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/development' }} + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/fallback-treatment' }} strategy: matrix: environment: diff --git a/package-lock.json b/package-lock.json index 0faf40dec..8df02a203 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.4", + "version": "11.7.2-rc.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.4", + "version": "11.7.2-rc.5", "license": "Apache-2.0", "dependencies": { "@splitsoftware/splitio-commons": "2.7.9-rc.3", diff --git a/package.json b/package.json index 67633080f..9e2fbd767 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.4", + "version": "11.7.2-rc.5", "description": "Split SDK", "files": [ "README.md", diff --git a/src/settings/defaults/version.js b/src/settings/defaults/version.js index 500d24409..6df138e24 100644 --- a/src/settings/defaults/version.js +++ b/src/settings/defaults/version.js @@ -1 +1 @@ -export const packageVersion = '11.7.2-rc.4'; +export const packageVersion = '11.7.2-rc.5'; From 7495443bfb1e6abcb7d183429ab26245e777000f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 30 Oct 2025 16:48:31 -0300 Subject: [PATCH 21/21] stable version --- .github/workflows/ci-cd.yml | 4 ++-- CHANGES.txt | 2 +- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- src/settings/defaults/version.js | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 2a4095f63..f94fb108b 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -57,7 +57,7 @@ jobs: run: BUILD_BRANCH=$(echo "${GITHUB_REF#refs/heads/}") npm run build - name: Store assets - if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/fallback-treatment' || github.ref == 'refs/heads/main') }} + if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/development' || github.ref == 'refs/heads/main') }} uses: actions/upload-artifact@v4 with: name: assets @@ -68,7 +68,7 @@ jobs: name: Upload assets runs-on: ubuntu-latest needs: build - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/fallback-treatment' }} + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/development' }} strategy: matrix: environment: diff --git a/CHANGES.txt b/CHANGES.txt index b1a126457..4a3e729d1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -11.8.0 (October 28, 2025) +11.8.0 (October 30, 2025) - Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs. - Added `client.getStatus()` method to retrieve the client readiness status properties (`isReady`, `isReadyFromCache`, etc). - Added `client.whenReady()` and `client.whenReadyFromCache()` methods to replace the deprecated `client.ready()` method, which has an issue causing the returned promise to hang when using async/await syntax if it was rejected. diff --git a/package-lock.json b/package-lock.json index 8df02a203..136e015cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.5", + "version": "11.8.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.5", + "version": "11.8.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.7.9-rc.3", + "@splitsoftware/splitio-commons": "2.8.0", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", @@ -351,9 +351,9 @@ "dev": true }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.7.9-rc.3", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.3.tgz", - "integrity": "sha512-momlpLuBt0yQXzo7blDWbNIs+H0fIPcxWukZVXMIKHiLiZtfu608diLT8EB/PNtA245OUMIRzachk5If4BBOWw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.8.0.tgz", + "integrity": "sha512-QgHUreMOEDwf4GZzVPu4AzkZJvuaeSoHsiJc4tT3CxSIYl2bKMz1SSDlI1tW/oVbIFeWjkrIp2lCYEyUBgcvyA==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -7740,9 +7740,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "2.7.9-rc.3", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.7.9-rc.3.tgz", - "integrity": "sha512-momlpLuBt0yQXzo7blDWbNIs+H0fIPcxWukZVXMIKHiLiZtfu608diLT8EB/PNtA245OUMIRzachk5If4BBOWw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.8.0.tgz", + "integrity": "sha512-QgHUreMOEDwf4GZzVPu4AzkZJvuaeSoHsiJc4tT3CxSIYl2bKMz1SSDlI1tW/oVbIFeWjkrIp2lCYEyUBgcvyA==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" diff --git a/package.json b/package.json index 9e2fbd767..9e8e13770 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio", - "version": "11.7.2-rc.5", + "version": "11.8.0", "description": "Split SDK", "files": [ "README.md", @@ -38,7 +38,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@splitsoftware/splitio-commons": "2.7.9-rc.3", + "@splitsoftware/splitio-commons": "2.8.0", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", diff --git a/src/settings/defaults/version.js b/src/settings/defaults/version.js index 6df138e24..378697e28 100644 --- a/src/settings/defaults/version.js +++ b/src/settings/defaults/version.js @@ -1 +1 @@ -export const packageVersion = '11.7.2-rc.5'; +export const packageVersion = '11.8.0';