From 9157744e1fa597a2ae23bcf466547d1bc81f3720 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Tue, 2 Dec 2025 21:18:20 +0600 Subject: [PATCH 1/6] wip --- lib/optimizely_user_context/index.spec.ts | 1023 +++++++++++++++++++++ 1 file changed, 1023 insertions(+) create mode 100644 lib/optimizely_user_context/index.spec.ts diff --git a/lib/optimizely_user_context/index.spec.ts b/lib/optimizely_user_context/index.spec.ts new file mode 100644 index 000000000..0fe0b33c0 --- /dev/null +++ b/lib/optimizely_user_context/index.spec.ts @@ -0,0 +1,1023 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { describe, it, beforeEach, afterEach, expect, vi, Mocked } from 'vitest'; +import { sprintf } from '../utils/fns'; +import { NOTIFICATION_TYPES } from '../notification_center/type'; +import OptimizelyUserContext from './'; +import Optimizely from '../optimizely'; +import testData from '../tests/test_data'; +import { EventDispatcher, OptimizelyDecideOption } from '../shared_types'; +import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; +import { createProjectConfig } from '../project_config/project_config'; +import { getForwardingEventProcessor } from '../event_processor/event_processor_factory'; +import { FORCED_DECISION_NULL_RULE_KEY } from './index'; +import { getMockLogger } from '../tests/mock/mock_logger'; + +import { + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, +} from '../core/decision_service'; +import { R } from 'vitest/dist/chunks/environment.LoooBwUu.js'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; + +const getMockEventDispatcher = () => { + const dispatcher = { + dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })), + }; + return dispatcher; +}; + +interface GetOptlyInstanceParams { + datafileObj: any; + defaultDecideOptions?: OptimizelyDecideOption[]; +} + +const getOptlyInstance = ({ datafileObj, defaultDecideOptions }: GetOptlyInstanceParams) => { + const createdLogger = getMockLogger(); + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(datafileObj), + }); + const eventDispatcher = getMockEventDispatcher(); + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + + const optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager: mockConfigManager, + eventProcessor, + logger: createdLogger, + cmabService: {} as any, + defaultDecideOptions: defaultDecideOptions || [], + }); + + return { optlyInstance, eventProcessor, eventDispatcher, createdLogger }; +}; + +describe('OptimizelyUserContext', () => { + let fakeOptimizely: any; + const userId = 'tester'; + const options = ['fakeOption']; + + describe('setAttribute', () => { + beforeEach(() => { + fakeOptimizely = { + decide: vi.fn().mockReturnValue({}), + }; + }); + + it('should set attributes when provided at instantiation of OptimizelyUserContext', () => { + const userId = 'user1'; + const attributes = { test_attribute: 'test_value' }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + attributes, + }); + user.setAttribute('k1', { hello: 'there' } as any); + user.setAttribute('k2', true); + user.setAttribute('k3', 100); + user.setAttribute('k4', 3.5); + expect(user.getOptimizely()).toEqual(fakeOptimizely); + expect(user.getUserId()).toEqual(userId); + + const newAttributes = user.getAttributes(); + expect(newAttributes['test_attribute']).toEqual('test_value'); + expect(newAttributes['k1']).toEqual({ hello: 'there' }); + expect(newAttributes['k2']).toEqual(true); + expect(newAttributes['k3']).toEqual(100); + expect(newAttributes['k4']).toEqual(3.5); + }); + + it('should set attributes when none provided at instantiation of OptimizelyUserContext', () => { + const userId = 'user2'; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + user.setAttribute('k1', { hello: 'there' } as any); + user.setAttribute('k2', true); + user.setAttribute('k3', 100); + user.setAttribute('k4', 3.5); + expect(user.getOptimizely()).toEqual(fakeOptimizely); + expect(user.getUserId()).toEqual(userId); + + const newAttributes = user.getAttributes(); + expect(newAttributes['k1']).toEqual({ hello: 'there' }); + expect(newAttributes['k2']).toEqual(true); + expect(newAttributes['k3']).toEqual(100); + expect(newAttributes['k4']).toEqual(3.5); + }); + + it('should override existing attributes', () => { + const userId = 'user3'; + const attributes = { test_attribute: 'test_value' }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + attributes, + }); + user.setAttribute('k1', { hello: 'there' } as any); + user.setAttribute('test_attribute', 'overwritten_value'); + expect(user.getOptimizely()).toEqual(fakeOptimizely); + expect(user.getUserId()).toEqual(userId); + + const newAttributes = user.getAttributes(); + expect(newAttributes['k1']).toEqual({ hello: 'there' }); + expect(newAttributes['test_attribute']).toEqual('overwritten_value'); + expect(Object.keys(newAttributes).length).toEqual(2); + }); + + it('should allow to set attributes with value of null', () => { + const userId = 'user4'; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + user.setAttribute('null_attribute', null); + expect(user.getOptimizely()).toEqual(fakeOptimizely); + expect(user.getUserId()).toEqual(userId); + + const newAttributes = user.getAttributes(); + expect(newAttributes['null_attribute']).toEqual(null); + }); + + it('should set attributes by value in constructor', () => { + const userId = 'user1'; + const attributes: any = { initial_attribute: 'initial_value' }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + attributes, + }); + attributes['attribute2'] = 100; + expect(user.getAttributes()).toEqual({ initial_attribute: 'initial_value' }); + user.setAttribute('attribute3', 'hello'); + expect(attributes).toEqual({ initial_attribute: 'initial_value', attribute2: 100 }); + }); + + it('should not change user attributes if returned by getAttributes object is updated', () => { + const userId = 'user1'; + const attributes = { initial_attribute: 'initial_value' }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + attributes, + }); + const attributes2: any = user.getAttributes(); + attributes2['new_attribute'] = { value: 100 }; + expect(user.getAttributes()).toEqual(attributes); + const expectedAttributes = { + initial_attribute: 'initial_value', + new_attribute: { value: 100 }, + }; + expect(attributes2).toEqual(expectedAttributes); + }); + }); + + describe('decide', () => { + it('should return an expected decision object', () => { + const flagKey = 'feature_1'; + const fakeDecision = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: {}, + ruleKey: 'exp_no_audience', + flagKey: flagKey, + userContext: 'fakeUserContext', + reasons: [], + }; + fakeOptimizely = { + decide: vi.fn().mockReturnValue(fakeDecision), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + const decision = user.decide(flagKey, options as any); + expect(fakeOptimizely.decide).toHaveBeenCalledWith(user, flagKey, options); + expect(decision).toEqual(fakeDecision); + }); + }); + + describe('decideForKeys', () => { + it('should return an expected decision results object', () => { + const flagKey1 = 'feature_1'; + const flagKey2 = 'feature_2'; + const fakeDecisionMap = { + flagKey1: { + variationKey: '18257766532', + enabled: true, + variables: {}, + ruleKey: '18322080788', + flagKey: flagKey1, + userContext: 'fakeUserContext', + reasons: [], + }, + flagKey2: { + variationKey: 'variation_with_traffic', + enabled: true, + variables: {}, + ruleKey: 'exp_no_audience', + flagKey: flagKey2, + userContext: 'fakeUserContext', + reasons: [], + }, + }; + fakeOptimizely = { + decideForKeys: vi.fn().mockReturnValue(fakeDecisionMap), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + const decisionMap = user.decideForKeys([flagKey1, flagKey2], options as any); + expect(fakeOptimizely.decideForKeys).toHaveBeenCalledWith(user, [flagKey1, flagKey2], options); + expect(decisionMap).toEqual(fakeDecisionMap); + }); + }); + + describe('decideAll', () => { + it('should return an expected decision results object', () => { + const flagKey1 = 'feature_1'; + const flagKey2 = 'feature_2'; + const flagKey3 = 'feature_3'; + const fakeDecisionMap: any = { + flagKey1: { + variationKey: '18257766532', + enabled: true, + variables: {}, + ruleKey: '18322080788', + flagKey: flagKey1, + userContext: 'fakeUserContext', + reasons: [], + }, + flagKey2: { + variationKey: 'variation_with_traffic', + enabled: true, + variables: {}, + ruleKey: 'exp_no_audience', + flagKey: flagKey2, + userContext: 'fakeUserContext', + reasons: [], + }, + flagKey3: { + variationKey: '', + enabled: false, + variables: {}, + ruleKey: '', + flagKey: flagKey3, + userContext: undefined, + reasons: [], + }, + }; + fakeOptimizely = { + decideAll: vi.fn().mockReturnValue(fakeDecisionMap), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + const decisionMap = user.decideAll(options as any); + expect(fakeOptimizely.decideAll).toHaveBeenCalledWith(user, options); + expect(decisionMap).toEqual(fakeDecisionMap); + }); + }); + + describe('trackEvent', () => { + it('should call track from optimizely client', () => { + fakeOptimizely = { + track: vi.fn(), + }; + const eventName = 'myEvent'; + const eventTags = { eventTag1: 1000 }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + user.trackEvent(eventName, eventTags); + expect(fakeOptimizely.track).toHaveBeenCalledWith( + eventName, + user.getUserId(), + user.getAttributes(), + eventTags + ); + }); + }); + + describe('setForcedDecision', () => { + it('should return true when client is not ready', () => { + fakeOptimizely = { + onReady(): Promise { + return resolvablePromise().promise; + }, + onRunning(): Promise { + return resolvablePromise().promise; + }, + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + const result = user.setForcedDecision({ flagKey: 'feature_1' }, { variationKey: '3324490562' }); + expect(result).toEqual(true); + }); + + it('should return true when provided empty string flagKey', () => { + fakeOptimizely = { + onReady(): Promise { + return resolvablePromise().promise; + }, + onRunning(): Promise { + return resolvablePromise().promise; + }, + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId: 'user123', + }); + const result = user.setForcedDecision({ flagKey: '' }, { variationKey: '3324490562' }); + expect(result).toEqual(true); + }); + + it('should return true when provided flagKey and variationKey', () => { + fakeOptimizely = { + onReady(): Promise { + return resolvablePromise().promise; + }, + onRunning(): Promise { + return resolvablePromise().promise; + }, + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId: 'user123', + }); + const result = user.setForcedDecision({ flagKey: 'feature_1' }, { variationKey: '3324490562' }); + expect(result).toEqual(true); + }); + + describe('when valid forced decision is set', () => { + // let optlyInstance: Optimizely; + // let eventDispatcher: ReturnType; + // beforeEach(() => { + + // (optlyInstance, eventDispatcher) = getOptlyInstancenew Optimizely({ + // clientEngine: 'node-sdk', + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + // }), + // cmabService: {} as any, + // }); + + + // vi.spyOn(optlyInstance.notificationCenter, 'sendNotifications'); + // }); + + // afterEach(() => { + // eventDispatcher.dispatchEvent.mockClear(); + // vi.restoreAllMocks(); + // }); + + it('should return an expected decision object when forced decision is called and variation of different experiment but same flag key', () => { + const flagKey = 'feature_1'; + const ruleKey = 'exp_with_audience'; + const variationKey = '3324490633'; + + getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + + const user = optlyInstance.createUserContext(userId); + user.setForcedDecision({ flagKey: flagKey, ruleKey }, { variationKey }); + const decision = user.decide(flagKey, options as any); + + expect(decision.variationKey).toEqual(variationKey); + expect(decision.ruleKey).toEqual(ruleKey); + expect(decision.enabled).toEqual(true); + expect(decision.userContext.getUserId()).toEqual(userId); + expect(decision.userContext.getAttributes()).toEqual({}); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); + }); + + it('should return forced decision object when forced decision is set for a flag and do NOT dispatch an event with DISABLE_DECISION_EVENT passed in decide options', () => { + const user = optlyInstance.createUserContext(userId); + const featureKey = 'feature_1'; + const variationKey = '3324490562'; + user.setForcedDecision({ flagKey: featureKey }, { variationKey }); + const decision = user.decide(featureKey, [ + OptimizelyDecideOption.INCLUDE_REASONS, + OptimizelyDecideOption.DISABLE_DECISION_EVENT, + ]); + expect(decision.variationKey).toEqual(variationKey); + expect(decision.ruleKey).toEqual(null); + expect(decision.enabled).toEqual(true); + expect(decision.userContext.getUserId()).toEqual(userId); + expect(decision.userContext.getAttributes()).toEqual({}); + expect(Object.keys(decision.userContext.forcedDecisionsMap).length).toEqual(1); + expect(decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY]).toEqual({ + variationKey, + }); + expect( + decision.reasons.includes( + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) + ) + ).toEqual(true); + + expect(eventDispatcher.dispatchEvent).not.toHaveBeenCalled(); + }); + + it('should return forced decision object when forced decision is set for a flag and do NOT dispatch an event with DISABLE_DECISION_EVENT string passed in decide options', () => { + const user = optlyInstance.createUserContext(userId); + const featureKey = 'feature_1'; + const variationKey = '3324490562'; + user.setForcedDecision({ flagKey: featureKey }, { variationKey }); + const decision = user.decide(featureKey, ['INCLUDE_REASONS', 'DISABLE_DECISION_EVENT'] as any); + expect(decision.variationKey).toEqual(variationKey); + expect(decision.ruleKey).toEqual(null); + expect(decision.enabled).toEqual(true); + expect(decision.userContext.getUserId()).toEqual(userId); + expect(decision.userContext.getAttributes()).toEqual({}); + expect(Object.keys(decision.userContext.forcedDecisionsMap).length).toEqual(1); + expect(decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY]).toEqual({ + variationKey, + }); + expect( + decision.reasons.includes( + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) + ) + ).toEqual(true); + + expect(eventDispatcher.dispatchEvent).not.toHaveBeenCalled(); + }); + + it('should return forced decision object when forced decision is set for a flag and dispatch an event', () => { + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + + const notificationCenter = optlyInstance.notificationCenter; + vi.spyOn(notificationCenter, 'sendNotifications'); + + const user = optlyInstance.createUserContext(userId); + const featureKey = 'feature_1'; + const variationKey = '3324490562'; + user.setForcedDecision({ flagKey: featureKey }, { variationKey }); + const decision = user.decide(featureKey, [OptimizelyDecideOption.INCLUDE_REASONS]); + + expect(decision.variationKey).toEqual(variationKey); + expect(decision.ruleKey).toEqual(null); + expect(decision.enabled).toEqual(true); + expect(decision.userContext.getUserId()).toEqual(userId); + expect(decision.userContext.getAttributes()).toEqual({}); + expect(Object.values((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); + expect((decision.userContext as any).forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY]).toEqual({ + variationKey, + }); + expect( + decision.reasons.includes( + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) + ) + ).toEqual(true); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + const callArgs: any = eventDispatcher.dispatchEvent.mock.calls[0]; + const impressionEvent: any = callArgs[0]; + const eventDecision: any = impressionEvent.params.visitors[0].snapshots[0].decisions[0]; + const metadata = eventDecision.metadata; + + expect(eventDecision.experiment_id).toEqual(''); + expect(eventDecision.variation_id).toEqual('3324490562'); + + expect(metadata.flag_key).toEqual(featureKey); + expect(metadata.rule_key).toEqual(''); + expect(metadata.rule_type).toEqual('feature-test'); + expect(metadata.variation_key).toEqual(variationKey); + expect(metadata.enabled).toEqual(true); + + expect(notificationCenter.sendNotifications).toHaveBeenCalledTimes(3); + const notificationCallArgs: any = (notificationCenter.sendNotifications as any).mock.calls[2]; + const expectedNotificationCallArgs = [ + NOTIFICATION_TYPES.DECISION, + { + type: 'flag', + userId: 'tester', + attributes: {}, + decisionInfo: { + flagKey: featureKey, + enabled: true, + ruleKey: null, + variationKey, + variables: { + b_true: true, + d_4_2: 4.2, + i_1: 'invalid', + i_42: 42, + j_1: null, + s_foo: 'foo', + }, + decisionEventDispatched: true, + reasons: [ + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId), + ], + experimentId: null, + variationId: '3324490562', + }, + }, + ]; + expect(notificationCallArgs).toEqual(expectedNotificationCallArgs); + }); + + it('should return forced decision object when forced decision is set for an experiment rule and dispatch an event', () => { + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + + const notificationCenter = optlyInstance.notificationCenter; + vi.spyOn(notificationCenter, 'sendNotifications'); + + const attributes = { country: 'US' }; + const user = optlyInstance.createUserContext(userId, attributes); + const featureKey = 'feature_1'; + const variationKey = 'b'; + const ruleKey = 'exp_with_audience'; + user.setForcedDecision({ flagKey: featureKey, ruleKey }, { variationKey }); + const decision = user.decide(featureKey, [OptimizelyDecideOption.INCLUDE_REASONS]); + + expect(decision.variationKey).toEqual(variationKey); + expect(decision.ruleKey).toEqual(ruleKey); + expect(decision.enabled).toEqual(false); + expect(decision.userContext.getUserId()).toEqual(userId); + expect(decision.userContext.getAttributes()).toEqual(attributes); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap[featureKey]).length).toEqual(1); + expect((decision.userContext as any).forcedDecisionsMap[featureKey][ruleKey]).toEqual({ variationKey }); + expect( + decision.reasons.includes( + sprintf(USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, variationKey, featureKey, ruleKey, userId) + ) + ).toEqual(true); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + const callArgs = (eventDispatcher.dispatchEvent.mock.calls[0] as any); + const impressionEvent = callArgs[0]; + const eventDecision = impressionEvent.params.visitors[0].snapshots[0].decisions[0]; + const metadata = eventDecision.metadata; + + expect(eventDecision.experiment_id).toEqual('10390977673'); + expect(eventDecision.variation_id).toEqual('10416523121'); + + expect(metadata.flag_key).toEqual(featureKey); + expect(metadata.rule_key).toEqual('exp_with_audience'); + expect(metadata.rule_type).toEqual('feature-test'); + expect(metadata.variation_key).toEqual('b'); + expect(metadata.enabled).toEqual(false); + + expect(notificationCenter.sendNotifications).toHaveBeenCalledTimes(3); + const notificationCallArgs = (notificationCenter.sendNotifications as any).mock.calls[2]; + const expectedNotificationCallArgs = [ + NOTIFICATION_TYPES.DECISION, + { + type: 'flag', + userId: 'tester', + attributes, + decisionInfo: { + flagKey: featureKey, + enabled: false, + ruleKey: 'exp_with_audience', + variationKey, + variables: { + b_true: true, + d_4_2: 4.2, + i_1: 'invalid', + i_42: 42, + j_1: null, + s_foo: 'foo', + }, + decisionEventDispatched: true, + reasons: [ + sprintf(USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, variationKey, featureKey, ruleKey, userId), + ], + experimentId: '10390977673', + variationId: '10416523121', + }, + }, + ]; + expect(notificationCallArgs).toEqual(expectedNotificationCallArgs); + }); + + it('should return forced decision object when forced decision is set for a delivery rule and dispatch an event', () => { + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + + const notificationCenter = optlyInstance.notificationCenter; + vi.spyOn(notificationCenter, 'sendNotifications'); + + const user = optlyInstance.createUserContext(userId); + const featureKey = 'feature_1'; + const variationKey = '3324490633'; + const ruleKey = '3332020515'; + user.setForcedDecision({ flagKey: featureKey, ruleKey }, { variationKey }); + const decision = user.decide(featureKey); + + expect(decision.variationKey).toEqual(variationKey); + expect(decision.ruleKey).toEqual(ruleKey); + expect(decision.enabled).toEqual(true); + expect(decision.userContext.getUserId()).toEqual(userId); + expect(decision.userContext.getAttributes()).toEqual({}); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap[featureKey]).length).toEqual(1); + expect((decision.userContext as any).forcedDecisionsMap[featureKey][ruleKey]).toEqual({ variationKey }); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + const callArgs = (eventDispatcher.dispatchEvent.mock.calls[0] as any); + const impressionEvent = callArgs[0]; + const eventDecision = impressionEvent.params.visitors[0].snapshots[0].decisions[0]; + const metadata = eventDecision.metadata; + + expect(eventDecision.experiment_id).toEqual('3332020515'); + expect(eventDecision.variation_id).toEqual('3324490633'); + + expect(metadata.flag_key).toEqual(featureKey); + expect(metadata.rule_key).toEqual('3332020515'); + expect(metadata.rule_type).toEqual('rollout'); + expect(metadata.variation_key).toEqual('3324490633'); + expect(metadata.enabled).toEqual(true); + + expect(notificationCenter.sendNotifications).toHaveBeenCalledTimes(3); + const notificationCallArgs = (notificationCenter.sendNotifications as any).mock.calls[2]; + const expectedNotificationCallArgs = [ + NOTIFICATION_TYPES.DECISION, + { + type: 'flag', + userId: 'tester', + attributes: {}, + decisionInfo: { + flagKey: featureKey, + enabled: true, + ruleKey: '3332020515', + variationKey, + variables: { + b_true: true, + d_4_2: 4.2, + i_1: 'invalid', + i_42: 42, + j_1: null, + s_foo: 'foo', + }, + decisionEventDispatched: true, + reasons: [], + experimentId: '3332020515', + variationId: '3324490633', + }, + }, + ]; + expect(notificationCallArgs).toEqual(expectedNotificationCallArgs); + }); + }); + + describe('when invalid forced decision is set', () => { + let optlyInstance: any; + let eventDispatcher: any; + let eventProcessor: any; + let createdLogger: any; + + beforeEach(() => { + createdLogger = getMockLogger(); + eventDispatcher = getMockEventDispatcher(); + eventProcessor = getForwardingEventProcessor(eventDispatcher); + + optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }), + eventProcessor, + cmabService: {} as any, + logger: createdLogger as any, + }); + }); + + afterEach(() => { + eventDispatcher.dispatchEvent.mockClear(); + }); + + it('should NOT return forced decision object when forced decision is set for a flag', () => { + const user = optlyInstance.createUserContext(userId); + const featureKey = 'feature_1'; + const variationKey = 'invalid'; + user.setForcedDecision({ flagKey: featureKey }, { variationKey }); + const decision = user.decide(featureKey, [OptimizelyDecideOption.INCLUDE_REASONS]); + + // invalid forced decision will be ignored and regular decision will return + expect(decision.variationKey).toEqual('18257766532'); + expect(decision.ruleKey).toEqual('18322080788'); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); + expect((decision.userContext as any).forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY]).toEqual({ + variationKey, + }); + expect( + decision.reasons.includes( + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, featureKey, userId) + ) + ).toEqual(true); + }); + + it('should NOT return forced decision object when forced decision is set for an experiment rule', () => { + const user = optlyInstance.createUserContext(userId); + const featureKey = 'feature_1'; + const ruleKey = 'exp_with_audience'; + const variationKey = 'invalid'; + user.setForcedDecision({ flagKey: featureKey, ruleKey }, { variationKey }); + const decision = user.decide(featureKey, [OptimizelyDecideOption.INCLUDE_REASONS]); + + // invalid forced-decision will be ignored and regular decision will return + expect(decision.variationKey).toEqual('18257766532'); + expect(decision.ruleKey).toEqual('18322080788'); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap[featureKey]).length).toEqual(1); + expect((decision.userContext as any).forcedDecisionsMap[featureKey][ruleKey]).toEqual({ variationKey }); + expect( + decision.reasons.includes( + sprintf(USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, featureKey, ruleKey, userId) + ) + ).toEqual(true); + }); + + it('should NOT return forced decision object when forced decision is set for a delivery rule', () => { + const user = optlyInstance.createUserContext(userId); + const featureKey = 'feature_1'; + const variationKey = 'invalid'; + const ruleKey = '3332020515'; + user.setForcedDecision({ flagKey: featureKey, ruleKey }, { variationKey }); + const decision = user.decide(featureKey, [OptimizelyDecideOption.INCLUDE_REASONS]); + + // invalid forced decision will be ignored and regular decision will return + expect(decision.variationKey).toEqual('18257766532'); + expect(decision.ruleKey).toEqual('18322080788'); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap[featureKey]).length).toEqual(1); + expect((decision.userContext as any).forcedDecisionsMap[featureKey][ruleKey]).toEqual({ variationKey }); + expect( + decision.reasons.includes( + sprintf(USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, featureKey, ruleKey, userId) + ) + ).toEqual(true); + }); + }); + + describe('when forced decision is set for a flag and an experiment rule', () => { + let optlyInstance: any; + let eventDispatcher: any; + let eventProcessor: any; + let createdLogger: any; + + beforeEach(() => { + createdLogger = getMockLogger(); + eventDispatcher = getMockEventDispatcher(); + eventProcessor = getForwardingEventProcessor(eventDispatcher); + + optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }), + eventProcessor, + cmabService: {} as any, + logger: createdLogger as any, + }); + }); + + afterEach(() => { + eventDispatcher.dispatchEvent.mockClear(); + }); + + it('should prioritize flag forced decision over experiment rule', () => { + const user = optlyInstance.createUserContext(userId); + const featureKey = 'feature_1'; + const flagVariationKey = '3324490562'; + const experimentRuleVariationKey = 'b'; + const ruleKey = 'exp_with_audience'; + user.setForcedDecision({ flagKey: featureKey }, { variationKey: flagVariationKey }); + user.setForcedDecision({ flagKey: featureKey, ruleKey }, { variationKey: experimentRuleVariationKey }); + const decision = user.decide(featureKey, [OptimizelyDecideOption.INCLUDE_REASONS]); + + // flag-to-decision is the 1st priority + expect(decision.variationKey).toEqual(flagVariationKey); + expect(decision.ruleKey).toEqual(null); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); + expect(Object.keys((decision.userContext as any).forcedDecisionsMap[featureKey]).length).toEqual(2); + }); + }); + }); + + describe('#getForcedDecision', () => { + it('should return correct forced variation', () => { + const createdLogger = getMockLogger(); + const eventDispatcher = getMockEventDispatcher(); + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + const optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }), + eventProcessor, + cmabService: {} as any, + logger: createdLogger as any, + }); + const user = optlyInstance.createUserContext(userId); + const featureKey = 'feature_1'; + const ruleKey = 'r'; + user.setForcedDecision({ flagKey: featureKey }, { variationKey: 'fv1' }); + expect(user.getForcedDecision({ flagKey: featureKey })).toEqual({ variationKey: 'fv1' }); + + // override forced variation + user.setForcedDecision({ flagKey: featureKey }, { variationKey: 'fv2' }); + expect(user.getForcedDecision({ flagKey: featureKey })).toEqual({ variationKey: 'fv2' }); + + user.setForcedDecision({ flagKey: featureKey, ruleKey }, { variationKey: 'ev1' }); + expect(user.getForcedDecision({ flagKey: featureKey, ruleKey })).toEqual({ variationKey: 'ev1' }); + + // override forced variation + user.setForcedDecision({ flagKey: featureKey, ruleKey }, { variationKey: 'ev2' }); + expect(user.getForcedDecision({ flagKey: featureKey, ruleKey })).toEqual({ variationKey: 'ev2' }); + + expect(user.getForcedDecision({ flagKey: featureKey })).toEqual({ variationKey: 'fv2' }); + }); + }); + + describe('#removeForcedDecision', () => { + it('should return true when client is not ready and the forced decision has been removed successfully', () => { + fakeOptimizely = { + isValidInstance: vi.fn().mockReturnValue(false), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId: 'user123', + }); + user.setForcedDecision({ flagKey: 'feature_1' }, { variationKey: '3324490562' }); + const result = user.removeForcedDecision({ flagKey: 'feature_1' }); + expect(result).toEqual(true); + }); + + it('should return true when the forced decision has been removed successfully and false otherwise', () => { + fakeOptimizely = { + isValidInstance: vi.fn().mockReturnValue(true), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId: 'user123', + }); + user.setForcedDecision({ flagKey: 'feature_1' }, { variationKey: '3324490562' }); + const result1 = user.removeForcedDecision({ flagKey: 'feature_1' }); + expect(result1).toEqual(true); + + const result2 = user.removeForcedDecision({ flagKey: 'non-existent_feature' }); + expect(result2).toEqual(false); + }); + + it('should successfully remove forced decision when multiple forced decisions set with same feature key', () => { + fakeOptimizely = { + isValidInstance: vi.fn().mockReturnValue(true), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId: 'user123', + }); + + const featureKey = 'feature_1'; + const ruleKey = 'r'; + + user.setForcedDecision({ flagKey: featureKey }, { variationKey: 'fv1' }); + user.setForcedDecision({ flagKey: featureKey, ruleKey }, { variationKey: 'ev1' }); + + expect(user.getForcedDecision({ flagKey: featureKey })).toEqual({ variationKey: 'fv1' }); + expect(user.getForcedDecision({ flagKey: featureKey, ruleKey })).toEqual({ variationKey: 'ev1' }); + + expect(user.removeForcedDecision({ flagKey: featureKey })).toEqual(true); + expect(user.getForcedDecision({ flagKey: featureKey })).toEqual(null); + expect(user.getForcedDecision({ flagKey: featureKey, ruleKey })).toEqual({ variationKey: 'ev1' }); + + expect(user.removeForcedDecision({ flagKey: featureKey, ruleKey })).toEqual(true); + expect(user.getForcedDecision({ flagKey: featureKey })).toEqual(null); + expect(user.getForcedDecision({ flagKey: featureKey, ruleKey })).toEqual(null); + + expect(user.removeForcedDecision({ flagKey: featureKey })).toEqual(false); // no more saved decisions + }); + }); + + describe('#removeAllForcedDecisions', () => { + it('should return true when client is not ready', () => { + fakeOptimizely = { + isValidInstance: vi.fn().mockReturnValue(false), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + const result = user.removeAllForcedDecisions(); + expect(result).toEqual(true); + }); + + it('should return true when all forced decisions have been removed successfully', () => { + fakeOptimizely = { + isValidInstance: vi.fn().mockReturnValue(true), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + user.setForcedDecision({ flagKey: 'feature_1' }, { variationKey: '3324490562' }); + user.setForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' }, { variationKey: 'b' }); + expect(Object.keys((user as any).forcedDecisionsMap).length).toEqual(1); + expect(Object.keys((user as any).forcedDecisionsMap['feature_1']).length).toEqual(2); + + expect(user.getForcedDecision({ flagKey: 'feature_1' })).toEqual({ variationKey: '3324490562' }); + expect(user.getForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' })).toEqual({ + variationKey: 'b', + }); + + const result1 = user.removeAllForcedDecisions(); + expect(result1).toEqual(true); + expect(Object.keys((user as any).forcedDecisionsMap).length).toEqual(0); + + expect(user.getForcedDecision({ flagKey: 'feature_1' })).toEqual(null); + expect(user.getForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' })).toEqual(null); + }); + }); + + describe('fetchQualifiedSegments', () => { + it('should successfully get segments', async () => { + fakeOptimizely = { + fetchQualifiedSegments: vi.fn().mockResolvedValue(['a']), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + + const successfullyFetched = await user.fetchQualifiedSegments(); + expect(successfullyFetched).toEqual(true); + + expect(fakeOptimizely.fetchQualifiedSegments).toHaveBeenCalledWith(userId, undefined); + + expect(user.qualifiedSegments).toEqual(['a']); + }); + + it('should return true empty returned segements', async () => { + fakeOptimizely = { + fetchQualifiedSegments: vi.fn().mockResolvedValue([]), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + + const successfullyFetched = await user.fetchQualifiedSegments(); + expect(successfullyFetched).toEqual(true); + }); + + it('should return false in other cases', async () => { + fakeOptimizely = { + fetchQualifiedSegments: vi.fn().mockResolvedValue(null), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + + const successfullyFetched = await user.fetchQualifiedSegments(); + expect(successfullyFetched).toEqual(false); + }); + }); + + describe('isQualifiedFor', () => { + it('should successfully return the expected result for if a user is qualified for a particular segment or not', () => { + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + + user.qualifiedSegments = ['a', 'b']; + + expect(user.isQualifiedFor('a')).toEqual(true); + expect(user.isQualifiedFor('b')).toEqual(true); + expect(user.isQualifiedFor('c')).toEqual(false); + }); + }); +}); From 00f930a4d866ef24ba59e35d00700d52ba22ed89 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Wed, 3 Dec 2025 20:02:18 +0600 Subject: [PATCH 2/6] save --- lib/optimizely_user_context/index.spec.ts | 223 ++++++++-------------- vitest.config.mts | 2 +- 2 files changed, 84 insertions(+), 141 deletions(-) diff --git a/lib/optimizely_user_context/index.spec.ts b/lib/optimizely_user_context/index.spec.ts index 0fe0b33c0..75339f3db 100644 --- a/lib/optimizely_user_context/index.spec.ts +++ b/lib/optimizely_user_context/index.spec.ts @@ -20,7 +20,7 @@ import { NOTIFICATION_TYPES } from '../notification_center/type'; import OptimizelyUserContext from './'; import Optimizely from '../optimizely'; import testData from '../tests/test_data'; -import { EventDispatcher, OptimizelyDecideOption } from '../shared_types'; +import { EventDispatcher, NotificationCenter, OptimizelyDecideOption } from '../shared_types'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import { createProjectConfig } from '../project_config/project_config'; import { getForwardingEventProcessor } from '../event_processor/event_processor_factory'; @@ -33,12 +33,13 @@ import { USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, } from '../core/decision_service'; -import { R } from 'vitest/dist/chunks/environment.LoooBwUu.js'; import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { LogEvent } from '../event_processor/event_dispatcher/event_dispatcher'; +import { DefaultNotificationCenter } from '../notification_center'; const getMockEventDispatcher = () => { const dispatcher = { - dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })), + dispatchEvent: vi.fn((event: LogEvent) => Promise.resolve({ statusCode: 200 })), }; return dispatcher; }; @@ -372,37 +373,26 @@ describe('OptimizelyUserContext', () => { }); describe('when valid forced decision is set', () => { - // let optlyInstance: Optimizely; - // let eventDispatcher: ReturnType; - // beforeEach(() => { - - // (optlyInstance, eventDispatcher) = getOptlyInstancenew Optimizely({ - // clientEngine: 'node-sdk', - // projectConfigManager: getMockProjectConfigManager({ - // initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), - // }), - // cmabService: {} as any, - // }); - - - // vi.spyOn(optlyInstance.notificationCenter, 'sendNotifications'); - // }); + let optlyInstance: Optimizely; + let eventDispatcher: ReturnType; + beforeEach(() => { + ({ optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + })); - // afterEach(() => { - // eventDispatcher.dispatchEvent.mockClear(); - // vi.restoreAllMocks(); - // }); + vi.spyOn(optlyInstance.notificationCenter, 'sendNotifications'); + }); it('should return an expected decision object when forced decision is called and variation of different experiment but same flag key', () => { const flagKey = 'feature_1'; const ruleKey = 'exp_with_audience'; const variationKey = '3324490633'; - getOptlyInstance({ - datafileObj: testData.getTestDecideProjectConfig(), + const user = new OptimizelyUserContext({ + optimizely: optlyInstance, + userId, }); - const user = optlyInstance.createUserContext(userId); user.setForcedDecision({ flagKey: flagKey, ruleKey }, { variationKey }); const decision = user.decide(flagKey, options as any); @@ -415,7 +405,11 @@ describe('OptimizelyUserContext', () => { }); it('should return forced decision object when forced decision is set for a flag and do NOT dispatch an event with DISABLE_DECISION_EVENT passed in decide options', () => { - const user = optlyInstance.createUserContext(userId); + const user = new OptimizelyUserContext({ + optimizely: optlyInstance, + userId, + }); + const featureKey = 'feature_1'; const variationKey = '3324490562'; user.setForcedDecision({ flagKey: featureKey }, { variationKey }); @@ -428,10 +422,7 @@ describe('OptimizelyUserContext', () => { expect(decision.enabled).toEqual(true); expect(decision.userContext.getUserId()).toEqual(userId); expect(decision.userContext.getAttributes()).toEqual({}); - expect(Object.keys(decision.userContext.forcedDecisionsMap).length).toEqual(1); - expect(decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY]).toEqual({ - variationKey, - }); + expect( decision.reasons.includes( sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) @@ -441,8 +432,12 @@ describe('OptimizelyUserContext', () => { expect(eventDispatcher.dispatchEvent).not.toHaveBeenCalled(); }); - it('should return forced decision object when forced decision is set for a flag and do NOT dispatch an event with DISABLE_DECISION_EVENT string passed in decide options', () => { - const user = optlyInstance.createUserContext(userId); + it.only('should return forced decision object when forced decision is set for a flag and do NOT dispatch an event with DISABLE_DECISION_EVENT string passed in decide options', () => { + const user = new OptimizelyUserContext({ + optimizely: optlyInstance, + userId, + }); + const featureKey = 'feature_1'; const variationKey = '3324490562'; user.setForcedDecision({ flagKey: featureKey }, { variationKey }); @@ -452,10 +447,6 @@ describe('OptimizelyUserContext', () => { expect(decision.enabled).toEqual(true); expect(decision.userContext.getUserId()).toEqual(userId); expect(decision.userContext.getAttributes()).toEqual({}); - expect(Object.keys(decision.userContext.forcedDecisionsMap).length).toEqual(1); - expect(decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY]).toEqual({ - variationKey, - }); expect( decision.reasons.includes( sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) @@ -466,13 +457,6 @@ describe('OptimizelyUserContext', () => { }); it('should return forced decision object when forced decision is set for a flag and dispatch an event', () => { - const { optlyInstance, eventDispatcher } = getOptlyInstance({ - datafileObj: testData.getTestDecideProjectConfig(), - }); - - const notificationCenter = optlyInstance.notificationCenter; - vi.spyOn(notificationCenter, 'sendNotifications'); - const user = optlyInstance.createUserContext(userId); const featureKey = 'feature_1'; const variationKey = '3324490562'; @@ -495,20 +479,21 @@ describe('OptimizelyUserContext', () => { ).toEqual(true); expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); - const callArgs: any = eventDispatcher.dispatchEvent.mock.calls[0]; - const impressionEvent: any = callArgs[0]; - const eventDecision: any = impressionEvent.params.visitors[0].snapshots[0].decisions[0]; - const metadata = eventDecision.metadata; + const callArgs = eventDispatcher.dispatchEvent.mock.calls[0]; + const impressionEvent = callArgs[0]; + const eventDecision = impressionEvent.params.visitors?.[0].snapshots?.[0].decisions?.[0]; + const metadata = eventDecision?.metadata; - expect(eventDecision.experiment_id).toEqual(''); - expect(eventDecision.variation_id).toEqual('3324490562'); + expect(eventDecision?.experiment_id).toEqual(''); + expect(eventDecision?.variation_id).toEqual('3324490562'); - expect(metadata.flag_key).toEqual(featureKey); - expect(metadata.rule_key).toEqual(''); - expect(metadata.rule_type).toEqual('feature-test'); - expect(metadata.variation_key).toEqual(variationKey); - expect(metadata.enabled).toEqual(true); + expect(metadata?.flag_key).toEqual(featureKey); + expect(metadata?.rule_key).toEqual(''); + expect(metadata?.rule_type).toEqual('feature-test'); + expect(metadata?.variation_key).toEqual(variationKey); + expect(metadata?.enabled).toEqual(true); + const notificationCenter = optlyInstance.notificationCenter as Mocked; expect(notificationCenter.sendNotifications).toHaveBeenCalledTimes(3); const notificationCallArgs: any = (notificationCenter.sendNotifications as any).mock.calls[2]; const expectedNotificationCallArgs = [ @@ -543,13 +528,6 @@ describe('OptimizelyUserContext', () => { }); it('should return forced decision object when forced decision is set for an experiment rule and dispatch an event', () => { - const { optlyInstance, eventDispatcher } = getOptlyInstance({ - datafileObj: testData.getTestDecideProjectConfig(), - }); - - const notificationCenter = optlyInstance.notificationCenter; - vi.spyOn(notificationCenter, 'sendNotifications'); - const attributes = { country: 'US' }; const user = optlyInstance.createUserContext(userId, attributes); const featureKey = 'feature_1'; @@ -587,6 +565,8 @@ describe('OptimizelyUserContext', () => { expect(metadata.variation_key).toEqual('b'); expect(metadata.enabled).toEqual(false); + const notificationCenter = optlyInstance.notificationCenter as Mocked; + expect(notificationCenter.sendNotifications).toHaveBeenCalledTimes(3); const notificationCallArgs = (notificationCenter.sendNotifications as any).mock.calls[2]; const expectedNotificationCallArgs = [ @@ -621,13 +601,6 @@ describe('OptimizelyUserContext', () => { }); it('should return forced decision object when forced decision is set for a delivery rule and dispatch an event', () => { - const { optlyInstance, eventDispatcher } = getOptlyInstance({ - datafileObj: testData.getTestDecideProjectConfig(), - }); - - const notificationCenter = optlyInstance.notificationCenter; - vi.spyOn(notificationCenter, 'sendNotifications'); - const user = optlyInstance.createUserContext(userId); const featureKey = 'feature_1'; const variationKey = '3324490633'; @@ -658,7 +631,8 @@ describe('OptimizelyUserContext', () => { expect(metadata.rule_type).toEqual('rollout'); expect(metadata.variation_key).toEqual('3324490633'); expect(metadata.enabled).toEqual(true); - + + const notificationCenter = optlyInstance.notificationCenter as Mocked; expect(notificationCenter.sendNotifications).toHaveBeenCalledTimes(3); const notificationCallArgs = (notificationCenter.sendNotifications as any).mock.calls[2]; const expectedNotificationCallArgs = [ @@ -692,29 +666,14 @@ describe('OptimizelyUserContext', () => { }); describe('when invalid forced decision is set', () => { - let optlyInstance: any; - let eventDispatcher: any; - let eventProcessor: any; - let createdLogger: any; - + let optlyInstance: Optimizely; + let eventDispatcher: ReturnType; beforeEach(() => { - createdLogger = getMockLogger(); - eventDispatcher = getMockEventDispatcher(); - eventProcessor = getForwardingEventProcessor(eventDispatcher); - - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), - }), - eventProcessor, - cmabService: {} as any, - logger: createdLogger as any, - }); - }); + ({ optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + })); - afterEach(() => { - eventDispatcher.dispatchEvent.mockClear(); + vi.spyOn(optlyInstance.notificationCenter, 'sendNotifications'); }); it('should NOT return forced decision object when forced decision is set for a flag', () => { @@ -727,10 +686,7 @@ describe('OptimizelyUserContext', () => { // invalid forced decision will be ignored and regular decision will return expect(decision.variationKey).toEqual('18257766532'); expect(decision.ruleKey).toEqual('18322080788'); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); - expect((decision.userContext as any).forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY]).toEqual({ - variationKey, - }); + expect( decision.reasons.includes( sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, featureKey, userId) @@ -738,7 +694,7 @@ describe('OptimizelyUserContext', () => { ).toEqual(true); }); - it('should NOT return forced decision object when forced decision is set for an experiment rule', () => { + it.only('should NOT return forced decision object when forced decision is set for an experiment rule', () => { const user = optlyInstance.createUserContext(userId); const featureKey = 'feature_1'; const ruleKey = 'exp_with_audience'; @@ -749,9 +705,7 @@ describe('OptimizelyUserContext', () => { // invalid forced-decision will be ignored and regular decision will return expect(decision.variationKey).toEqual('18257766532'); expect(decision.ruleKey).toEqual('18322080788'); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap[featureKey]).length).toEqual(1); - expect((decision.userContext as any).forcedDecisionsMap[featureKey][ruleKey]).toEqual({ variationKey }); + expect( decision.reasons.includes( sprintf(USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, featureKey, ruleKey, userId) @@ -759,7 +713,7 @@ describe('OptimizelyUserContext', () => { ).toEqual(true); }); - it('should NOT return forced decision object when forced decision is set for a delivery rule', () => { + it.only('should NOT return forced decision object when forced decision is set for a delivery rule', () => { const user = optlyInstance.createUserContext(userId); const featureKey = 'feature_1'; const variationKey = 'invalid'; @@ -770,9 +724,7 @@ describe('OptimizelyUserContext', () => { // invalid forced decision will be ignored and regular decision will return expect(decision.variationKey).toEqual('18257766532'); expect(decision.ruleKey).toEqual('18322080788'); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap[featureKey]).length).toEqual(1); - expect((decision.userContext as any).forcedDecisionsMap[featureKey][ruleKey]).toEqual({ variationKey }); + expect( decision.reasons.includes( sprintf(USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, featureKey, ruleKey, userId) @@ -782,32 +734,17 @@ describe('OptimizelyUserContext', () => { }); describe('when forced decision is set for a flag and an experiment rule', () => { - let optlyInstance: any; - let eventDispatcher: any; - let eventProcessor: any; - let createdLogger: any; + let optlyInstance: Optimizely; beforeEach(() => { - createdLogger = getMockLogger(); - eventDispatcher = getMockEventDispatcher(); - eventProcessor = getForwardingEventProcessor(eventDispatcher); - - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), - }), - eventProcessor, - cmabService: {} as any, - logger: createdLogger as any, - }); - }); + ({ optlyInstance } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + })); - afterEach(() => { - eventDispatcher.dispatchEvent.mockClear(); + vi.spyOn(optlyInstance.notificationCenter, 'sendNotifications'); }); - it('should prioritize flag forced decision over experiment rule', () => { + it.only('should prioritize flag forced decision over experiment rule', () => { const user = optlyInstance.createUserContext(userId); const featureKey = 'feature_1'; const flagVariationKey = '3324490562'; @@ -820,27 +757,33 @@ describe('OptimizelyUserContext', () => { // flag-to-decision is the 1st priority expect(decision.variationKey).toEqual(flagVariationKey); expect(decision.ruleKey).toEqual(null); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap[featureKey]).length).toEqual(2); }); }); }); - describe('#getForcedDecision', () => { - it('should return correct forced variation', () => { - const createdLogger = getMockLogger(); - const eventDispatcher = getMockEventDispatcher(); - const eventProcessor = getForwardingEventProcessor(eventDispatcher); - const optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - projectConfigManager: getMockProjectConfigManager({ - initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), - }), - eventProcessor, - cmabService: {} as any, - logger: createdLogger as any, - }); - const user = optlyInstance.createUserContext(userId); + describe('getForcedDecision', () => { + it.only('should return correct forced variation', () => { + const { optlyInstance, createdLogger, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + + // const createdLogger = getMockLogger(); + // const eventDispatcher = getMockEventDispatcher(); + // const eventProcessor = getForwardingEventProcessor(eventDispatcher); + // const optlyInstance = new Optimizely({ + // clientEngine: 'node-sdk', + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + // }), + // eventProcessor, + // cmabService: {} as any, + // logger: createdLogger, + // }); + const user = new OptimizelyUserContext({ + optimizely: optlyInstance, + userId, + }); + const featureKey = 'feature_1'; const ruleKey = 'r'; user.setForcedDecision({ flagKey: featureKey }, { variationKey: 'fv1' }); diff --git a/vitest.config.mts b/vitest.config.mts index 1bce36eb0..d6ec0160e 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -26,7 +26,7 @@ export default defineConfig({ test: { onConsoleLog: () => true, environment: 'happy-dom', - include: ['**/*.spec.ts'], + include: ['**/optimizely_user_context/*.spec.ts'], typecheck: { enabled: true, tsconfig: 'tsconfig.spec.json', From 5f0fb4fc6019832952cc9ecfab05845bf0d43ca3 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Wed, 3 Dec 2025 22:34:01 +0600 Subject: [PATCH 3/6] upd --- lib/optimizely_user_context/index.spec.ts | 165 +++++++++++----------- 1 file changed, 79 insertions(+), 86 deletions(-) diff --git a/lib/optimizely_user_context/index.spec.ts b/lib/optimizely_user_context/index.spec.ts index 75339f3db..d6a6d9d53 100644 --- a/lib/optimizely_user_context/index.spec.ts +++ b/lib/optimizely_user_context/index.spec.ts @@ -70,11 +70,11 @@ const getOptlyInstance = ({ datafileObj, defaultDecideOptions }: GetOptlyInstanc }; describe('OptimizelyUserContext', () => { - let fakeOptimizely: any; const userId = 'tester'; const options = ['fakeOption']; describe('setAttribute', () => { + let fakeOptimizely: any; beforeEach(() => { fakeOptimizely = { decide: vi.fn().mockReturnValue({}), @@ -202,7 +202,7 @@ describe('OptimizelyUserContext', () => { userContext: 'fakeUserContext', reasons: [], }; - fakeOptimizely = { + const fakeOptimizely: any = { decide: vi.fn().mockReturnValue(fakeDecision), }; const user = new OptimizelyUserContext({ @@ -239,7 +239,7 @@ describe('OptimizelyUserContext', () => { reasons: [], }, }; - fakeOptimizely = { + const fakeOptimizely: any = { decideForKeys: vi.fn().mockReturnValue(fakeDecisionMap), }; const user = new OptimizelyUserContext({ @@ -286,7 +286,7 @@ describe('OptimizelyUserContext', () => { reasons: [], }, }; - fakeOptimizely = { + const fakeOptimizely: any = { decideAll: vi.fn().mockReturnValue(fakeDecisionMap), }; const user = new OptimizelyUserContext({ @@ -301,7 +301,7 @@ describe('OptimizelyUserContext', () => { describe('trackEvent', () => { it('should call track from optimizely client', () => { - fakeOptimizely = { + const fakeOptimizely: any = { track: vi.fn(), }; const eventName = 'myEvent'; @@ -322,7 +322,7 @@ describe('OptimizelyUserContext', () => { describe('setForcedDecision', () => { it('should return true when client is not ready', () => { - fakeOptimizely = { + const fakeOptimizely: any = { onReady(): Promise { return resolvablePromise().promise; }, @@ -339,7 +339,7 @@ describe('OptimizelyUserContext', () => { }); it('should return true when provided empty string flagKey', () => { - fakeOptimizely = { + const fakeOptimizely: any = { onReady(): Promise { return resolvablePromise().promise; }, @@ -356,7 +356,7 @@ describe('OptimizelyUserContext', () => { }); it('should return true when provided flagKey and variationKey', () => { - fakeOptimizely = { + const fakeOptimizely: any = { onReady(): Promise { return resolvablePromise().promise; }, @@ -401,7 +401,6 @@ describe('OptimizelyUserContext', () => { expect(decision.enabled).toEqual(true); expect(decision.userContext.getUserId()).toEqual(userId); expect(decision.userContext.getAttributes()).toEqual({}); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); }); it('should return forced decision object when forced decision is set for a flag and do NOT dispatch an event with DISABLE_DECISION_EVENT passed in decide options', () => { @@ -432,7 +431,7 @@ describe('OptimizelyUserContext', () => { expect(eventDispatcher.dispatchEvent).not.toHaveBeenCalled(); }); - it.only('should return forced decision object when forced decision is set for a flag and do NOT dispatch an event with DISABLE_DECISION_EVENT string passed in decide options', () => { + it('should return forced decision object when forced decision is set for a flag and do NOT dispatch an event with DISABLE_DECISION_EVENT string passed in decide options', () => { const user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -468,10 +467,7 @@ describe('OptimizelyUserContext', () => { expect(decision.enabled).toEqual(true); expect(decision.userContext.getUserId()).toEqual(userId); expect(decision.userContext.getAttributes()).toEqual({}); - expect(Object.values((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); - expect((decision.userContext as any).forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY]).toEqual({ - variationKey, - }); + expect( decision.reasons.includes( sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) @@ -495,7 +491,7 @@ describe('OptimizelyUserContext', () => { const notificationCenter = optlyInstance.notificationCenter as Mocked; expect(notificationCenter.sendNotifications).toHaveBeenCalledTimes(3); - const notificationCallArgs: any = (notificationCenter.sendNotifications as any).mock.calls[2]; + const notificationCallArgs = notificationCenter.sendNotifications.mock.calls[2]; const expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -541,9 +537,7 @@ describe('OptimizelyUserContext', () => { expect(decision.enabled).toEqual(false); expect(decision.userContext.getUserId()).toEqual(userId); expect(decision.userContext.getAttributes()).toEqual(attributes); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap[featureKey]).length).toEqual(1); - expect((decision.userContext as any).forcedDecisionsMap[featureKey][ruleKey]).toEqual({ variationKey }); + expect( decision.reasons.includes( sprintf(USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, variationKey, featureKey, ruleKey, userId) @@ -551,24 +545,24 @@ describe('OptimizelyUserContext', () => { ).toEqual(true); expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); - const callArgs = (eventDispatcher.dispatchEvent.mock.calls[0] as any); + const callArgs = eventDispatcher.dispatchEvent.mock.calls[0]; const impressionEvent = callArgs[0]; - const eventDecision = impressionEvent.params.visitors[0].snapshots[0].decisions[0]; - const metadata = eventDecision.metadata; + const eventDecision = impressionEvent.params.visitors?.[0].snapshots?.[0].decisions?.[0]; + const metadata = eventDecision?.metadata; - expect(eventDecision.experiment_id).toEqual('10390977673'); - expect(eventDecision.variation_id).toEqual('10416523121'); + expect(eventDecision?.experiment_id).toEqual('10390977673'); + expect(eventDecision?.variation_id).toEqual('10416523121'); - expect(metadata.flag_key).toEqual(featureKey); - expect(metadata.rule_key).toEqual('exp_with_audience'); - expect(metadata.rule_type).toEqual('feature-test'); - expect(metadata.variation_key).toEqual('b'); - expect(metadata.enabled).toEqual(false); + expect(metadata?.flag_key).toEqual(featureKey); + expect(metadata?.rule_key).toEqual('exp_with_audience'); + expect(metadata?.rule_type).toEqual('feature-test'); + expect(metadata?.variation_key).toEqual('b'); + expect(metadata?.enabled).toEqual(false); const notificationCenter = optlyInstance.notificationCenter as Mocked; expect(notificationCenter.sendNotifications).toHaveBeenCalledTimes(3); - const notificationCallArgs = (notificationCenter.sendNotifications as any).mock.calls[2]; + const notificationCallArgs = notificationCenter.sendNotifications.mock.calls[2]; const expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -613,28 +607,26 @@ describe('OptimizelyUserContext', () => { expect(decision.enabled).toEqual(true); expect(decision.userContext.getUserId()).toEqual(userId); expect(decision.userContext.getAttributes()).toEqual({}); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap).length).toEqual(1); - expect(Object.keys((decision.userContext as any).forcedDecisionsMap[featureKey]).length).toEqual(1); - expect((decision.userContext as any).forcedDecisionsMap[featureKey][ruleKey]).toEqual({ variationKey }); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); - const callArgs = (eventDispatcher.dispatchEvent.mock.calls[0] as any); + const callArgs = eventDispatcher.dispatchEvent.mock.calls[0]; const impressionEvent = callArgs[0]; - const eventDecision = impressionEvent.params.visitors[0].snapshots[0].decisions[0]; - const metadata = eventDecision.metadata; + const eventDecision = impressionEvent.params.visitors?.[0].snapshots?.[0].decisions?.[0]; + const metadata = eventDecision?.metadata; - expect(eventDecision.experiment_id).toEqual('3332020515'); - expect(eventDecision.variation_id).toEqual('3324490633'); + expect(eventDecision?.experiment_id).toEqual('3332020515'); + expect(eventDecision?.variation_id).toEqual('3324490633'); - expect(metadata.flag_key).toEqual(featureKey); - expect(metadata.rule_key).toEqual('3332020515'); - expect(metadata.rule_type).toEqual('rollout'); - expect(metadata.variation_key).toEqual('3324490633'); - expect(metadata.enabled).toEqual(true); + expect(metadata?.flag_key).toEqual(featureKey); + expect(metadata?.rule_key).toEqual('3332020515'); + expect(metadata?.rule_type).toEqual('rollout'); + expect(metadata?.variation_key).toEqual('3324490633'); + expect(metadata?.enabled).toEqual(true); const notificationCenter = optlyInstance.notificationCenter as Mocked; expect(notificationCenter.sendNotifications).toHaveBeenCalledTimes(3); - const notificationCallArgs = (notificationCenter.sendNotifications as any).mock.calls[2]; + const notificationCallArgs = notificationCenter.sendNotifications.mock.calls[2]; const expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -694,7 +686,7 @@ describe('OptimizelyUserContext', () => { ).toEqual(true); }); - it.only('should NOT return forced decision object when forced decision is set for an experiment rule', () => { + it('should NOT return forced decision object when forced decision is set for an experiment rule', () => { const user = optlyInstance.createUserContext(userId); const featureKey = 'feature_1'; const ruleKey = 'exp_with_audience'; @@ -713,7 +705,7 @@ describe('OptimizelyUserContext', () => { ).toEqual(true); }); - it.only('should NOT return forced decision object when forced decision is set for a delivery rule', () => { + it('should NOT return forced decision object when forced decision is set for a delivery rule', () => { const user = optlyInstance.createUserContext(userId); const featureKey = 'feature_1'; const variationKey = 'invalid'; @@ -744,7 +736,7 @@ describe('OptimizelyUserContext', () => { vi.spyOn(optlyInstance.notificationCenter, 'sendNotifications'); }); - it.only('should prioritize flag forced decision over experiment rule', () => { + it('should prioritize flag forced decision over experiment rule', () => { const user = optlyInstance.createUserContext(userId); const featureKey = 'feature_1'; const flagVariationKey = '3324490562'; @@ -762,23 +754,11 @@ describe('OptimizelyUserContext', () => { }); describe('getForcedDecision', () => { - it.only('should return correct forced variation', () => { - const { optlyInstance, createdLogger, eventDispatcher } = getOptlyInstance({ + it('should return correct forced variation', () => { + const { optlyInstance } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig(), }); - // const createdLogger = getMockLogger(); - // const eventDispatcher = getMockEventDispatcher(); - // const eventProcessor = getForwardingEventProcessor(eventDispatcher); - // const optlyInstance = new Optimizely({ - // clientEngine: 'node-sdk', - // projectConfigManager: getMockProjectConfigManager({ - // initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), - // }), - // eventProcessor, - // cmabService: {} as any, - // logger: createdLogger, - // }); const user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -804,26 +784,34 @@ describe('OptimizelyUserContext', () => { }); }); - describe('#removeForcedDecision', () => { + describe('removeForcedDecision', () => { it('should return true when client is not ready and the forced decision has been removed successfully', () => { - fakeOptimizely = { - isValidInstance: vi.fn().mockReturnValue(false), + const fakeOptimizely: any = { + onReady(): Promise { + return resolvablePromise().promise; + }, + onRunning(): Promise { + return resolvablePromise().promise; + }, }; + const user = new OptimizelyUserContext({ optimizely: fakeOptimizely, userId: 'user123', }); + user.setForcedDecision({ flagKey: 'feature_1' }, { variationKey: '3324490562' }); const result = user.removeForcedDecision({ flagKey: 'feature_1' }); expect(result).toEqual(true); }); it('should return true when the forced decision has been removed successfully and false otherwise', () => { - fakeOptimizely = { - isValidInstance: vi.fn().mockReturnValue(true), - }; + const { optlyInstance } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + const user = new OptimizelyUserContext({ - optimizely: fakeOptimizely, + optimizely: optlyInstance, userId: 'user123', }); user.setForcedDecision({ flagKey: 'feature_1' }, { variationKey: '3324490562' }); @@ -835,11 +823,12 @@ describe('OptimizelyUserContext', () => { }); it('should successfully remove forced decision when multiple forced decisions set with same feature key', () => { - fakeOptimizely = { - isValidInstance: vi.fn().mockReturnValue(true), - }; + const { optlyInstance } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + const user = new OptimizelyUserContext({ - optimizely: fakeOptimizely, + optimizely: optlyInstance, userId: 'user123', }); @@ -864,11 +853,17 @@ describe('OptimizelyUserContext', () => { }); }); - describe('#removeAllForcedDecisions', () => { + describe('removeAllForcedDecisions', () => { it('should return true when client is not ready', () => { - fakeOptimizely = { - isValidInstance: vi.fn().mockReturnValue(false), + const fakeOptimizely: any = { + onReady(): Promise { + return resolvablePromise().promise; + }, + onRunning(): Promise { + return resolvablePromise().promise; + }, }; + const user = new OptimizelyUserContext({ optimizely: fakeOptimizely, userId, @@ -878,17 +873,16 @@ describe('OptimizelyUserContext', () => { }); it('should return true when all forced decisions have been removed successfully', () => { - fakeOptimizely = { - isValidInstance: vi.fn().mockReturnValue(true), - }; + const { optlyInstance } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + const user = new OptimizelyUserContext({ - optimizely: fakeOptimizely, + optimizely: optlyInstance, userId, }); user.setForcedDecision({ flagKey: 'feature_1' }, { variationKey: '3324490562' }); user.setForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' }, { variationKey: 'b' }); - expect(Object.keys((user as any).forcedDecisionsMap).length).toEqual(1); - expect(Object.keys((user as any).forcedDecisionsMap['feature_1']).length).toEqual(2); expect(user.getForcedDecision({ flagKey: 'feature_1' })).toEqual({ variationKey: '3324490562' }); expect(user.getForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' })).toEqual({ @@ -897,7 +891,6 @@ describe('OptimizelyUserContext', () => { const result1 = user.removeAllForcedDecisions(); expect(result1).toEqual(true); - expect(Object.keys((user as any).forcedDecisionsMap).length).toEqual(0); expect(user.getForcedDecision({ flagKey: 'feature_1' })).toEqual(null); expect(user.getForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' })).toEqual(null); @@ -906,7 +899,7 @@ describe('OptimizelyUserContext', () => { describe('fetchQualifiedSegments', () => { it('should successfully get segments', async () => { - fakeOptimizely = { + const fakeOptimizely: any = { fetchQualifiedSegments: vi.fn().mockResolvedValue(['a']), }; const user = new OptimizelyUserContext({ @@ -922,8 +915,8 @@ describe('OptimizelyUserContext', () => { expect(user.qualifiedSegments).toEqual(['a']); }); - it('should return true empty returned segements', async () => { - fakeOptimizely = { + it('should return true for empty returned segements', async () => { + const fakeOptimizely: any = { fetchQualifiedSegments: vi.fn().mockResolvedValue([]), }; const user = new OptimizelyUserContext({ @@ -936,7 +929,7 @@ describe('OptimizelyUserContext', () => { }); it('should return false in other cases', async () => { - fakeOptimizely = { + const fakeOptimizely: any = { fetchQualifiedSegments: vi.fn().mockResolvedValue(null), }; const user = new OptimizelyUserContext({ @@ -952,7 +945,7 @@ describe('OptimizelyUserContext', () => { describe('isQualifiedFor', () => { it('should successfully return the expected result for if a user is qualified for a particular segment or not', () => { const user = new OptimizelyUserContext({ - optimizely: fakeOptimizely, + optimizely: {} as any, userId, }); From 3b198d5c3e3648de987939475563a8b9764cac2d Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Wed, 3 Dec 2025 22:36:02 +0600 Subject: [PATCH 4/6] rm --- vitest.config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vitest.config.mts b/vitest.config.mts index d6ec0160e..1bce36eb0 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -26,7 +26,7 @@ export default defineConfig({ test: { onConsoleLog: () => true, environment: 'happy-dom', - include: ['**/optimizely_user_context/*.spec.ts'], + include: ['**/*.spec.ts'], typecheck: { enabled: true, tsconfig: 'tsconfig.spec.json', From 091eb40945980b88c94e8f266b0a691e4d14d5de Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Fri, 5 Dec 2025 19:39:48 +0600 Subject: [PATCH 5/6] up --- lib/optimizely_user_context/index.spec.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/optimizely_user_context/index.spec.ts b/lib/optimizely_user_context/index.spec.ts index d6a6d9d53..303c25d98 100644 --- a/lib/optimizely_user_context/index.spec.ts +++ b/lib/optimizely_user_context/index.spec.ts @@ -14,18 +14,17 @@ * limitations under the License. */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { describe, it, beforeEach, afterEach, expect, vi, Mocked } from 'vitest'; -import { sprintf } from '../utils/fns'; +import { beforeEach, describe, expect, it, Mocked, vi } from 'vitest'; +import { getForwardingEventProcessor } from '../event_processor/event_processor_factory'; import { NOTIFICATION_TYPES } from '../notification_center/type'; -import OptimizelyUserContext from './'; import Optimizely from '../optimizely'; -import testData from '../tests/test_data'; -import { EventDispatcher, NotificationCenter, OptimizelyDecideOption } from '../shared_types'; -import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import { createProjectConfig } from '../project_config/project_config'; -import { getForwardingEventProcessor } from '../event_processor/event_processor_factory'; -import { FORCED_DECISION_NULL_RULE_KEY } from './index'; +import { OptimizelyDecideOption } from '../shared_types'; import { getMockLogger } from '../tests/mock/mock_logger'; +import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; +import testData from '../tests/test_data'; +import { sprintf } from '../utils/fns'; +import OptimizelyUserContext from './'; import { USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, @@ -33,9 +32,9 @@ import { USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, } from '../core/decision_service'; -import { resolvablePromise } from '../utils/promise/resolvablePromise'; import { LogEvent } from '../event_processor/event_dispatcher/event_dispatcher'; import { DefaultNotificationCenter } from '../notification_center'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; const getMockEventDispatcher = () => { const dispatcher = { @@ -915,7 +914,7 @@ describe('OptimizelyUserContext', () => { expect(user.qualifiedSegments).toEqual(['a']); }); - it('should return true for empty returned segements', async () => { + it('should return true for empty returned segments', async () => { const fakeOptimizely: any = { fetchQualifiedSegments: vi.fn().mockResolvedValue([]), }; From 68d66186c1f00a6e87b7d0c45378eb94a5c06bcc Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Fri, 5 Dec 2025 19:56:19 +0600 Subject: [PATCH 6/6] up --- lib/optimizely_user_context/index.spec.ts | 119 +++++++++++++++++++++- 1 file changed, 114 insertions(+), 5 deletions(-) diff --git a/lib/optimizely_user_context/index.spec.ts b/lib/optimizely_user_context/index.spec.ts index 303c25d98..5f451ccb4 100644 --- a/lib/optimizely_user_context/index.spec.ts +++ b/lib/optimizely_user_context/index.spec.ts @@ -70,7 +70,7 @@ const getOptlyInstance = ({ datafileObj, defaultDecideOptions }: GetOptlyInstanc describe('OptimizelyUserContext', () => { const userId = 'tester'; - const options = ['fakeOption']; + const options = [OptimizelyDecideOption.IGNORE_CMAB_CACHE]; describe('setAttribute', () => { let fakeOptimizely: any; @@ -208,12 +208,37 @@ describe('OptimizelyUserContext', () => { optimizely: fakeOptimizely, userId, }); - const decision = user.decide(flagKey, options as any); + const decision = user.decide(flagKey, options); expect(fakeOptimizely.decide).toHaveBeenCalledWith(user, flagKey, options); expect(decision).toEqual(fakeDecision); }); }); + describe('decideAsync', () => { + it('should return an expected decision object', async () => { + const flagKey = 'feature_1'; + const fakeDecision = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: {}, + ruleKey: 'exp_no_audience', + flagKey: flagKey, + userContext: 'fakeUserContext', + reasons: [], + }; + const fakeOptimizely: any = { + decideAsync: vi.fn().mockResolvedValue(fakeDecision), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + const decision = await user.decideAsync(flagKey, options); + expect(fakeOptimizely.decideAsync).toHaveBeenCalledWith(user, flagKey, options); + expect(decision).toEqual(fakeDecision); + }); + }); + describe('decideForKeys', () => { it('should return an expected decision results object', () => { const flagKey1 = 'feature_1'; @@ -245,12 +270,49 @@ describe('OptimizelyUserContext', () => { optimizely: fakeOptimizely, userId, }); - const decisionMap = user.decideForKeys([flagKey1, flagKey2], options as any); + const decisionMap = user.decideForKeys([flagKey1, flagKey2], options); expect(fakeOptimizely.decideForKeys).toHaveBeenCalledWith(user, [flagKey1, flagKey2], options); expect(decisionMap).toEqual(fakeDecisionMap); }); }); + describe('decideForKeysAsync', () => { + it('should return an expected decision results object', async () => { + const flagKey1 = 'feature_1'; + const flagKey2 = 'feature_2'; + const fakeDecisionMap = { + flagKey1: { + variationKey: '18257766532', + enabled: true, + variables: {}, + ruleKey: '18322080788', + flagKey: flagKey1, + userContext: 'fakeUserContext', + reasons: [], + }, + flagKey2: { + variationKey: 'variation_with_traffic', + enabled: true, + variables: {}, + ruleKey: 'exp_no_audience', + flagKey: flagKey2, + userContext: 'fakeUserContext', + reasons: [], + }, + }; + const fakeOptimizely: any = { + decideForKeysAsync: vi.fn().mockResolvedValue(fakeDecisionMap), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + const decisionMap = await user.decideForKeysAsync([flagKey1, flagKey2], options); + expect(fakeOptimizely.decideForKeysAsync).toHaveBeenCalledWith(user, [flagKey1, flagKey2], options); + expect(decisionMap).toEqual(fakeDecisionMap); + }); + }); + describe('decideAll', () => { it('should return an expected decision results object', () => { const flagKey1 = 'feature_1'; @@ -292,12 +354,59 @@ describe('OptimizelyUserContext', () => { optimizely: fakeOptimizely, userId, }); - const decisionMap = user.decideAll(options as any); + const decisionMap = user.decideAll(options); expect(fakeOptimizely.decideAll).toHaveBeenCalledWith(user, options); expect(decisionMap).toEqual(fakeDecisionMap); }); }); + describe('decideAllAsync', () => { + it('should return an expected decision results object', async () => { + const flagKey1 = 'feature_1'; + const flagKey2 = 'feature_2'; + const flagKey3 = 'feature_3'; + const fakeDecisionMap: any = { + flagKey1: { + variationKey: '18257766532', + enabled: true, + variables: {}, + ruleKey: '18322080788', + flagKey: flagKey1, + userContext: 'fakeUserContext', + reasons: [], + }, + flagKey2: { + variationKey: 'variation_with_traffic', + enabled: true, + variables: {}, + ruleKey: 'exp_no_audience', + flagKey: flagKey2, + userContext: 'fakeUserContext', + reasons: [], + }, + flagKey3: { + variationKey: '', + enabled: false, + variables: {}, + ruleKey: '', + flagKey: flagKey3, + userContext: undefined, + reasons: [], + }, + }; + const fakeOptimizely: any = { + decideAllAsync: vi.fn().mockResolvedValue(fakeDecisionMap), + }; + const user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + const decisionMap = await user.decideAllAsync(options); + expect(fakeOptimizely.decideAllAsync).toHaveBeenCalledWith(user, options); + expect(decisionMap).toEqual(fakeDecisionMap); + }); + }); + describe('trackEvent', () => { it('should call track from optimizely client', () => { const fakeOptimizely: any = { @@ -393,7 +502,7 @@ describe('OptimizelyUserContext', () => { }); user.setForcedDecision({ flagKey: flagKey, ruleKey }, { variationKey }); - const decision = user.decide(flagKey, options as any); + const decision = user.decide(flagKey, options); expect(decision.variationKey).toEqual(variationKey); expect(decision.ruleKey).toEqual(ruleKey);