diff --git a/core-api/src/main/java/com/optimizely/ab/odp/ODPEventManager.java b/core-api/src/main/java/com/optimizely/ab/odp/ODPEventManager.java index 79e250219..79ba84f2f 100644 --- a/core-api/src/main/java/com/optimizely/ab/odp/ODPEventManager.java +++ b/core-api/src/main/java/com/optimizely/ab/odp/ODPEventManager.java @@ -151,14 +151,18 @@ public void identifyUser(@Nonnull Map identifiers) { } } + // android-sdk sets vuid in commonIdentifiers. Augment here so the vuid is included + // when counting identifiers. Idempotent with augment in sendEvent. + Map allIdentifiers = augmentCommonIdentifiers(validIdentifiers); + // An identify event requires at least 2 identifiers to link (e.g., vuid + fs_user_id). // A single identifier has no cross-reference value and would generate unnecessary traffic. - if (validIdentifiers.size() < 2) { + if (allIdentifiers.size() < 2) { logger.debug("ODP identify event is not dispatched (fewer than 2 valid identifiers)."); return; } - ODPEvent event = new ODPEvent("fullstack", "identified", validIdentifiers, null); + ODPEvent event = new ODPEvent("fullstack", "identified", allIdentifiers, null); sendEvent(event); } diff --git a/core-api/src/test/java/com/optimizely/ab/odp/ODPEventManagerTest.java b/core-api/src/test/java/com/optimizely/ab/odp/ODPEventManagerTest.java index 6c1a6fd9b..f25982abb 100644 --- a/core-api/src/test/java/com/optimizely/ab/odp/ODPEventManagerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/odp/ODPEventManagerTest.java @@ -357,6 +357,32 @@ public void identifyUserSkippedWithEmptyMap() throws InterruptedException { logbackVerifier.expectMessage(Level.DEBUG, "ODP identify event is not dispatched (fewer than 2 valid identifiers)."); } + @Test + public void identifyUserSendsWhenCommonIdentifiersProvideSecondIdentifier() throws InterruptedException { + ODPEventManager eventManager = spy(new ODPEventManager(mockApiManager)); + ArgumentCaptor captor = ArgumentCaptor.forClass(ODPEvent.class); + + // VUID is set as a common identifier (e.g., vuid enabled in ODPManager) + Map commonIdentifiers = new HashMap<>(); + commonIdentifiers.put("vuid", "vuid_abc123"); + eventManager.setUserCommonIdentifiers(commonIdentifiers); + + // createUserContext passes only fs_user_id — a single identifier in the call + Map identifiers = new HashMap<>(); + identifiers.put("fs_user_id", "test-user"); + eventManager.identifyUser(identifiers); + + // Should NOT be dropped: common identifiers provide the second identifier (vuid), + // making this a valid identify event with 2 identifiers total. + verify(eventManager, times(1)).sendEvent(captor.capture()); + + ODPEvent event = captor.getValue(); + Map eventIdentifiers = event.getIdentifiers(); + assertEquals(2, eventIdentifiers.size()); + assertEquals("test-user", eventIdentifiers.get("fs_user_id")); + assertEquals("vuid_abc123", eventIdentifiers.get("vuid")); + } + @Test public void identifyUserSendsWithThreeIdentifiers() throws InterruptedException { ODPEventManager eventManager = spy(new ODPEventManager(mockApiManager));