@@ -210,6 +210,112 @@ describe('RoktManager', () => {
210210 } ) ;
211211 } ) ;
212212
213+ describe ( '#hashSha256' , ( ) => {
214+ interface Hasher {
215+ sha256Hex ( input : string ) : Promise < string >
216+ }
217+
218+ const nodeCrypto = require ( 'crypto' ) ;
219+ let shaSpy : jest . SpyInstance ;
220+
221+ 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' ) ) ,
225+ ) ;
226+ } ) ;
227+
228+ afterEach ( ( ) => {
229+ shaSpy . mockRestore ( ) ;
230+ } ) ;
231+
232+ it ( 'should hash a single string value using SHA-256' , async ( ) => {
233+ const result = await roktManager . hashSha256 ( '[email protected] ' ) ; 234+ const expected = nodeCrypto . createHash ( 'sha256' ) . update ( '[email protected] ' ) . digest ( 'hex' ) ; 235+
236+ expect ( result ) . toBe ( expected ) ;
237+ expect ( shaSpy ) . toHaveBeenCalledWith ( '[email protected] ' ) ; 238+ expect ( shaSpy ) . toHaveBeenCalledTimes ( 1 ) ;
239+ } ) ;
240+
241+ it ( 'should hash values without kit being attached' , async ( ) => {
242+ // Verify kit is not attached
243+ expect ( roktManager [ 'kit' ] ) . toBeNull ( ) ;
244+
245+ const result = await roktManager . hashSha256 ( '[email protected] ' ) ; 246+ const expected = nodeCrypto . createHash ( 'sha256' ) . update ( '[email protected] ' ) . digest ( 'hex' ) ; 247+
248+ expect ( result ) . toBe ( expected ) ;
249+ } ) ;
250+
251+ it ( 'should handle empty string' , async ( ) => {
252+ const emptyStringHash = await roktManager . hashSha256 ( '' ) ;
253+
254+ // Empty string after trim becomes '', hash of empty string
255+ expect ( emptyStringHash ) . toBe ( 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' ) ;
256+ } ) ;
257+
258+ it ( 'should reject when value is null' , async ( ) => {
259+ await expect ( roktManager . hashSha256 ( null ) ) . rejects . toThrow ( 'Value cannot be null or undefined' ) ;
260+ } ) ;
261+
262+ it ( 'should reject when value is undefined' , async ( ) => {
263+ await expect ( roktManager . hashSha256 ( undefined ) ) . rejects . toThrow ( 'Value cannot be null or undefined' ) ;
264+ } ) ;
265+
266+ it ( 'should log error when hashing fails' , async ( ) => {
267+ shaSpy . mockRejectedValue ( new Error ( 'Hash failed' ) ) ;
268+
269+ await expect ( roktManager . hashSha256 ( '[email protected] ' ) ) . rejects . toThrow ( ) ; 270+ expect ( mockMPInstance . Logger . error ) . toHaveBeenCalledWith ( expect . stringContaining ( 'Failed hashSha256' ) ) ;
271+ } ) ;
272+
273+ it ( 'should hash firstName to known SHA-256 value' , async ( ) => {
274+ const hashedFirstName = await roktManager . hashSha256 ( 'jane' ) ;
275+
276+ // Expected SHA-256 hash of 'jane'
277+ expect ( hashedFirstName ) . toBe ( '81f8f6dde88365f3928796ec7aa53f72820b06db8664f5fe76a7eb13e24546a2' ) ;
278+ } ) ;
279+
280+ it ( 'should produce same hash for different case and whitespace variations' , async ( ) => {
281+ const lowercaseEmail = await roktManager . hashSha256 ( '[email protected] ' ) ; 282+ const mixedCaseEmail = await roktManager . hashSha256 ( '[email protected] ' ) ; 283+ const emailWithWhitespace = await roktManager . hashSha256 ( ' [email protected] ' ) ; 284+
285+ // All should normalize to same hash
286+ expect ( lowercaseEmail ) . toBe ( mixedCaseEmail ) ;
287+ expect ( mixedCaseEmail ) . toBe ( emailWithWhitespace ) ;
288+ expect ( lowercaseEmail ) . toBe ( '831f6494ad6be4fcb3a724c3d5fef22d3ceffa3c62ef3a7984e45a0ea177f982' ) ;
289+ } ) ;
290+
291+ it ( 'should handle numeric values and match known SHA-256' , async ( ) => {
292+ const hashedNumber = await roktManager . hashSha256 ( 42 ) ;
293+ const hashedString = await roktManager . hashSha256 ( '42' ) ;
294+
295+ // Numeric value should be converted to string and produce same hash
296+ expect ( hashedNumber ) . toBe ( hashedString ) ;
297+ // Expected SHA-256 hash of '42'
298+ expect ( hashedNumber ) . toBe ( '73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049' ) ;
299+ } ) ;
300+
301+ it ( 'should handle boolean values and match known SHA-256' , async ( ) => {
302+ const hashedBoolean = await roktManager . hashSha256 ( true ) ;
303+ const hashedString = await roktManager . hashSha256 ( 'true' ) ;
304+
305+ // Boolean value should be converted to string and produce same hash
306+ expect ( hashedBoolean ) . toBe ( hashedString ) ;
307+ // Expected SHA-256 hash of 'true'
308+ expect ( hashedBoolean ) . toBe ( 'b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b' ) ;
309+ } ) ;
310+
311+ it ( 'should hash phone number to known SHA-256 value' , async ( ) => {
312+ const hashedPhone = await roktManager . hashSha256 ( '1234567890' ) ;
313+
314+ // Expected SHA-256 hash of '1234567890'
315+ expect ( hashedPhone ) . toBe ( 'c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646' ) ;
316+ } ) ;
317+ } ) ;
318+
213319 describe ( '#init' , ( ) => {
214320 it ( 'should initialize the manager with defaults when no config is provided' , ( ) => {
215321 roktManager . init (
0 commit comments