From 0f955e92b37ec072c926234b6c2cad8759482667 Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Wed, 28 Aug 2024 11:36:22 +0530 Subject: [PATCH 1/6] Remove invalid user EIDs and UIDs from bid request --- endpoints/openrtb2/auction.go | 50 ++++- endpoints/openrtb2/auction_test.go | 176 ++++++++++++++++++ .../user-ext-eids-uids-id-empty.json | 42 ----- .../user-ext-eids-uids-missing.json | 49 ----- errortypes/code.go | 2 + 5 files changed, 226 insertions(+), 93 deletions(-) delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 57d9e73a27d..2fdbd2c475a 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1296,8 +1296,14 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases // Check Universal User ID eids := userExt.GetEid() if eids != nil { - eidsValue := *eids - for eidIndex, eid := range eidsValue { + + validEids, eidErrors := validateEIDs(*eids) + + if len(eidErrors) > 0 { + errL = append(errL, eidErrors...) + } + + for eidIndex, eid := range validEids { if eid.Source == "" { return append(errL, fmt.Errorf("request.user.ext.eids[%d] missing required field: \"source\"", eidIndex)) } @@ -1317,6 +1323,46 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases return errL } +func validateEIDs(eids []openrtb2.EID) ([]openrtb2.EID, []error) { + var errorsList []error + validEIDs := make([]openrtb2.EID, 0, len(eids)) + + for _, eid := range eids { + validUIDs, uidErrors := validateUIDs(eid.UIDs) + errorsList = append(errorsList, uidErrors...) + + if len(validUIDs) > 0 { + eid.UIDs = validUIDs + validEIDs = append(validEIDs, eid) + } else { + errorsList = append(errorsList, &errortypes.Warning{ + Message: fmt.Sprintf("Removed EID with empty UIDs (source: %s)", eid.Source), + WarningCode: errortypes.InvalidUserEIDsWarningCode, + }) + } + } + + return validEIDs, errorsList +} + +func validateUIDs(uids []openrtb2.UID) ([]openrtb2.UID, []error) { + var validUIDs []openrtb2.UID + var uidErrors []error + + for _, uid := range uids { + if uid.ID != "" { + validUIDs = append(validUIDs, uid) + } else { + uidErrors = append(uidErrors, &errortypes.Warning{ + Message: "Removed UID due to empty ID", + WarningCode: errortypes.InvalidUserUIDsWarningCode, + }) + } + } + + return validUIDs, uidErrors +} + func validateRegs(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) []error { var errL []error diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 717609e23d9..d7c6ddf0ba2 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -5922,3 +5922,179 @@ func sortUserData(user *openrtb2.User) { } } } +func TestValidateEIDs(t *testing.T) { + testCases := []struct { + name string + input []openrtb2.EID + expected []openrtb2.EID + numErrors int + expectedErrorMessages []string + }{ + { + name: "Valid EID with non-empty UID", + input: []openrtb2.EID{ + {Source: "src1", UIDs: []openrtb2.UID{{ID: "id1"}}}, + }, + expected: []openrtb2.EID{ + {Source: "src1", UIDs: []openrtb2.UID{{ID: "id1"}}}, + }, + numErrors: 0, + expectedErrorMessages: nil, + }, + { + name: "EID with empty UID", + input: []openrtb2.EID{ + {Source: "src2", UIDs: []openrtb2.UID{{ID: ""}}}, + }, + expected: []openrtb2.EID{}, + numErrors: 2, + expectedErrorMessages: []string{ + "Removed UID due to empty ID", + "Removed EID with empty UIDs (source: src2)", + }, + }, + { + name: "Multiple EIDs with some empty UIDs", + input: []openrtb2.EID{ + {Source: "src3", UIDs: []openrtb2.UID{{ID: "ID1"}, {ID: "ID2"}}}, + {Source: "src4", UIDs: []openrtb2.UID{{ID: ""}, {ID: "ID1"}}}, + {Source: "src5", UIDs: []openrtb2.UID{{ID: ""}, {ID: ""}}}, + }, + expected: []openrtb2.EID{ + {Source: "src3", UIDs: []openrtb2.UID{{ID: "ID1"}, {ID: "ID2"}}}, + {Source: "src4", UIDs: []openrtb2.UID{{ID: "ID1"}}}, + }, + numErrors: 4, + expectedErrorMessages: []string{ + "Removed UID due to empty ID", + "Removed UID due to empty ID", + "Removed UID due to empty ID", + "Removed EID with empty UIDs (source: src5)", + }, + }, + { + name: "All UIDs are empty", + input: []openrtb2.EID{ + {Source: "src6", UIDs: []openrtb2.UID{{ID: ""}, {ID: ""}}}, + }, + expected: []openrtb2.EID{}, + numErrors: 3, + expectedErrorMessages: []string{ + "Removed UID due to empty ID", + "Removed UID due to empty ID", + "Removed EID with empty UIDs (source: src6)", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + validEIDs, errorsList := validateEIDs(tc.input) + + if len(validEIDs) != len(tc.expected) { + t.Errorf("Expected %d valid EIDs, but got %d", len(tc.expected), len(validEIDs)) + } + + if len(errorsList) != tc.numErrors { + t.Errorf("Expected %d errors, but got %d", tc.numErrors, len(errorsList)) + } + + // Assert error messages + if tc.numErrors > 0 { + var errorMessages []string + for _, err := range errorsList { + if warning, ok := err.(*errortypes.Warning); ok { + errorMessages = append(errorMessages, warning.Message) + } + } + + for i, expectedMsg := range tc.expectedErrorMessages { + if i >= len(errorMessages) { + t.Errorf("Expected error message %q but got none", expectedMsg) + } else if expectedMsg != errorMessages[i] { + t.Errorf("Expected error message %q but got %q", expectedMsg, errorMessages[i]) + } + } + } + }) + } +} + +func TestValidateUIDs(t *testing.T) { + testCases := []struct { + name string + input []openrtb2.UID + expectedValidUIDs []openrtb2.UID + expectedErrors []error + }{ + { + name: "All valid UIDs", + input: []openrtb2.UID{ + {ID: "id1"}, + {ID: "id2"}, + }, + expectedValidUIDs: []openrtb2.UID{ + {ID: "id1"}, + {ID: "id2"}, + }, + expectedErrors: nil, + }, + { + name: "All empty UIDs", + input: []openrtb2.UID{ + {ID: ""}, + {ID: ""}, + }, + expectedValidUIDs: nil, + expectedErrors: []error{ + &errortypes.Warning{ + Message: "Removed UID due to empty ID", + WarningCode: errortypes.InvalidUserUIDsWarningCode, + }, + &errortypes.Warning{ + Message: "Removed UID due to empty ID", + WarningCode: errortypes.InvalidUserUIDsWarningCode, + }, + }, + }, + { + name: "Mixed valid and empty UIDs", + input: []openrtb2.UID{ + {ID: "id1"}, + {ID: ""}, + {ID: "id2"}, + {ID: ""}, + }, + expectedValidUIDs: []openrtb2.UID{ + {ID: "id1"}, + {ID: "id2"}, + }, + expectedErrors: []error{ + &errortypes.Warning{ + Message: "Removed UID due to empty ID", + WarningCode: errortypes.InvalidUserUIDsWarningCode, + }, + &errortypes.Warning{ + Message: "Removed UID due to empty ID", + WarningCode: errortypes.InvalidUserUIDsWarningCode, + }, + }, + }, + { + name: "No UIDs", + input: []openrtb2.UID{}, + expectedValidUIDs: nil, + expectedErrors: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + validUIDs, errorsList := validateUIDs(tc.input) + + assert.ElementsMatch(t, tc.expectedValidUIDs, validUIDs, "Valid UIDs mismatch") + + assert.ElementsMatch(t, tc.expectedErrors, errorsList, "Errors mismatch") + }) + } +} diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json deleted file mode 100644 index 910e9650d75..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "description": "Bid request where a request.user.ext.eids.uids array element is missing its id field", - "mockBidRequest": { - "id": "anyRequestID", - "site": { - "page": "prebid.org", - "publisher": { - "id": "anyPublisher" - } - }, - "imp": [{ - "id": "anyImpID", - "ext": { - "appnexus": { - "placementId": 42 - } - }, - "banner": { - "format": [{ - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - } - }], - "tmax": 1000, - "user": { - "ext": { - "eids": [{ - "source": "source1", - "uids": [{}] - }] - } - } - }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids[0].uids[0] missing required field: \"id\"\n" -} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json deleted file mode 100644 index eed386b4c7d..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "description": "Bid request with user.ext.eids array element array element that does not contain uids", - "mockBidRequest": { - "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", - "site": { - "page": "prebid.org", - "publisher": { - "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" - } - }, - "source": { - "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" - }, - "tmax": 1000, - "imp": [{ - "id": "/19968336/header-bid-tag-0", - "ext": { - "appnexus": { - "placementId": 12883451 - } - }, - "banner": { - "format": [{ - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - } - }], - "regs": { - "ext": { - "gdpr": 1 - } - }, - "user": { - "ext": { - "eids": [{ - "source": "source1" - }] - } - } - }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids[0].uids must contain at least one element or be undefined\n" -} \ No newline at end of file diff --git a/errortypes/code.go b/errortypes/code.go index 552beffbcb8..acd4c737e76 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -35,6 +35,8 @@ const ( InvalidBidResponseDSAWarningCode SecCookieDeprecationLenWarningCode SecBrowsingTopicsWarningCode + InvalidUserEIDsWarningCode + InvalidUserUIDsWarningCode ) // Coder provides an error or warning code with severity. From 22e6b2147b21b7903a1bd8a1ce73561da7039c6f Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Tue, 14 Jan 2025 14:19:12 +0530 Subject: [PATCH 2/6] Removed redundant check for uid.id validation --- endpoints/openrtb2/auction.go | 12 +- endpoints/openrtb2/auction_test.go | 207 +++++++++++++++++++++++++++-- 2 files changed, 200 insertions(+), 19 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 7f08e22c553..92f71443273 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1293,12 +1293,8 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases return append(errL, fmt.Errorf("request.user.eids[%d].uids must contain at least one element or be undefined", eidIndex)) } - for uidIndex, uid := range eid.UIDs { - if uid.ID == "" { - return append(errL, fmt.Errorf("request.user.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex)) - } - } } + req.User.EIDs = validEids } return errL @@ -1309,7 +1305,7 @@ func validateEIDs(eids []openrtb2.EID) ([]openrtb2.EID, []error) { validEIDs := make([]openrtb2.EID, 0, len(eids)) for eidIndex, eid := range eids { - validUIDs, uidErrors := validateUIDs(eid.UIDs) + validUIDs, uidErrors := validateUIDs(eid.UIDs, eidIndex) errorsList = append(errorsList, uidErrors...) if len(validUIDs) > 0 { @@ -1326,7 +1322,7 @@ func validateEIDs(eids []openrtb2.EID) ([]openrtb2.EID, []error) { return validEIDs, errorsList } -func validateUIDs(uids []openrtb2.UID) ([]openrtb2.UID, []error) { +func validateUIDs(uids []openrtb2.UID, eidIndex int) ([]openrtb2.UID, []error) { var validUIDs []openrtb2.UID var uidErrors []error @@ -1335,7 +1331,7 @@ func validateUIDs(uids []openrtb2.UID) ([]openrtb2.UID, []error) { validUIDs = append(validUIDs, uid) } else { uidErrors = append(uidErrors, &errortypes.Warning{ - Message: fmt.Sprintf("request.user.eids.uids[%d] contains empty ids and is removed from the request", uidIndex), + Message: fmt.Sprintf("request.user.eids[%d].uids[%d] contains empty ids and is removed from the request", eidIndex, uidIndex), WarningCode: errortypes.InvalidUserUIDsWarningCode, }) } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index fe6cf5d9f19..8ef5c953511 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -21,6 +21,7 @@ import ( "github.com/buger/jsonparser" jsoniter "github.com/json-iterator/go" "github.com/julienschmidt/httprouter" + gpplib "github.com/prebid/go-gpp" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v3/analytics" analyticsBuild "github.com/prebid/prebid-server/v3/analytics/build" @@ -6025,7 +6026,7 @@ func TestValidateEIDs(t *testing.T) { expected: []openrtb2.EID{}, numErrors: 2, expectedErrorMessages: []string{ - "request.user.eids.uids[0] contains empty ids and is removed from the request", + "request.user.eids[0].uids[0] contains empty ids and is removed from the request", "request.user.eids[0] (source: src2) contains only empty uids and is removed from the request", }, }, @@ -6042,9 +6043,9 @@ func TestValidateEIDs(t *testing.T) { }, numErrors: 4, expectedErrorMessages: []string{ - "request.user.eids.uids[0] contains empty ids and is removed from the request", - "request.user.eids.uids[0] contains empty ids and is removed from the request", - "request.user.eids.uids[1] contains empty ids and is removed from the request", + "request.user.eids[1].uids[0] contains empty ids and is removed from the request", + "request.user.eids[2].uids[0] contains empty ids and is removed from the request", + "request.user.eids[2].uids[1] contains empty ids and is removed from the request", "request.user.eids[2] (source: src5) contains only empty uids and is removed from the request", }, }, @@ -6056,8 +6057,8 @@ func TestValidateEIDs(t *testing.T) { expected: []openrtb2.EID{}, numErrors: 3, expectedErrorMessages: []string{ - "request.user.eids.uids[0] contains empty ids and is removed from the request", - "request.user.eids.uids[1] contains empty ids and is removed from the request", + "request.user.eids[0].uids[0] contains empty ids and is removed from the request", + "request.user.eids[0].uids[1] contains empty ids and is removed from the request", "request.user.eids[0] (source: src6) contains only empty uids and is removed from the request", }, }, @@ -6119,11 +6120,11 @@ func TestValidateUIDs(t *testing.T) { expectedValidUIDs: nil, expectedErrors: []error{ &errortypes.Warning{ - Message: "request.user.eids.uids[0] contains empty ids and is removed from the request", + Message: "request.user.eids[0].uids[0] contains empty ids and is removed from the request", WarningCode: errortypes.InvalidUserUIDsWarningCode, }, &errortypes.Warning{ - Message: "request.user.eids.uids[1] contains empty ids and is removed from the request", + Message: "request.user.eids[0].uids[1] contains empty ids and is removed from the request", WarningCode: errortypes.InvalidUserUIDsWarningCode, }, }, @@ -6142,11 +6143,11 @@ func TestValidateUIDs(t *testing.T) { }, expectedErrors: []error{ &errortypes.Warning{ - Message: "request.user.eids.uids[1] contains empty ids and is removed from the request", + Message: "request.user.eids[0].uids[1] contains empty ids and is removed from the request", WarningCode: errortypes.InvalidUserUIDsWarningCode, }, &errortypes.Warning{ - Message: "request.user.eids.uids[3] contains empty ids and is removed from the request", + Message: "request.user.eids[0].uids[3] contains empty ids and is removed from the request", WarningCode: errortypes.InvalidUserUIDsWarningCode, }, }, @@ -6167,10 +6168,194 @@ func TestValidateUIDs(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - validUIDs, errorsList := validateUIDs(tc.input) + validUIDs, errorsList := validateUIDs(tc.input, 0) assert.ElementsMatch(t, tc.expectedValidUIDs, validUIDs, "Valid UIDs mismatch") assert.ElementsMatch(t, tc.expectedErrors, errorsList, "Errors mismatch") }) } } +func TestValidateUser(t *testing.T) { + + testCases := []struct { + description string + req *openrtb_ext.RequestWrapper + expectedErr []error + expectedEids []openrtb2.EID + }{ + { + description: "Valid user with Geo accuracy", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + Geo: &openrtb2.Geo{ + Accuracy: 10, + }, + }, + }, + }, + expectedErr: nil, + }, + { + description: "Invalid user with negative Geo accuracy", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + Geo: &openrtb2.Geo{ + Accuracy: -10, + }, + }, + }, + }, + expectedErr: []error{errors.New("request.user.geo.accuracy must be a positive number")}, + }, + { + description: "Invalid user.ext.prebid with empty buyeruids", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid": {"buyeruids": {}}}`), + }, + }, + }, + expectedErr: []error{errors.New(`request.user.ext.prebid requires a "buyeruids" property with at least one ID defined. If none exist, then request.user.ext.prebid should not be defined.`)}, + }, + { + description: "Invalid user.eids with empty id", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + EIDs: []openrtb2.EID{ + { + Source: "source", + UIDs: []openrtb2.UID{ + {ID: ""}, + }, + }, + }, + }, + }, + }, + expectedErr: []error{&errortypes.Warning{ + Message: "request.user.eids[0].uids[0] contains empty ids and is removed from the request", + WarningCode: errortypes.InvalidUserUIDsWarningCode, + }, &errortypes.Warning{ + Message: "request.user.eids[0] (source: source) contains only empty uids and is removed from the request", + WarningCode: errortypes.InvalidUserEIDsWarningCode, + }}, + expectedEids: nil, + }, + { + description: "Valid user.eids with UID1 id", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + EIDs: []openrtb2.EID{ + { + Source: "source", + UIDs: []openrtb2.UID{ + {ID: "UID1"}, + }, + }, + }, + }, + }, + }, + expectedErr: nil, + expectedEids: []openrtb2.EID{ + { + Source: "source", + UIDs: []openrtb2.UID{ + {ID: "UID1"}, + }, + }, + }, + }, + { + description: "user.eids with empty UIDs", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + EIDs: []openrtb2.EID{ + { + Source: "source", + UIDs: []openrtb2.UID{ + {}, + }, + }, + }, + }, + }, + }, + expectedErr: []error{ + &errortypes.Warning{ + Message: "request.user.eids[0].uids[0] contains empty ids and is removed from the request", + WarningCode: errortypes.InvalidUserUIDsWarningCode, + }, + &errortypes.Warning{ + Message: "request.user.eids[0] (source: source) contains only empty uids and is removed from the request", + WarningCode: errortypes.InvalidUserEIDsWarningCode, + }, + }, + expectedEids: nil, + }, + { + description: "user.eids with empty UIDs", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + EIDs: []openrtb2.EID{ + { + Source: "source", + UIDs: []openrtb2.UID{}, + }, + }, + }, + }, + }, + expectedErr: []error{ + &errortypes.Warning{ + Message: "request.user.eids[0] (source: source) contains only empty uids and is removed from the request", + WarningCode: errortypes.InvalidUserEIDsWarningCode, + }, + }, + expectedEids: nil, + }, + { + description: "user.eids with No UIDs", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + EIDs: []openrtb2.EID{ + { + Source: "source", + }, + }, + }, + }, + }, + expectedErr: []error{ + &errortypes.Warning{ + Message: "request.user.eids[0] (source: source) contains only empty uids and is removed from the request", + WarningCode: errortypes.InvalidUserEIDsWarningCode, + }, + }, + expectedEids: nil, + }, + } + + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + deps := &endpointDeps{ + bidderMap: map[string]openrtb_ext.BidderName{ + "appnexus": "appnexus", + }, + } + errs := deps.validateUser(test.req, nil, gpplib.GppContainer{}) + assert.Equal(t, test.expectedErr, errs) + if test.req.User != nil { + assert.ElementsMatch(t, test.expectedEids, test.req.User.EIDs) + } + }) + } +} From 5514eae22c23c1591318829d51c31c7c0a583146 Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Tue, 4 Feb 2025 18:03:54 +0530 Subject: [PATCH 3/6] Refactor EID validation to issue warnings for missing source field instead of errors --- endpoints/openrtb2/auction.go | 12 +++++++----- .../invalid-whole/user-eids-source-empty.json | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 92f71443273..d8566bad3c2 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1285,14 +1285,9 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases errL = append(errL, eidErrors...) } for eidIndex, eid := range validEids { - if eid.Source == "" { - return append(errL, fmt.Errorf("request.user.eids[%d] missing required field: \"source\"", eidIndex)) - } - if len(eid.UIDs) == 0 { return append(errL, fmt.Errorf("request.user.eids[%d].uids must contain at least one element or be undefined", eidIndex)) } - } req.User.EIDs = validEids } @@ -1305,6 +1300,13 @@ func validateEIDs(eids []openrtb2.EID) ([]openrtb2.EID, []error) { validEIDs := make([]openrtb2.EID, 0, len(eids)) for eidIndex, eid := range eids { + if eid.Source == "" { + errorsList = append(errorsList, &errortypes.Warning{ + Message: fmt.Sprintf("request.user.eids[%d] missing required field: source", eidIndex), + WarningCode: errortypes.InvalidUserEIDsWarningCode, + }) + continue + } validUIDs, uidErrors := validateUIDs(eid.UIDs, eidIndex) errorsList = append(errorsList, uidErrors...) diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json index 64918452325..611cf21f65f 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json @@ -36,6 +36,6 @@ }] } }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.eids[0] missing required field: \"source\"" + "expectedReturnCode": 200, + "expectedErrorMessage": "request.user.eids[0] missing required field: source" } \ No newline at end of file From 74d9c26ba50c37e68bd48c2013db0ba251102d33 Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Fri, 28 Feb 2025 14:20:25 +0530 Subject: [PATCH 4/6] Remove validation for empty UIDs in user EIDs --- endpoints/openrtb2/auction.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index d8566bad3c2..12f7501fadf 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1284,11 +1284,6 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases if len(eidErrors) > 0 { errL = append(errL, eidErrors...) } - for eidIndex, eid := range validEids { - if len(eid.UIDs) == 0 { - return append(errL, fmt.Errorf("request.user.eids[%d].uids must contain at least one element or be undefined", eidIndex)) - } - } req.User.EIDs = validEids } From 4dcd6c9a422fef9be930089e10e8879daa66e744 Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Fri, 7 Mar 2025 13:16:33 +0530 Subject: [PATCH 5/6] Update EID validation to remove entries with empty UIDs and add new test cases for missing source field --- endpoints/openrtb2/auction.go | 6 +- endpoints/openrtb2/auction_test.go | 110 ++++++++++-------- .../user-eids-uids-id-empty.json | 10 +- .../invalid-whole/user-eids-uids-missing.json | 47 -------- .../user-eids-empty-uids-removed.json | 101 ++++++++++++++++ .../user-eids-source-empty.json | 9 +- .../valid-whole/user-eids-uids-missing.json | 65 +++++++++++ 7 files changed, 246 insertions(+), 102 deletions(-) delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/user-eids-empty-uids-removed.json rename endpoints/openrtb2/sample-requests/{invalid-whole => valid-whole}/user-eids-source-empty.json (79%) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/user-eids-uids-missing.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 12f7501fadf..192780c4818 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1297,7 +1297,7 @@ func validateEIDs(eids []openrtb2.EID) ([]openrtb2.EID, []error) { for eidIndex, eid := range eids { if eid.Source == "" { errorsList = append(errorsList, &errortypes.Warning{ - Message: fmt.Sprintf("request.user.eids[%d] missing required field: source", eidIndex), + Message: fmt.Sprintf("request.user.eids[%d] removed due to missing source", eidIndex), WarningCode: errortypes.InvalidUserEIDsWarningCode, }) continue @@ -1310,7 +1310,7 @@ func validateEIDs(eids []openrtb2.EID) ([]openrtb2.EID, []error) { validEIDs = append(validEIDs, eid) } else { errorsList = append(errorsList, &errortypes.Warning{ - Message: fmt.Sprintf("request.user.eids[%d] (source: %s) contains only empty uids and is removed from the request", eidIndex, eid.Source), + Message: fmt.Sprintf("request.user.eids[%d] (source: %s) removed due to empty uids", eidIndex, eid.Source), WarningCode: errortypes.InvalidUserEIDsWarningCode, }) } @@ -1328,7 +1328,7 @@ func validateUIDs(uids []openrtb2.UID, eidIndex int) ([]openrtb2.UID, []error) { validUIDs = append(validUIDs, uid) } else { uidErrors = append(uidErrors, &errortypes.Warning{ - Message: fmt.Sprintf("request.user.eids[%d].uids[%d] contains empty ids and is removed from the request", eidIndex, uidIndex), + Message: fmt.Sprintf("request.user.eids[%d].uids[%d] removed due to empty ids", eidIndex, uidIndex), WarningCode: errortypes.InvalidUserUIDsWarningCode, }) } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 8ef5c953511..c4de9a33e07 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -6026,8 +6026,8 @@ func TestValidateEIDs(t *testing.T) { expected: []openrtb2.EID{}, numErrors: 2, expectedErrorMessages: []string{ - "request.user.eids[0].uids[0] contains empty ids and is removed from the request", - "request.user.eids[0] (source: src2) contains only empty uids and is removed from the request", + "request.user.eids[0].uids[0] removed due to empty ids", + "request.user.eids[0] (source: src2) removed due to empty uids", }, }, { @@ -6043,10 +6043,10 @@ func TestValidateEIDs(t *testing.T) { }, numErrors: 4, expectedErrorMessages: []string{ - "request.user.eids[1].uids[0] contains empty ids and is removed from the request", - "request.user.eids[2].uids[0] contains empty ids and is removed from the request", - "request.user.eids[2].uids[1] contains empty ids and is removed from the request", - "request.user.eids[2] (source: src5) contains only empty uids and is removed from the request", + "request.user.eids[1].uids[0] removed due to empty ids", + "request.user.eids[2].uids[0] removed due to empty ids", + "request.user.eids[2].uids[1] removed due to empty ids", + "request.user.eids[2] (source: src5) removed due to empty uids", }, }, { @@ -6057,9 +6057,38 @@ func TestValidateEIDs(t *testing.T) { expected: []openrtb2.EID{}, numErrors: 3, expectedErrorMessages: []string{ - "request.user.eids[0].uids[0] contains empty ids and is removed from the request", - "request.user.eids[0].uids[1] contains empty ids and is removed from the request", - "request.user.eids[0] (source: src6) contains only empty uids and is removed from the request", + "request.user.eids[0].uids[0] removed due to empty ids", + "request.user.eids[0].uids[1] removed due to empty ids", + "request.user.eids[0] (source: src6) removed due to empty uids", + }, + }, + { + name: "eid_nil", + input: nil, + expected: nil, + numErrors: 0, + expectedErrorMessages: nil, + }, + { + name: "eid_uid_nil", + input: []openrtb2.EID{ + {Source: "src7", UIDs: nil}, + }, + expected: []openrtb2.EID{}, + numErrors: 1, + expectedErrorMessages: []string{ + "request.user.eids[0] (source: src7) removed due to empty uids", + }, + }, + { + name: "source_missing", + input: []openrtb2.EID{ + {UIDs: []openrtb2.UID{{ID: "id1"}}}, + }, + expected: []openrtb2.EID{}, + numErrors: 1, + expectedErrorMessages: []string{ + "request.user.eids[0] removed due to missing source", }, }, } @@ -6072,22 +6101,11 @@ func TestValidateEIDs(t *testing.T) { assert.Equal(t, tc.numErrors, len(errorsList)) // Assert error messages - if tc.numErrors > 0 { - var errorMessages []string - for _, err := range errorsList { - if warning, ok := err.(*errortypes.Warning); ok { - errorMessages = append(errorMessages, warning.Message) - } - } - - for i, expectedMsg := range tc.expectedErrorMessages { - if i >= len(errorMessages) { - t.Errorf("Expected error message %q but got none", expectedMsg) - } else if expectedMsg != errorMessages[i] { - t.Errorf("Expected error message %q but got %q", expectedMsg, errorMessages[i]) - } - } + assert.Equal(t, len(tc.expectedErrorMessages), len(errorsList)) + for _, err := range errorsList { + assert.Contains(t, tc.expectedErrorMessages, err.Error()) } + }) } } @@ -6120,11 +6138,11 @@ func TestValidateUIDs(t *testing.T) { expectedValidUIDs: nil, expectedErrors: []error{ &errortypes.Warning{ - Message: "request.user.eids[0].uids[0] contains empty ids and is removed from the request", + Message: "request.user.eids[0].uids[0] removed due to empty ids", WarningCode: errortypes.InvalidUserUIDsWarningCode, }, &errortypes.Warning{ - Message: "request.user.eids[0].uids[1] contains empty ids and is removed from the request", + Message: "request.user.eids[0].uids[1] removed due to empty ids", WarningCode: errortypes.InvalidUserUIDsWarningCode, }, }, @@ -6143,11 +6161,11 @@ func TestValidateUIDs(t *testing.T) { }, expectedErrors: []error{ &errortypes.Warning{ - Message: "request.user.eids[0].uids[1] contains empty ids and is removed from the request", + Message: "request.user.eids[0].uids[1] removed due to empty ids", WarningCode: errortypes.InvalidUserUIDsWarningCode, }, &errortypes.Warning{ - Message: "request.user.eids[0].uids[3] contains empty ids and is removed from the request", + Message: "request.user.eids[0].uids[3] removed due to empty ids", WarningCode: errortypes.InvalidUserUIDsWarningCode, }, }, @@ -6170,21 +6188,21 @@ func TestValidateUIDs(t *testing.T) { t.Run(tc.name, func(t *testing.T) { validUIDs, errorsList := validateUIDs(tc.input, 0) - assert.ElementsMatch(t, tc.expectedValidUIDs, validUIDs, "Valid UIDs mismatch") - assert.ElementsMatch(t, tc.expectedErrors, errorsList, "Errors mismatch") + assert.ElementsMatch(t, tc.expectedValidUIDs, validUIDs) + assert.ElementsMatch(t, tc.expectedErrors, errorsList) }) } } func TestValidateUser(t *testing.T) { testCases := []struct { - description string + name string req *openrtb_ext.RequestWrapper expectedErr []error expectedEids []openrtb2.EID }{ { - description: "Valid user with Geo accuracy", + name: "Valid_user_with_Geo_accuracy", req: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ User: &openrtb2.User{ @@ -6197,7 +6215,7 @@ func TestValidateUser(t *testing.T) { expectedErr: nil, }, { - description: "Invalid user with negative Geo accuracy", + name: "Invalid_user_with_negative_Geo_accuracy", req: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ User: &openrtb2.User{ @@ -6210,7 +6228,7 @@ func TestValidateUser(t *testing.T) { expectedErr: []error{errors.New("request.user.geo.accuracy must be a positive number")}, }, { - description: "Invalid user.ext.prebid with empty buyeruids", + name: "Invalid_user.ext.prebid_with_empty_buyeruids", req: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ User: &openrtb2.User{ @@ -6221,7 +6239,7 @@ func TestValidateUser(t *testing.T) { expectedErr: []error{errors.New(`request.user.ext.prebid requires a "buyeruids" property with at least one ID defined. If none exist, then request.user.ext.prebid should not be defined.`)}, }, { - description: "Invalid user.eids with empty id", + name: "Invalid_user.eids_with_empty_id", req: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ User: &openrtb2.User{ @@ -6237,16 +6255,16 @@ func TestValidateUser(t *testing.T) { }, }, expectedErr: []error{&errortypes.Warning{ - Message: "request.user.eids[0].uids[0] contains empty ids and is removed from the request", + Message: "request.user.eids[0].uids[0] removed due to empty ids", WarningCode: errortypes.InvalidUserUIDsWarningCode, }, &errortypes.Warning{ - Message: "request.user.eids[0] (source: source) contains only empty uids and is removed from the request", + Message: "request.user.eids[0] (source: source) removed due to empty uids", WarningCode: errortypes.InvalidUserEIDsWarningCode, }}, expectedEids: nil, }, { - description: "Valid user.eids with UID1 id", + name: "Valid_user.eids_with_UID1_id", req: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ User: &openrtb2.User{ @@ -6272,7 +6290,7 @@ func TestValidateUser(t *testing.T) { }, }, { - description: "user.eids with empty UIDs", + name: "user.eids_with_empty_UIDs", req: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ User: &openrtb2.User{ @@ -6289,18 +6307,18 @@ func TestValidateUser(t *testing.T) { }, expectedErr: []error{ &errortypes.Warning{ - Message: "request.user.eids[0].uids[0] contains empty ids and is removed from the request", + Message: "request.user.eids[0].uids[0] removed due to empty ids", WarningCode: errortypes.InvalidUserUIDsWarningCode, }, &errortypes.Warning{ - Message: "request.user.eids[0] (source: source) contains only empty uids and is removed from the request", + Message: "request.user.eids[0] (source: source) removed due to empty uids", WarningCode: errortypes.InvalidUserEIDsWarningCode, }, }, expectedEids: nil, }, { - description: "user.eids with empty UIDs", + name: "user.eids_with_empty_UIDs", req: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ User: &openrtb2.User{ @@ -6315,14 +6333,14 @@ func TestValidateUser(t *testing.T) { }, expectedErr: []error{ &errortypes.Warning{ - Message: "request.user.eids[0] (source: source) contains only empty uids and is removed from the request", + Message: "request.user.eids[0] (source: source) removed due to empty uids", WarningCode: errortypes.InvalidUserEIDsWarningCode, }, }, expectedEids: nil, }, { - description: "user.eids with No UIDs", + name: "user.eids_with_No_UIDs", req: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ User: &openrtb2.User{ @@ -6336,7 +6354,7 @@ func TestValidateUser(t *testing.T) { }, expectedErr: []error{ &errortypes.Warning{ - Message: "request.user.eids[0] (source: source) contains only empty uids and is removed from the request", + Message: "request.user.eids[0] (source: source) removed due to empty uids", WarningCode: errortypes.InvalidUserEIDsWarningCode, }, }, @@ -6345,7 +6363,7 @@ func TestValidateUser(t *testing.T) { } for _, test := range testCases { - t.Run(test.description, func(t *testing.T) { + t.Run(test.name, func(t *testing.T) { deps := &endpointDeps{ bidderMap: map[string]openrtb_ext.BidderName{ "appnexus": "appnexus", diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json index 3587f8364e0..64d55492d9d 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json @@ -29,12 +29,12 @@ }], "tmax": 1000, "user": { - "eids": [{ + "eids": [{ "source": "source1", - "uids": [{}] - }] - } + "uids": [] + }] +} }, "expectedReturnCode": 200, - "expectedErrorMessage": "request.user.eids[0] (source: source1) contains only empty uids and is removed from the request" + "expectedErrorMessage": "request.user.eids[0] (source: source1) removed due to empty uids" } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json deleted file mode 100644 index b077ff450cd..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "description": "Bid request with user.eids array element array element that does not contain uids", - "mockBidRequest": { - "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", - "site": { - "page": "prebid.org", - "publisher": { - "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" - } - }, - "source": { - "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" - }, - "tmax": 1000, - "imp": [{ - "id": "/19968336/header-bid-tag-0", - "ext": { - "appnexus": { - "placementId": 12883451 - } - }, - "banner": { - "format": [{ - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - } - }], - "regs": { - "ext": { - "gdpr": 1 - } - }, - "user": { - "eids": [{ - "source": "source1" - }] - } - }, - "expectedReturnCode": 200, - "expectedErrorMessage": "request.user.eids[0] (source: source1) contains only empty uids and is removed from the request" -} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/user-eids-empty-uids-removed.json b/endpoints/openrtb2/sample-requests/valid-whole/user-eids-empty-uids-removed.json new file mode 100644 index 00000000000..9a4e1373d1f --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/user-eids-empty-uids-removed.json @@ -0,0 +1,101 @@ +{ + "description": "Bid request where a request.user.eids.uids are removed when uid is empty, ensuring no bids are returned.", + "mockBidRequest": { + "id": "anyRequestID", + "site": { + "page": "prebid.org", + "publisher": { + "id": "anyPublisher" + } + }, + "imp": [ + { + "id": "anyImpID", + "ext": { + "appnexus": { + "placementId": 42 + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + } + } + ], + "tmax": 1000, + "user": { + "eids": [ + { + "source": "source-1", + "uids": [ + { + "id": "" + }, + { + "id": "id-a" + } + ] + }, + { + "source": "source-2", + "uids": [ + { + "id": "id-b" + } + ] + }, + { + "source": "source-3", + "uids": [ + { + "id": "" + }, + { + "id": "" + } + ] + } + ] + } + }, + "expectedReturnCode": 200, + "expectedBidResponse": { + "id": "anyRequestID", + "cur": "USD", + "seatbid": [], + "ext": { + "warnings": { + "general": [ + { + "code": 10013, + "message": "debug turned off for account" + }, + { + "code": 10013, + "message": "request.user.eids[0].uids[0] removed due to empty ids" + }, + { + "code": 10013, + "message": "request.user.eids[2].uids[0] removed due to empty ids" + }, + { + "code": 10013, + "message": "request.user.eids[2].uids[1] removed due to empty ids" + }, + { + "code": 10013, + "message": "request.user.eids[2] (source: source-3) removed due to empty uids" + } + ] + } + } + } +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json b/endpoints/openrtb2/sample-requests/valid-whole/user-eids-source-empty.json similarity index 79% rename from endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json rename to endpoints/openrtb2/sample-requests/valid-whole/user-eids-source-empty.json index 611cf21f65f..fc770b5d9ae 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/user-eids-source-empty.json @@ -37,5 +37,12 @@ } }, "expectedReturnCode": 200, - "expectedErrorMessage": "request.user.eids[0] missing required field: source" + "expectedBidResponse": { + "ext": { + "warnings": { + "source": "prebid-server", + "message": "request.user.eids[0] missing required field: source" + } + } + } } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/user-eids-uids-missing.json b/endpoints/openrtb2/sample-requests/valid-whole/user-eids-uids-missing.json new file mode 100644 index 00000000000..e33a8495a2a --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/user-eids-uids-missing.json @@ -0,0 +1,65 @@ +{ + "description": "Bid request with user.eids array element array element that does not contain uids", + "mockBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "publisher": { + "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" + } + }, + "source": { + "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" + }, + "tmax": 1000, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "ext": { + "appnexus": { + "placementId": 12883451 + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + } + } + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "eids": [ + { + "source": "source1" + } + ] + } + }, + "expectedReturnCode": 200, + "expectedBidResponse": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "cur": "USD", + "ext": { + "warnings": { + "general": [ + { + "code": 10013, + "message": "request.user.eids[0] (source: source1) removed due to empty uids" + } + ] + } + } + } +} \ No newline at end of file From f6eea3c2a83452d38d36e10c06fd5bc5c7762402 Mon Sep 17 00:00:00 2001 From: supriya-patil Date: Wed, 19 Mar 2025 21:57:53 +0530 Subject: [PATCH 6/6] Move user-eids-uids-empty.json to valid-whole folder --- .../user-eids-uids-empty.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename endpoints/openrtb2/sample-requests/{invalid-whole/user-eids-uids-id-empty.json => valid-whole/user-eids-uids-empty.json} (100%) diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json b/endpoints/openrtb2/sample-requests/valid-whole/user-eids-uids-empty.json similarity index 100% rename from endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json rename to endpoints/openrtb2/sample-requests/valid-whole/user-eids-uids-empty.json