diff --git a/src/storages/inMemory/SplitsCacheInMemory.ts b/src/storages/inMemory/SplitsCacheInMemory.ts index 6c5ecb36..36a55fe1 100644 --- a/src/storages/inMemory/SplitsCacheInMemory.ts +++ b/src/storages/inMemory/SplitsCacheInMemory.ts @@ -16,9 +16,9 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync { private splitsWithSegmentsCount: number = 0; private flagSetsCache: Record> = {}; - constructor(splitFiltersValidation: ISplitFiltersValidation = { queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, validFilters: [] }) { + constructor(splitFiltersValidation?: ISplitFiltersValidation) { super(); - this.flagSetsFilter = splitFiltersValidation.groupedFilters.bySet; + this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : []; } clear() { diff --git a/src/storages/pluggable/SplitsCachePluggable.ts b/src/storages/pluggable/SplitsCachePluggable.ts index 8aa7da58..8f355fb1 100644 --- a/src/storages/pluggable/SplitsCachePluggable.ts +++ b/src/storages/pluggable/SplitsCachePluggable.ts @@ -2,7 +2,7 @@ import { isFiniteNumber, isNaNNumber } from '../../utils/lang'; import { KeyBuilder } from '../KeyBuilder'; import { IPluggableStorageWrapper } from '../types'; import { ILogger } from '../../logger/types'; -import { ISplit } from '../../dtos/types'; +import { ISplit, ISplitFiltersValidation } from '../../dtos/types'; import { LOG_PREFIX } from './constants'; import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync'; import { ISet, _Set, returnListDifference } from '../../utils/lang/sets'; @@ -23,12 +23,12 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync { * @param keys Key builder. * @param wrapper Adapted wrapper storage. */ - constructor(log: ILogger, keys: KeyBuilder, wrapper: IPluggableStorageWrapper, flagSetsFilter: string[] = []) { + constructor(log: ILogger, keys: KeyBuilder, wrapper: IPluggableStorageWrapper, splitFiltersValidation?: ISplitFiltersValidation) { super(); this.log = log; this.keys = keys; this.wrapper = wrapper; - this.flagSetsFilter = flagSetsFilter; + this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : []; } private _decrementCounts(split: ISplit) { diff --git a/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts b/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts index 6cad1e2a..3d67e454 100644 --- a/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts +++ b/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts @@ -2,8 +2,9 @@ import { SplitsCachePluggable } from '../SplitsCachePluggable'; import { KeyBuilder } from '../../KeyBuilder'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { wrapperMockFactory } from './wrapper.mock'; -import { splitWithUserTT, splitWithAccountTT } from '../../__tests__/testUtils'; +import { splitWithUserTT, splitWithAccountTT, featureFlagOne, featureFlagThree, featureFlagTwo, featureFlagWithEmptyFS, featureFlagWithoutFS } from '../../__tests__/testUtils'; import { ISplit } from '../../../dtos/types'; +import { _Set } from '../../../utils/lang/sets'; const keysBuilder = new KeyBuilder(); @@ -150,4 +151,63 @@ describe('SPLITS CACHE PLUGGABLE', () => { expect(await wrapper.getKeysByPrefix('SPLITIO')).toHaveLength(0); }); + test('flag set cache tests', async () => { + // @ts-ignore + const cache = new SplitsCachePluggable(loggerMock, keysBuilder, wrapperMockFactory(), { groupedFilters: { bySet: ['o', 'n', 'e', 'x'] } }); + const emptySet = new _Set([]); + + await cache.addSplits([ + [featureFlagOne.name, featureFlagOne], + [featureFlagTwo.name, featureFlagTwo], + [featureFlagThree.name, featureFlagThree], + ]); + await cache.addSplit(featureFlagWithEmptyFS.name, featureFlagWithEmptyFS); + + expect(await cache.getNamesByFlagSets(['o'])).toEqual(new _Set(['ff_one', 'ff_two'])); + expect(await cache.getNamesByFlagSets(['n'])).toEqual(new _Set(['ff_one'])); + expect(await cache.getNamesByFlagSets(['e'])).toEqual(new _Set(['ff_one', 'ff_three'])); + expect(await cache.getNamesByFlagSets(['t'])).toEqual(emptySet); // 't' not in filter + expect(await cache.getNamesByFlagSets(['o', 'n', 'e'])).toEqual(new _Set(['ff_one', 'ff_two', 'ff_three'])); + + await cache.addSplit(featureFlagOne.name, { ...featureFlagOne, sets: ['1'] }); + + expect(await cache.getNamesByFlagSets(['1'])).toEqual(emptySet); // '1' not in filter + expect(await cache.getNamesByFlagSets(['o'])).toEqual(new _Set(['ff_two'])); + expect(await cache.getNamesByFlagSets(['n'])).toEqual(emptySet); + + await cache.addSplit(featureFlagOne.name, { ...featureFlagOne, sets: ['x'] }); + expect(await cache.getNamesByFlagSets(['x'])).toEqual(new _Set(['ff_one'])); + expect(await cache.getNamesByFlagSets(['o', 'e', 'x'])).toEqual(new _Set(['ff_one', 'ff_two', 'ff_three'])); + + await cache.removeSplit(featureFlagOne.name); + expect(await cache.getNamesByFlagSets(['x'])).toEqual(emptySet); + + await cache.removeSplit(featureFlagOne.name); + expect(await cache.getNamesByFlagSets(['y'])).toEqual(emptySet); // 'y' not in filter + expect(await cache.getNamesByFlagSets([])).toEqual(emptySet); + + await cache.addSplit(featureFlagWithEmptyFS.name, featureFlagWithoutFS); + expect(await cache.getNamesByFlagSets([])).toEqual(emptySet); + }); + + // if FlagSets filter is not defined, it should store all FlagSets in memory. + test('flag set cache tests without filters', async () => { + const cacheWithoutFilters = new SplitsCachePluggable(loggerMock, keysBuilder, wrapperMockFactory()); + const emptySet = new _Set([]); + + await cacheWithoutFilters.addSplits([ + [featureFlagOne.name, featureFlagOne], + [featureFlagTwo.name, featureFlagTwo], + [featureFlagThree.name, featureFlagThree], + ]); + await cacheWithoutFilters.addSplit(featureFlagWithEmptyFS.name, featureFlagWithEmptyFS); + + expect(await cacheWithoutFilters.getNamesByFlagSets(['o'])).toEqual(new _Set(['ff_one', 'ff_two'])); + expect(await cacheWithoutFilters.getNamesByFlagSets(['n'])).toEqual(new _Set(['ff_one'])); + expect(await cacheWithoutFilters.getNamesByFlagSets(['e'])).toEqual(new _Set(['ff_one', 'ff_three'])); + expect(await cacheWithoutFilters.getNamesByFlagSets(['t'])).toEqual(new _Set(['ff_two', 'ff_three'])); + expect(await cacheWithoutFilters.getNamesByFlagSets(['y'])).toEqual(emptySet); + expect(await cacheWithoutFilters.getNamesByFlagSets(['o', 'n', 'e'])).toEqual(new _Set(['ff_one', 'ff_two', 'ff_three'])); + }); + }); diff --git a/src/storages/pluggable/index.ts b/src/storages/pluggable/index.ts index 93442b73..3e72d791 100644 --- a/src/storages/pluggable/index.ts +++ b/src/storages/pluggable/index.ts @@ -105,7 +105,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn }); return { - splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation.groupedFilters.bySet), + splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation), segments: new SegmentsCachePluggable(log, keys, wrapper), impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata), impressionCounts: impressionCountsCache,