From 28b7fb994217f92c55c3819b810d50e159c1b60b Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 19 Dec 2024 17:24:30 -0300 Subject: [PATCH] Add unit test --- .../inLocalStorage/SplitsCacheInLocal.ts | 2 - .../__tests__/validateCache.spec.ts | 125 ++++++++++++++++++ 2 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 src/storages/inLocalStorage/__tests__/validateCache.spec.ts diff --git a/src/storages/inLocalStorage/SplitsCacheInLocal.ts b/src/storages/inLocalStorage/SplitsCacheInLocal.ts index 14767d72..7d4c002f 100644 --- a/src/storages/inLocalStorage/SplitsCacheInLocal.ts +++ b/src/storages/inLocalStorage/SplitsCacheInLocal.ts @@ -71,8 +71,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync { * We cannot simply call `localStorage.clear()` since that implies removing user items from the storage. */ clear() { - this.log.info(LOG_PREFIX + 'Flushing Splits data from localStorage'); - // collect item keys const len = localStorage.length; const accum = []; diff --git a/src/storages/inLocalStorage/__tests__/validateCache.spec.ts b/src/storages/inLocalStorage/__tests__/validateCache.spec.ts new file mode 100644 index 00000000..a1f1784d --- /dev/null +++ b/src/storages/inLocalStorage/__tests__/validateCache.spec.ts @@ -0,0 +1,125 @@ +import { validateCache } from '../validateCache'; + +import { KeyBuilderCS } from '../../KeyBuilderCS'; +import { fullSettings } from '../../../utils/settingsValidation/__tests__/settings.mocks'; +import { SplitsCacheInLocal } from '../SplitsCacheInLocal'; +import { nearlyEqual } from '../../../__tests__/testUtils'; +import { MySegmentsCacheInLocal } from '../MySegmentsCacheInLocal'; + +const FULL_SETTINGS_HASH = '404832b3'; + +describe('validateCache', () => { + const keys = new KeyBuilderCS('SPLITIO', 'user'); + const logSpy = jest.spyOn(fullSettings.log, 'info'); + const segments = new MySegmentsCacheInLocal(fullSettings.log, keys); + const largeSegments = new MySegmentsCacheInLocal(fullSettings.log, keys); + const splits = new SplitsCacheInLocal(fullSettings, keys); + + jest.spyOn(splits, 'clear'); + jest.spyOn(splits, 'getChangeNumber'); + jest.spyOn(segments, 'clear'); + jest.spyOn(largeSegments, 'clear'); + + beforeEach(() => { + jest.clearAllMocks(); + localStorage.clear(); + }); + + test('if there is no cache, it should return false', () => { + expect(validateCache({}, fullSettings, keys, splits, segments, largeSegments)).toBe(false); + + expect(logSpy).not.toHaveBeenCalled(); + + expect(splits.clear).not.toHaveBeenCalled(); + expect(segments.clear).not.toHaveBeenCalled(); + expect(largeSegments.clear).not.toHaveBeenCalled(); + expect(splits.getChangeNumber).toHaveBeenCalledTimes(1); + + expect(localStorage.getItem(keys.buildHashKey())).toBe(FULL_SETTINGS_HASH); + expect(localStorage.getItem(keys.buildLastClear())).toBeNull(); + }); + + test('if there is cache and it must not be cleared, it should return true', () => { + localStorage.setItem(keys.buildSplitsTillKey(), '1'); + localStorage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); + + expect(validateCache({}, fullSettings, keys, splits, segments, largeSegments)).toBe(true); + + expect(logSpy).not.toHaveBeenCalled(); + + expect(splits.clear).not.toHaveBeenCalled(); + expect(segments.clear).not.toHaveBeenCalled(); + expect(largeSegments.clear).not.toHaveBeenCalled(); + expect(splits.getChangeNumber).toHaveBeenCalledTimes(1); + + expect(localStorage.getItem(keys.buildHashKey())).toBe(FULL_SETTINGS_HASH); + expect(localStorage.getItem(keys.buildLastClear())).toBeNull(); + }); + + test('if there is cache and it has expired, it should clear cache and return false', () => { + localStorage.setItem(keys.buildSplitsTillKey(), '1'); + localStorage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); + localStorage.setItem(keys.buildLastUpdatedKey(), Date.now() - 1000 * 60 * 60 * 24 * 2 + ''); // 2 days ago + + expect(validateCache({ expirationDays: 1 }, fullSettings, keys, splits, segments, largeSegments)).toBe(false); + + expect(logSpy).toHaveBeenCalledWith('storage:localstorage: Cache expired more than 1 days ago. Cleaning up cache'); + + expect(splits.clear).toHaveBeenCalledTimes(1); + expect(segments.clear).toHaveBeenCalledTimes(1); + expect(largeSegments.clear).toHaveBeenCalledTimes(1); + + expect(localStorage.getItem(keys.buildHashKey())).toBe(FULL_SETTINGS_HASH); + expect(nearlyEqual(parseInt(localStorage.getItem(keys.buildLastClear()) as string), Date.now())).toBe(true); + }); + + test('if there is cache and its hash has changed, it should clear cache and return false', () => { + localStorage.setItem(keys.buildSplitsTillKey(), '1'); + localStorage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); + + expect(validateCache({}, { ...fullSettings, core: { ...fullSettings.core, authorizationKey: 'another' } }, keys, splits, segments, largeSegments)).toBe(false); + + expect(logSpy).toHaveBeenCalledWith('storage:localstorage: SDK key, flags filter criteria or flags spec version has changed. Cleaning up cache'); + + expect(splits.clear).toHaveBeenCalledTimes(1); + expect(segments.clear).toHaveBeenCalledTimes(1); + expect(largeSegments.clear).toHaveBeenCalledTimes(1); + + expect(localStorage.getItem(keys.buildHashKey())).toBe('aa4877c2'); + expect(nearlyEqual(parseInt(localStorage.getItem(keys.buildLastClear()) as string), Date.now())).toBe(true); + }); + + test('if there is cache and clearOnInit is true, it should clear cache and return false', () => { + // Older cache version (without last clear) + localStorage.setItem(keys.buildSplitsTillKey(), '1'); + localStorage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); + + expect(validateCache({ clearOnInit: true }, fullSettings, keys, splits, segments, largeSegments)).toBe(false); + + expect(logSpy).toHaveBeenCalledWith('storage:localstorage: clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'); + + expect(splits.clear).toHaveBeenCalledTimes(1); + expect(segments.clear).toHaveBeenCalledTimes(1); + expect(largeSegments.clear).toHaveBeenCalledTimes(1); + + expect(localStorage.getItem(keys.buildHashKey())).toBe(FULL_SETTINGS_HASH); + const lastClear = localStorage.getItem(keys.buildLastClear()); + expect(nearlyEqual(parseInt(lastClear as string), Date.now())).toBe(true); + + // If cache is cleared, it should not clear again until a day has passed + logSpy.mockClear(); + localStorage.setItem(keys.buildSplitsTillKey(), '1'); + expect(validateCache({ clearOnInit: true }, fullSettings, keys, splits, segments, largeSegments)).toBe(true); + expect(logSpy).not.toHaveBeenCalled(); + expect(localStorage.getItem(keys.buildLastClear())).toBe(lastClear); // Last clear should not have changed + + // If a day has passed, it should clear again + localStorage.setItem(keys.buildLastClear(), (Date.now() - 1000 * 60 * 60 * 24 - 1) + ''); + expect(validateCache({ clearOnInit: true }, fullSettings, keys, splits, segments, largeSegments)).toBe(false); + expect(logSpy).toHaveBeenCalledWith('storage:localstorage: clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'); + expect(splits.clear).toHaveBeenCalledTimes(2); + expect(segments.clear).toHaveBeenCalledTimes(2); + expect(largeSegments.clear).toHaveBeenCalledTimes(2); + expect(nearlyEqual(parseInt(localStorage.getItem(keys.buildLastClear()) as string), Date.now())).toBe(true); + }); +});