From 63508b24c62a870f39420aae52aa2ce9acd03f11 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 17 Mar 2025 20:39:07 +0600 Subject: [PATCH 01/12] [FSSDK-11238] notification_center test --- lib/notification_center/index.spec.ts | 606 ++++++++++++++++++++++++++ 1 file changed, 606 insertions(+) create mode 100644 lib/notification_center/index.spec.ts diff --git a/lib/notification_center/index.spec.ts b/lib/notification_center/index.spec.ts new file mode 100644 index 000000000..74c082056 --- /dev/null +++ b/lib/notification_center/index.spec.ts @@ -0,0 +1,606 @@ +/** + * 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 + * + * http://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. + */ + +import { describe, beforeEach, it, vi, expect } from 'vitest'; +import { createNotificationCenter, DefaultNotificationCenter } from './'; +import { + ActivateListenerPayload, + DecisionListenerPayload, + LogEventListenerPayload, + NOTIFICATION_TYPES, + TrackListenerPayload, + OptimizelyConfigUpdateListenerPayload, +} from './type'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { LoggerFacade } from '../logging/logger'; + +describe('addNotificationListener', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should return -1 if notification type is not a valid type', () => { + const INVALID_LISTENER_TYPE = 'INVALID_LISTENER_TYPE' as const; + const mockFn = vi.fn(); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const listenerId = notificationCenterInstance.addNotificationListener(INVALID_LISTENER_TYPE, mockFn); + + expect(listenerId).toBe(-1); + }); + + it('should return an id (listernId) > 0 of the notification listener if callback is not already added', () => { + let activateCallback; + let decisionCallback; + let logEventCallback; + let configUpdateCallback; + let trackCallback; + // store a listenerId for each type + const activateListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.ACTIVATE, + activateCallback + ); + const decisionListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + decisionCallback + ); + const logEventListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.LOG_EVENT, + logEventCallback + ); + const configUpdateListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallback + ); + const trackListenerId = notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallback); + + expect(activateListenerId).toBeGreaterThan(0); + expect(decisionListenerId).toBeGreaterThan(0); + expect(logEventListenerId).toBeGreaterThan(0); + expect(configUpdateListenerId).toBeGreaterThan(0); + expect(trackListenerId).toBeGreaterThan(0); + }); +}); + +describe('removeNotificationListener', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should return false if listernId does not exist', () => { + const notListenerId = notificationCenterInstance.removeNotificationListener(5); + + expect(notListenerId).toBe(false); + }); + + it('should return true when eixsting listener is removed', () => { + let activateCallback; + let decisionCallback; + let logEventCallback; + let configUpdateCallback; + let trackCallback; + // add listeners for each type + const activateListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.ACTIVATE, + activateCallback + ); + const decisionListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + decisionCallback + ); + const logEventListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.LOG_EVENT, + logEventCallback + ); + const configListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallback + ); + const trackListenerId = notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallback); + // remove listeners for each type + const activateListenerRemoved = notificationCenterInstance.removeNotificationListener(activateListenerId); + const decisionListenerRemoved = notificationCenterInstance.removeNotificationListener(decisionListenerId); + const logEventListenerRemoved = notificationCenterInstance.removeNotificationListener(logEventListenerId); + const trackListenerRemoved = notificationCenterInstance.removeNotificationListener(trackListenerId); + const configListenerRemoved = notificationCenterInstance.removeNotificationListener(configListenerId); + + expect(activateListenerRemoved).toBe(true); + expect(decisionListenerRemoved).toBe(true); + expect(logEventListenerRemoved).toBe(true); + expect(trackListenerRemoved).toBe(true); + expect(configListenerRemoved).toBe(true); + }); + it('should only remove the specified listener', () => { + const activateCallbackSpy1 = vi.fn(); + const activateCallbackSpy2 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const decisionCallbackSpy2 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const logEventCallbackSpy2 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy2 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + const trackCallbackSpy2 = vi.fn(); + // register listeners for each type + const activateListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.ACTIVATE, + activateCallbackSpy1 + ); + const decisionListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + decisionCallbackSpy1 + ); + const logeventlistenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.LOG_EVENT, + logEventCallbackSpy1 + ); + const configUpdateListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + const trackListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.TRACK, + trackCallbackSpy1 + ); + // register second listeners for each type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy2 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + // remove first listener + const activateListenerRemoved1 = notificationCenterInstance.removeNotificationListener(activateListenerId1); + const decisionListenerRemoved1 = notificationCenterInstance.removeNotificationListener(decisionListenerId1); + const logEventListenerRemoved1 = notificationCenterInstance.removeNotificationListener(logeventlistenerId1); + const configUpdateListenerRemoved1 = notificationCenterInstance.removeNotificationListener(configUpdateListenerId1); + const trackListenerRemoved1 = notificationCenterInstance.removeNotificationListener(trackListenerId1); + // send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(activateListenerRemoved1).toBe(true); + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(activateCallbackSpy2).toHaveBeenCalledTimes(1); + expect(decisionListenerRemoved1).toBe(true); + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy2).toHaveBeenCalledTimes(1); + expect(logEventListenerRemoved1).toBe(true); + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy2).toHaveBeenCalledTimes(1); + expect(configUpdateListenerRemoved1).toBe(true); + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy2).toHaveBeenCalledTimes(1); + expect(trackListenerRemoved1).toBe(true); + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy2).toHaveBeenCalledTimes(1); + }); +}); + +describe('clearAllNotificationListeners', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should remove all notification listeners for all types', () => { + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove all listeners + notificationCenterInstance.clearAllNotificationListeners(); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + }); +}); + +describe('clearNotificationListeners', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should remove all notification listeners for the ACTIVATE type', () => { + const activateCallbackSpy1 = vi.fn(); + const activateCallbackSpy2 = vi.fn(); + //add 2 different listeners for ACTIVATE + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + // remove ACTIVATE listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(activateCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the DECISION type', () => { + const decisionCallbackSpy1 = vi.fn(); + const decisionCallbackSpy2 = vi.fn(); + //add 2 different listeners for DECISION + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + // remove DECISION listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.DECISION); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the LOG_EVENT type', () => { + const logEventCallbackSpy1 = vi.fn(); + const logEventCallbackSpy2 = vi.fn(); + //add 2 different listeners for LOG_EVENT + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + // remove LOG_EVENT listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.LOG_EVENT); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the OPTIMIZELY_CONFIG_UPDATE type', () => { + const configUpdateCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy2 = vi.fn(); + //add 2 different listeners for OPTIMIZELY_CONFIG_UPDATE + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy2 + ); + // remove OPTIMIZELY_CONFIG_UPDATE listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + // trigger send notifications + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the TRACK type', () => { + const trackCallbackSpy1 = vi.fn(); + const trackCallbackSpy2 = vi.fn(); + //add 2 different listeners for TRACK + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + // remove TRACK listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.TRACK); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should only remove ACTIVATE type listeners and not any other types', () => { + const activateCallbackSpy1 = vi.fn(); + const activateCallbackSpy2 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + //add 2 different listeners for ACTIVATE + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only ACTIVATE type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(activateCallbackSpy2).not.toHaveBeenCalled(); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove DECISION type listeners and not any other types', () => { + const decisionCallbackSpy1 = vi.fn(); + const decisionCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add 2 different listeners for DECISION + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only DECISION type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.DECISION); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove LOG_EVENT type listeners and not any other types', () => { + const logEventCallbackSpy1 = vi.fn(); + const logEventCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add 2 different listeners for LOG_EVENT + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only LOG_EVENT type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.LOG_EVENT); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove OPTIMIZELY_CONFIG_UPDATE type listeners and not any other types', () => { + const configUpdateCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add 2 different listeners for OPTIMIZELY_CONFIG_UPDATE + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy2 + ); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only OPTIMIZELY_CONFIG_UPDATE type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove TRACK type listeners and not any other types', () => { + const trackCallbackSpy1 = vi.fn(); + const trackCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + // add 2 different listeners for TRACK + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + // remove only TRACK type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.TRACK); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + }); +}); + +describe('sendNotifications', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + it('should call the listener callback with exact arguments', () => { + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // listener object data for each type + const activateData = { + experiment: {}, + userId: '', + attributes: {}, + variation: {}, + logEvent: {}, + }; + const decisionData = { + type: '', + userId: 'use1', + attributes: {}, + decisionInfo: {}, + }; + const logEventData = { + url: '', + httpVerb: '', + params: {}, + }; + const configUpdateData = {}; + const trackData = { + eventKey: '', + userId: '', + attributes: {}, + eventTags: {}, + }; + // add listeners + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, activateData as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, decisionData as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, logEventData as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + (configUpdateData as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, trackData as TrackListenerPayload); + + expect(activateCallbackSpy1).toHaveBeenCalledWith(activateData); + expect(decisionCallbackSpy1).toHaveBeenCalledWith(decisionData); + expect(logEventCallbackSpy1).toHaveBeenCalledWith(logEventData); + expect(configUpdateCallbackSpy1).toHaveBeenCalledWith(configUpdateData); + expect(trackCallbackSpy1).toHaveBeenCalledWith(trackData); + }); +}); From c30743e45c3ecf409764ce275df4a36ca505d4ba Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 18 Mar 2025 00:03:00 +0600 Subject: [PATCH 02/12] [FSSDK-11238] type fix --- lib/notification_center/index.spec.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/notification_center/index.spec.ts b/lib/notification_center/index.spec.ts index 74c082056..4ba54a0c3 100644 --- a/lib/notification_center/index.spec.ts +++ b/lib/notification_center/index.spec.ts @@ -48,11 +48,11 @@ describe('addNotificationListener', () => { }); it('should return an id (listernId) > 0 of the notification listener if callback is not already added', () => { - let activateCallback; - let decisionCallback; - let logEventCallback; - let configUpdateCallback; - let trackCallback; + const activateCallback = vi.fn(); + const decisionCallback = vi.fn(); + const logEventCallback = vi.fn(); + const configUpdateCallback = vi.fn(); + const trackCallback = vi.fn(); // store a listenerId for each type const activateListenerId = notificationCenterInstance.addNotificationListener( NOTIFICATION_TYPES.ACTIVATE, @@ -96,11 +96,11 @@ describe('removeNotificationListener', () => { }); it('should return true when eixsting listener is removed', () => { - let activateCallback; - let decisionCallback; - let logEventCallback; - let configUpdateCallback; - let trackCallback; + const activateCallback = vi.fn(); + const decisionCallback = vi.fn(); + const logEventCallback = vi.fn(); + const configUpdateCallback = vi.fn(); + const trackCallback = vi.fn(); // add listeners for each type const activateListenerId = notificationCenterInstance.addNotificationListener( NOTIFICATION_TYPES.ACTIVATE, From 04ab78d8b5516d7d561143b6e71453b77cfbedb6 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 18 Mar 2025 19:31:04 +0600 Subject: [PATCH 03/12] [FSSDK-11238] attributes validator test addition --- lib/utils/attributes_validator/index.spec.ts | 87 ++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 lib/utils/attributes_validator/index.spec.ts diff --git a/lib/utils/attributes_validator/index.spec.ts b/lib/utils/attributes_validator/index.spec.ts new file mode 100644 index 000000000..300f47ebb --- /dev/null +++ b/lib/utils/attributes_validator/index.spec.ts @@ -0,0 +1,87 @@ +/** + * 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 + * + * http://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. + */ + +import { describe, it, expect } from 'vitest'; +import * as attributesValidator from './'; +import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +describe('validate', () => { + it('should validate the given attributes if attributes is an object', () => { + // assert.isTrue(attributesValidator.validate({ testAttribute: 'testValue' })); + expect(attributesValidator.validate({ testAttribute: 'testValue' })).toBe(true); + }); + + it('should throw an error if attributes is an array', () => { + const attributesArray = ['notGonnaWork']; + + expect(() => attributesValidator.validate(attributesArray)).toThrowError(new OptimizelyError(INVALID_ATTRIBUTES)); + }); + + it('should throw an error if attributes is null', () => { + expect(() => attributesValidator.validate(null)).toThrowError(new OptimizelyError(INVALID_ATTRIBUTES)); + }); + + it('should throw an error if attributes is a function', () => { + function invalidInput() { + console.log('This is an invalid input!'); + } + + expect(() => attributesValidator.validate(invalidInput)).toThrowError(new OptimizelyError(INVALID_ATTRIBUTES)); + }); + + it('should throw an error if attributes contains a key with an undefined value', () => { + const attributeKey = 'testAttribute'; + const attributes: Record = {}; + attributes[attributeKey] = undefined; + + expect(() => attributesValidator.validate(attributes)).toThrowError(new OptimizelyError(UNDEFINED_ATTRIBUTE)); + }); +}); + +describe('isAttributeValid', () => { + it('isAttributeValid returns true for valid values', () => { + const userAttributes: Record = { + browser_type: 'Chrome', + is_firefox: false, + num_users: 10, + pi_value: 3.14, + '': 'javascript', + }; + + Object.keys(userAttributes).forEach((key) => { + const value = userAttributes[key]; + + expect(attributesValidator.isAttributeValid(key, value)).toBe(true); + }); + }); + it('isAttributeValid returns false for invalid values', () => { + const userAttributes: Record = { + null: null, + objects: { a: 'b' }, + array: [1, 2, 3], + infinity: Infinity, + negativeInfinity: -Infinity, + NaN: NaN, + }; + + Object.keys(userAttributes).forEach((key) => { + const value = userAttributes[key]; + + expect(attributesValidator.isAttributeValid(key, value)).toBe(false); + }); + }); +}); From c563da0d15a7448df2bfb20b347edc68ae6968d8 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 18 Mar 2025 19:48:41 +0600 Subject: [PATCH 04/12] [FSSDK-11238] config validator test addition --- lib/utils/config_validator/index.spec.ts | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 lib/utils/config_validator/index.spec.ts diff --git a/lib/utils/config_validator/index.spec.ts b/lib/utils/config_validator/index.spec.ts new file mode 100644 index 000000000..4d163d6a4 --- /dev/null +++ b/lib/utils/config_validator/index.spec.ts @@ -0,0 +1,45 @@ +/** + * 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 + * + * http://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. + */ + +import { describe, it, expect } from 'vitest'; +import configValidator from './'; +import testData from '../../tests/test_data'; +import { INVALID_DATAFILE_MALFORMED, INVALID_DATAFILE_VERSION, NO_DATAFILE_SPECIFIED } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +describe('validate', () => { + it('should complain if datafile is not provided', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => configValidator.validateDatafile()).toThrowError(new OptimizelyError(NO_DATAFILE_SPECIFIED)); + }); + + it('should complain if datafile is malformed', () => { + expect(() => configValidator.validateDatafile('abc')).toThrowError(new OptimizelyError(INVALID_DATAFILE_MALFORMED)); + }); + + it('should complain if datafile version is not supported', () => { + expect(() => + configValidator + .validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())) + .toThrowError(new OptimizelyError(INVALID_DATAFILE_VERSION)) + ); + }); + + it('should not complain if datafile is valid', function() { + expect(() => configValidator.validateDatafile(JSON.stringify(testData.getTestProjectConfig())).not.toThrowError()); + }); +}); From b0694d9d6a33bf8684af3a3fc302b8252452703c Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 18 Mar 2025 20:21:04 +0600 Subject: [PATCH 05/12] [FSSDK-11238] event tag utils test addition --- lib/utils/attributes_validator/index.spec.ts | 1 - lib/utils/event_tag_utils/index.spec.ts | 90 ++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 lib/utils/event_tag_utils/index.spec.ts diff --git a/lib/utils/attributes_validator/index.spec.ts b/lib/utils/attributes_validator/index.spec.ts index 300f47ebb..fa8d4d7f1 100644 --- a/lib/utils/attributes_validator/index.spec.ts +++ b/lib/utils/attributes_validator/index.spec.ts @@ -21,7 +21,6 @@ import { OptimizelyError } from '../../error/optimizly_error'; describe('validate', () => { it('should validate the given attributes if attributes is an object', () => { - // assert.isTrue(attributesValidator.validate({ testAttribute: 'testValue' })); expect(attributesValidator.validate({ testAttribute: 'testValue' })).toBe(true); }); diff --git a/lib/utils/event_tag_utils/index.spec.ts b/lib/utils/event_tag_utils/index.spec.ts new file mode 100644 index 000000000..b3d023a51 --- /dev/null +++ b/lib/utils/event_tag_utils/index.spec.ts @@ -0,0 +1,90 @@ +/** + * 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 + * + * http://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. + */ + +import { describe, it, expect, beforeEach } from 'vitest'; +import { sprintf } from '../../utils/fns'; + +import * as eventTagUtils from './'; +import { + FAILED_TO_PARSE_REVENUE, + PARSED_REVENUE_VALUE, + PARSED_NUMERIC_VALUE, + FAILED_TO_PARSE_VALUE, +} from 'log_message'; + +const buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { LoggerFacade } from '../../logging/logger'; + +describe('getRevenueValue', () => { + let logger: LoggerFacade; + + beforeEach(() => { + logger = getMockLogger(); + }); + + it('should return the parseed integer for a valid revenue value', () => { + let parsedRevenueValue = eventTagUtils.getRevenueValue({ revenue: '1337' }, logger); + + expect(parsedRevenueValue).toBe(1337); + expect(logger.info).toHaveBeenCalledWith(PARSED_REVENUE_VALUE, 1337); + + parsedRevenueValue = eventTagUtils.getRevenueValue({ revenue: '13.37' }, logger); + + expect(parsedRevenueValue).toBe(13); + }); + + it('should return null and log a message for invalid value', () => { + const parsedRevenueValue = eventTagUtils.getRevenueValue({ revenue: 'invalid' }, logger); + + expect(parsedRevenueValue).toBe(null); + expect(logger.info).toHaveBeenCalledWith(FAILED_TO_PARSE_REVENUE, 'invalid'); + }); + + it('should return null if the revenue value is not present in the event tags', () => { + const parsedRevenueValue = eventTagUtils.getRevenueValue({ not_revenue: '1337' }, logger); + + expect(parsedRevenueValue).toBe(null); + }); +}); + +describe('getEventValue', () => { + let logger: LoggerFacade; + + beforeEach(() => { + logger = getMockLogger(); + }); + + it('should return the parsed integer for a valid numeric value', () => { + const parsedNumericValue = eventTagUtils.getEventValue({ value: '1337' }, logger); + + expect(parsedNumericValue).toBe(1337); + expect(logger.info).toHaveBeenCalledWith(PARSED_NUMERIC_VALUE, 1337); + }); + + it('should return null and log a message for invalid value', () => { + const parsedNumericValue = eventTagUtils.getEventValue({ value: 'invalid' }, logger); + + expect(parsedNumericValue).toBe(null); + expect(logger.info).toHaveBeenCalledWith(FAILED_TO_PARSE_VALUE, 'invalid'); + }); + + it('should return null if the value is not present in the event tags', () => { + const parsedNumericValue = eventTagUtils.getEventValue({ not_value: '13.37' }, logger); + + expect(parsedNumericValue).toBe(null); + }); +}) From c0ae1bea5434e4d496ecaf92ad903f4dd35f9f07 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Wed, 19 Mar 2025 20:42:35 +0600 Subject: [PATCH 06/12] [FSSDK-11238] event tag validator test addition --- .../bucketer/bucket_value_generator.spec.ts | 12 +++- lib/core/bucketer/index.spec.ts | 11 +++- lib/project_config/project_config.spec.ts | 3 - lib/utils/attributes_validator/index.spec.ts | 42 ++++++++++--- lib/utils/config_validator/index.spec.ts | 35 ++++++++--- lib/utils/event_tags_validator/index.spec.ts | 63 +++++++++++++++++++ 6 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 lib/utils/event_tags_validator/index.spec.ts diff --git a/lib/core/bucketer/bucket_value_generator.spec.ts b/lib/core/bucketer/bucket_value_generator.spec.ts index a7662e1f0..e68db6348 100644 --- a/lib/core/bucketer/bucket_value_generator.spec.ts +++ b/lib/core/bucketer/bucket_value_generator.spec.ts @@ -36,8 +36,14 @@ describe('generateBucketValue', () => { it('should return an error if it cannot generate the hash value', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - expect(() => generateBucketValue(null)).toThrowError( - new OptimizelyError(INVALID_BUCKETING_ID) - ); + expect(() => generateBucketValue(null)).toThrow(OptimizelyError); + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + generateBucketValue(null); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_BUCKETING_ID); + } }); }); diff --git a/lib/core/bucketer/index.spec.ts b/lib/core/bucketer/index.spec.ts index 36f23b2eb..b3aac5158 100644 --- a/lib/core/bucketer/index.spec.ts +++ b/lib/core/bucketer/index.spec.ts @@ -198,9 +198,14 @@ describe('including groups: random', () => { const bucketerParamsWithInvalidGroupId = cloneDeep(bucketerParams); bucketerParamsWithInvalidGroupId.experimentIdMap[configObj.experiments[4].id].groupId = '6969'; - expect(() => bucketer.bucket(bucketerParamsWithInvalidGroupId)).toThrowError( - new OptimizelyError(INVALID_GROUP_ID, '6969') - ); + expect(()=> bucketer.bucket(bucketerParamsWithInvalidGroupId)).toThrow(OptimizelyError); + + try { + bucketer.bucket(bucketerParamsWithInvalidGroupId); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_GROUP_ID); + } }); }); diff --git a/lib/project_config/project_config.spec.ts b/lib/project_config/project_config.spec.ts index bb5370ef4..36ffbe89a 100644 --- a/lib/project_config/project_config.spec.ts +++ b/lib/project_config/project_config.spec.ts @@ -322,9 +322,6 @@ describe('getLayerId', () => { }); it('should throw error for invalid experiment key in getLayerId', function() { - // expect(() => projectConfig.getLayerId(configObj, 'invalidExperimentKey')).toThrowError( - // sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentKey') - // ); expect(() => projectConfig.getLayerId(configObj, 'invalidExperimentKey')).toThrowError( expect.objectContaining({ baseMessage: INVALID_EXPERIMENT_ID, diff --git a/lib/utils/attributes_validator/index.spec.ts b/lib/utils/attributes_validator/index.spec.ts index fa8d4d7f1..58163b55d 100644 --- a/lib/utils/attributes_validator/index.spec.ts +++ b/lib/utils/attributes_validator/index.spec.ts @@ -27,11 +27,25 @@ describe('validate', () => { it('should throw an error if attributes is an array', () => { const attributesArray = ['notGonnaWork']; - expect(() => attributesValidator.validate(attributesArray)).toThrowError(new OptimizelyError(INVALID_ATTRIBUTES)); + expect(() => attributesValidator.validate(attributesArray)).toThrow(OptimizelyError); + + try { + attributesValidator.validate(attributesArray); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_ATTRIBUTES); + } }); it('should throw an error if attributes is null', () => { - expect(() => attributesValidator.validate(null)).toThrowError(new OptimizelyError(INVALID_ATTRIBUTES)); + expect(() => attributesValidator.validate(null)).toThrowError(OptimizelyError); + + try { + attributesValidator.validate(null); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_ATTRIBUTES); + } }); it('should throw an error if attributes is a function', () => { @@ -39,7 +53,14 @@ describe('validate', () => { console.log('This is an invalid input!'); } - expect(() => attributesValidator.validate(invalidInput)).toThrowError(new OptimizelyError(INVALID_ATTRIBUTES)); + expect(() => attributesValidator.validate(invalidInput)).toThrowError(OptimizelyError); + + try { + attributesValidator.validate(invalidInput); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_ATTRIBUTES); + } }); it('should throw an error if attributes contains a key with an undefined value', () => { @@ -47,7 +68,14 @@ describe('validate', () => { const attributes: Record = {}; attributes[attributeKey] = undefined; - expect(() => attributesValidator.validate(attributes)).toThrowError(new OptimizelyError(UNDEFINED_ATTRIBUTE)); + expect(() => attributesValidator.validate(attributes)).toThrowError(OptimizelyError); + + try { + attributesValidator.validate(attributes); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(UNDEFINED_ATTRIBUTE); + } }); }); @@ -61,7 +89,7 @@ describe('isAttributeValid', () => { '': 'javascript', }; - Object.keys(userAttributes).forEach((key) => { + Object.keys(userAttributes).forEach(key => { const value = userAttributes[key]; expect(attributesValidator.isAttributeValid(key, value)).toBe(true); @@ -77,10 +105,10 @@ describe('isAttributeValid', () => { NaN: NaN, }; - Object.keys(userAttributes).forEach((key) => { + Object.keys(userAttributes).forEach(key => { const value = userAttributes[key]; - expect(attributesValidator.isAttributeValid(key, value)).toBe(false); + expect(attributesValidator.isAttributeValid(key, value)).toBe(false); }); }); }); diff --git a/lib/utils/config_validator/index.spec.ts b/lib/utils/config_validator/index.spec.ts index 4d163d6a4..b5a2f51b9 100644 --- a/lib/utils/config_validator/index.spec.ts +++ b/lib/utils/config_validator/index.spec.ts @@ -24,22 +24,41 @@ describe('validate', () => { it('should complain if datafile is not provided', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - expect(() => configValidator.validateDatafile()).toThrowError(new OptimizelyError(NO_DATAFILE_SPECIFIED)); + expect(() => configValidator.validateDatafile()).toThrow(OptimizelyError); + + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + configValidator.validateDatafile(); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(NO_DATAFILE_SPECIFIED); + } }); it('should complain if datafile is malformed', () => { - expect(() => configValidator.validateDatafile('abc')).toThrowError(new OptimizelyError(INVALID_DATAFILE_MALFORMED)); + expect(() => configValidator.validateDatafile('abc')).toThrow( OptimizelyError); + + try { + configValidator.validateDatafile('abc'); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_DATAFILE_MALFORMED); + } }); it('should complain if datafile version is not supported', () => { - expect(() => - configValidator - .validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())) - .toThrowError(new OptimizelyError(INVALID_DATAFILE_VERSION)) - ); + expect(() => configValidator.validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())).toThrow(OptimizelyError)); + + try { + configValidator.validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_DATAFILE_VERSION); + } }); - it('should not complain if datafile is valid', function() { + it('should not complain if datafile is valid', () => { expect(() => configValidator.validateDatafile(JSON.stringify(testData.getTestProjectConfig())).not.toThrowError()); }); }); diff --git a/lib/utils/event_tags_validator/index.spec.ts b/lib/utils/event_tags_validator/index.spec.ts new file mode 100644 index 000000000..1b372ff0a --- /dev/null +++ b/lib/utils/event_tags_validator/index.spec.ts @@ -0,0 +1,63 @@ +/** + * 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 + * + * http://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. + */ +import { describe, it, expect, beforeEach } from 'vitest'; +import { validate } from '.'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { INVALID_EVENT_TAGS } from 'error_message'; + +describe('validate', () => { + it('should validate the given event tags if event tag is an object', () => { + expect(validate({ testAttribute: 'testValue' })).toBe(true); + }); + + it('should throw an error if event tags is an array', () => { + const eventTagsArray = ['notGonnaWork']; + + expect(() => validate(eventTagsArray)).toThrow(OptimizelyError) + + try { + validate(eventTagsArray); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_EVENT_TAGS); + } + }); + + it('should throw an error if event tags is null', () => { + expect(() => validate(null)).toThrow(OptimizelyError); + + try { + validate(null); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_EVENT_TAGS); + } + }); + + it('should throw an error if event tags is a function', () => { + function invalidInput() { + console.log('This is an invalid input!'); + } + expect(() => validate(invalidInput)).toThrow(OptimizelyError); + + try { + validate(invalidInput); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_EVENT_TAGS); + } + }); +}); From 848625e3ecaa896787972c1dd1b1b58215d9855c Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Wed, 19 Mar 2025 20:51:30 +0600 Subject: [PATCH 07/12] [FSSDK-11238] json_schema_validator test addition --- lib/utils/json_schema_validator/index.spec.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 lib/utils/json_schema_validator/index.spec.ts diff --git a/lib/utils/json_schema_validator/index.spec.ts b/lib/utils/json_schema_validator/index.spec.ts new file mode 100644 index 000000000..20af5b51d --- /dev/null +++ b/lib/utils/json_schema_validator/index.spec.ts @@ -0,0 +1,49 @@ +/** + * 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 + * + * http://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. + */ +import { describe, it, expect } from 'vitest'; +import { validate } from '.'; +import testData from '../../tests/test_data'; +import { NO_JSON_PROVIDED, INVALID_DATAFILE } from 'error_message'; + +describe('validate', () => { + it('should throw an error if the object is not valid', () => { + expect(() => validate({})).toThrow(); + + try { + validate({}); + } catch (err) { + expect(err.baseMessage).toBe(INVALID_DATAFILE); + } + }); + + it('should throw an error if no json object is passed in', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => validate()).toThrow(); + + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + validate(); + } catch (err) { + expect(err.baseMessage).toBe(NO_JSON_PROVIDED); + } + }); + + it('should validate specified Optimizely datafile', () => { + expect(validate(testData.getTestProjectConfig())).toBe(true); + }); +}); From 5e272cf96e0f1ad83520898fa3944fa150f8aef6 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Wed, 19 Mar 2025 21:59:25 +0600 Subject: [PATCH 08/12] [FSSDK-11238] semantic_version test addition --- lib/utils/event_tag_utils/index.spec.ts | 5 - lib/utils/semantic_version/index.spec.ts | 112 +++++++++++++++++++++++ 2 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 lib/utils/semantic_version/index.spec.ts diff --git a/lib/utils/event_tag_utils/index.spec.ts b/lib/utils/event_tag_utils/index.spec.ts index b3d023a51..0955655cc 100644 --- a/lib/utils/event_tag_utils/index.spec.ts +++ b/lib/utils/event_tag_utils/index.spec.ts @@ -13,10 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { describe, it, expect, beforeEach } from 'vitest'; -import { sprintf } from '../../utils/fns'; - import * as eventTagUtils from './'; import { FAILED_TO_PARSE_REVENUE, @@ -24,8 +21,6 @@ import { PARSED_NUMERIC_VALUE, FAILED_TO_PARSE_VALUE, } from 'log_message'; - -const buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); import { getMockLogger } from '../../tests/mock/mock_logger'; import { LoggerFacade } from '../../logging/logger'; diff --git a/lib/utils/semantic_version/index.spec.ts b/lib/utils/semantic_version/index.spec.ts new file mode 100644 index 000000000..15dbbdbb9 --- /dev/null +++ b/lib/utils/semantic_version/index.spec.ts @@ -0,0 +1,112 @@ +/** + * 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 + * + * http://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. + */ +import { describe, it, expect } from 'vitest'; +import * as semanticVersion from '.'; + +describe('compareVersion', () => { + it('should return 0 if user version and target version are equal', () => { + const versions = [ + ['2.0.1', '2.0.1'], + ['2.9.9-beta', '2.9.9-beta'], + ['2.1', '2.1.0'], + ['2', '2.12'], + ['2.9', '2.9.1'], + ['2.9+beta', '2.9+beta'], + ['2.9.9+beta', '2.9.9+beta'], + ['2.9.9+beta-alpha', '2.9.9+beta-alpha'], + ['2.2.3', '2.2.3+beta'], + ]; + + versions.forEach(([targetVersion, userVersion]) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(0); + }) + }); + + it('should return 1 when user version is greater than target version', () => { + const versions = [ + ['2.0.0', '2.0.1'], + ['2.0', '3.0.1'], + ['2.0.0', '2.1'], + ['2.1.2-beta', '2.1.2-release'], + ['2.1.3-beta1', '2.1.3-beta2'], + ['2.9.9-beta', '2.9.9'], + ['2.9.9+beta', '2.9.9'], + ['2.0.0', '2.1'], + ['3.7.0-prerelease+build', '3.7.0-prerelease+rc'], + ['2.2.3-beta-beta1', '2.2.3-beta-beta2'], + ['2.2.3-beta+beta1', '2.2.3-beta+beta2'], + ['2.2.3+beta2-beta1', '2.2.3+beta3-beta2'], + ['2.2.3+beta', '2.2.3'], + ]; + + versions.forEach(([targetVersion, userVersion]) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(1); + }) + }); + + it('should return -1 when user version is less than target version', () => { + const versions = [ + ['2.0.1', '2.0.0'], + ['3.0', '2.0.1'], + ['2.3', '2.0.1'], + ['2.3.5', '2.3.1'], + ['2.9.8', '2.9'], + ['3.1', '3'], + ['2.1.2-release', '2.1.2-beta'], + ['2.9.9+beta', '2.9.9-beta'], + ['3.7.0+build3.7.0-prerelease+build', '3.7.0-prerelease'], + ['2.1.3-beta-beta2', '2.1.3-beta'], + ['2.1.3-beta1+beta3', '2.1.3-beta1+beta2'], + ['2.1.3', '2.1.3-beta'], + ]; + + versions.forEach(([targetVersion, userVersion]) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(-1); + }) + }); + + it('should return null when user version is invalid', () => { + const versions = [ + '-', + '.', + '..', + '+', + '+test', + ' ', + '2 .3. 0', + '2.', + '.2.2', + '3.7.2.2', + '3.x', + ',', + '+build-prerelease', + '2..2', + ]; + const targetVersion = '2.1.0'; + + versions.forEach((userVersion) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(null); + }) + }); +}); From cd1f5508de864a719e5bc0da078307aac6d80022 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Thu, 20 Mar 2025 01:22:24 +0600 Subject: [PATCH 09/12] [FSSDK-11238] string value validator test addition --- .../string_value_validator/index.spec.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 lib/utils/string_value_validator/index.spec.ts diff --git a/lib/utils/string_value_validator/index.spec.ts b/lib/utils/string_value_validator/index.spec.ts new file mode 100644 index 000000000..87f6c2b3a --- /dev/null +++ b/lib/utils/string_value_validator/index.spec.ts @@ -0,0 +1,32 @@ +/** + * 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 + * + * http://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. + */ +import { describe, it, expect } from 'vitest'; +import { validate } from './'; + +describe('validate', () => { + it('should validate the given value is valid string', () => { + expect(validate('validStringValue')).toBe(true); + }); + + it('should return false if given value is invalid string', () => { + expect(validate(null)).toBe(false); + expect(validate(undefined)).toBe(false); + expect(validate('')).toBe(false); + expect(validate(5)).toBe(false); + expect(validate(true)).toBe(false); + expect(validate([])).toBe(false); + }); +}); From 864c78533fd5ef04253c836b9b431a521891f088 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Thu, 20 Mar 2025 01:31:45 +0600 Subject: [PATCH 10/12] [FSSDK-11238] user profile service validator --- .../index.spec.ts | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 lib/utils/user_profile_service_validator/index.spec.ts diff --git a/lib/utils/user_profile_service_validator/index.spec.ts b/lib/utils/user_profile_service_validator/index.spec.ts new file mode 100644 index 000000000..22ca1db07 --- /dev/null +++ b/lib/utils/user_profile_service_validator/index.spec.ts @@ -0,0 +1,96 @@ +/** + * 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 + * + * http://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. + */ +import { describe, it, expect } from 'vitest'; +import { validate } from './'; +import { INVALID_USER_PROFILE_SERVICE } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +describe('validate', () => { + it("should throw if the instance does not provide a 'lookup' function", () => { + const missingLookupFunction = { + save: function() {}, + }; + + expect(() => validate(missingLookupFunction)).toThrowError(OptimizelyError); + + try { + validate(missingLookupFunction); + } catch(err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'lookup'"]); + } + }); + + it("should throw if 'lookup' is not a function", () => { + const lookupNotFunction = { + save: function() {}, + lookup: 'notGonnaWork', + }; + + expect(() => validate(lookupNotFunction)).toThrowError(OptimizelyError); + + try { + validate(lookupNotFunction); + } catch(err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'lookup'"]); + } + }); + + it("should throw if the instance does not provide a 'save' function", () => { + const missingSaveFunction = { + lookup: function() {}, + }; + + expect(() => validate(missingSaveFunction)).toThrowError(OptimizelyError); + + try { + validate(missingSaveFunction); + } catch(err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'save'"]); + } + }); + + it("should throw if 'save' is not a function", () => { + const saveNotFunction = { + lookup: function() {}, + save: 'notGonnaWork', + }; + + expect(() => validate(saveNotFunction)).toThrowError(OptimizelyError); + + try { + validate(saveNotFunction); + } catch(err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'save'"]); + } + }); + + it('should return true if the instance is valid', () => { + const validInstance = { + save: function() {}, + lookup: function() {}, + }; + + expect(validate(validInstance)).toBe(true); + }); +}); From 123524eeefd26cedae39074387aef1f1edcec557 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Thu, 20 Mar 2025 01:34:42 +0600 Subject: [PATCH 11/12] [FSSDK-11238] space fix --- .../string_value_validator/index.spec.ts | 14 ++-- .../index.spec.ts | 66 +++++++++---------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/lib/utils/string_value_validator/index.spec.ts b/lib/utils/string_value_validator/index.spec.ts index 87f6c2b3a..a9c7f6a91 100644 --- a/lib/utils/string_value_validator/index.spec.ts +++ b/lib/utils/string_value_validator/index.spec.ts @@ -18,15 +18,15 @@ import { validate } from './'; describe('validate', () => { it('should validate the given value is valid string', () => { - expect(validate('validStringValue')).toBe(true); + expect(validate('validStringValue')).toBe(true); }); it('should return false if given value is invalid string', () => { - expect(validate(null)).toBe(false); - expect(validate(undefined)).toBe(false); - expect(validate('')).toBe(false); - expect(validate(5)).toBe(false); - expect(validate(true)).toBe(false); - expect(validate([])).toBe(false); + expect(validate(null)).toBe(false); + expect(validate(undefined)).toBe(false); + expect(validate('')).toBe(false); + expect(validate(5)).toBe(false); + expect(validate(true)).toBe(false); + expect(validate([])).toBe(false); }); }); diff --git a/lib/utils/user_profile_service_validator/index.spec.ts b/lib/utils/user_profile_service_validator/index.spec.ts index 22ca1db07..98a47ef60 100644 --- a/lib/utils/user_profile_service_validator/index.spec.ts +++ b/lib/utils/user_profile_service_validator/index.spec.ts @@ -24,15 +24,15 @@ describe('validate', () => { save: function() {}, }; - expect(() => validate(missingLookupFunction)).toThrowError(OptimizelyError); + expect(() => validate(missingLookupFunction)).toThrowError(OptimizelyError); - try { - validate(missingLookupFunction); - } catch(err) { - expect(err).instanceOf(OptimizelyError); - expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); - expect(err.params).toEqual(["Missing function 'lookup'"]); - } + try { + validate(missingLookupFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'lookup'"]); + } }); it("should throw if 'lookup' is not a function", () => { @@ -41,15 +41,15 @@ describe('validate', () => { lookup: 'notGonnaWork', }; - expect(() => validate(lookupNotFunction)).toThrowError(OptimizelyError); + expect(() => validate(lookupNotFunction)).toThrowError(OptimizelyError); - try { - validate(lookupNotFunction); - } catch(err) { - expect(err).instanceOf(OptimizelyError); - expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); - expect(err.params).toEqual(["Missing function 'lookup'"]); - } + try { + validate(lookupNotFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'lookup'"]); + } }); it("should throw if the instance does not provide a 'save' function", () => { @@ -57,15 +57,15 @@ describe('validate', () => { lookup: function() {}, }; - expect(() => validate(missingSaveFunction)).toThrowError(OptimizelyError); + expect(() => validate(missingSaveFunction)).toThrowError(OptimizelyError); - try { - validate(missingSaveFunction); - } catch(err) { - expect(err).instanceOf(OptimizelyError); - expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); - expect(err.params).toEqual(["Missing function 'save'"]); - } + try { + validate(missingSaveFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'save'"]); + } }); it("should throw if 'save' is not a function", () => { @@ -74,15 +74,15 @@ describe('validate', () => { save: 'notGonnaWork', }; - expect(() => validate(saveNotFunction)).toThrowError(OptimizelyError); + expect(() => validate(saveNotFunction)).toThrowError(OptimizelyError); - try { - validate(saveNotFunction); - } catch(err) { - expect(err).instanceOf(OptimizelyError); - expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); - expect(err.params).toEqual(["Missing function 'save'"]); - } + try { + validate(saveNotFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'save'"]); + } }); it('should return true if the instance is valid', () => { @@ -91,6 +91,6 @@ describe('validate', () => { lookup: function() {}, }; - expect(validate(validInstance)).toBe(true); + expect(validate(validInstance)).toBe(true); }); }); From 8f6c70bad6baa4b843bb86e94ec02e171eb3acfa Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Thu, 27 Mar 2025 12:53:05 +0600 Subject: [PATCH 12/12] update --- lib/utils/attributes_validator/index.spec.ts | 3 ++- lib/utils/config_validator/index.spec.ts | 1 + lib/utils/event_tag_utils/index.spec.ts | 7 +++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/utils/attributes_validator/index.spec.ts b/lib/utils/attributes_validator/index.spec.ts index 58163b55d..645fa2113 100644 --- a/lib/utils/attributes_validator/index.spec.ts +++ b/lib/utils/attributes_validator/index.spec.ts @@ -74,7 +74,8 @@ describe('validate', () => { attributesValidator.validate(attributes); } catch(err) { expect(err).toBeInstanceOf(OptimizelyError); - expect(err.baseMessage).toBe(UNDEFINED_ATTRIBUTE); + expect(err.baseMessage).toBe(UNDEFINED_ATTRIBUTE); + expect(err.params).toEqual([attributeKey]); } }); }); diff --git a/lib/utils/config_validator/index.spec.ts b/lib/utils/config_validator/index.spec.ts index b5a2f51b9..c8496ecc4 100644 --- a/lib/utils/config_validator/index.spec.ts +++ b/lib/utils/config_validator/index.spec.ts @@ -55,6 +55,7 @@ describe('validate', () => { } catch(err) { expect(err).toBeInstanceOf(OptimizelyError); expect(err.baseMessage).toBe(INVALID_DATAFILE_VERSION); + expect(err.params).toEqual(['5']); } }); diff --git a/lib/utils/event_tag_utils/index.spec.ts b/lib/utils/event_tag_utils/index.spec.ts index 0955655cc..a1208b601 100644 --- a/lib/utils/event_tag_utils/index.spec.ts +++ b/lib/utils/event_tag_utils/index.spec.ts @@ -64,10 +64,13 @@ describe('getEventValue', () => { }); it('should return the parsed integer for a valid numeric value', () => { - const parsedNumericValue = eventTagUtils.getEventValue({ value: '1337' }, logger); + let parsedEventValue = eventTagUtils.getEventValue({ value: '1337' }, logger); - expect(parsedNumericValue).toBe(1337); + expect(parsedEventValue).toBe(1337); expect(logger.info).toHaveBeenCalledWith(PARSED_NUMERIC_VALUE, 1337); + + parsedEventValue = eventTagUtils.getEventValue({ value: '13.37' }, logger); + expect(parsedEventValue).toBe(13.37); }); it('should return null and log a message for invalid value', () => {