Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cache expiration] E2E tests and Type Definitions #135

Draft
wants to merge 3 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
1.1.0 (January XX, 2025)
- Added two new configuration options for the SDK's `InLocalStorage` module to control the behavior of the persisted rollout plan cache in the browser:
- `expirationDays` to specify the validity period of the rollout plan cache in days.
- `clearOnInit` to clear the rollout plan cache on SDK initialization.
- Updated @splitsoftware/splitio-commons package to version 2.1.0.

1.0.1 (November 11, 2024)
- Bugfixing - Revert removal of TypeScript `SplitIO` namespace at `/types/splitio.d.ts` to allow explicit imports of types from the Browser SDK package. E.g., `import type { IClientSideSettings } from '@splitsoftware/splitio-browserjs/types/splitio';`.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,4 @@ For a comprehensive list of open source projects visit our [Github page](https:/

**Learn more about Split:**

Visit [split.io/product](https://www.split.io/product) for an overview of Split, or visit our documentation at [help.split.io](http://help.split.io) for more detailed information.
Visit [split.io/product](https://www.split.io/product) for an overview of Split, or visit our documentation at [help.split.io](https://help.split.io) for more detailed information.
30 changes: 15 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-browserjs",
"version": "1.0.1",
"version": "1.1.0-rc.0",
"description": "Split SDK for JavaScript on Browser",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down Expand Up @@ -59,7 +59,7 @@
"bugs": "https://github.com/splitio/javascript-browser-client/issues",
"homepage": "https://github.com/splitio/javascript-browser-client#readme",
"dependencies": {
"@splitsoftware/splitio-commons": "2.0.0",
"@splitsoftware/splitio-commons": "2.1.0-rc.0",
"tslib": "^2.3.1",
"unfetch": "^4.2.0"
},
Expand Down
111 changes: 94 additions & 17 deletions src/__tests__/browserSuites/ready-from-cache.spec.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import sinon from 'sinon';
import { nearlyEqual } from '../testUtils';
import { getStorageHash } from '@splitsoftware/splitio-commons/src/storages/KeyBuilder';
import { SplitFactory, InLocalStorage } from '../../';

import splitChangesMock1 from '../mocks/splitchanges.since.-1.json';
import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json';
import membershipsNicolas from '../mocks/[email protected]';

import { nearlyEqual } from '../testUtils';

const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days

const alwaysOnSplitInverted = JSON.stringify({
Expand Down Expand Up @@ -177,7 +177,6 @@ export default function (fetchMock, assert) {
readyTimeout: 0.85
},
urls: testUrls,
debug: true
});
const client = splitio.client();
const client2 = splitio.client('[email protected]');
Expand Down Expand Up @@ -245,7 +244,7 @@ export default function (fetchMock, assert) {
});
});

assert.test(t => { // Testing when we start with cached data and not expired (lastUpdate item higher than expirationTimestamp)
assert.test(t => { // Testing when we start with cached data and not expired (lastUpdate timestamp higher than default (10) expirationDays ago)
const testUrls = {
sdk: 'https://sdk.baseurl/readyFromCacheWithData3',
events: 'https://events.baseurl/readyFromCacheWithData3'
Expand Down Expand Up @@ -287,7 +286,6 @@ export default function (fetchMock, assert) {
readyTimeout: 0.85
},
urls: testUrls,
debug: true
});
const client = splitio.client();
const client2 = splitio.client('[email protected]');
Expand Down Expand Up @@ -362,7 +360,7 @@ export default function (fetchMock, assert) {
});
});

assert.test(t => { // Testing when we start with cached data but expired (lastUpdate item lower than expirationTimestamp)
assert.test(t => { // Testing when we start with cached data but expired (lastUpdate timestamp lower than custom (1) expirationDays ago)
const testUrls = {
sdk: 'https://sdk.baseurl/readyFromCacheWithData4',
events: 'https://events.baseurl/readyFromCacheWithData4'
Expand All @@ -372,7 +370,8 @@ export default function (fetchMock, assert) {
fetchMock.get(testUrls.sdk + '/splitChanges?s=1.2&since=-1', function () {
t.equal(localStorage.getItem('some_user_item'), 'user_item', 'user items at localStorage must not be changed');
t.equal(localStorage.getItem('readyFromCache_4.SPLITIO.hash'), expectedHashNullFilter, 'storage hash must not be changed');
t.equal(localStorage.length, 2, 'feature flags cache data must be cleaned from localStorage');
t.true(nearlyEqual(parseInt(localStorage.getItem('readyFromCache_4.SPLITIO.lastClear'), 10), Date.now()), 'storage lastClear timestamp must be updated');
t.equal(localStorage.length, 3, 'feature flags cache data must be cleaned from localStorage');
return { status: 200, body: splitChangesMock1 };
});
fetchMock.get(testUrls.sdk + '/splitChanges?s=1.2&since=1457552620999', { status: 200, body: splitChangesMock2 });
Expand All @@ -390,21 +389,21 @@ export default function (fetchMock, assert) {

localStorage.setItem('some_user_item', 'user_item');
localStorage.setItem('readyFromCache_4.SPLITIO.splits.till', 25);
localStorage.setItem('readyFromCache_4.SPLITIO.splits.lastUpdated', Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS - 1); // -1 to ensure having an expired lastUpdated item
localStorage.setItem('readyFromCache_4.SPLITIO.splits.lastUpdated', Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS / 10 - 1); // -1 to ensure having an expired lastUpdated item
localStorage.setItem('readyFromCache_4.SPLITIO.split.always_on', alwaysOnSplitInverted);
localStorage.setItem('readyFromCache_4.SPLITIO.hash', expectedHashNullFilter);

const startTime = Date.now();
const splitio = SplitFactory({
...baseConfig,
storage: InLocalStorage({
prefix: 'readyFromCache_4'
prefix: 'readyFromCache_4',
expirationDays: 1
}),
startup: {
readyTimeout: 0.85
},
urls: testUrls,
debug: true
});
const client = splitio.client();
const client2 = splitio.client('[email protected]');
Expand Down Expand Up @@ -502,7 +501,6 @@ export default function (fetchMock, assert) {
sync: {
splitFilters: [{ type: 'byName', values: ['p2__split', 'p1__split'] }, { type: 'byName', values: ['p2__split', null] }]
},
debug: true
});
const client = splitio.client();
const manager = splitio.manager();
Expand Down Expand Up @@ -547,7 +545,6 @@ export default function (fetchMock, assert) {
sync: {
splitFilters: [{ type: 'byName', values: ['p2__split', 'p1__split'] }, { type: 'byName', values: ['p2__split', null] }]
},
debug: true
});
const client = splitio.client();
const manager = splitio.manager();
Expand Down Expand Up @@ -597,7 +594,6 @@ export default function (fetchMock, assert) {
sync: {
splitFilters: [{ type: 'byName', values: [undefined, true, 'p2__split'] }, { type: 'byPrefix', values: ['p1'] }, { type: 'byName', values: ['p2__split'] }]
},
debug: true
});
const client = splitio.client();
const manager = splitio.manager();
Expand Down Expand Up @@ -642,13 +638,13 @@ export default function (fetchMock, assert) {
const splitio = SplitFactory({
...baseConfig,
storage: InLocalStorage({
prefix: 'readyFromCache_7'
prefix: 'readyFromCache_7',
expirationDays: 0, // invalid value, will use default (10)
}),
urls: testUrls,
sync: {
splitFilters: [{ type: 'byPrefix', values: ['p2'] }, { type: 'byPrefix', values: ['p1', ''] }, { type: '', values: [] }, {}, { type: 'byPrefix' }]
},
debug: true
});
const client = splitio.client();
const manager = splitio.manager();
Expand Down Expand Up @@ -710,7 +706,6 @@ export default function (fetchMock, assert) {
}),
urls: testUrls,
sync: syncParam,
debug: true
});
const client = splitio.client();
const manager = splitio.manager();
Expand Down Expand Up @@ -764,7 +759,6 @@ export default function (fetchMock, assert) {
sync: {
splitFilters: [{ type: 'byName', values: ['p3__split'] }, { type: 'byPrefix', values: [' p2', ' p2', ' p2', ' p2', 'no exist trim '] }, { type: 'byName', values: ['no_exist', ' no exist trim '] }]
},
debug: true
});
const client = splitio.client();
const manager = splitio.manager();
Expand All @@ -788,4 +782,87 @@ export default function (fetchMock, assert) {
});
});

assert.test(async t => { // Testing clearOnInit config true
sinon.spy(console, 'log');

const testUrls = {
sdk: 'https://sdk.baseurl/readyFromCache_10',
events: 'https://events.baseurl/readyFromCache_10'
};
const clearOnInitConfig = {
...baseConfig,
storage: InLocalStorage({
type: 'LOCALSTORAGE',
prefix: 'readyFromCache_10',
clearOnInit: true
}),
urls: testUrls,
debug: true
};

// Start with cached data but without lastClear item (JS SDK below 11.1.0) -> cache cleanup
localStorage.clear();
localStorage.setItem('readyFromCache_10.SPLITIO.splits.till', 25);
localStorage.setItem('readyFromCache_10.SPLITIO.split.p1__split', JSON.stringify(splitDeclarations.p1__split));
localStorage.setItem('readyFromCache_10.SPLITIO.split.p2__split', JSON.stringify(splitDeclarations.p2__split));
localStorage.setItem('readyFromCache_10.SPLITIO.hash', expectedHashNullFilter);

fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.2&since=-1', { status: 200, body: splitChangesMock1 });
fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.2&since=1457552620999', { status: 200, body: splitChangesMock2 });
fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: { ms: {} } });

let splitio = SplitFactory(clearOnInitConfig);
let client = splitio.client();
let manager = splitio.manager();

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');

client.once(client.Event.SDK_READY_FROM_CACHE, () => t.fail('It should not emit SDK_READY_FROM_CACHE because clearOnInit is true.'));

await client.ready();
t.equal(manager.names().sort().length, 32, 'active splits should be present for evaluation');

await splitio.destroy();
t.equal(localStorage.getItem('readyFromCache_10.SPLITIO.splits.till'), '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits');
t.equal(localStorage.getItem('readyFromCache_10.SPLITIO.hash'), expectedHashNullFilter, 'Storage hash must not be changed');
t.true(nearlyEqual(parseInt(localStorage.getItem('readyFromCache_10.SPLITIO.lastClear')), Date.now()), 'lastClear timestamp must be set');

// Start again with cached data and lastClear item within the last 24 hours -> no cache cleanup
console.log.resetHistory();
fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.2&since=1457552620999', { status: 200, body: splitChangesMock2 });

splitio = SplitFactory(clearOnInitConfig);
client = splitio.client();
manager = splitio.manager();

await new Promise(res => client.once(client.Event.SDK_READY_FROM_CACHE, res));

t.equal(manager.names().sort().length, 32, 'active splits should be present for evaluation');
t.false(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache');

await splitio.destroy();

// Start again with cached data and lastClear item older than 24 hours -> cache cleanup
console.log.resetHistory();
localStorage.setItem('readyFromCache_10.SPLITIO.lastClear', Date.now() - 25 * 60 * 60 * 1000); // 25 hours ago
fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.2&since=-1', { status: 200, body: splitChangesMock1 });
fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.2&since=1457552620999', { status: 200, body: splitChangesMock2 });

splitio = SplitFactory(clearOnInitConfig);
client = splitio.client();
manager = splitio.manager();

client.once(client.Event.SDK_READY_FROM_CACHE, () => t.fail('It should not emit SDK_READY_FROM_CACHE because clearOnInit is true.'));

await new Promise(res => client.once(client.Event.SDK_READY, res));

t.equal(manager.names().sort().length, 32, 'active splits should be present for evaluation');
t.true(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache');

await splitio.destroy();

console.log.restore();
t.end();
});

}
4 changes: 2 additions & 2 deletions src/__tests__/testUtils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export function nearlyEqual(actual, expected, epsilon = DEFAULT_ERROR_MARGIN) {
* - when `?since=-1`, it returns the given segment `keys` in `added` list.
* - otherwise, it returns empty `added` and `removed` lists, and the same since and till values.
*
* @param {Object} fetchMock see http://www.wheresrhys.co.uk/fetch-mock
* @param {string|RegExp|...} matcher see http://www.wheresrhys.co.uk/fetch-mock/#api-mockingmock_matcher
* @param {Object} fetchMock see https://www.wheresrhys.co.uk/fetch-mock
* @param {string|RegExp|...} matcher see https://www.wheresrhys.co.uk/fetch-mock/#api-mockingmock_matcher
* @param {string[]} keys array of segment keys to fetch
* @param {number} changeNumber optional changeNumber
*/
Expand Down
2 changes: 1 addition & 1 deletion src/settings/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type SplitIO from '@splitsoftware/splitio-commons/types/splitio';
import { LogLevels, isLogLevelString } from '@splitsoftware/splitio-commons/src/logger/index';
import { CONSENT_GRANTED } from '@splitsoftware/splitio-commons/src/utils/constants';

const packageVersion = '1.0.1';
const packageVersion = '1.1.0-rc.0';

/**
* In browser, the default debug level, can be set via the `localStorage.splitio_debug` item.
Expand Down
4 changes: 3 additions & 1 deletion ts-tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ let impressionData: SplitIO.ImpressionData;
let syncStorage: SplitIO.StorageSync;
let syncStorageFactory: SplitIO.StorageSyncFactory = InLocalStorage();
let localStorageOptions: SplitIO.InLocalStorageOptions = {
prefix: 'PREFIX'
prefix: 'PREFIX',
expirationDays: 1,
clearOnInit: true
};
syncStorageFactory = InLocalStorage(localStorageOptions);

Expand Down
2 changes: 1 addition & 1 deletion types/full/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Declaration file for JavaScript Browser Split Software SDK
// Project: http://www.split.io/
// Project: https://www.split.io/
// Definitions by: Nico Zelaya <https://github.com/NicoZelaya/>

/// <reference path="../splitio.d.ts" />
Expand Down
2 changes: 1 addition & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Declaration file for JavaScript Browser Split Software SDK
// Project: http://www.split.io/
// Project: https://www.split.io/
// Definitions by: Nico Zelaya <https://github.com/NicoZelaya/>

/// <reference path="./splitio.d.ts" />
Expand Down