Skip to content

Commit 97dcc38

Browse files
committed
[AI-FSSDK] [FSSDK-12670] Block ODP identify event for single identifier
Skip sending ODP identify event when fewer than 2 valid (non-null, non-empty) identifiers exist. Changed ODPEventManager.identifyUser to accept a Map<String, String> of identifiers and filter out null/empty values before checking count. Updated Optimizely.identifyUser to construct identifier map from userId string and pass to ODPEventManager. Updated all related tests to use Map-based API.
1 parent 0369d44 commit 97dcc38

6 files changed

Lines changed: 98 additions & 52 deletions

File tree

core-api/src/main/java/com/optimizely/ab/Optimizely.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import com.optimizely.ab.odp.ODPManager;
5858
import com.optimizely.ab.odp.ODPSegmentManager;
5959
import com.optimizely.ab.odp.ODPSegmentOption;
60+
import com.optimizely.ab.odp.ODPUserKey;
6061
import com.optimizely.ab.optimizelyconfig.OptimizelyConfig;
6162
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigManager;
6263
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigService;
@@ -1794,7 +1795,13 @@ public void identifyUser(@Nonnull String userId) {
17941795
}
17951796
ODPManager odpManager = getODPManager();
17961797
if (odpManager != null) {
1797-
odpManager.getEventManager().identifyUser(userId);
1798+
Map<String, String> identifiers = new HashMap<>();
1799+
if (ODPManager.isVuid(userId)) {
1800+
identifiers.put(ODPUserKey.VUID.getKeyString(), userId);
1801+
} else {
1802+
identifiers.put(ODPUserKey.FS_USER_ID.getKeyString(), userId);
1803+
}
1804+
odpManager.getEventManager().identifyUser(identifiers);
17981805
}
17991806
}
18001807

core-api/src/main/java/com/optimizely/ab/odp/ODPEventManager.java

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -111,23 +111,21 @@ public void updateSettings(ODPConfig newConfig) {
111111
}
112112
}
113113

114-
public void identifyUser(String userId) {
115-
identifyUser(null, userId);
116-
}
117-
118-
public void identifyUser(@Nullable String vuid, @Nullable String userId) {
119-
Map<String, String> identifiers = new HashMap<>();
120-
if (vuid != null) {
121-
identifiers.put(ODPUserKey.VUID.getKeyString(), vuid);
122-
}
123-
if (userId != null) {
124-
if (ODPManager.isVuid(userId)) {
125-
identifiers.put(ODPUserKey.VUID.getKeyString(), userId);
126-
} else {
127-
identifiers.put(ODPUserKey.FS_USER_ID.getKeyString(), userId);
114+
public void identifyUser(@Nonnull Map<String, String> identifiers) {
115+
// Filter out identifiers with null or empty values
116+
Map<String, String> validIdentifiers = new HashMap<>();
117+
for (Map.Entry<String, String> entry : identifiers.entrySet()) {
118+
if (entry.getValue() != null && !entry.getValue().isEmpty()) {
119+
validIdentifiers.put(entry.getKey(), entry.getValue());
128120
}
129121
}
130-
ODPEvent event = new ODPEvent("fullstack", "identified", identifiers, null);
122+
123+
if (validIdentifiers.size() < 2) {
124+
logger.debug("ODP identify event is not dispatched (only one identifier provided).");
125+
return;
126+
}
127+
128+
ODPEvent event = new ODPEvent("fullstack", "identified", validIdentifiers, null);
131129
sendEvent(event);
132130
}
133131

core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5084,7 +5084,10 @@ public void identifyUser() {
50845084
.build();
50855085

50865086
optimizely.identifyUser("the-user");
5087-
Mockito.verify(mockODPEventManager, times(1)).identifyUser("the-user");
5087+
ArgumentCaptor<Map> identifiersCaptor = ArgumentCaptor.forClass(Map.class);
5088+
Mockito.verify(mockODPEventManager, times(1)).identifyUser(identifiersCaptor.capture());
5089+
Map<String, String> capturedIdentifiers = identifiersCaptor.getValue();
5090+
assertEquals("the-user", capturedIdentifiers.get("fs_user_id"));
50885091
}
50895092

50905093
@Test

core-api/src/test/java/com/optimizely/ab/OptimizelyUserContextTest.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,7 +1970,7 @@ public void identifyUserErrorWhenConfigIsInvalid() {
19701970
.build();
19711971

19721972
optimizely.createUserContext("test-user");
1973-
verify(mockODPEventManager, never()).identifyUser("test-user");
1973+
verify(mockODPEventManager, never()).identifyUser(any(Map.class));
19741974
Mockito.reset(mockODPEventManager);
19751975

19761976
logbackVerifier.expectMessage(Level.ERROR, "Optimizely instance is not valid, failing identifyUser call.");
@@ -1992,13 +1992,16 @@ public void identifyUser() {
19921992
.build();
19931993

19941994
OptimizelyUserContext userContext = optimizely.createUserContext("test-user");
1995-
verify(mockODPEventManager).identifyUser("test-user");
1995+
ArgumentCaptor<Map> identifiersCaptor = ArgumentCaptor.forClass(Map.class);
1996+
verify(mockODPEventManager).identifyUser(identifiersCaptor.capture());
1997+
Map<String, String> capturedIdentifiers = identifiersCaptor.getValue();
1998+
assertEquals("test-user", capturedIdentifiers.get("fs_user_id"));
19961999

19972000
Mockito.reset(mockODPEventManager);
19982001
OptimizelyUserContext userContextClone = userContext.copy();
19992002

2000-
// identifyUser should not be called the new userContext is created through copy
2001-
verify(mockODPEventManager, never()).identifyUser("test-user");
2003+
// identifyUser should not be called when the new userContext is created through copy
2004+
verify(mockODPEventManager, never()).identifyUser(any(Map.class));
20022005

20032006
assertNotSame(userContextClone, userContext);
20042007
}

core-api/src/test/java/com/optimizely/ab/odp/ODPEventManagerTest.java

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,10 @@ public void prepareCorrectPayloadForIdentifyUser() throws InterruptedException {
203203
eventManager.updateSettings(odpConfig);
204204
eventManager.start();
205205
for (int i = 0; i < 2; i++) {
206-
eventManager.identifyUser("the-vuid-" + i, "the-fs-user-id-" + i);
206+
Map<String, String> identifiers = new HashMap<>();
207+
identifiers.put("vuid", "the-vuid-" + i);
208+
identifiers.put("fs_user_id", "the-fs-user-id-" + i);
209+
eventManager.identifyUser(identifiers);
207210
}
208211

209212
Thread.sleep(1500);
@@ -290,61 +293,88 @@ public void preparePayloadForIdentifyUserWithVariationsOfFsUserId() throws Inter
290293
}
291294

292295
@Test
293-
public void identifyUserWithVuidAndUserId() throws InterruptedException {
296+
public void identifyUserWithMultipleIdentifiers() throws InterruptedException {
294297
ODPEventManager eventManager = spy(new ODPEventManager(mockApiManager));
295298
ArgumentCaptor<ODPEvent> captor = ArgumentCaptor.forClass(ODPEvent.class);
296299

297-
eventManager.identifyUser("vuid_123", "test-user");
300+
Map<String, String> identifiers = new HashMap<>();
301+
identifiers.put("vuid", "vuid_123");
302+
identifiers.put("fs_user_id", "test-user");
303+
eventManager.identifyUser(identifiers);
298304
verify(eventManager, times(1)).sendEvent(captor.capture());
299305

300306
ODPEvent event = captor.getValue();
301-
Map<String, String> identifiers = event.getIdentifiers();
302-
assertEquals(identifiers.size(), 2);
303-
assertEquals(identifiers.get("vuid"), "vuid_123");
304-
assertEquals(identifiers.get("fs_user_id"), "test-user");
307+
Map<String, String> eventIdentifiers = event.getIdentifiers();
308+
assertEquals(eventIdentifiers.size(), 2);
309+
assertEquals(eventIdentifiers.get("vuid"), "vuid_123");
310+
assertEquals(eventIdentifiers.get("fs_user_id"), "test-user");
305311
}
306312

307313
@Test
308-
public void identifyUserWithVuidOnly() throws InterruptedException {
314+
public void identifyUserSkippedWithSingleIdentifier() throws InterruptedException {
309315
ODPEventManager eventManager = spy(new ODPEventManager(mockApiManager));
310-
ArgumentCaptor<ODPEvent> captor = ArgumentCaptor.forClass(ODPEvent.class);
311316

312-
eventManager.identifyUser("vuid_123", null);
313-
verify(eventManager, times(1)).sendEvent(captor.capture());
317+
Map<String, String> identifiers = new HashMap<>();
318+
identifiers.put("fs_user_id", "test-user");
319+
eventManager.identifyUser(identifiers);
320+
verify(eventManager, never()).sendEvent(any(ODPEvent.class));
321+
logbackVerifier.expectMessage(Level.DEBUG, "ODP identify event is not dispatched (only one identifier provided).");
322+
}
314323

315-
ODPEvent event = captor.getValue();
316-
Map<String, String> identifiers = event.getIdentifiers();
317-
assertEquals(identifiers.size(), 1);
318-
assertEquals(identifiers.get("vuid"), "vuid_123");
324+
@Test
325+
public void identifyUserSkippedWithEmptyValues() throws InterruptedException {
326+
ODPEventManager eventManager = spy(new ODPEventManager(mockApiManager));
327+
328+
// Two keys but one has empty value - only 1 valid identifier
329+
Map<String, String> identifiers = new HashMap<>();
330+
identifiers.put("fs_user_id", "test-user");
331+
identifiers.put("email", "");
332+
eventManager.identifyUser(identifiers);
333+
verify(eventManager, never()).sendEvent(any(ODPEvent.class));
334+
logbackVerifier.expectMessage(Level.DEBUG, "ODP identify event is not dispatched (only one identifier provided).");
319335
}
320336

321337
@Test
322-
public void identifyUserWithUserIdOnly() throws InterruptedException {
338+
public void identifyUserSkippedWithNullValues() throws InterruptedException {
323339
ODPEventManager eventManager = spy(new ODPEventManager(mockApiManager));
324-
ArgumentCaptor<ODPEvent> captor = ArgumentCaptor.forClass(ODPEvent.class);
325340

326-
eventManager.identifyUser(null, "test-user");
327-
verify(eventManager, times(1)).sendEvent(captor.capture());
341+
// Two keys but one has null value - only 1 valid identifier
342+
Map<String, String> identifiers = new HashMap<>();
343+
identifiers.put("fs_user_id", "test-user");
344+
identifiers.put("vuid", null);
345+
eventManager.identifyUser(identifiers);
346+
verify(eventManager, never()).sendEvent(any(ODPEvent.class));
347+
logbackVerifier.expectMessage(Level.DEBUG, "ODP identify event is not dispatched (only one identifier provided).");
348+
}
328349

329-
ODPEvent event = captor.getValue();
330-
Map<String, String> identifiers = event.getIdentifiers();
331-
assertEquals(identifiers.size(), 1);
332-
assertEquals(identifiers.get("fs_user_id"), "test-user");
350+
@Test
351+
public void identifyUserSkippedWithEmptyMap() throws InterruptedException {
352+
ODPEventManager eventManager = spy(new ODPEventManager(mockApiManager));
353+
354+
Map<String, String> identifiers = new HashMap<>();
355+
eventManager.identifyUser(identifiers);
356+
verify(eventManager, never()).sendEvent(any(ODPEvent.class));
357+
logbackVerifier.expectMessage(Level.DEBUG, "ODP identify event is not dispatched (only one identifier provided).");
333358
}
334359

335360
@Test
336-
public void identifyUserWithVuidAsUserId() throws InterruptedException {
361+
public void identifyUserSendsWithThreeIdentifiers() throws InterruptedException {
337362
ODPEventManager eventManager = spy(new ODPEventManager(mockApiManager));
338363
ArgumentCaptor<ODPEvent> captor = ArgumentCaptor.forClass(ODPEvent.class);
339364

340-
eventManager.identifyUser(null, "vuid_123");
365+
Map<String, String> identifiers = new HashMap<>();
366+
identifiers.put("vuid", "vuid_123");
367+
identifiers.put("fs_user_id", "test-user");
368+
identifiers.put("email", "test@example.com");
369+
eventManager.identifyUser(identifiers);
341370
verify(eventManager, times(1)).sendEvent(captor.capture());
342371

343372
ODPEvent event = captor.getValue();
344-
Map<String, String> identifiers = event.getIdentifiers();
345-
assertEquals(identifiers.size(), 1);
346-
// SDK will convert userId to vuid when userId has a valid vuid format.
347-
assertEquals(identifiers.get("vuid"), "vuid_123");
373+
Map<String, String> eventIdentifiers = event.getIdentifiers();
374+
assertEquals(3, eventIdentifiers.size());
375+
assertEquals("vuid_123", eventIdentifiers.get("vuid"));
376+
assertEquals("test-user", eventIdentifiers.get("fs_user_id"));
377+
assertEquals("test@example.com", eventIdentifiers.get("email"));
348378
}
349379

350380
@Test

core-api/src/test/java/com/optimizely/ab/odp/ODPManagerTest.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323

2424
import java.util.Arrays;
2525
import java.util.Collections;
26+
import java.util.HashMap;
2627
import java.util.HashSet;
2728
import java.util.List;
29+
import java.util.Map;
2830

2931
import static org.mockito.Matchers.*;
3032
import static org.mockito.Mockito.*;
@@ -75,13 +77,16 @@ public void shouldUseNewSettingsInEventManagerWhenODPConfigIsUpdated() throws In
7577
ODPManager odpManager = ODPManager.builder().withApiManager(mockApiManager).build();
7678
odpManager.updateSettings("test-host", "test-key", new HashSet<>(Arrays.asList("segment1", "segment2")));
7779

78-
odpManager.getEventManager().identifyUser("vuid", "fsuid");
80+
Map<String, String> identifiers = new HashMap<>();
81+
identifiers.put("vuid", "vuid_value");
82+
identifiers.put("fs_user_id", "fsuid");
83+
odpManager.getEventManager().identifyUser(identifiers);
7984
Thread.sleep(2000);
8085
verify(mockApiManager, times(1))
8186
.sendEvents(eq("test-key"), eq("test-host/v3/events"), any());
8287

8388
odpManager.updateSettings("test-host-updated", "test-key-updated", new HashSet<>(Arrays.asList("segment1")));
84-
odpManager.getEventManager().identifyUser("vuid", "fsuid");
89+
odpManager.getEventManager().identifyUser(identifiers);
8590
Thread.sleep(1200);
8691
verify(mockApiManager, times(1))
8792
.sendEvents(eq("test-key-updated"), eq("test-host-updated/v3/events"), any());

0 commit comments

Comments
 (0)