From 8fcc713f73400ada5773857dfca489643a6c3d21 Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Tue, 30 Sep 2025 14:59:19 -0400 Subject: [PATCH 1/2] fix: SDKE-317 Call deferred methods on Rokt Manager instead of kit --- src/roktManager.ts | 9 +- test/jest/roktManager.spec.ts | 169 ++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 3 deletions(-) diff --git a/src/roktManager.ts b/src/roktManager.ts index 9d5f040e8..bdb8089e4 100644 --- a/src/roktManager.ts +++ b/src/roktManager.ts @@ -319,15 +319,15 @@ export default class RoktManager { this.logger?.verbose(`RoktManager: Processing ${this.messageQueue.size} queued messages`); this.messageQueue.forEach((message) => { - if(!(message.methodName in this.kit) || !isFunction(this.kit[message.methodName])) { - this.logger?.error(`RoktManager: Method ${message.methodName} not found in kit`); + if(!(message.methodName in this) || !isFunction(this[message.methodName])) { + this.logger?.error(`RoktManager: Method ${message.methodName} not found`); return; } this.logger?.verbose(`RoktManager: Processing queued message: ${message.methodName} with payload: ${JSON.stringify(message.payload)}`); try { - const result = (this.kit[message.methodName] as Function)(message.payload); + const result = (this[message.methodName] as Function)(message.payload); this.completePendingPromise(message.messageId, result); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); @@ -335,6 +335,9 @@ export default class RoktManager { this.completePendingPromise(message.messageId, Promise.reject(error)); } }); + + // Clear the queue after processing all messages + this.messageQueue.clear(); } private queueMessage(message: IRoktMessage): void { diff --git a/test/jest/roktManager.spec.ts b/test/jest/roktManager.spec.ts index 31b5e2509..bfdb54955 100644 --- a/test/jest/roktManager.spec.ts +++ b/test/jest/roktManager.spec.ts @@ -420,6 +420,175 @@ describe('RoktManager', () => { expect(roktManager['messageQueue'].size).toBe(0); expect(kit.selectPlacements).toHaveBeenCalledTimes(3); }); + + it('should call RoktManager methods (not kit methods directly) when processing queue', () => { + const kit: IRoktKit = { + launcher: { + selectPlacements: jest.fn(), + hashAttributes: jest.fn(), + use: jest.fn(), + }, + filters: undefined, + selectPlacements: jest.fn(), + hashAttributes: jest.fn(), + filteredUser: undefined, + userAttributes: undefined, + setExtensionData: jest.fn(), + use: jest.fn(), + }; + + // Queue some calls before kit is ready (these will be deferred) + const selectOptions = { attributes: { test: 'value' } } as IRoktSelectPlacementsOptions; + const hashAttrs = { email: 'test@example.com' }; + const extensionData = { 'test-ext': { config: true } }; + const useName = 'TestExtension'; + + roktManager.selectPlacements(selectOptions); + roktManager.hashAttributes(hashAttrs); + roktManager.setExtensionData(extensionData); + roktManager.use(useName); + + // Verify calls were queued + expect(roktManager['messageQueue'].size).toBe(4); + expect(kit.selectPlacements).not.toHaveBeenCalled(); // Kit methods not called yet + expect(kit.hashAttributes).not.toHaveBeenCalled(); // Kit methods not called yet + expect(kit.setExtensionData).not.toHaveBeenCalled(); // Kit methods not called yet + expect(kit.use).not.toHaveBeenCalled(); // Kit methods not called yet + + // Spy on RoktManager methods AFTER initial calls to track queue processing + const selectPlacementsSpy = jest.spyOn(roktManager, 'selectPlacements'); + const hashAttributesSpy = jest.spyOn(roktManager, 'hashAttributes'); + const setExtensionDataSpy = jest.spyOn(roktManager, 'setExtensionData'); + const useSpy = jest.spyOn(roktManager, 'use'); + + // Attach kit (triggers processMessageQueue) + roktManager.attachKit(kit); + + // Verify RoktManager methods were called during queue processing + expect(selectPlacementsSpy).toHaveBeenCalledTimes(1); + expect(selectPlacementsSpy).toHaveBeenCalledWith(selectOptions); + + expect(hashAttributesSpy).toHaveBeenCalledTimes(1); + expect(hashAttributesSpy).toHaveBeenCalledWith(hashAttrs); + + expect(setExtensionDataSpy).toHaveBeenCalledTimes(1); + expect(setExtensionDataSpy).toHaveBeenCalledWith(extensionData); + + expect(useSpy).toHaveBeenCalledTimes(1); + expect(useSpy).toHaveBeenCalledWith(useName); + + // Verify queue was cleared + expect(roktManager['messageQueue'].size).toBe(0); + + // Clean up spies + selectPlacementsSpy.mockRestore(); + hashAttributesSpy.mockRestore(); + setExtensionDataSpy.mockRestore(); + useSpy.mockRestore(); + }); + + it('should preserve RoktManager preprocessing logic when processing deferred selectPlacements calls', () => { + const kit: IRoktKit = { + launcher: { + selectPlacements: jest.fn(), + hashAttributes: jest.fn(), + use: jest.fn(), + }, + filters: undefined, + selectPlacements: jest.fn(), + hashAttributes: jest.fn(), + filteredUser: undefined, + userAttributes: undefined, + setExtensionData: jest.fn(), + use: jest.fn(), + }; + + // Set up placement attributes mapping to test preprocessing + roktManager['placementAttributesMapping'] = [ + { + jsmap: null, + map: 'original_key', + maptype: 'UserAttributeClass.Name', + value: 'mapped_key' + } + ]; + + // Set up current user mock + roktManager['currentUser'] = { + setUserAttributes: jest.fn(), + getUserIdentities: jest.fn().mockReturnValue({ + userIdentities: {} + }) + } as unknown as IMParticleUser; + + // Queue a selectPlacements call with attributes that need mapping + const originalOptions: IRoktSelectPlacementsOptions = { + attributes: { + original_key: 'test_value', + other_attr: 'other_value' + } + }; + + roktManager.selectPlacements(originalOptions); + expect(roktManager['messageQueue'].size).toBe(1); + + // Attach kit (triggers processMessageQueue) + roktManager.attachKit(kit); + + // Verify the kit method was called with MAPPED attributes (proving preprocessing occurred) + const expectedMappedOptions = { + attributes: { + mapped_key: 'test_value', // This key should be mapped + other_attr: 'other_value' // This key should remain unchanged + } + }; + + expect(kit.selectPlacements).toHaveBeenCalledWith(expectedMappedOptions); + expect(roktManager['currentUser'].setUserAttributes).toHaveBeenCalledWith({ + mapped_key: 'test_value', + other_attr: 'other_value' + }); + }); + + + it('should skip processing if method does not exist on RoktManager', () => { + const kit: IRoktKit = { + launcher: { + selectPlacements: jest.fn(), + hashAttributes: jest.fn(), + use: jest.fn(), + }, + filters: undefined, + selectPlacements: jest.fn(), + hashAttributes: jest.fn(), + filteredUser: undefined, + userAttributes: undefined, + setExtensionData: jest.fn(), + use: jest.fn(), + }; + + // Manually add a message with a non-existent method name + roktManager['queueMessage']({ + messageId: 'test_123', + methodName: 'nonExistentMethod', + payload: { test: 'data' }, + resolve: jest.fn(), + reject: jest.fn() + }); + + expect(roktManager['messageQueue'].size).toBe(1); + + // Attach kit (triggers processMessageQueue) + roktManager.attachKit(kit); + + // Verify error was logged for non-existent method + expect(mockMPInstance.Logger.error).toHaveBeenCalledWith( + 'RoktManager: Method nonExistentMethod not found' + ); + + // Verify message was removed from queue even though method didn't exist + expect(roktManager['messageQueue'].size).toBe(0); + }); }); describe('#selectPlacements', () => { From db4c70c126af91a4a0f3d4849f54f56527ae4ad7 Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Tue, 30 Sep 2025 15:21:33 -0400 Subject: [PATCH 2/2] test: DRY up tests --- test/jest/roktManager.spec.ts | 54 +++++------------------------------ 1 file changed, 7 insertions(+), 47 deletions(-) diff --git a/test/jest/roktManager.spec.ts b/test/jest/roktManager.spec.ts index bfdb54955..3c3bccae2 100644 --- a/test/jest/roktManager.spec.ts +++ b/test/jest/roktManager.spec.ts @@ -389,8 +389,10 @@ describe('RoktManager', () => { }); describe('#processMessageQueue', () => { - it('should process the message queue if a launcher and kit are attached', () => { - const kit: IRoktKit = { + let kit: IRoktKit; + + beforeEach(() => { + kit = { launcher: { selectPlacements: jest.fn(), hashAttributes: jest.fn(), @@ -404,6 +406,9 @@ describe('RoktManager', () => { setExtensionData: jest.fn(), use: jest.fn(), }; + }); + + it('should process the message queue if a launcher and kit are attached', () => { roktManager.selectPlacements({} as IRoktSelectPlacementsOptions); @@ -422,21 +427,6 @@ describe('RoktManager', () => { }); it('should call RoktManager methods (not kit methods directly) when processing queue', () => { - const kit: IRoktKit = { - launcher: { - selectPlacements: jest.fn(), - hashAttributes: jest.fn(), - use: jest.fn(), - }, - filters: undefined, - selectPlacements: jest.fn(), - hashAttributes: jest.fn(), - filteredUser: undefined, - userAttributes: undefined, - setExtensionData: jest.fn(), - use: jest.fn(), - }; - // Queue some calls before kit is ready (these will be deferred) const selectOptions = { attributes: { test: 'value' } } as IRoktSelectPlacementsOptions; const hashAttrs = { email: 'test@example.com' }; @@ -488,21 +478,6 @@ describe('RoktManager', () => { }); it('should preserve RoktManager preprocessing logic when processing deferred selectPlacements calls', () => { - const kit: IRoktKit = { - launcher: { - selectPlacements: jest.fn(), - hashAttributes: jest.fn(), - use: jest.fn(), - }, - filters: undefined, - selectPlacements: jest.fn(), - hashAttributes: jest.fn(), - filteredUser: undefined, - userAttributes: undefined, - setExtensionData: jest.fn(), - use: jest.fn(), - }; - // Set up placement attributes mapping to test preprocessing roktManager['placementAttributesMapping'] = [ { @@ -552,21 +527,6 @@ describe('RoktManager', () => { it('should skip processing if method does not exist on RoktManager', () => { - const kit: IRoktKit = { - launcher: { - selectPlacements: jest.fn(), - hashAttributes: jest.fn(), - use: jest.fn(), - }, - filters: undefined, - selectPlacements: jest.fn(), - hashAttributes: jest.fn(), - filteredUser: undefined, - userAttributes: undefined, - setExtensionData: jest.fn(), - use: jest.fn(), - }; - // Manually add a message with a non-existent method name roktManager['queueMessage']({ messageId: 'test_123',