From 388f4af0eae0d549dfb41c411daf60dfa3b680b8 Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Tue, 26 May 2026 11:53:45 -0700 Subject: [PATCH 1/5] [FSSDK-12670] Block ODP identify events with single identifier Change IdentifyUser in event manager to accept identifiers map instead of single userID. Filter out empty values and require 2+ valid identifiers before dispatching. Server-side Go SDK only has fs_user_id (no VUID), so identify events will be correctly skipped. --- pkg/odp/event/event_manager.go | 21 +++++++++++++++++---- pkg/odp/event/event_manager_test.go | 28 ++++++++++++++++++++++------ pkg/odp/odp_manager.go | 10 +++++++++- pkg/odp/odp_manager_test.go | 7 ++++--- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/pkg/odp/event/event_manager.go b/pkg/odp/event/event_manager.go index 89413450..1554eb1a 100644 --- a/pkg/odp/event/event_manager.go +++ b/pkg/odp/event/event_manager.go @@ -52,7 +52,7 @@ func getRetryInterval(retryCount int) time.Duration { type Manager interface { // odpConfig is required here since it can be updated anytime and ticker needs to be aware of latest changes Start(ctx context.Context, odpConfig config.Config) - IdentifyUser(apiKey, apiHost, userID string) + IdentifyUser(apiKey, apiHost string, identifiers map[string]string) ProcessEvent(apiKey, apiHost string, odpEvent Event) error FlushEvents(apiKey, apiHost string) } @@ -162,16 +162,29 @@ func (bm *BatchEventManager) Start(ctx context.Context, odpConfig config.Config) } // IdentifyUser associates a full-stack userid with an established VUID -func (bm *BatchEventManager) IdentifyUser(apiKey, apiHost, userID string) { +func (bm *BatchEventManager) IdentifyUser(apiKey, apiHost string, identifiers map[string]string) { if !bm.IsOdpServiceIntegrated(apiKey, apiHost) { bm.logger.Debug(utils.IdentityOdpNotIntegrated) return } - identifiers := map[string]string{utils.OdpFSUserIDKey: userID} + + // Filter out empty identifier values + validIdentifiers := make(map[string]string) + for k, v := range identifiers { + if v != "" { + validIdentifiers[k] = v + } + } + + if len(validIdentifiers) < 2 { + bm.logger.Debug("ODP identify event is not dispatched (only one identifier provided).") + return + } + odpEvent := Event{ Type: utils.OdpEventType, Action: utils.OdpActionIdentified, - Identifiers: identifiers, + Identifiers: validIdentifiers, } _ = bm.ProcessEvent(apiKey, apiHost, odpEvent) } diff --git a/pkg/odp/event/event_manager_test.go b/pkg/odp/event/event_manager_test.go index 764b5aa0..be3789bc 100644 --- a/pkg/odp/event/event_manager_test.go +++ b/pkg/odp/event/event_manager_test.go @@ -178,18 +178,19 @@ func (e *EventManagerTestSuite) TestEventsDispatchedWhenFlushIntervalReached() { } func (e *EventManagerTestSuite) TestIdentifyUserWhenODPNotIntegrated() { - e.eventManager.IdentifyUser("", "1", "123") + identifiers := map[string]string{utils.OdpFSUserIDKey: "123", "vuid": "vuid-123"} + e.eventManager.IdentifyUser("", "1", identifiers) e.Nil(e.eventManager.ticker) e.Equal(0, e.eventAPIManager.timesSendEventsCalled) } -func (e *EventManagerTestSuite) TestIdentifyUserWhenODPIntegrated() { - userID := "123" - expectedEvent := Event{Identifiers: map[string]string{utils.OdpFSUserIDKey: userID}, Type: utils.OdpEventType, Action: utils.OdpActionIdentified} +func (e *EventManagerTestSuite) TestIdentifyUserWhenODPIntegratedWithTwoIdentifiers() { + identifiers := map[string]string{utils.OdpFSUserIDKey: "123", "vuid": "vuid-456"} + expectedEvent := Event{Identifiers: identifiers, Type: utils.OdpEventType, Action: utils.OdpActionIdentified} e.eventManager.addCommonData(&expectedEvent) e.eventAPIManager.wg.Add(1) e.eventManager.batchSize = 1 - e.eventManager.IdentifyUser("1", "2", userID) + e.eventManager.IdentifyUser("1", "2", identifiers) e.eventAPIManager.wg.Wait() e.Equal(1, e.eventAPIManager.timesSendEventsCalled) @@ -200,6 +201,20 @@ func (e *EventManagerTestSuite) TestIdentifyUserWhenODPIntegrated() { e.Equal(expectedEvent, actualEvent) } +func (e *EventManagerTestSuite) TestIdentifyUserSkippedWithSingleIdentifier() { + identifiers := map[string]string{utils.OdpFSUserIDKey: "123"} + e.eventManager.IdentifyUser("1", "2", identifiers) + e.Equal(0, e.eventAPIManager.timesSendEventsCalled) + e.Equal(0, e.eventManager.eventQueue.Size()) +} + +func (e *EventManagerTestSuite) TestIdentifyUserSkippedWithEmptyValues() { + identifiers := map[string]string{utils.OdpFSUserIDKey: "123", "vuid": ""} + e.eventManager.IdentifyUser("1", "2", identifiers) + e.Equal(0, e.eventAPIManager.timesSendEventsCalled) + e.Equal(0, e.eventManager.eventQueue.Size()) +} + func (e *EventManagerTestSuite) TestProcessEventWithInvalidODPConfig() { em := NewBatchEventManager(WithAPIManager(&MockEventAPIManager{})) e.Error(em.ProcessEvent("", "", Event{Action: "123"})) @@ -442,7 +457,8 @@ func (e *EventManagerTestSuite) TestEventManagerAsyncBehaviour() { eventAPIManager.shouldNotInformWaitgroup = true eg := newExecutionContext() callAllMethods := func(id string) { - eventManager.IdentifyUser("-1", "-1", id) + identifiers := map[string]string{utils.OdpFSUserIDKey: id, "vuid": "vuid-" + id} + eventManager.IdentifyUser("-1", "-1", identifiers) eventManager.ProcessEvent("-1", "-1", Event{Action: "123"}) } for i := 0; i < iterations; i++ { diff --git a/pkg/odp/odp_manager.go b/pkg/odp/odp_manager.go index eb2c8041..e1eb99de 100644 --- a/pkg/odp/odp_manager.go +++ b/pkg/odp/odp_manager.go @@ -40,6 +40,13 @@ type Manager interface { Update(apiKey, apiHost string, segmentsToCheck []string) } +// identifyUserIdentifiers builds the identifiers map for an identify event. +// Server-side SDKs only have fs_user_id (no VUID), so identify events +// will be skipped by the event manager's count check (requires 2+ identifiers). +func identifyUserIdentifiers(userID string) map[string]string { + return map[string]string{utils.OdpFSUserIDKey: userID} +} + // DefaultOdpManager represents default implementation of odp manager type DefaultOdpManager struct { enabled bool @@ -141,7 +148,8 @@ func (om *DefaultOdpManager) IdentifyUser(userID string) { om.logger.Debug(utils.IdentityOdpDisabled) return } - om.EventManager.IdentifyUser(om.OdpConfig.GetAPIKey(), om.OdpConfig.GetAPIHost(), userID) + identifiers := identifyUserIdentifiers(userID) + om.EventManager.IdentifyUser(om.OdpConfig.GetAPIKey(), om.OdpConfig.GetAPIHost(), identifiers) } // SendOdpEvent sends an event to the ODP server. diff --git a/pkg/odp/odp_manager_test.go b/pkg/odp/odp_manager_test.go index 69d594f7..1e5b27fa 100644 --- a/pkg/odp/odp_manager_test.go +++ b/pkg/odp/odp_manager_test.go @@ -41,8 +41,8 @@ func (m *MockEventManager) Start(ctx context.Context, odpConfig config.Config) { m.Called(ctx, odpConfig) } -func (m *MockEventManager) IdentifyUser(apiKey, apiHost, userID string) { - m.Called(apiKey, apiHost, userID) +func (m *MockEventManager) IdentifyUser(apiKey, apiHost string, identifiers map[string]string) { + m.Called(apiKey, apiHost, identifiers) } func (m *MockEventManager) ProcessEvent(apiKey, apiHost string, odpEvent event.Event) error { @@ -192,7 +192,8 @@ func (o *ODPManagerTestSuite) TestFetchQualifiedSegments() { func (o *ODPManagerTestSuite) TestIdentifyUser() { o.config.On("GetAPIKey").Return("") o.config.On("GetAPIHost").Return("") - o.eventManager.On("IdentifyUser", "", "", o.userID) + expectedIdentifiers := map[string]string{utils.OdpFSUserIDKey: o.userID} + o.eventManager.On("IdentifyUser", "", "", expectedIdentifiers) o.odpManager.IdentifyUser(o.userID) o.segmentManager.AssertExpectations(o.T()) } From 192a1a4dfe86402e45d7025091a6187cc4f45c69 Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Wed, 27 May 2026 09:38:54 -0700 Subject: [PATCH 2/5] [FSSDK-12670] Address review feedback: fix log message --- pkg/odp/event/event_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/odp/event/event_manager.go b/pkg/odp/event/event_manager.go index 1554eb1a..610b0151 100644 --- a/pkg/odp/event/event_manager.go +++ b/pkg/odp/event/event_manager.go @@ -177,7 +177,7 @@ func (bm *BatchEventManager) IdentifyUser(apiKey, apiHost string, identifiers ma } if len(validIdentifiers) < 2 { - bm.logger.Debug("ODP identify event is not dispatched (only one identifier provided).") + bm.logger.Debug("ODP identify event is not dispatched (fewer than 2 valid identifiers).") return } From 54a138180dc89be24a1eb96d7b81262510ee1dfc Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Wed, 27 May 2026 10:00:20 -0700 Subject: [PATCH 3/5] [FSSDK-12670] Retrigger CI From 87d3527d75153f1186159d74790cb38773c2dcee Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Wed, 27 May 2026 13:49:07 -0700 Subject: [PATCH 4/5] [FSSDK-12670] Add comment explaining the < 2 identifiers guard Co-Authored-By: Claude Opus 4.6 --- pkg/odp/event/event_manager.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/odp/event/event_manager.go b/pkg/odp/event/event_manager.go index 610b0151..759a50c1 100644 --- a/pkg/odp/event/event_manager.go +++ b/pkg/odp/event/event_manager.go @@ -176,6 +176,8 @@ func (bm *BatchEventManager) IdentifyUser(apiKey, apiHost string, identifiers ma } } + // Identify requires 2+ identifiers to link (e.g., vuid + fs_user_id). + // A single identifier has no cross-reference value and generates unnecessary traffic. if len(validIdentifiers) < 2 { bm.logger.Debug("ODP identify event is not dispatched (fewer than 2 valid identifiers).") return From 90657fe992a7da20acaa6e9741c13ca26189658e Mon Sep 17 00:00:00 2001 From: Jae Kim Date: Wed, 27 May 2026 16:40:43 -0700 Subject: [PATCH 5/5] [FSSDK-12670] retrigger CI