diff --git a/clientlibs/js/src/DataService/DataService.spec.ts b/clientlibs/js/src/DataService/DataService.spec.ts index ff0a1b2a7..c1e4df28b 100644 --- a/clientlibs/js/src/DataService/DataService.spec.ts +++ b/clientlibs/js/src/DataService/DataService.spec.ts @@ -118,6 +118,16 @@ describe('DataService', () => { }); }); + describe('#clearFeatureFlags', () => { + it('should clear previously set feature flags', () => { + dataService.setFeatureFlags(['testFlagKey']); + + dataService.clearFeatureFlags(); + + expect(dataService.getFeatureFlags()).toBeNull(); + }); + }); + describe('#rotateAssignmentList', () => { it('should return the rotated assignment list', () => { const assignmentList: IExperimentAssignmentv5 = { diff --git a/clientlibs/js/src/DataService/DataService.ts b/clientlibs/js/src/DataService/DataService.ts index c1f4266a7..3122afa2f 100644 --- a/clientlibs/js/src/DataService/DataService.ts +++ b/clientlibs/js/src/DataService/DataService.ts @@ -42,6 +42,10 @@ export class DataService { this.featureFlags = featureFlags; } + clearFeatureFlags() { + this.featureFlags = null; + } + public rotateAssignmentList(assignment: IExperimentAssignmentv5) { if (assignment.assignedCondition.length > 1) { assignment.assignedCondition.push(assignment.assignedCondition.shift()); diff --git a/clientlibs/js/src/UpGradeClient/UpgradeClient.spec.ts b/clientlibs/js/src/UpGradeClient/UpgradeClient.spec.ts index 01ccba789..7afd932a6 100644 --- a/clientlibs/js/src/UpGradeClient/UpgradeClient.spec.ts +++ b/clientlibs/js/src/UpGradeClient/UpgradeClient.spec.ts @@ -155,6 +155,58 @@ describe('UpgradeClient', () => { expect(ApiService.prototype.setFeatureFlagUserGroupsForSession).toHaveBeenCalledWith(undefined, undefined); }); + + it('should clear the cached feature flags', () => { + ApiService.prototype.setFeatureFlagUserGroupsForSession = jest.fn(); + const clearFeatureFlags = jest.spyOn(DataService.prototype, 'clearFeatureFlags'); + + upgradeClient.setFeatureFlagUserGroupsForSession({ + groupsForSession: { classId: ['classB'] }, + includeStoredUserGroups: false, + }); + + expect(clearFeatureFlags).toHaveBeenCalled(); + clearFeatureFlags.mockRestore(); + }); + + it('should not clear the cached feature flags when the options are invalid', () => { + ApiService.prototype.setFeatureFlagUserGroupsForSession = jest.fn(); + const clearFeatureFlags = jest.spyOn(DataService.prototype, 'clearFeatureFlags'); + + expect(() => { + upgradeClient.setFeatureFlagUserGroupsForSession({ groupsForSession: null, includeStoredUserGroups: false }); + }).toThrow(); + + expect(clearFeatureFlags).not.toHaveBeenCalled(); + clearFeatureFlags.mockRestore(); + }); + + it('should cause the next getAllFeatureFlags call to refetch against the new groups', async () => { + // the real DataService cache is left unmocked here, since invalidating it is the behavior under test + ApiService.prototype.setFeatureFlagUserGroupsForSession = jest.fn(); + const getAllFeatureFlags = jest + .spyOn(ApiService.prototype, 'getAllFeatureFlags') + .mockResolvedValue(['classAFlag']); + + // baseline: the first call fetches and caches, the second is served from the cache + expect(await upgradeClient.getAllFeatureFlags()).toEqual(['classAFlag']); + expect(await upgradeClient.getAllFeatureFlags()).toEqual(['classAFlag']); + expect(getAllFeatureFlags).toHaveBeenCalledTimes(1); + + getAllFeatureFlags.mockResolvedValue(['classBFlag']); + upgradeClient.setFeatureFlagUserGroupsForSession({ + groupsForSession: { classId: ['classB'] }, + includeStoredUserGroups: false, + }); + + // the cleared cache forces exactly one refetch, whose result becomes the new cached value + expect(await upgradeClient.getAllFeatureFlags()).toEqual(['classBFlag']); + expect(getAllFeatureFlags).toHaveBeenCalledTimes(2); + expect(await upgradeClient.getAllFeatureFlags()).toEqual(['classBFlag']); + expect(getAllFeatureFlags).toHaveBeenCalledTimes(2); + + getAllFeatureFlags.mockRestore(); + }); }); describe('#getAllExperimentConditions', () => { diff --git a/clientlibs/js/src/UpGradeClient/UpgradeClient.ts b/clientlibs/js/src/UpGradeClient/UpgradeClient.ts index 976984edc..27fd63db9 100644 --- a/clientlibs/js/src/UpGradeClient/UpgradeClient.ts +++ b/clientlibs/js/src/UpGradeClient/UpgradeClient.ts @@ -176,6 +176,11 @@ export default class UpgradeClient { * Note: This is a convenience method, this can also be set directly in the constructor of UpgradeClient. * See example usage in the constructor documentation. * + * Note: Calling this clears any cached feature flags, since those were resolved against the previous + * groups. The next `getAllFeatureFlags()`/`hasFeatureFlag()` call will refetch against the new groups. + * If you need to retain flags for the previous groups, capture the array returned by + * `getAllFeatureFlags()` before switching. + * * @example * ```typescript * @@ -211,6 +216,8 @@ export default class UpgradeClient { featureFlagOptions?.groupsForSession, featureFlagOptions?.includeStoredUserGroups ); + + this.dataService.clearFeatureFlags(); } /** @@ -515,7 +522,7 @@ export default class UpgradeClient { */ async getAllFeatureFlags(options = { ignoreCache: false }): Promise { - let response = options.ignoreCache ? null : await this.dataService.getFeatureFlags(); + let response = options.ignoreCache ? null : this.dataService.getFeatureFlags(); if (response == null) { response = await this.apiService.getAllFeatureFlags(); if (Array.isArray(response)) {