Skip to content

Commit 8c91a8b

Browse files
committed
Update hashAttributes to use hashSha256 internally for hashing and update tests
1 parent a78419e commit 8c91a8b

File tree

2 files changed

+92
-130
lines changed

2 files changed

+92
-130
lines changed

src/roktManager.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,39 @@ export default class RoktManager {
252252
}
253253
}
254254

255-
public hashAttributes(attributes: IRoktPartnerAttributes): Promise<IRoktPartnerAttributes> {
256-
if (!this.isReady()) {
257-
return this.deferredCall<IRoktPartnerAttributes>('hashAttributes', attributes);
258-
}
259-
255+
/**
256+
* Hashes attributes and returns both original and hashed versions
257+
* with Rokt-compatible key names (like emailsha256, mobilesha256)
258+
*
259+
*
260+
* @param {IRoktPartnerAttributes} attributes - Attributes to hash
261+
* @returns {Promise<IRoktPartnerAttributes>} Object with both original and hashed attributes
262+
*
263+
*/
264+
public async hashAttributes(attributes: IRoktPartnerAttributes): Promise<IRoktPartnerAttributes> {
260265
try {
261-
return this.kit.hashAttributes(attributes);
266+
if (!attributes || typeof attributes !== 'object') {
267+
return {};
268+
}
269+
270+
const hashedAttributes: IRoktPartnerAttributes = {};
271+
272+
for (const key in attributes) {
273+
const attributeValue = attributes[key];
274+
275+
hashedAttributes[key] = attributeValue;
276+
277+
const hashedValue = await this.hashSha256(attributeValue);
278+
279+
if (hashedValue) {
280+
hashedAttributes[`${key}sha256`] = hashedValue;
281+
}
282+
}
283+
284+
return hashedAttributes;
285+
262286
} catch (error) {
263-
return Promise.reject(error instanceof Error ? error : new Error('Unknown error occurred'));
287+
return Promise.reject(error instanceof Error ? error : new Error('Failed to hash attributes'));
264288
}
265289
}
266290

test/jest/roktManager.spec.ts

Lines changed: 61 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -76,152 +76,98 @@ describe('RoktManager', () => {
7676
});
7777

7878
describe('#hashAttributes', () => {
79+
const nodeCrypto = require('crypto');
80+
let shaSpy: jest.SpyInstance;
81+
7982
beforeEach(() => {
8083
roktManager['currentUser'] = currentUser;
84+
shaSpy = jest.spyOn(roktManager as any, 'sha256Hex');
85+
shaSpy.mockImplementation((s: string) =>
86+
Promise.resolve(nodeCrypto.createHash('sha256').update(s).digest('hex')),
87+
);
8188
});
8289

83-
it('should call kit.hashAttributes with empty attributes', () => {
84-
const kit: IRoktKit = {
85-
launcher: {
86-
selectPlacements: jest.fn(),
87-
hashAttributes: jest.fn(),
88-
use: jest.fn(),
89-
},
90-
filters: undefined,
91-
filteredUser: undefined,
92-
hashAttributes: jest.fn(),
93-
selectPlacements: jest.fn(),
94-
setExtensionData: jest.fn(),
95-
use: jest.fn(),
96-
userAttributes: undefined,
97-
};
98-
99-
roktManager.attachKit(kit);
100-
101-
const attributes = {};
102-
103-
roktManager.hashAttributes(attributes);
104-
expect(kit.hashAttributes).toHaveBeenCalledWith(attributes);
90+
afterEach(() => {
91+
shaSpy.mockRestore();
10592
});
10693

107-
it('should call kit.hashAttributes with passed in attributes', () => {
108-
const kit: IRoktKit = {
109-
launcher: {
110-
selectPlacements: jest.fn(),
111-
hashAttributes: jest.fn(),
112-
use: jest.fn(),
113-
},
114-
filters: undefined,
115-
filteredUser: undefined,
116-
hashAttributes: jest.fn(),
117-
selectPlacements: jest.fn(),
118-
setExtensionData: jest.fn(),
119-
use: jest.fn(),
120-
userAttributes: undefined,
121-
};
122-
123-
roktManager.attachKit(kit);
124-
94+
it('should hash attributes with passed in attributes', async () => {
12595
const attributes = {
12696
12797
phone: '1234567890'
12898
};
12999

130-
roktManager.hashAttributes(attributes);
131-
expect(kit.hashAttributes).toHaveBeenCalledWith(attributes);
100+
const result = await roktManager.hashAttributes(attributes);
101+
102+
expect(result).toEqual({
103+
104+
emailsha256: nodeCrypto.createHash('sha256').update('[email protected]').digest('hex'),
105+
phone: '1234567890',
106+
phonesha256: nodeCrypto.createHash('sha256').update('1234567890').digest('hex'),
107+
});
132108
});
133109

134-
it('should queue the hashAttributes method if no launcher or kit is attached', () => {
110+
it('should hash all non-null string, number, and boolean values', async () => {
135111
const attributes = {
136-
112+
113+
age: 25,
114+
active: true,
115+
nullable: null,
116+
undefinedVal: undefined
137117
};
138118

139-
roktManager.hashAttributes(attributes);
119+
const result = await roktManager.hashAttributes(attributes);
140120

141-
expect(roktManager['kit']).toBeNull();
142-
expect(roktManager['messageQueue'].size).toBe(1);
143-
const queuedMessage = Array.from(roktManager['messageQueue'].values())[0];
144-
expect(queuedMessage.methodName).toBe('hashAttributes');
145-
expect(queuedMessage.payload).toBe(attributes);
121+
expect(result).toEqual({
122+
123+
emailsha256: nodeCrypto.createHash('sha256').update('[email protected]').digest('hex'),
124+
age: 25,
125+
agesha256: nodeCrypto.createHash('sha256').update('25').digest('hex'),
126+
active: true,
127+
activesha256: nodeCrypto.createHash('sha256').update('true').digest('hex'),
128+
nullable: null,
129+
undefinedVal: undefined
130+
});
146131
});
147132

148-
it('should process queued hashAttributes calls once the launcher and kit are attached', () => {
149-
const kit: IRoktKit = {
150-
launcher: {
151-
selectPlacements: jest.fn(),
152-
hashAttributes: jest.fn(),
153-
use: jest.fn(),
154-
},
155-
filters: undefined,
156-
filteredUser: undefined,
157-
hashAttributes: jest.fn(),
158-
selectPlacements: jest.fn(),
159-
setExtensionData: jest.fn(),
160-
use: jest.fn(),
161-
userAttributes: undefined,
162-
};
163-
164-
const attributes = {
165-
166-
};
167-
168-
roktManager.hashAttributes(attributes);
169-
expect(roktManager['kit']).toBeNull();
170-
expect(roktManager['messageQueue'].size).toBe(1);
171-
const queuedMessage = Array.from(roktManager['messageQueue'].values())[0];
172-
expect(queuedMessage.methodName).toBe('hashAttributes');
173-
expect(queuedMessage.payload).toBe(attributes);
174-
expect(kit.hashAttributes).not.toHaveBeenCalled();
175-
176-
roktManager.attachKit(kit);
177-
expect(roktManager['kit']).not.toBeNull();
178-
expect(roktManager['messageQueue'].size).toBe(0);
179-
expect(kit.hashAttributes).toHaveBeenCalledWith(attributes);
133+
it('should return empty object if attributes is null', async () => {
134+
const result = await roktManager.hashAttributes(null as any);
135+
expect(result).toEqual({});
180136
});
181137

182-
it('should pass through the correct attributes to kit.launcher.hashAttributes', async () => {
183-
const kit: Partial<IRoktKit> = {
184-
launcher: {
185-
selectPlacements: jest.fn(),
186-
hashAttributes: jest.fn(),
187-
use: jest.fn(),
188-
},
138+
it('should return empty object if attributes is undefined', async () => {
139+
const result = await roktManager.hashAttributes(undefined as any);
140+
expect(result).toEqual({});
141+
});
189142

190-
// We are mocking the hashAttributes method to return the
191-
// launcher's hashAttributes method and verify that
192-
// both the kit's and the launcher's methods
193-
// are called with the correct attributes.
194-
// This will happen through the Web Kit's hashAttributes method
195-
hashAttributes: jest.fn().mockImplementation((attributes) => {
196-
return kit.launcher.hashAttributes(attributes);
197-
})
198-
};
143+
it('should handle empty attributes object', async () => {
144+
const result = await roktManager.hashAttributes({});
145+
expect(result).toEqual({});
146+
});
199147

200-
roktManager.attachKit(kit as IRoktKit);
148+
it('should reject if hashSha256 throws an error', async () => {
149+
shaSpy.mockRestore();
150+
151+
// Mock hashSha256 to throw an error
152+
const hashError = new Error('Hashing failed');
153+
jest.spyOn(roktManager, 'hashSha256').mockRejectedValue(hashError);
201154

202-
const attributes = {
203-
204-
phone: '1234567890'
205-
};
155+
const attributes = { email: '[email protected]' };
206156

207-
roktManager.hashAttributes(attributes);
208-
expect(kit.hashAttributes).toHaveBeenCalledWith(attributes);
209-
expect(kit.launcher.hashAttributes).toHaveBeenCalledWith(attributes);
157+
await expect(roktManager.hashAttributes(attributes))
158+
.rejects
159+
.toThrow('Hashing failed');
210160
});
211161
});
212162

213163
describe('#hashSha256', () => {
214-
interface Hasher {
215-
sha256Hex(input: string): Promise<string>
216-
}
217-
218164
const nodeCrypto = require('crypto');
219165
let shaSpy: jest.SpyInstance;
220166

221167
beforeEach(() => {
222-
shaSpy = jest.spyOn(roktManager as unknown as Hasher, 'sha256Hex');
223-
shaSpy.mockImplementation((s: any) =>
224-
Promise.resolve(nodeCrypto.createHash('sha256').update(String(s)).digest('hex')),
168+
shaSpy = jest.spyOn(roktManager as any, 'sha256Hex');
169+
shaSpy.mockImplementation((s: string) =>
170+
Promise.resolve(nodeCrypto.createHash('sha256').update(s).digest('hex')),
225171
);
226172
});
227173

@@ -556,25 +502,21 @@ describe('RoktManager', () => {
556502
it('should call RoktManager methods (not kit methods directly) when processing queue', () => {
557503
// Queue some calls before kit is ready (these will be deferred)
558504
const selectOptions = { attributes: { test: 'value' } } as IRoktSelectPlacementsOptions;
559-
const hashAttrs = { email: '[email protected]' };
560505
const extensionData = { 'test-ext': { config: true } };
561506
const useName = 'TestExtension';
562507

563508
roktManager.selectPlacements(selectOptions);
564-
roktManager.hashAttributes(hashAttrs);
565509
roktManager.setExtensionData(extensionData);
566510
roktManager.use(useName);
567511

568512
// Verify calls were queued
569-
expect(roktManager['messageQueue'].size).toBe(4);
513+
expect(roktManager['messageQueue'].size).toBe(3);
570514
expect(kit.selectPlacements).not.toHaveBeenCalled(); // Kit methods not called yet
571-
expect(kit.hashAttributes).not.toHaveBeenCalled(); // Kit methods not called yet
572515
expect(kit.setExtensionData).not.toHaveBeenCalled(); // Kit methods not called yet
573516
expect(kit.use).not.toHaveBeenCalled(); // Kit methods not called yet
574517

575518
// Spy on RoktManager methods AFTER initial calls to track queue processing
576519
const selectPlacementsSpy = jest.spyOn(roktManager, 'selectPlacements');
577-
const hashAttributesSpy = jest.spyOn(roktManager, 'hashAttributes');
578520
const setExtensionDataSpy = jest.spyOn(roktManager, 'setExtensionData');
579521
const useSpy = jest.spyOn(roktManager, 'use');
580522

@@ -585,9 +527,6 @@ describe('RoktManager', () => {
585527
expect(selectPlacementsSpy).toHaveBeenCalledTimes(1);
586528
expect(selectPlacementsSpy).toHaveBeenCalledWith(selectOptions);
587529

588-
expect(hashAttributesSpy).toHaveBeenCalledTimes(1);
589-
expect(hashAttributesSpy).toHaveBeenCalledWith(hashAttrs);
590-
591530
expect(setExtensionDataSpy).toHaveBeenCalledTimes(1);
592531
expect(setExtensionDataSpy).toHaveBeenCalledWith(extensionData);
593532

@@ -599,7 +538,6 @@ describe('RoktManager', () => {
599538

600539
// Clean up spies
601540
selectPlacementsSpy.mockRestore();
602-
hashAttributesSpy.mockRestore();
603541
setExtensionDataSpy.mockRestore();
604542
useSpy.mockRestore();
605543
});
@@ -846,7 +784,7 @@ describe('RoktManager', () => {
846784
expect(kit.launcher.selectPlacements).toHaveBeenCalledWith(options);
847785
});
848786

849-
it('should pass sandbox flag as an attribute through to kit.selectPlacements', ()=> {
787+
it('should pass sandbox flag as an attribute through to kit.selectPlacements', () => {
850788
const kit: IRoktKit = {
851789
launcher: {
852790
selectPlacements: jest.fn(),

0 commit comments

Comments
 (0)