@@ -331,4 +331,170 @@ internal class KlaviyoStateTest : BaseTest() {
331331 " createEvent should return a new event object, not mutate the original"
332332 }
333333 }
334+
335+ @Test
336+ fun `setProfile with same external ID does not reset anonymous ID` () {
337+ // Setup: create initial state with identifiers
338+ state.externalId = EXTERNAL_ID
339+ state.email = EMAIL
340+ val initialAnonId = state.anonymousId
341+
342+ // Call setProfile with same identifiers
343+ state.setProfile(
344+ Profile (externalId = EXTERNAL_ID , email = EMAIL )
345+ )
346+
347+ // Verify anonymous ID is preserved (no reset)
348+ assertEquals(initialAnonId, state.anonymousId)
349+ }
350+
351+ @Test
352+ fun `setProfile with same email does not reset anonymous ID` () {
353+ state.email = EMAIL
354+ val initialAnonId = state.anonymousId
355+
356+ state.setProfile(Profile (email = EMAIL ))
357+
358+ assertEquals(initialAnonId, state.anonymousId)
359+ }
360+
361+ @Test
362+ fun `setProfile with same phone number does not reset anonymous ID` () {
363+ state.phoneNumber = PHONE
364+ val initialAnonId = state.anonymousId
365+
366+ state.setProfile(Profile (phoneNumber = PHONE ))
367+
368+ assertEquals(initialAnonId, state.anonymousId)
369+ }
370+
371+ @Test
372+ fun `setProfile with different external ID triggers reset and new anonymous ID` () {
373+ state.externalId = EXTERNAL_ID
374+ val initialAnonId = state.anonymousId
375+
376+ state.setProfile(Profile (externalId = " different_external_id" ))
377+
378+ assertNotEquals(initialAnonId, state.anonymousId)
379+ assertEquals(" different_external_id" , state.externalId)
380+ }
381+
382+ @Test
383+ fun `setProfile with different email triggers reset and new anonymous ID` () {
384+ state.email = EMAIL
385+ val initialAnonId = state.anonymousId
386+
387+ state.setProfile(Profile (email = " different@email.com" ))
388+
389+ assertNotEquals(initialAnonId, state.anonymousId)
390+ assertEquals(" different@email.com" , state.email)
391+ }
392+
393+ @Test
394+ fun `setProfile with different phone triggers reset and new anonymous ID` () {
395+ state.phoneNumber = PHONE
396+ val initialAnonId = state.anonymousId
397+
398+ state.setProfile(Profile (phoneNumber = " 9999999999" ))
399+
400+ assertNotEquals(initialAnonId, state.anonymousId)
401+ assertEquals(" 9999999999" , state.phoneNumber)
402+ }
403+
404+ @Test
405+ fun `setProfile repeated calls with same identifiers do not trigger spurious resets` () {
406+ state.externalId = EXTERNAL_ID
407+ state.email = EMAIL
408+ val initialAnonId = state.anonymousId
409+
410+ // Call setProfile multiple times with same identifiers (simulates Wyze behavior)
411+ repeat(3 ) {
412+ state.setProfile(Profile (externalId = EXTERNAL_ID , email = EMAIL ))
413+ assertEquals(initialAnonId, state.anonymousId)
414+ }
415+ }
416+
417+ @Test
418+ fun `setProfile with same identifiers but different attributes does not reset` () {
419+ state.externalId = EXTERNAL_ID
420+ state.email = EMAIL
421+ val initialAnonId = state.anonymousId
422+
423+ state.setProfile(
424+ Profile (
425+ externalId = EXTERNAL_ID ,
426+ email = EMAIL ,
427+ properties = mapOf (ProfileKey .FIRST_NAME to " Kermit" )
428+ )
429+ )
430+
431+ // Key assertion: anonymous ID should not change even though attributes differ
432+ assertEquals(initialAnonId, state.anonymousId)
433+ }
434+
435+ @Test
436+ fun `resetProfile explicitly clobbers all state regardless of setProfile fix` () {
437+ state.externalId = EXTERNAL_ID
438+ state.email = EMAIL
439+ state.phoneNumber = PHONE
440+ state.setAttribute(ProfileKey .FIRST_NAME , " Kermit" )
441+
442+ val anonIdBeforeReset = state.anonymousId
443+
444+ state.reset()
445+
446+ assertNull(state.externalId)
447+ assertNull(state.email)
448+ assertNull(state.phoneNumber)
449+ assertNull(state.getAsProfile()[ProfileKey .FIRST_NAME ])
450+ assertNotEquals(anonIdBeforeReset, state.anonymousId)
451+ }
452+
453+ @Test
454+ fun `setProfile with one identifier changed triggers reset while others are same` () {
455+ state.externalId = EXTERNAL_ID
456+ state.email = EMAIL
457+ state.phoneNumber = PHONE
458+ val initialAnonId = state.anonymousId
459+
460+ state.setProfile(
461+ Profile (
462+ externalId = EXTERNAL_ID ,
463+ email = " different@email.com" ,
464+ phoneNumber = PHONE
465+ )
466+ )
467+
468+ assertNotEquals(initialAnonId, state.anonymousId)
469+ assertEquals(EXTERNAL_ID , state.externalId)
470+ assertEquals(" different@email.com" , state.email)
471+ assertEquals(PHONE , state.phoneNumber)
472+ }
473+
474+ @Test
475+ fun `setProfile on fresh state with no prior identifiers sets identifiers without reset` () {
476+ // Don't set any identifiers on state first — all are null
477+ val initialAnonId = state.anonymousId
478+
479+ state.setProfile(Profile (externalId = EXTERNAL_ID , email = EMAIL ))
480+
481+ assertEquals(EXTERNAL_ID , state.externalId)
482+ assertEquals(EMAIL , state.email)
483+ assertNotEquals(null , state.anonymousId)
484+ // Anonymous ID should be preserved since no prior identifiers existed (outer guard is false)
485+ assertEquals(initialAnonId, state.anonymousId)
486+ }
487+
488+ @Test
489+ fun `setProfile with all-null identifiers triggers reset when state has non-null identifiers` () {
490+ state.externalId = EXTERNAL_ID
491+ state.email = EMAIL
492+ val initialAnonId = state.anonymousId
493+
494+ // Pass an empty profile — all identifier fields are null
495+ state.setProfile(Profile ())
496+
497+ // Reset should fire because null != "abcdefg" is true for the existing identifiers
498+ assertNotEquals(initialAnonId, state.anonymousId)
499+ }
334500}
0 commit comments