diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index 83d50b2b56b..9e16b3438b8 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -173,7 +173,7 @@ func preprocess(imp *openrtb2.Imp, reqExt *openxReqExt) error { if imp.Video != nil { videoCopy := *imp.Video - if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory != nil && *bidderExt.Prebid.IsRewardedInventory == 1 { + if imp.Rwdd == 1 { videoCopy.Ext = json.RawMessage(`{"rewarded":1}`) } else { videoCopy.Ext = nil diff --git a/adapters/openx/openxtest/exemplary/video-rewarded.json b/adapters/openx/openxtest/exemplary/video-rewarded.json index f2eee34b465..58eadd7df9f 100644 --- a/adapters/openx/openxtest/exemplary/video-rewarded.json +++ b/adapters/openx/openxtest/exemplary/video-rewarded.json @@ -14,13 +14,11 @@ } }, "instl": 1, + "rwdd": 1, "ext": { "bidder": { "unit": "539439964", "delDomain": "se-demo-d.openx.net" - }, - "prebid": { - "is_rewarded_inventory": 1 } } } @@ -46,7 +44,8 @@ } }, "tagid": "539439964", - "instl": 1 + "instl": 1, + "rwdd": 1 } ], "ext": { diff --git a/amp/parse.go b/amp/parse.go index 12663ee93bd..a39888eee9f 100644 --- a/amp/parse.go +++ b/amp/parse.go @@ -117,7 +117,7 @@ func buildGdprTCF2ConsentWriter(ampParams Params) gdpr.ConsentWriter { // set regs.ext.gdpr if non-nil gdpr_applies was set to true gdprValue = parseGdprApplies(ampParams.GdprApplies) } - writer.RegExtGDPR = &gdprValue + writer.GDPR = &gdprValue return writer } diff --git a/amp/parse_test.go b/amp/parse_test.go index f2f097284c5..98006325e72 100644 --- a/amp/parse_test.go +++ b/amp/parse_test.go @@ -311,8 +311,8 @@ func TestPrivacyReader(t *testing.T) { }, expected: expectedResults{ policyWriter: gdpr.ConsentWriter{ - Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", - RegExtGDPR: &int8One, + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + GDPR: &int8One, }, warning: nil, }, @@ -378,8 +378,8 @@ func TestPrivacyReader(t *testing.T) { }, expected: expectedResults{ policyWriter: gdpr.ConsentWriter{ - Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", - RegExtGDPR: &int8One, + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + GDPR: &int8One, }, warning: nil, }, @@ -400,8 +400,8 @@ func TestPrivacyReader(t *testing.T) { }, expected: expectedResults{ policyWriter: gdpr.ConsentWriter{ - Consent: "INVALID_GDPR", - RegExtGDPR: &int8One, + Consent: "INVALID_GDPR", + GDPR: &int8One, }, warning: &errortypes.Warning{ Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", @@ -420,8 +420,8 @@ func TestPrivacyReader(t *testing.T) { }, expected: expectedResults{ policyWriter: gdpr.ConsentWriter{ - Consent: "INVALID_GDPR", - RegExtGDPR: &int8Zero, + Consent: "INVALID_GDPR", + GDPR: &int8Zero, }, warning: &errortypes.Warning{ Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", @@ -440,8 +440,8 @@ func TestPrivacyReader(t *testing.T) { }, expected: expectedResults{ policyWriter: gdpr.ConsentWriter{ - Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", - RegExtGDPR: &int8Zero, + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + GDPR: &int8Zero, }, warning: nil, }, @@ -457,8 +457,8 @@ func TestPrivacyReader(t *testing.T) { }, expected: expectedResults{ policyWriter: gdpr.ConsentWriter{ - Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", - RegExtGDPR: &int8One, + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + GDPR: &int8One, }, warning: nil, }, @@ -473,8 +473,8 @@ func TestPrivacyReader(t *testing.T) { }, expected: expectedResults{ policyWriter: gdpr.ConsentWriter{ - Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", - RegExtGDPR: &int8One, + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + GDPR: &int8One, }, warning: nil, }, @@ -559,8 +559,8 @@ func TestBuildGdprTCF2ConsentWriter(t *testing.T) { desc: "gdpr_applies not set", inParams: Params{Consent: consentString}, expectedWriter: gdpr.ConsentWriter{ - Consent: consentString, - RegExtGDPR: &int8One, + Consent: consentString, + GDPR: &int8One, }, }, { @@ -570,8 +570,8 @@ func TestBuildGdprTCF2ConsentWriter(t *testing.T) { GdprApplies: &boolFalse, }, expectedWriter: gdpr.ConsentWriter{ - Consent: consentString, - RegExtGDPR: &int8Zero, + Consent: consentString, + GDPR: &int8Zero, }, }, { @@ -581,8 +581,8 @@ func TestBuildGdprTCF2ConsentWriter(t *testing.T) { GdprApplies: &boolTrue, }, expectedWriter: gdpr.ConsentWriter{ - Consent: consentString, - RegExtGDPR: &int8One, + Consent: consentString, + GDPR: &int8One, }, }, } diff --git a/analytics/agma/agma_module.go b/analytics/agma/agma_module.go index b1a94537466..58133db2608 100644 --- a/analytics/agma/agma_module.go +++ b/analytics/agma/agma_module.go @@ -178,15 +178,11 @@ func (l *AgmaLogger) extractPublisherAndSite(requestWrapper *openrtb_ext.Request } func (l *AgmaLogger) shouldTrackEvent(requestWrapper *openrtb_ext.RequestWrapper) (bool, string) { - userExt, err := requestWrapper.GetUserExt() - if err != nil || userExt == nil { + if requestWrapper.User == nil { return false, "" } - consent := userExt.GetConsent() - if consent == nil { - return false, "" - } - consentStr := *consent + consentStr := requestWrapper.User.Consent + parsedConsent, err := vendorconsent.ParseString(consentStr) if err != nil { return false, "" diff --git a/analytics/agma/agma_module_test.go b/analytics/agma/agma_module_test.go index 8dfbb054d19..213af1860be 100644 --- a/analytics/agma/agma_module_test.go +++ b/analytics/agma/agma_module_test.go @@ -1,7 +1,6 @@ package agma import ( - "encoding/json" "io" "net/http" "net/http/httptest" @@ -37,7 +36,7 @@ var mockValidAuctionObject = analytics.AuctionObject{ UA: "ua", }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent": "` + agmaConsent + `"}`), + Consent: agmaConsent, }, }, }, @@ -59,7 +58,7 @@ var mockValidVideoObject = analytics.VideoObject{ UA: "ua", }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent": "` + agmaConsent + `"}`), + Consent: agmaConsent, }, }, }, @@ -81,7 +80,7 @@ var mockValidAmpObject = analytics.AmpObject{ UA: "ua", }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent": "` + agmaConsent + `"}`), + Consent: agmaConsent, }, }, }, @@ -212,7 +211,7 @@ func TestShouldTrackEvent(t *testing.T) { }, }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent": "` + agmaConsent + `"}`), + Consent: agmaConsent, }, }, }) @@ -245,7 +244,7 @@ func TestShouldTrackEvent(t *testing.T) { }, }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent": "CP4LywcP4LywcLRAAAENCZCAAAIAAAIAAAAAIxQAQIwgAAAA.II7Nd_X__bX9n-_7_6ft0eY1f9_r37uQzDhfNs-8F3L_W_LwX32E7NF36tq4KmR4ku1bBIQNtHMnUDUmxaolVrzHsak2cpyNKJ_JkknsZe2dYGF9Pn9lD-YKZ7_5_9_f52T_9_9_-39z3_9f___dv_-__-vjf_599n_v9fV_78_Kf9______-____________8A"}`), + Consent: "CP4LywcP4LywcLRAAAENCZCAAAIAAAIAAAAAIxQAQIwgAAAA.II7Nd_X__bX9n-_7_6ft0eY1f9_r37uQzDhfNs-8F3L_W_LwX32E7NF36tq4KmR4ku1bBIQNtHMnUDUmxaolVrzHsak2cpyNKJ_JkknsZe2dYGF9Pn9lD-YKZ7_5_9_f52T_9_9_-39z3_9f___dv_-__-vjf_599n_v9fV_78_Kf9______-____________8A", }, }, }) @@ -263,7 +262,7 @@ func TestShouldTrackEvent(t *testing.T) { }, }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent": "CP4LywcP4LywcLRAAAENCZCAAIAAAAAAAAAAIxQAQIxAAAAA.II7Nd_X__bX9n-_7_6ft0eY1f9_r37uQzDhfNs-8F3L_W_LwX32E7NF36tq4KmR4ku1bBIQNtHMnUDUmxaolVrzHsak2cpyNKJ_JkknsZe2dYGF9Pn9lD-YKZ7_5_9_f52T_9_9_-39z3_9f___dv_-__-vjf_599n_v9fV_78_Kf9______-____________8A"}`), + Consent: "CP4LywcP4LywcLRAAAENCZCAAIAAAAAAAAAAIxQAQIxAAAAA.II7Nd_X__bX9n-_7_6ft0eY1f9_r37uQzDhfNs-8F3L_W_LwX32E7NF36tq4KmR4ku1bBIQNtHMnUDUmxaolVrzHsak2cpyNKJ_JkknsZe2dYGF9Pn9lD-YKZ7_5_9_f52T_9_9_-39z3_9f___dv_-__-vjf_599n_v9fV_78_Kf9______-____________8A", }, }, }) @@ -281,7 +280,7 @@ func TestShouldTrackEvent(t *testing.T) { }, }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent": "` + agmaConsent + `"}`), + Consent: agmaConsent, }, }, }) @@ -296,7 +295,7 @@ func TestShouldTrackEvent(t *testing.T) { ID: "track-me", }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent": "` + agmaConsent + `"}`), + Consent: agmaConsent, }, }, }) @@ -311,7 +310,7 @@ func TestShouldTrackEvent(t *testing.T) { Bundle: "track-me", }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent": "` + agmaConsent + `"}`), + Consent: agmaConsent, }, }, }) @@ -359,7 +358,7 @@ func TestShouldTrackMultipleAccounts(t *testing.T) { }, }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent": "` + agmaConsent + `"}`), + Consent: agmaConsent, }, }, }) @@ -377,7 +376,7 @@ func TestShouldTrackMultipleAccounts(t *testing.T) { }, }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent": "` + agmaConsent + `"}`), + Consent: agmaConsent, }, }, }) @@ -691,7 +690,7 @@ func TestRaceEnd2End(t *testing.T) { time.Sleep(250 * time.Millisecond) - expected := "[{\"type\":\"amp\",\"id\":\"some-id\",\"code\":\"abcd\",\"site\":{\"id\":\"track-me-site\",\"publisher\":{\"id\":\"track-me\"}},\"device\":{\"ua\":\"ua\"},\"user\":{\"ext\":{\"consent\": \"" + agmaConsent + "\"}},\"created_at\":\"2023-02-01T00:00:00Z\"},{\"type\":\"amp\",\"id\":\"some-id\",\"code\":\"abcd\",\"site\":{\"id\":\"track-me-site\",\"publisher\":{\"id\":\"track-me\"}},\"device\":{\"ua\":\"ua\"},\"user\":{\"ext\":{\"consent\": \"" + agmaConsent + "\"}},\"created_at\":\"2023-02-01T00:00:00Z\"}]" + expected := "[{\"type\":\"amp\",\"id\":\"some-id\",\"code\":\"abcd\",\"site\":{\"id\":\"track-me-site\",\"publisher\":{\"id\":\"track-me\"}},\"device\":{\"ua\":\"ua\"},\"user\":{\"consent\":\"" + agmaConsent + "\"},\"created_at\":\"2023-02-01T00:00:00Z\"},{\"type\":\"amp\",\"id\":\"some-id\",\"code\":\"abcd\",\"site\":{\"id\":\"track-me-site\",\"publisher\":{\"id\":\"track-me\"}},\"device\":{\"ua\":\"ua\"},\"user\":{\"consent\":\"" + agmaConsent + "\"},\"created_at\":\"2023-02-01T00:00:00Z\"}]" mu.Lock() actual := requestBodyAsString diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 57410463b12..e8e135acd39 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -510,6 +510,14 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr // move to using the request wrapper req = &openrtb_ext.RequestWrapper{BidRequest: reqNormal} + // normalize to openrtb 2.6 + if err := openrtb_ext.ConvertUpTo26(req); err != nil { + errs = append(errs, err) + } + if errortypes.ContainsFatalError(errs) { + return + } + // Need to ensure cache and targeting are turned on e = initAmpTargetingAndCache(req) if errs = append(errs, e...); errortypes.ContainsFatalError(errs) { diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 8ec67552d74..e3533589859 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -56,6 +56,7 @@ func TestGoodAmpRequests(t *testing.T) { "buyeruids-case-insensitive.json", "buyeruids-camel-case.json", "aliased-buyeruids-case-insensitive.json", + "ortb-2.5-to-2.6-upconvert.json", }, }, { @@ -138,6 +139,17 @@ func TestGoodAmpRequests(t *testing.T) { assert.JSONEq(t, string(test.ExpectedValidatedBidReq), string(actualJson), "Not the expected validated request. Test file: %s", filename) } } + if test.ExpectedMockBidderRequests != nil { + for bidder, req := range test.ExpectedMockBidderRequests { + a, ok := ex.adapters[openrtb_ext.BidderName(bidder)] + if !ok { + t.Fatalf("Unexpected bidder %s has an expected mock bidder request. Test file: %s", bidder, filename) + } + aa := a.(*exchange.BidderAdapter) + ma := aa.Bidder.(*mockAdapter) + assert.JSONEq(t, string(req), string(ma.requestData[0]), "Not the expected mock bidder request for bidder %s. Test file: %s", bidder, filename) + } + } } } } @@ -235,55 +247,47 @@ func TestGDPRConsent(t *testing.T) { existingConsent := "BONV8oqONXwgmADACHENAO7pqzAAppY" testCases := []struct { - description string - consent string - userExt *openrtb_ext.ExtUser - nilUser bool - expectedUserExt openrtb_ext.ExtUser + description string + consent string + user *openrtb2.User + nilUser bool + expectedUser *openrtb2.User }{ { description: "Nil User", consent: consent, nilUser: true, - expectedUserExt: openrtb_ext.ExtUser{ - Consent: consent, - }, - }, - { - description: "Nil User Ext", - consent: consent, - userExt: nil, - expectedUserExt: openrtb_ext.ExtUser{ + expectedUser: &openrtb2.User{ Consent: consent, }, }, { description: "Overrides Existing Consent", consent: consent, - userExt: &openrtb_ext.ExtUser{ + user: &openrtb2.User{ Consent: existingConsent, }, - expectedUserExt: openrtb_ext.ExtUser{ + expectedUser: &openrtb2.User{ Consent: consent, }, }, { description: "Overrides Existing Consent - With Sibling Data", consent: consent, - userExt: &openrtb_ext.ExtUser{ + user: &openrtb2.User{ Consent: existingConsent, }, - expectedUserExt: openrtb_ext.ExtUser{ + expectedUser: &openrtb2.User{ Consent: consent, }, }, { description: "Does Not Override Existing Consent If Empty", consent: "", - userExt: &openrtb_ext.ExtUser{ + user: &openrtb2.User{ Consent: existingConsent, }, - expectedUserExt: openrtb_ext.ExtUser{ + expectedUser: &openrtb2.User{ Consent: existingConsent, }, }, @@ -291,7 +295,7 @@ func TestGDPRConsent(t *testing.T) { for _, test := range testCases { // Build Request - bid, err := getTestBidRequest(test.nilUser, test.userExt, true, nil) + bid, err := getTestBidRequest(test.nilUser, test.user, true, nil) if err != nil { t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } @@ -341,15 +345,8 @@ func TestGDPRConsent(t *testing.T) { if !assert.NotNil(t, result.User, test.description+":lastRequest.User") { return } - if !assert.NotNil(t, result.User.Ext, test.description+":lastRequest.User.Ext") { - return - } - var ue openrtb_ext.ExtUser - err = jsonutil.UnmarshalValid(result.User.Ext, &ue) - if !assert.NoError(t, err, test.description+":deserialize") { - return - } - assert.Equal(t, test.expectedUserExt, ue, test.description) + + assert.Equal(t, test.expectedUser, result.User, test.description) assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors, test.description+":errors") assert.Empty(t, response.ORTB2.Ext.Warnings, test.description+":warnings") @@ -372,15 +369,8 @@ func TestGDPRConsent(t *testing.T) { if !assert.NotNil(t, resultLegacy.User, test.description+":legacy:lastRequest.User") { return } - if !assert.NotNil(t, resultLegacy.User.Ext, test.description+":legacy:lastRequest.User.Ext") { - return - } - var ueLegacy openrtb_ext.ExtUser - err = jsonutil.UnmarshalValid(resultLegacy.User.Ext, &ueLegacy) - if !assert.NoError(t, err, test.description+":legacy:deserialize") { - return - } - assert.Equal(t, test.expectedUserExt, ueLegacy, test.description+":legacy") + + assert.Equal(t, test.expectedUser, resultLegacy.User, test.description+":legacy") assert.Equal(t, expectedErrorsFromHoldAuction, responseLegacy.ORTB2.Ext.Errors, test.description+":legacy:errors") assert.Empty(t, responseLegacy.ORTB2.Ext.Warnings, test.description+":legacy:warnings") } @@ -553,28 +543,6 @@ func TestOverrideWithParams(t *testing.T) { errorMsgs: []string{"unable to merge imp.ext with targeting data, check targeting data is correct: Invalid JSON Patch"}, }, }, - { - desc: "bid request with malformed user.ext.prebid - amp.Params with GDPR consent values - expect policy writer to return error", - given: testInput{ - ampParams: amp.Params{ - ConsentType: amp.ConsentTCF2, - Consent: "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", - }, - bidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, - User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)}, - }, - }, - expected: testOutput{ - bidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, - User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)}, - Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}, - }, - errorMsgs: []string{"expect \" after {, but found m"}, - expectFatalErrors: true, - }, - }, } for _, test := range testCases { @@ -661,46 +629,46 @@ func TestCCPAConsent(t *testing.T) { var gdpr int8 = 1 testCases := []struct { - description string - consent string - regsExt *openrtb_ext.ExtRegs - nilRegs bool - expectedRegExt openrtb_ext.ExtRegs + description string + consent string + regs openrtb2.Regs + nilRegs bool + expectedReg *openrtb2.Regs }{ { description: "Nil Regs", consent: consent, nilRegs: true, - expectedRegExt: openrtb_ext.ExtRegs{ + expectedReg: &openrtb2.Regs{ USPrivacy: consent, }, }, { description: "Nil Regs Ext", consent: consent, - regsExt: nil, - expectedRegExt: openrtb_ext.ExtRegs{ + nilRegs: true, + expectedReg: &openrtb2.Regs{ USPrivacy: consent, }, }, { description: "Overrides Existing Consent", consent: consent, - regsExt: &openrtb_ext.ExtRegs{ + regs: openrtb2.Regs{ USPrivacy: existingConsent, }, - expectedRegExt: openrtb_ext.ExtRegs{ + expectedReg: &openrtb2.Regs{ USPrivacy: consent, }, }, { description: "Overrides Existing Consent - With Sibling Data", consent: consent, - regsExt: &openrtb_ext.ExtRegs{ + regs: openrtb2.Regs{ USPrivacy: existingConsent, GDPR: &gdpr, }, - expectedRegExt: openrtb_ext.ExtRegs{ + expectedReg: &openrtb2.Regs{ USPrivacy: consent, GDPR: &gdpr, }, @@ -708,10 +676,10 @@ func TestCCPAConsent(t *testing.T) { { description: "Does Not Override Existing Consent If Empty", consent: "", - regsExt: &openrtb_ext.ExtRegs{ + regs: openrtb2.Regs{ USPrivacy: existingConsent, }, - expectedRegExt: openrtb_ext.ExtRegs{ + expectedReg: &openrtb2.Regs{ USPrivacy: existingConsent, }, }, @@ -719,7 +687,7 @@ func TestCCPAConsent(t *testing.T) { for _, test := range testCases { // Build Request - bid, err := getTestBidRequest(true, nil, test.nilRegs, test.regsExt) + bid, err := getTestBidRequest(true, nil, test.nilRegs, &test.regs) if err != nil { t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } @@ -765,15 +733,8 @@ func TestCCPAConsent(t *testing.T) { if !assert.NotNil(t, result.Regs, test.description+":lastRequest.Regs") { return } - if !assert.NotNil(t, result.Regs.Ext, test.description+":lastRequest.Regs.Ext") { - return - } - var re openrtb_ext.ExtRegs - err = jsonutil.UnmarshalValid(result.Regs.Ext, &re) - if !assert.NoError(t, err, test.description+":deserialize") { - return - } - assert.Equal(t, test.expectedRegExt, re, test.description) + + assert.Equal(t, test.expectedReg, result.Regs, test.description) assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors) assert.Empty(t, response.ORTB2.Ext.Warnings) } @@ -781,7 +742,7 @@ func TestCCPAConsent(t *testing.T) { func TestConsentWarnings(t *testing.T) { type inputTest struct { - regs *openrtb_ext.ExtRegs + regs *openrtb2.Regs invalidConsentURL bool expectedWarnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage } @@ -812,7 +773,7 @@ func TestConsentWarnings(t *testing.T) { expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderReservedGeneral: {invalidCCPAWarning}}, }, { - regs: &openrtb_ext.ExtRegs{USPrivacy: "invalid"}, + regs: &openrtb2.Regs{USPrivacy: "invalid"}, invalidConsentURL: true, expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ openrtb_ext.BidderReservedGeneral: {invalidCCPAWarning, invalidConsentWarning}, @@ -820,7 +781,7 @@ func TestConsentWarnings(t *testing.T) { }, }, { - regs: &openrtb_ext.ExtRegs{USPrivacy: "1NYN"}, + regs: &openrtb2.Regs{USPrivacy: "1NYN"}, invalidConsentURL: false, expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderName("appnexus"): {bidderWarning}}, }, @@ -903,17 +864,18 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { validConsentGDPR2 := "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" testCases := []struct { - description string - consent string - consentLegacy string - userExt *openrtb_ext.ExtUser - expectedUserExt openrtb_ext.ExtUser + description string + consent string + consentLegacy string + user *openrtb2.User + expectedUser *openrtb2.User }{ { description: "New Consent Wins", consent: validConsentGDPR1, consentLegacy: validConsentGDPR2, - expectedUserExt: openrtb_ext.ExtUser{ + user: &openrtb2.User{}, + expectedUser: &openrtb2.User{ Consent: validConsentGDPR1, }, }, @@ -921,7 +883,8 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { description: "New Consent Wins - Reverse", consent: validConsentGDPR2, consentLegacy: validConsentGDPR1, - expectedUserExt: openrtb_ext.ExtUser{ + user: &openrtb2.User{}, + expectedUser: &openrtb2.User{ Consent: validConsentGDPR2, }, }, @@ -929,7 +892,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { for _, test := range testCases { // Build Request - bid, err := getTestBidRequest(false, nil, true, nil) + bid, err := getTestBidRequest(false, test.user, true, nil) if err != nil { t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } @@ -978,15 +941,8 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { if !assert.NotNil(t, result.User, test.description+":lastRequest.User") { return } - if !assert.NotNil(t, result.User.Ext, test.description+":lastRequest.User.Ext") { - return - } - var ue openrtb_ext.ExtUser - err = jsonutil.UnmarshalValid(result.User.Ext, &ue) - if !assert.NoError(t, err, test.description+":deserialize") { - return - } - assert.Equal(t, test.expectedUserExt, ue, test.description) + + assert.Equal(t, test.expectedUser, result.User, test.description) assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors) assert.Empty(t, response.ORTB2.Ext.Warnings) } @@ -1030,6 +986,7 @@ func TestAMPSiteExt(t *testing.T) { } // TestBadRequests makes sure we return 400's on bad requests. +// RTB26: Will need to be fixed once all validation functions are updated to rtb 2.6 func TestAmpBadRequests(t *testing.T) { dir := "sample-requests/invalid-whole/" files, err := os.ReadDir(dir) @@ -1037,6 +994,7 @@ func TestAmpBadRequests(t *testing.T) { mockAmpStoredReq := make(map[string]json.RawMessage, len(files)) badRequests := make(map[string]testCase, len(files)) + filemap := make(map[string]string, len(files)) for index, file := range files { filename := file.Name() fileData := readFile(t, dir+filename) @@ -1055,6 +1013,7 @@ func TestAmpBadRequests(t *testing.T) { badRequests[requestID] = test mockAmpStoredReq[requestID] = test.BidRequest + filemap[requestID] = filename } addAmpBadRequests(badRequests, mockAmpStoredReq) @@ -1076,15 +1035,17 @@ func TestAmpBadRequests(t *testing.T) { nil, ) - for _, test := range badRequests { - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?%s", test.Query), nil) - recorder := httptest.NewRecorder() + for id, test := range badRequests { + t.Run(filemap[id], func(t *testing.T) { + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?%s", test.Query), nil) + recorder := httptest.NewRecorder() - endpoint(recorder, request, nil) + endpoint(recorder, request, nil) - response := recorder.Body.String() - assert.Equal(t, test.ExpectedReturnCode, recorder.Code, test.Description) - assert.Contains(t, response, test.ExpectedErrorMessage, "Actual: %s \nExpected: %s. Description: %s \n", response, test.ExpectedErrorMessage, test.Description) + response := recorder.Body.String() + assert.Equal(t, test.ExpectedReturnCode, recorder.Code, test.Description) + assert.Contains(t, response, test.ExpectedErrorMessage, "Actual: %s \nExpected: %s. Description: %s \n", response, test.ExpectedErrorMessage, test.Description) + }) } } @@ -1555,7 +1516,7 @@ func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r *exchange.A return &exchange.AuctionResponse{BidResponse: response}, nil } -func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, regsExt *openrtb_ext.ExtRegs) ([]byte, error) { +func getTestBidRequest(nilUser bool, user *openrtb2.User, nilRegs bool, regs *openrtb2.Regs) ([]byte, error) { var width int64 = 300 var height int64 = 300 bidRequest := &openrtb2.BidRequest{ @@ -1586,37 +1547,12 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, }, } - var userExtData []byte - if userExt != nil { - var err error - userExtData, err = jsonutil.Marshal(userExt) - if err != nil { - return nil, err - } - } - if !nilUser { - bidRequest.User = &openrtb2.User{ - ID: "aUserId", - BuyerUID: "aBuyerID", - Ext: userExtData, - } - } - - var regsExtData []byte - if regsExt != nil { - var err error - regsExtData, err = jsonutil.Marshal(regsExt) - if err != nil { - return nil, err - } + bidRequest.User = user } if !nilRegs { - bidRequest.Regs = &openrtb2.Regs{ - COPPA: 1, - Ext: regsExtData, - } + bidRequest.Regs = regs } return jsonutil.Marshal(bidRequest) } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index a383e297318..2ef86f13252 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -27,6 +27,7 @@ import ( "github.com/prebid/prebid-server/v2/ortb" "github.com/prebid/prebid-server/v2/privacy" "github.com/prebid/prebid-server/v2/privacysandbox" + "github.com/prebid/prebid-server/v2/schain" "golang.org/x/net/publicsuffix" jsonpatch "gopkg.in/evanphx/json-patch.v4" @@ -43,7 +44,6 @@ import ( "github.com/prebid/prebid-server/v2/prebid_cache_client" "github.com/prebid/prebid-server/v2/privacy/ccpa" "github.com/prebid/prebid-server/v2/privacy/lmt" - "github.com/prebid/prebid-server/v2/schain" "github.com/prebid/prebid-server/v2/stored_requests" "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/v2/stored_responses" @@ -534,6 +534,12 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return } + // normalize to openrtb 2.6 + if err := openrtb_ext.ConvertUpTo26(req); err != nil { + errs = []error{err} + return + } + if err := mergeBidderParams(req); err != nil { errs = []error{err} return @@ -824,10 +830,6 @@ func (deps *endpointDeps) validateRequest(account *config.Account, httpReq *http } } - if err := mapSChains(req); err != nil { - return []error{err} - } - if err := validateOrFillChannel(req, isAmp); err != nil { return []error{err} } @@ -931,32 +933,6 @@ func (deps *endpointDeps) validateRequest(account *config.Account, httpReq *http return errL } -// mapSChains maps an schain defined in an ORTB 2.4 location (req.ext.schain) to the ORTB 2.5 location -// (req.source.ext.schain) if no ORTB 2.5 schain (req.source.ext.schain, req.ext.prebid.schains) exists. -// An ORTB 2.4 schain is always deleted from the 2.4 location regardless of whether an ORTB 2.5 schain exists. -func mapSChains(req *openrtb_ext.RequestWrapper) error { - reqExt, err := req.GetRequestExt() - if err != nil { - return fmt.Errorf("req.ext is invalid: %v", err) - } - sourceExt, err := req.GetSourceExt() - if err != nil { - return fmt.Errorf("source.ext is invalid: %v", err) - } - - reqExtSChain := reqExt.GetSChain() - reqExt.SetSChain(nil) - - if reqPrebid := reqExt.GetPrebid(); reqPrebid != nil && reqPrebid.SChains != nil { - return nil - } else if sourceExt.GetSChain() != nil { - return nil - } else if reqExtSChain != nil { - sourceExt.SetSChain(reqExtSChain) - } - return nil -} - func validateAndFillSourceTID(req *openrtb_ext.RequestWrapper, generateRequestID bool, hasStoredBidRequest bool, isAmp bool) error { if req.Source == nil { req.Source = &openrtb2.Source{} @@ -1295,22 +1271,18 @@ 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 { - if eid.Source == "" { - return append(errL, fmt.Errorf("request.user.ext.eids[%d] missing required field: \"source\"", eidIndex)) - } + for eidIndex, eid := range req.User.EIDs { + 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.ext.eids[%d].uids must contain at least one element or be undefined", 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)) + } - for uidIndex, uid := range eid.UIDs { - if uid.ID == "" { - return append(errL, fmt.Errorf("request.user.ext.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex)) - } + 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)) } } } @@ -1339,16 +1311,11 @@ func validateRegs(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) []er WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) } } - regsExt, err := req.GetRegExt() - if err != nil { - return append(errL, fmt.Errorf("request.regs.ext is invalid: %v", err)) - } - gdpr := regsExt.GetGDPR() - if gdpr != nil && *gdpr != 0 && *gdpr != 1 { - return append(errL, errors.New("request.regs.ext.gdpr must be either 0 or 1")) + reqGDPR := req.BidRequest.Regs.GDPR + if reqGDPR != nil && *reqGDPR != 0 && *reqGDPR != 1 { + return append(errL, errors.New("request.regs.gdpr must be either 0 or 1")) } - return errL } @@ -1371,7 +1338,6 @@ func validateDevice(device *openrtb2.Device) error { if device.Geo != nil && device.Geo.Accuracy < 0 { return errors.New("request.device.geo.accuracy must be a positive number") } - return nil } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 9654a39ea89..72f5089bbf9 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -176,11 +176,30 @@ func runJsonBasedTest(t *testing.T, filename, desc string) { cfg.MarshalAccountDefaults() test.endpointType = OPENRTB_ENDPOINT - auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) + auctionEndpointHandler, ex, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) if assert.NoError(t, err) { assert.NotPanics(t, func() { runEndToEndTest(t, auctionEndpointHandler, test, fileData, filename) }, filename) } + if test.ExpectedValidatedBidReq != nil { + // compare as json to ignore whitespace and ext field ordering + actualJson, err := jsonutil.Marshal(ex.actualValidatedBidReq) + if assert.NoError(t, err, "Error converting actual bid request to json. Test file: %s", filename) { + assert.JSONEq(t, string(test.ExpectedValidatedBidReq), string(actualJson), "Not the expected validated request. Test file: %s", filename) + } + } + if test.ExpectedMockBidderRequests != nil { + for bidder, req := range test.ExpectedMockBidderRequests { + a, ok := ex.adapters[openrtb_ext.BidderName(bidder)] + if !ok { + t.Fatalf("Unexpected bidder %s has an expected mock bidder request. Test file: %s", bidder, filename) + } + aa := a.(*exchange.BidderAdapter) + ma := aa.Bidder.(*mockAdapter) + assert.JSONEq(t, string(req), string(ma.requestData[0]), "Not the expected mock bidder request for bidder %s. Test file: %s", bidder, filename) + } + } + // Close servers regardless if the test case was run or not for _, mockBidServer := range mockBidServers { mockBidServer.Close() @@ -2775,7 +2794,7 @@ func TestCCPAInvalid(t *testing.T) { ID: "anySiteID", }, Regs: &openrtb2.Regs{ - Ext: json.RawMessage(`{"us_privacy": "invalid by length"}`), + USPrivacy: "invalid by length", }, } @@ -2938,134 +2957,6 @@ func TestSChainInvalid(t *testing.T) { assert.ElementsMatch(t, errL, []error{expectedError}) } -func TestMapSChains(t *testing.T) { - const seller1SChain string = `"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}` - const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}` - - seller1SChainUnpacked := openrtb2.SupplyChain{ - Complete: 1, - Nodes: []openrtb2.SupplyChainNode{{ - ASI: "directseller1.com", - SID: "00001", - RID: "BidRequest1", - HP: openrtb2.Int8Ptr(1), - }}, - Ver: "1.0", - } - - tests := []struct { - description string - bidRequest openrtb2.BidRequest - wantReqExtSChain *openrtb2.SupplyChain - wantSourceExtSChain *openrtb2.SupplyChain - wantError bool - }{ - { - description: "invalid req.ext", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":invalid}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{}`), - }, - }, - wantError: true, - }, - { - description: "invalid source.ext", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{"schain":invalid}}`), - }, - }, - wantError: true, - }, - { - description: "req.ext.prebid.schains, req.source.ext.schain and req.ext.schain are nil", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{}`), - }, - }, - wantReqExtSChain: nil, - wantSourceExtSChain: nil, - }, - { - description: "req.ext.prebid.schains is not nil", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{}`), - }, - }, - wantReqExtSChain: nil, - wantSourceExtSChain: nil, - }, - { - description: "req.source.ext is not nil", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller1SChain + `}`), - }, - }, - wantReqExtSChain: nil, - wantSourceExtSChain: &seller1SChainUnpacked, - }, - { - description: "req.ext.schain is not nil", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{` + seller1SChain + `}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{}`), - }, - }, - wantReqExtSChain: nil, - wantSourceExtSChain: &seller1SChainUnpacked, - }, - { - description: "req.source.ext.schain and req.ext.schain are not nil", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{` + seller2SChain + `}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller1SChain + `}`), - }, - }, - wantReqExtSChain: nil, - wantSourceExtSChain: &seller1SChainUnpacked, - }, - } - - for _, test := range tests { - reqWrapper := openrtb_ext.RequestWrapper{ - BidRequest: &test.bidRequest, - } - - err := mapSChains(&reqWrapper) - - if test.wantError { - assert.NotNil(t, err, test.description) - } else { - assert.Nil(t, err, test.description) - - reqExt, err := reqWrapper.GetRequestExt() - if err != nil { - assert.Fail(t, "Error getting request ext from wrapper", test.description) - } - reqExtSChain := reqExt.GetSChain() - assert.Equal(t, test.wantReqExtSChain, reqExtSChain, test.description) - - sourceExt, err := reqWrapper.GetSourceExt() - if err != nil { - assert.Fail(t, "Error getting source ext from wrapper", test.description) - } - sourceExtSChain := sourceExt.GetSChain() - assert.Equal(t, test.wantSourceExtSChain, sourceExtSChain, test.description) - } - } -} - func TestSearchAccountID(t *testing.T) { // Correctness for lookup within Publisher object left to TestGetAccountID // This however tests the expected lookup paths in outer site, app and dooh diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json index 8dc19f6f24d..30d8b9ca240 100644 --- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json @@ -74,9 +74,7 @@ } ], "regs": { - "ext": { - "us_privacy": "1YYY" - } + "us_privacy": "1YYY" }, "ext": { "prebid": { diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json index 4003abf99cb..8c232192a63 100644 --- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json @@ -74,14 +74,10 @@ } ], "regs": { - "ext": { - "gdpr": 1 - } + "gdpr": 1 }, "user": { - "ext": { - "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" - } + "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" }, "ext": { "prebid": { diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json index c6389dadc29..0249bbc3f96 100644 --- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json @@ -79,9 +79,7 @@ } ], "regs": { - "ext": { - "gdpr": 1 - } + "gdpr": 1 }, "ext": { "prebid": { diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json index b62a745b1bf..6f0f5780b43 100644 --- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json @@ -74,14 +74,10 @@ } ], "regs": { - "ext": { - "gdpr": 1 - } + "gdpr": 1 }, "user": { - "ext": { - "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" - } + "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" }, "ext": { "prebid": { diff --git a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json new file mode 100644 index 00000000000..766b7fc6ba9 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json @@ -0,0 +1,306 @@ +{ + "description": "Amp request with all 2.5 ext fields that were moved into 2.6 ortb fields", + "query": "tag_id=101", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 15 + } + ] + }, + "mockBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + }, + "is_rewarded_inventory": 1 + } + } + } + ], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + } + }, + "expectedValidatedBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "ext": { + "amp": 1 + } + }, + "device": { + "ip": "192.0.2.1" + }, + "at": 1, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + }, + "secure": 1, + "rwdd": 1 + } + ], + "regs": { + "gdpr": 1, + "us_privacy": "1YYY" + }, + "user": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + }, + "source": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + }, + "ext": { + "prebid": { + "cache": { + "bids": {} + }, + "channel": { + "name": "amp", + "version": "" + }, + "targeting": { + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + }, + "includewinners": true, + "includebidderkeys": true, + "mediatypepricegranularity": {} + } + } + } + }, + "expectedMockBidderRequests": { + "appnexus": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "ext": { + "amp": 1 + } + }, + "device": { + "ip": "192.0.2.1" + }, + "at": 1, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 12883451 + }, + "prebid": { + "is_rewarded_inventory": 1 + } + }, + "secure": 1 + } + ], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "ext": { + "prebid": { + "channel": { + "name": "amp", + "version": "" + } + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "15.00", + "hb_pb_appnexus": "15.00" + }, + "ortb2": { + "ext": { + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json deleted file mode 100644 index dc15410a290..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "description": "Invalid GDPR value in regs field", - "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": "foo" - } - }, - "user": { - "ext": {} - } - }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: gdpr must be an integer\n" -} diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json index 7ab2631b701..4a513f703b1 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json @@ -42,5 +42,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: expect { or n, but found " + "expectedErrorMessage": "Invalid request: req.regs.ext is invalid: expect { or n, but found " } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-gdpr-invalid.json similarity index 86% rename from endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json rename to endpoints/openrtb2/sample-requests/invalid-whole/regs-gdpr-invalid.json index 03e789eef86..40b7281d572 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-gdpr-invalid.json @@ -35,14 +35,12 @@ } ], "regs": { - "ext": { - "gdpr": 2 - } + "gdpr": 2 }, "user": { "ext": {} } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1\n" + "expectedErrorMessage": "Invalid request: request.regs.gdpr must be either 0 or 1\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json similarity index 63% rename from endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json rename to endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json index 910e9650d75..902a2d9c1b6 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json @@ -1,5 +1,5 @@ { - "description": "Bid request where a request.user.ext.eids.uids array element is missing its id field", + "description": "Bid request with user.eids array element that does not contain source field", "mockBidRequest": { "id": "anyRequestID", "site": { @@ -29,14 +29,13 @@ }], "tmax": 1000, "user": { - "ext": { - "eids": [{ - "source": "source1", - "uids": [{}] + "eids": [{ + "uids": [{ + "id": "A" }] - } + }] } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids[0].uids[0] missing required field: \"id\"\n" + "expectedErrorMessage": "Invalid request: request.user.eids[0] missing required field: \"source\"\n" } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json similarity index 62% rename from endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json rename to endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json index 3a451ecbd76..c8eb07aa335 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json @@ -1,5 +1,5 @@ { - "description": "Bid request with user.ext.eids array element array element that does not contain source field", + "description": "Bid request where a request.user.eids.uids array element is missing its id field", "mockBidRequest": { "id": "anyRequestID", "site": { @@ -29,15 +29,12 @@ }], "tmax": 1000, "user": { - "ext": { - "eids": [{ - "uids": [{ - "id": "A" - }] - }] - } + "eids": [{ + "source": "source1", + "uids": [{}] + }] } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids[0] missing required field: \"source\"\n" + "expectedErrorMessage": "Invalid request: request.user.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-eids-uids-missing.json similarity index 70% rename from endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json rename to endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json index eed386b4c7d..3e55c33b849 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json @@ -1,5 +1,5 @@ { - "description": "Bid request with user.ext.eids array element array element that does not contain uids", + "description": "Bid request with user.eids array element array element that does not contain uids", "mockBidRequest": { "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", "site": { @@ -37,13 +37,11 @@ } }, "user": { - "ext": { - "eids": [{ - "source": "source1" - }] - } + "eids": [{ + "source": "source1" + }] } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids[0].uids must contain at least one element or be undefined\n" + "expectedErrorMessage": "Invalid request: request.user.eids[0].uids must contain at least one element or be undefined\n" } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json index af04627c3a9..222ffb993b7 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json @@ -46,5 +46,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: expects \" or n, but found 1" + "expectedErrorMessage": "Invalid request: req.user.ext is invalid: expects \" or n, but found 1" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json index b710d589ea5..ae9d72c8682 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json @@ -41,5 +41,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: expects \" or n, but found 2" + "expectedErrorMessage": "Invalid request: req.user.ext is invalid: expects \" or n, but found 2" } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/device-sua.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/device-sua.json new file mode 100644 index 00000000000..0f85f904166 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/device-sua.json @@ -0,0 +1,100 @@ +{ + "description": "Bid request defines an valid request.device.sua value", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "publisher": { + "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" + } + }, + "tmax": 1000, + "imp": [ + { + "id": "some-impression-id", + "ext": { + "appnexus": { + "placementId": 12883451 + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + } + } + ], + "device": { + "ua": "Mozilla", + "geo": { + "lat": 123.456, + "lon": 678.90, + "zip": "90210" + }, + "sua": { + "browsers": [ + { + "brand": "MS", + "ext": {} + }, + { + "brand": "MS", + "ext": {} + } + ], + "platform": { + "brand": "MS", + "ext": {} + }, + "model": "mac" + }, + "dnt": 1, + "lmt": 1 + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0, + "ext": { + "origbidcpm": 0, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.5-to-2.6-upconvert.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.5-to-2.6-upconvert.json new file mode 100644 index 00000000000..3ff235c4b30 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.5-to-2.6-upconvert.json @@ -0,0 +1,393 @@ +{ + "description": "Request with all 2.5 ext fields that were moved into 2.6 ortb fields", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 15 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } + ], + "bidderInfoOverrides": { + "appnexus": { + "openrtb": { + "version": "2.5" + } + }, + "rubicon": { + "openrtb": { + "version": "2.6" + } + } + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + }, + "is_rewarded_inventory": 1 + } + } + } + ], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "ext": {} + }, + "expectedValidatedBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + }, + "secure": 1, + "rwdd": 1 + } + ], + "regs": { + "gdpr": 1, + "us_privacy": "1YYY" + }, + "user": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + }, + "source": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "expectedMockBidderRequests": { + "appnexus": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 12883451 + }, + "prebid": { + "is_rewarded_inventory": 1 + } + }, + "secure": 1 + }], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + } + }, + "rubicon": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + }, + "secure": 1, + "rwdd": 1 + }], + "regs": { + "gdpr": 1, + "us_privacy": "1YYY" + }, + "source": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + }, + "user": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 15, + "ext": { + "origbidcpm": 15, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ], + "seat": "appnexus" + }, + { + "bid": [ + { + "id": "rubicon-bid", + "impid": "some-impression-id", + "price": 1.00, + "ext": { + "origbidcpm": 1.00, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "rubicon" + }, + "type": "banner" + } + } + } + ], + "seat": "rubicon" + } + ], + "bidid": "test-bid-id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.6-to-2.5-downconvert.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.6-to-2.5-downconvert.json new file mode 100644 index 00000000000..28ba8a707e0 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.6-to-2.5-downconvert.json @@ -0,0 +1,417 @@ +{ + "description": "Request with all 2.5 ext fields that were moved into 2.6 ortb fields", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 15 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } + ], + "bidderInfoOverrides": { + "appnexus": { + "openrtb": { + "version": "2.5" + } + }, + "rubicon": { + "openrtb": { + "version": "2.6" + } + } + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "inventorypartnerdomain": "any-domain" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + }, + "is_rewarded_inventory": 1 + } + }, + "refresh": { + "count": 10 + } + } + ], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "ext": {}, + "cattax": 20, + "acat": ["any-acat"] + }, + "expectedValidatedBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "inventorypartnerdomain": "any-domain", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + }, + "secure": 1, + "rwdd": 1, + "refresh": { + "count": 10 + } + } + ], + "regs": { + "gdpr": 1, + "us_privacy": "1YYY" + }, + "user": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + }, + "source": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + }, + "cattax": 20, + "acat": ["any-acat"] + }, + "expectedMockBidderRequests": { + "appnexus": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "inventorypartnerdomain": "any-domain", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 12883451 + }, + "prebid": { + "is_rewarded_inventory": 1 + } + }, + "refresh": { + "count": 10 + }, + "secure": 1 + }], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "cattax": 20, + "acat": ["any-acat"] + }, + "rubicon": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "inventorypartnerdomain": "any-domain", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + }, + "secure": 1, + "refresh": { + "count": 10 + }, + "rwdd": 1 + }], + "regs": { + "gdpr": 1, + "us_privacy": "1YYY" + }, + "source": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + }, + "user": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + }, + "cattax": 20, + "acat": ["any-acat"] + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 15, + "ext": { + "origbidcpm": 15, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ], + "seat": "appnexus" + }, + { + "bid": [ + { + "id": "rubicon-bid", + "impid": "some-impression-id", + "price": 1.00, + "ext": { + "origbidcpm": 1.00, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "rubicon" + }, + "type": "banner" + } + } + } + ], + "seat": "rubicon" + } + ], + "bidid": "test-bid-id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/source-schain.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/source-schain.json new file mode 100644 index 00000000000..c25f7ca1c47 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/source-schain.json @@ -0,0 +1,91 @@ +{ + "description": "Bid request defines a valid request.source.schain.nodes value", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "publisher": { + "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" + } + }, + "tmax": 1000, + "imp": [ + { + "id": "some-impression-id", + "ext": { + "appnexus": { + "placementId": 12883451 + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + } + } + ], + "source": { + "fd": 1, + "tid": "abc123", + "pchain": "tag_placement", + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "asi", + "sid": "sid", + "rid": "rid", + "ext": {} + } + ], + "ver": "ver", + "ext": {} + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0, + "ext": { + "origbidcpm": 0, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json index 3d6a0774b9d..9565e41af1f 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json @@ -37,10 +37,7 @@ "regs": { "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN", "gpp_sid": [6], - "gdpr": 1, - "ext": { - "us_privacy": "1YYY" - } + "gdpr": 1 }, "user": { "consent": "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json index 2102a8cd44b..54477ed0986 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json @@ -37,10 +37,7 @@ "regs": { "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN", "gpp_sid": [2,6], - "gdpr": 1, - "ext": { - "us_privacy": "1YYY" - } + "gdpr": 1 }, "user": { "consent": "Invalid", diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json index 2ccdfb7ccdc..df2c426d0c3 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json @@ -35,9 +35,7 @@ } ], "regs": { - "ext": { - "us_privacy": "{invalid}" - } + "us_privacy": "{invalid}" }, "user": { "ext": {} diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index 4122657fd24..329f7952369 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -64,15 +64,16 @@ const ( type testCase struct { // Common - endpointType int - Description string `json:"description"` - Config *testConfigValues `json:"config"` - BidRequest json.RawMessage `json:"mockBidRequest"` - ExpectedValidatedBidReq json.RawMessage `json:"expectedValidatedBidRequest"` - ExpectedReturnCode int `json:"expectedReturnCode,omitempty"` - ExpectedErrorMessage string `json:"expectedErrorMessage"` - Query string `json:"query"` - planBuilder hooks.ExecutionPlanBuilder + endpointType int + Description string `json:"description"` + Config *testConfigValues `json:"config"` + BidRequest json.RawMessage `json:"mockBidRequest"` + ExpectedValidatedBidReq json.RawMessage `json:"expectedValidatedBidRequest"` + ExpectedMockBidderRequests map[string]json.RawMessage `json:"expectedMockBidderRequests"` + ExpectedReturnCode int `json:"expectedReturnCode,omitempty"` + ExpectedErrorMessage string `json:"expectedErrorMessage"` + Query string `json:"query"` + planBuilder hooks.ExecutionPlanBuilder // "/openrtb2/auction" endpoint JSON test info ExpectedBidResponse json.RawMessage `json:"expectedBidResponse"` @@ -84,13 +85,20 @@ type testCase struct { } type testConfigValues struct { - AccountRequired bool `json:"accountRequired"` - AliasJSON string `json:"aliases"` - BlockedApps []string `json:"blockedApps"` - DisabledAdapters []string `json:"disabledAdapters"` - CurrencyRates map[string]map[string]float64 `json:"currencyRates"` - MockBidders []mockBidderHandler `json:"mockBidders"` - RealParamsValidator bool `json:"realParamsValidator"` + AccountRequired bool `json:"accountRequired"` + AliasJSON string `json:"aliases"` + BlockedApps []string `json:"blockedApps"` + DisabledAdapters []string `json:"disabledAdapters"` + CurrencyRates map[string]map[string]float64 `json:"currencyRates"` + MockBidders []mockBidderHandler `json:"mockBidders"` + RealParamsValidator bool `json:"realParamsValidator"` + BidderInfos map[string]bidderInfoOverrides `json:"bidderInfoOverrides"` +} +type bidderInfoOverrides struct { + OpenRTB *OpenRTBInfo `json:"openrtb"` +} +type OpenRTBInfo struct { + Version string `json:"version"` } type brokenExchange struct{} @@ -1002,6 +1010,7 @@ type mockAdapter struct { mockServerURL string Server config.Server seat string + requestData [][]byte } func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { @@ -1012,7 +1021,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return adapter, nil } -func (a mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var requests []*adapters.RequestData var errors []error @@ -1032,11 +1041,12 @@ func (a mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *ada Body: requestJSON, } requests = append(requests, requestData) + a.requestData = append(a.requestData, requestData.Body) } return requests, errors } -func (a mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { if responseData.StatusCode != http.StatusOK { switch responseData.StatusCode { case http.StatusNoContent: @@ -1159,6 +1169,18 @@ func parseTestData(fileData []byte, testFile string) (testCase, error) { return parsedTestData, fmt.Errorf("Test case %s should come with either a valid expectedBidResponse or a valid expectedErrorMessage, not both.", testFile) } + // Get optional expected validated bid request + parsedTestData.ExpectedValidatedBidReq, _, _, err = jsonparser.Get(fileData, "expectedValidatedBidRequest") + + // Get optional expected mock bidder requests + jsonExpectedMockBidderRequests, _, _, err := jsonparser.Get(fileData, "expectedMockBidderRequests") + if err == nil && jsonExpectedMockBidderRequests != nil { + parsedTestData.ExpectedMockBidderRequests = make(map[string]json.RawMessage) + if err = jsonutil.UnmarshalValid(jsonExpectedMockBidderRequests, &parsedTestData.ExpectedMockBidderRequests); err != nil { + return parsedTestData, fmt.Errorf("Error unmarshaling root.expectedMockBidderRequests from file %s. Desc: %v.", testFile, err) + } + } + parsedTestData.ExpectedReturnCode = int(parsedReturnCode) return parsedTestData, nil @@ -1180,6 +1202,7 @@ func (tc *testConfigValues) getBlockedAppLookup() map[string]bool { type exchangeTestWrapper struct { ex exchange.Exchange actualValidatedBidReq *openrtb2.BidRequest + adapters map[openrtb_ext.BidderName]exchange.AdaptedBidder } func (te *exchangeTestWrapper) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) { @@ -1206,7 +1229,7 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid bidderAdapter := mockAdapter{mockServerURL: bidServer.URL, seat: mockBidder.Seat} bidderName := openrtb_ext.BidderName(mockBidder.BidderName) - adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "") + adapterMap[bidderName] = exchange.AdaptBidder(&bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "") mockBidServersArray = append(mockBidServersArray, bidServer) } @@ -1218,6 +1241,7 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid }.Builder testExchange := exchange.NewExchange(adapterMap, + &wellBehavedCache{}, cfg, requestValidator, @@ -1233,7 +1257,8 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid ) testExchange = &exchangeTestWrapper{ - ex: testExchange, + ex: testExchange, + adapters: adapterMap, } return testExchange, mockBidServersArray @@ -1257,6 +1282,18 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han } bidderInfos, _ := config.LoadBidderInfoFromDisk("../../static/bidder-info") + for bidder, overrides := range test.Config.BidderInfos { + if bi, ok := bidderInfos[bidder]; ok { + if overrides.OpenRTB != nil && len(overrides.OpenRTB.Version) > 0 { + if bi.OpenRTB == nil { + bi.OpenRTB = &config.OpenRTBInfo{} + } + bi.OpenRTB.Version = overrides.OpenRTB.Version + bidderInfos[bidder] = bi + } + } + } + enableBidders(bidderInfos) disableBidders(test.Config.DisabledAdapters, bidderInfos) bidderMap := exchange.GetActiveBidders(bidderInfos) diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index c3b7610731d..9625228fa82 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -260,6 +260,11 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re // all code after this line should use the bidReqWrapper instead of bidReq directly bidReqWrapper := &openrtb_ext.RequestWrapper{BidRequest: bidReq} + if err := openrtb_ext.ConvertUpTo26(bidReqWrapper); err != nil { + handleError(&labels, w, []error{err}, &vo, &debugLog) + return + } + if err := ortb.SetDefaults(bidReqWrapper); err != nil { handleError(&labels, w, errL, &vo, &debugLog) return diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 879e7f5024b..9438c3699b5 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1091,14 +1091,11 @@ func TestCCPA(t *testing.T) { if ex.lastRequest == nil { t.Fatalf("%s: The request never made it into the exchange.", test.description) } - extRegs := &openrtb_ext.ExtRegs{} - if err := jsonutil.UnmarshalValid(ex.lastRequest.Regs.Ext, extRegs); err != nil { - t.Fatalf("%s: Failed to unmarshal reg.ext in request to the exchange: %v", test.description, err) - } + if test.expectConsentString { - assert.Len(t, extRegs.USPrivacy, 4, test.description+":consent") + assert.Len(t, ex.lastRequest.Regs.USPrivacy, 4, test.description+":consent") } else if test.expectEmptyConsent { - assert.Empty(t, extRegs.USPrivacy, test.description+":consent") + assert.Empty(t, ex.lastRequest.Regs.USPrivacy, test.description+":consent") } // Validate HTTP Response diff --git a/exchange/bidder.go b/exchange/bidder.go index cfaea36c0bb..6a37e689e77 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -98,7 +98,7 @@ const ( // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter" // (which is being phased out and replaced by Bidder for OpenRTB auctions) func AdaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo, endpointCompression string) AdaptedBidder { - return &bidderAdapter{ + return &BidderAdapter{ Bidder: bidder, BidderName: name, Client: client, @@ -119,7 +119,7 @@ func parseDebugInfo(info *config.DebugInfo) bool { return info.Allow } -type bidderAdapter struct { +type BidderAdapter struct { Bidder adapters.Bidder BidderName openrtb_ext.BidderName Client *http.Client @@ -134,7 +134,7 @@ type bidderAdapterConfig struct { EndpointCompression string } -func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) { +func (bidder *BidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) { request := openrtb_ext.RequestWrapper{BidRequest: bidderRequest.BidRequest} reject := hookExecutor.ExecuteBidderRequestStage(&request, string(bidderRequest.BidderName)) seatNonBidBuilder := SeatNonBidBuilder{} @@ -526,11 +526,11 @@ func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall { // doRequest makes a request, handles the response, and returns the data needed by the // Bidder interface. -func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo { +func (bidder *BidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo { return bidder.doRequestImpl(ctx, req, glog.Warningf, bidderRequestStartTime, tmaxAdjustments) } -func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo { +func (bidder *BidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo { requestBody, err := getRequestBody(req, bidder.config.EndpointCompression) if err != nil { return &httpCallInfo{ @@ -617,7 +617,7 @@ func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.Re } } -func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData, logger util.LogMsg) { +func (bidder *BidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData, logger util.LogMsg) { ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() toReq, errL := timeoutBidder.MakeTimeoutNotification(req) @@ -667,7 +667,7 @@ type httpCallInfo struct { // This function adds an httptrace.ClientTrace object to the context so, if connection with the bidder // endpoint is established, we can keep track of whether the connection was newly created, reused, and // the time from the connection request, to the connection creation. -func (bidder *bidderAdapter) addClientTrace(ctx context.Context) context.Context { +func (bidder *BidderAdapter) addClientTrace(ctx context.Context) context.Context { var connStart, dnsStart, tlsStart time.Time trace := &httptrace.ClientTrace{ diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 9d5faf47549..98f9d9961cc 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -556,7 +556,7 @@ func TestBidderTimeout(t *testing.T) { server := httptest.NewServer(handler) defer server.Close() - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: &mixedMultiBidder{}, BidderName: openrtb_ext.BidderAppnexus, Client: server.Client(), @@ -578,7 +578,7 @@ func TestBidderTimeout(t *testing.T) { // TestInvalidRequest makes sure that bidderAdapter.doRequest returns errors on bad requests. func TestInvalidRequest(t *testing.T) { server := httptest.NewServer(mockHandler(200, "getBody", "postBody")) - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: &mixedMultiBidder{}, Client: server.Client(), } @@ -599,7 +599,7 @@ func TestConnectionClose(t *testing.T) { }) server = httptest.NewServer(handler) - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: &mixedMultiBidder{}, Client: server.Client(), BidderName: openrtb_ext.BidderAppnexus, @@ -2149,7 +2149,7 @@ func TestCallRecordDNSTime(t *testing.T) { // Instantiate the bidder that will send the request. We'll make sure to use an // http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{}) // gets called - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: &mixedMultiBidder{}, Client: &http.Client{Transport: DNSDoneTripper{}}, me: metricsMock, @@ -2173,7 +2173,7 @@ func TestCallRecordTLSHandshakeTime(t *testing.T) { // Instantiate the bidder that will send the request. We'll make sure to use an // http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{}) // gets called - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: &mixedMultiBidder{}, Client: &http.Client{Transport: TLSHandshakeTripper{}}, me: metricsMock, @@ -2201,7 +2201,7 @@ func TestTimeoutNotificationOff(t *testing.T) { Headers: http.Header{}, }, } - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: bidderImpl, Client: server.Client(), config: bidderAdapterConfig{Debug: config.Debug{}}, @@ -2235,7 +2235,7 @@ func TestTimeoutNotificationOn(t *testing.T) { // Wrap with BidderInfo to mimic exchange.go flow. bidderWrappedWithInfo := wrapWithBidderInfo(bidder) - bidderAdapter := &bidderAdapter{ + bidderAdapter := &BidderAdapter{ Bidder: bidderWrappedWithInfo, Client: server.Client(), config: bidderAdapterConfig{ @@ -3381,7 +3381,7 @@ func TestDoRequestImplWithTmax(t *testing.T) { Body: []byte(`{"id":"this-id","app":{"publisher":{"id":"pub-id"}}}`), } - bidderAdapter := bidderAdapter{ + bidderAdapter := BidderAdapter{ me: &metricsConfig.NilMetricsEngine{}, Client: server.Client(), } @@ -3456,7 +3456,7 @@ func TestDoRequestImplWithTmaxTimeout(t *testing.T) { metricsMock.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once() metricsMock.On("RecordTMaxTimeout").Once() - bidderAdapter := bidderAdapter{ + bidderAdapter := BidderAdapter{ me: metricsMock, Client: server.Client(), } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index df272a1f5bf..7014448f14d 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2146,7 +2146,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { if spec.BidIDGenerator != nil { bidIdGenerator = spec.BidIDGenerator } - ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig, bidIdGenerator, spec.HostSChainFlag, spec.FloorsEnabled, spec.HostConfigBidValidation, spec.Server) + ex := newExchangeForTests(t, filename, aliases, privacyConfig, bidIdGenerator, spec) biddersInAuction := findBiddersInAuction(t, filename, &spec.IncomingRequest.OrtbRequest) debugLog := &DebugLog{} if spec.DebugLog != nil { @@ -2374,11 +2374,11 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidRespons } } -func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator, hostSChainFlag, floorsFlag bool, hostBidValidation config.Validations, server exchangeServer) Exchange { - bidderAdapters := make(map[openrtb_ext.BidderName]AdaptedBidder, len(expectations)) - bidderInfos := make(config.BidderInfos, len(expectations)) +func newExchangeForTests(t *testing.T, filename string, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator, exSpec *exchangeSpec) Exchange { + bidderAdapters := make(map[openrtb_ext.BidderName]AdaptedBidder, len(exSpec.OutgoingRequests)) + bidderInfos := make(config.BidderInfos, len(exSpec.OutgoingRequests)) for _, bidderName := range openrtb_ext.CoreBidderNames() { - if spec, ok := expectations[string(bidderName)]; ok { + if spec, ok := exSpec.OutgoingRequests[string(bidderName)]; ok { bidderAdapters[bidderName] = &validatingBidder{ t: t, fileName: filename, @@ -2386,14 +2386,18 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] expectations: map[string]*bidderRequest{string(bidderName): spec.ExpectedRequest}, mockResponses: map[string]bidderResponse{string(bidderName): spec.MockResponse}, } - bidderInfos[string(bidderName)] = config.BidderInfo{ModifyingVastXmlAllowed: spec.ModifyingVastXmlAllowed} + ortbVersion, _ := exSpec.ORTBVersion[string(bidderName)] + bidderInfos[string(bidderName)] = config.BidderInfo{ + ModifyingVastXmlAllowed: spec.ModifyingVastXmlAllowed, + OpenRTB: &config.OpenRTBInfo{Version: ortbVersion}, + } } else { bidderInfos[string(bidderName)] = config.BidderInfo{} } } for alias, coreBidder := range aliases { - if spec, ok := expectations[alias]; ok { + if spec, ok := exSpec.OutgoingRequests[alias]; ok { if bidder, ok := bidderAdapters[openrtb_ext.BidderName(coreBidder)]; ok { bidder.(*validatingBidder).expectations[alias] = spec.ExpectedRequest bidder.(*validatingBidder).mockResponses[alias] = spec.MockResponse @@ -2431,7 +2435,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] } var hostSChainNode *openrtb2.SupplyChainNode - if hostSChainFlag { + if exSpec.HostSChainFlag { hostSChainNode = &openrtb2.SupplyChainNode{ ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1), } @@ -2469,10 +2473,10 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] externalURL: "http://localhost", bidIDGenerator: bidIDGenerator, hostSChainNode: hostSChainNode, - server: config.Server{ExternalUrl: server.ExternalUrl, GvlID: server.GvlID, DataCenter: server.DataCenter}, - bidValidationEnforcement: hostBidValidation, + server: config.Server{ExternalUrl: exSpec.Server.ExternalUrl, GvlID: exSpec.Server.GvlID, DataCenter: exSpec.Server.DataCenter}, + bidValidationEnforcement: exSpec.HostConfigBidValidation, requestSplitter: requestSplitter, - priceFloorEnabled: floorsFlag, + priceFloorEnabled: exSpec.FloorsEnabled, priceFloorFetcher: &mockPriceFloorFetcher{}, } } @@ -5513,6 +5517,7 @@ type exchangeSpec struct { MultiBid *multiBidSpec `json:"multiBid,omitempty"` Server exchangeServer `json:"server,omitempty"` AccountPrivacy config.AccountPrivacy `json:"accountPrivacy,omitempty"` + ORTBVersion map[string]string `json:"ortbversion"` } type multiBidSpec struct { diff --git a/exchange/exchangetest/ccpa-featureflag-on.json b/exchange/exchangetest/ccpa-featureflag-on.json index 0c452098da8..c6d37ed91e4 100644 --- a/exchange/exchangetest/ccpa-featureflag-on.json +++ b/exchange/exchangetest/ccpa-featureflag-on.json @@ -1,5 +1,8 @@ { "enforceCcpa": true, + "ortbversion": { + "appnexus":"2.6" + }, "incomingRequest": { "ortbRequest": { "id": "some-request-id", @@ -26,9 +29,7 @@ } ], "regs": { - "ext": { - "us_privacy": "1-Y-" - } + "us_privacy": "1-Y-" }, "user": { "buyeruid": "some-buyer-id" @@ -59,9 +60,7 @@ } ], "regs": { - "ext": { - "us_privacy": "1-Y-" - } + "us_privacy": "1-Y-" }, "user": {} } diff --git a/exchange/exchangetest/eidpermissions-denied.json b/exchange/exchangetest/eidpermissions-denied.json index eb3e216ecba..d7b5eeea43d 100644 --- a/exchange/exchangetest/eidpermissions-denied.json +++ b/exchange/exchangetest/eidpermissions-denied.json @@ -6,18 +6,16 @@ "page": "test.somepage.com" }, "user": { - "ext": { - "eids": [ - { - "source": "source1", - "uids": [ - { - "id": "id1" - } - ] - } - ] - } + "eids": [ + { + "source": "source1", + "uids": [ + { + "id": "id1" + } + ] + } + ] }, "ext": { "prebid": { diff --git a/exchange/exchangetest/firstpartydata-multibidder-user-eids.json b/exchange/exchangetest/firstpartydata-multibidder-user-eids.json new file mode 100644 index 00000000000..f16907dcdb2 --- /dev/null +++ b/exchange/exchangetest/firstpartydata-multibidder-user-eids.json @@ -0,0 +1,239 @@ +{ + "requestType": "openrtb2-web", + "ortbversion": { + "appnexus":"2.6", + "rubicon":"2.6" + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "reqUserID", + "keywords": "userKeyword!", + "eids": [ + { + "source": "reqeid1", + "uids": [ + { + "id": "reqeiduid1" + } + ] + }, + { + "source": "reqieid2", + "uids": [ + { + "id": "reqeiduid2" + } + ] + } + ] + }, + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + } + } + ], + "ext": { + "prebid": { + "data": { + "eidpermissions": [ + { + "source": "reqeid1", + "bidders": [ + "rubicon" + ] + } + ], + "bidders": [ + "appnexus", + "rubicon" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + } + } + } + }, + { + "bidders": [ + "rubicon" + ], + "config": { + "ortb2": { + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords", + "eids": [ + { + "source": "fpdeid1", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + }, + { + "source": "fpdeid2", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + } + ] + } + } + } + } + ] + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + }, + "user": { + "id": "reqUserID", + "keywords": "userKeyword!", + "eids": [ + { + "source": "reqieid2", + "uids": [ + { + "id": "reqeiduid2" + } + ] + } + ] + } + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords", + "eids": [ + { + "source": "fpdeid1", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + }, + { + "source": "fpdeid2", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + } + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-user-eids-req-user-nil.json b/exchange/exchangetest/firstpartydata-user-eids-req-user-nil.json new file mode 100644 index 00000000000..2c3b47dc76e --- /dev/null +++ b/exchange/exchangetest/firstpartydata-user-eids-req-user-nil.json @@ -0,0 +1,203 @@ +{ + "requestType": "openrtb2-web", + "ortbversion": { + "appnexus":"2.6", + "rubicon":"2.6" + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + } + } + ], + "ext": { + "prebid": { + "data": { + "eidpermissions": [ + { + "source": "reqeid1", + "bidders": [ + "rubicon" + ] + } + ], + "bidders": [ + "appnexus", + "rubicon" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + } + } + } + }, + { + "bidders": [ + "rubicon" + ], + "config": { + "ortb2": { + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords", + "eids": [ + { + "source": "fpdeid1", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + }, + { + "source": "fpdeid2", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + } + ] + } + } + } + } + ] + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + } + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords", + "eids": [ + { + "source": "fpdeid1", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + }, + { + "source": "fpdeid2", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + } + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-user-nileids-req-user-nil.json b/exchange/exchangetest/firstpartydata-user-nileids-req-user-nil.json new file mode 100644 index 00000000000..3ea8382176c --- /dev/null +++ b/exchange/exchangetest/firstpartydata-user-nileids-req-user-nil.json @@ -0,0 +1,163 @@ +{ + "requestType": "openrtb2-web", + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + } + } + ], + "ext": { + "prebid": { + "data": { + "eidpermissions": [ + { + "source": "reqeid1", + "bidders": [ + "rubicon" + ] + } + ], + "bidders": [ + "appnexus", + "rubicon" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + } + } + } + }, + { + "bidders": [ + "rubicon" + ], + "config": { + "ortb2": { + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords" + } + } + } + } + ] + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + } + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords" + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/multi-bids-different-ortb-versions.json b/exchange/exchangetest/multi-bids-different-ortb-versions.json new file mode 100644 index 00000000000..37e7fc582c7 --- /dev/null +++ b/exchange/exchangetest/multi-bids-different-ortb-versions.json @@ -0,0 +1,276 @@ +{ + "requestType": "openrtb2-web", + "ortbversion": { + "appnexus":"2.6", + "rubicon":"2.5" + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "my-user-id", + "buyeruid": "my-buyer-uid", + "geo": { + "lat": 123.456, + "lon": 678.90, + "zip": "90210" + }, + "consent": "AAAA", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ], + "ext": {} + }, + "imp": [ + { + "rwdd": 1, + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + } + } + ], + "source": { + "fd": 1, + "tid": "abc123", + "pchain": "tag_placement", + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "asi", + "sid": "sid", + "rid": "rid", + "ext": {} + } + ], + "ver": "ver", + "ext": {} + }, + "ext": {} + }, + "regs": { + "coppa": 1, + "gdpr": 1, + "us_privacy": "1YYY", + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN", + "gppsid": [ + 6 + ] + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "geo": {}, + "consent": "AAAA", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ], + "ext": {} + }, + "imp": [ + { + "rwdd": 1, + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "source": { + "fd": 1, + "tid": "abc123", + "pchain": "tag_placement", + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "asi", + "sid": "sid", + "rid": "rid", + "ext": {} + } + ], + "ver": "ver", + "ext": {} + }, + "ext": {} + }, + "regs": { + "coppa": 1, + "gdpr": 1, + "us_privacy": "1YYY", + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN" + } + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "geo": {}, + "ext": { + "consent": "AAAA", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + }, + "prebid": { + "is_rewarded_inventory":1 + } + } + } + ], + "source": { + "fd": 1, + "tid": "abc123", + "pchain": "tag_placement", + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "asi", + "sid": "sid", + "rid": "rid", + "ext": {} + } + ], + "ver": "ver", + "ext": {} + } + } + }, + "regs": { + "coppa": 1, + "gpp":"DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN", + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/schain-host-and-request.json b/exchange/exchangetest/schain-host-and-request.json index 5bfcd1c3527..0552bb09b30 100644 --- a/exchange/exchangetest/schain-host-and-request.json +++ b/exchange/exchangetest/schain-host-and-request.json @@ -1,5 +1,8 @@ { "host_schain_flag": true, + "ortbversion": { + "appnexus":"2.6" + }, "incomingRequest": { "ortbRequest": { "id": "some-request-id", @@ -84,25 +87,23 @@ } }, "source": { - "ext": { - "schain": { - "complete": 1, - "nodes": [ - { - "asi": "whatever.com", - "sid": "1234", - "rid": "123-456-7890", - "hp": 1 - }, - { - "asi": "pbshostcompany.com", - "sid": "00001", - "rid": "BidRequest", - "hp": 1 - } - ], - "ver": "2.0" - } + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + }, + { + "asi": "pbshostcompany.com", + "sid": "00001", + "rid": "BidRequest", + "hp": 1 + } + ], + "ver": "2.0" } } }, diff --git a/exchange/exchangetest/schain-host-only.json b/exchange/exchangetest/schain-host-only.json index 0cc077e2fdb..89fb70c3676 100644 --- a/exchange/exchangetest/schain-host-only.json +++ b/exchange/exchangetest/schain-host-only.json @@ -1,5 +1,8 @@ { "host_schain_flag": true, + "ortbversion": { + "appnexus":"2.6" + }, "incomingRequest": { "ortbRequest": { "id": "some-request-id", @@ -64,19 +67,17 @@ } }, "source": { - "ext": { - "schain": { - "complete": 0, - "nodes": [ - { - "asi": "pbshostcompany.com", - "sid": "00001", - "rid": "BidRequest", - "hp": 1 - } - ], - "ver": "1.0" - } + "schain": { + "complete": 0, + "nodes": [ + { + "asi": "pbshostcompany.com", + "sid": "00001", + "rid": "BidRequest", + "hp": 1 + } + ], + "ver": "1.0" } } }, diff --git a/exchange/gdpr.go b/exchange/gdpr.go index 2f94eefdaef..fbba54da14f 100644 --- a/exchange/gdpr.go +++ b/exchange/gdpr.go @@ -16,11 +16,11 @@ func getGDPR(req *openrtb_ext.RequestWrapper) (gdpr.Signal, error) { } return gdpr.SignalNo, nil } - re, err := req.GetRegExt() - if re == nil || re.GetGDPR() == nil || err != nil { - return gdpr.SignalAmbiguous, err + if req.Regs != nil && req.Regs.GDPR != nil { + return gdpr.IntSignalParse(int(*req.Regs.GDPR)) } - return gdpr.Signal(*re.GetGDPR()), nil + return gdpr.SignalAmbiguous, nil + } // getConsent will pull the consent string from an openrtb request @@ -29,11 +29,10 @@ func getConsent(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) (conse consent = gpp.Sections[i].GetValue() return } - ue, err := req.GetUserExt() - if ue == nil || ue.GetConsent() == nil || err != nil { - return + if req.User != nil { + return req.User.Consent, nil } - return *ue.GetConsent(), nil + return } // enforceGDPR determines if GDPR should be enforced based on the request signal and whether the channel is enabled diff --git a/exchange/gdpr_test.go b/exchange/gdpr_test.go index c73112be6f3..a775e0b1fa6 100644 --- a/exchange/gdpr_test.go +++ b/exchange/gdpr_test.go @@ -1,7 +1,6 @@ package exchange import ( - "encoding/json" "testing" gpplib "github.com/prebid/go-gpp" @@ -9,6 +8,7 @@ import ( "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/gdpr" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -21,17 +21,17 @@ func TestGetGDPR(t *testing.T) { }{ { description: "Regs Ext GDPR = 0", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 0}`)}, + giveRegs: &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](0)}, wantGDPR: gdpr.SignalNo, }, { description: "Regs Ext GDPR = 1", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 1}`)}, + giveRegs: &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](1)}, wantGDPR: gdpr.SignalYes, }, { description: "Regs Ext GDPR = null", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": null}`)}, + giveRegs: &openrtb2.Regs{GDPR: nil}, wantGDPR: gdpr.SignalAmbiguous, }, { @@ -39,30 +39,19 @@ func TestGetGDPR(t *testing.T) { giveRegs: nil, wantGDPR: gdpr.SignalAmbiguous, }, - { - description: "Regs Ext is nil", - giveRegs: &openrtb2.Regs{Ext: nil}, - wantGDPR: gdpr.SignalAmbiguous, - }, - { - description: "JSON unmarshal error", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"`)}, - wantGDPR: gdpr.SignalAmbiguous, - wantError: true, - }, { description: "Regs Ext GDPR = null, GPPSID has tcf2", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": null}`), GPPSID: []int8{2}}, + giveRegs: &openrtb2.Regs{GDPR: nil, GPPSID: []int8{2}}, wantGDPR: gdpr.SignalYes, }, { description: "Regs Ext GDPR = 1, GPPSID has uspv1", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 1}`), GPPSID: []int8{6}}, + giveRegs: &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](1), GPPSID: []int8{6}}, wantGDPR: gdpr.SignalNo, }, { description: "Regs Ext GDPR = 0, GPPSID has tcf2", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 0}`), GPPSID: []int8{2}}, + giveRegs: &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](0), GPPSID: []int8{2}}, wantGDPR: gdpr.SignalYes, }, { @@ -105,18 +94,13 @@ func TestGetConsent(t *testing.T) { wantError bool }{ { - description: "User Ext Consent is not empty", - giveUser: &openrtb2.User{Ext: json.RawMessage(`{"consent": "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"}`)}, + description: "User Consent is not empty", + giveUser: &openrtb2.User{Consent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"}, wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA", }, { - description: "User Ext Consent is empty", - giveUser: &openrtb2.User{Ext: json.RawMessage(`{"consent": ""}`)}, - wantConsent: "", - }, - { - description: "User Ext is nil", - giveUser: &openrtb2.User{Ext: nil}, + description: "User Consent is empty", + giveUser: &openrtb2.User{Consent: ""}, wantConsent: "", }, { @@ -125,26 +109,20 @@ func TestGetConsent(t *testing.T) { wantConsent: "", }, { - description: "JSON unmarshal error", - giveUser: &openrtb2.User{Ext: json.RawMessage(`{`)}, - wantConsent: "", - wantError: true, - }, - { - description: "User Ext is nil, GPP has no GDPR", - giveUser: &openrtb2.User{Ext: nil}, + description: "User is nil, GPP has no GDPR", + giveUser: nil, giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{6}, Sections: []gpplib.Section{&upsv1Section}}, wantConsent: "", }, { - description: "User Ext is nil, GPP has GDPR", - giveUser: &openrtb2.User{Ext: nil}, + description: "User is nil, GPP has GDPR", + giveUser: nil, giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{2}, Sections: []gpplib.Section{&tcf1Section}}, wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA", }, { - description: "User Ext has GDPR, GPP has GDPR", - giveUser: &openrtb2.User{Ext: json.RawMessage(`{"consent": "BSOMECONSENT"}`)}, + description: "User has GDPR, GPP has GDPR", + giveUser: &openrtb2.User{Consent: "BSOMECONSENT"}, giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{2}, Sections: []gpplib.Section{&tcf1Section}}, wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA", }, diff --git a/exchange/utils.go b/exchange/utils.go index 2805f32a08a..5e3feab15f5 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -1,7 +1,6 @@ package exchange import ( - "bytes" "context" "encoding/json" "errors" @@ -63,8 +62,11 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, gdprSignal gdpr.Signal, gdprEnforced bool, bidAdjustmentFactors map[string]float64, -) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { +) (bidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { req := auctionReq.BidRequestWrapper + if err := PreloadExts(req); err != nil { + return + } requestAliases, requestAliasesGVLIDs, errs := getRequestAliases(req) if len(errs) > 0 { @@ -72,7 +74,6 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, } bidderImpWithBidResp := stored_responses.InitStoredBidResponses(req.BidRequest, auctionReq.StoredBidResponses) - hasStoredAuctionResponses := len(auctionReq.StoredAuctionResponses) > 0 impsByBidder, err := splitImps(req.BidRequest.Imp, rs.requestValidator, requestAliases, hasStoredAuctionResponses, auctionReq.StoredBidResponses) @@ -153,97 +154,198 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, gdprPerms = rs.gdprPermsBuilder(auctionReq.TCF2Config, gdprRequestInfo) } - allBidderRequests := make([]BidderRequest, 0, len(impsByBidder)) - var allBidderRequestErrs []error + bidderRequests = make([]BidderRequest, 0, len(impsByBidder)) for bidder, imps := range impsByBidder { - coreBidder, isRequestAlias := resolveBidder(bidder, requestAliases) + fpdUserEIDsPresent := fpdUserEIDExists(req, auctionReq.FirstPartyData, bidder) + reqWrapperCopy := req.CloneAndClearImpWrappers() + bidRequestCopy := *req.BidRequest + reqWrapperCopy.BidRequest = &bidRequestCopy + reqWrapperCopy.Imp = imps - reqCopy := *req.BidRequest - reqCopy.Imp = imps + coreBidder, isRequestAlias := resolveBidder(bidder, requestAliases) - sChainWriter.Write(&reqCopy, bidder) + // apply bidder-specific schains + sChainWriter.Write(reqWrapperCopy, bidder) - reqCopy.Ext, err = buildRequestExtForBidder(bidder, req.BidRequest.Ext, requestExt, bidderParamsInReqExt, auctionReq.Account.AlternateBidderCodes) - if err != nil { - errs = append(errs, err) + // eid scrubbing + if err := removeUnpermissionedEids(reqWrapperCopy, bidder); err != nil { + errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err)) continue } - if err := removeUnpermissionedEids(&reqCopy, bidder, requestExt); err != nil { - errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err)) + // generate bidder-specific request ext + err = buildRequestExtForBidder(bidder, reqWrapperCopy, bidderParamsInReqExt, auctionReq.Account.AlternateBidderCodes) + if err != nil { + errs = append(errs, err) continue } - bidderRequest := BidderRequest{ - BidderName: openrtb_ext.BidderName(bidder), - BidderCoreName: coreBidder, - IsRequestAlias: isRequestAlias, - BidRequest: &reqCopy, - BidderLabels: metrics.AdapterLabels{ - Source: auctionReq.LegacyLabels.Source, - RType: auctionReq.LegacyLabels.RType, - Adapter: coreBidder, - PubID: auctionReq.LegacyLabels.PubID, - CookieFlag: auctionReq.LegacyLabels.CookieFlag, - AdapterBids: metrics.AdapterBidPresent, - }, + // apply bid adjustments + if auctionReq.Account.PriceFloors.IsAdjustForBidAdjustmentEnabled() { + applyBidAdjustmentToFloor(reqWrapperCopy, bidder, bidAdjustmentFactors) } + // prepare user syncerKey := rs.bidderToSyncerKey[string(coreBidder)] - if hadSync := prepareUser(&reqCopy, bidder, syncerKey, lowerCaseExplicitBuyerUIDs, auctionReq.UserSyncs); !hadSync && req.BidRequest.App == nil { - bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagNo - } else { - bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagYes - } - - allBidderRequests = append(allBidderRequests, bidderRequest) - } - - if allBidderRequestErrs != nil { - errs = append(errs, allBidderRequestErrs...) - } - - bidderNameToBidderReq := buildBidResponseRequest(req.BidRequest, bidderImpWithBidResp, requestAliases, auctionReq.BidderImpReplaceImpID) - //this function should be executed after getAuctionBidderRequests - allBidderRequests = mergeBidderRequests(allBidderRequests, bidderNameToBidderReq) + hadSync := prepareUser(reqWrapperCopy, bidder, syncerKey, lowerCaseExplicitBuyerUIDs, auctionReq.UserSyncs) - if auctionReq.Account.PriceFloors.IsAdjustForBidAdjustmentEnabled() { - //Apply BidAdjustmentFactor to imp.BidFloor - applyBidAdjustmentToFloor(allBidderRequests, bidAdjustmentFactors) - } - - allowedBidderRequests = make([]BidderRequest, 0, len(allBidderRequests)) - - for _, bidderRequest := range allBidderRequests { - auctionPermissions := gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName) + auctionPermissions := gdprPerms.AuctionActivitiesAllowed(ctx, coreBidder, openrtb_ext.BidderName(bidder)) // privacy blocking - if rs.isBidderBlockedByPrivacy(req, auctionReq.Activities, auctionPermissions, bidderRequest.BidderCoreName, bidderRequest.BidderName) { + if rs.isBidderBlockedByPrivacy(reqWrapperCopy, auctionReq.Activities, auctionPermissions, coreBidder, openrtb_ext.BidderName(bidder)) { continue } // fpd - applyFPD(auctionReq.FirstPartyData, bidderRequest) + applyFPD(auctionReq.FirstPartyData, coreBidder, openrtb_ext.BidderName(bidder), isRequestAlias, reqWrapperCopy, fpdUserEIDsPresent) // privacy scrubbing - if err := rs.applyPrivacy(&bidderRequest, auctionReq, auctionPermissions, ccpaEnforcer, lmt, coppa); err != nil { + if err := rs.applyPrivacy(reqWrapperCopy, coreBidder, bidder, auctionReq, auctionPermissions, ccpaEnforcer, lmt, coppa); err != nil { errs = append(errs, err) continue } // GPP downgrade: always downgrade unless we can confirm GPP is supported - if shouldSetLegacyPrivacy(rs.bidderInfo, string(bidderRequest.BidderCoreName)) { - setLegacyGDPRFromGPP(bidderRequest.BidRequest, gpp) - setLegacyUSPFromGPP(bidderRequest.BidRequest, gpp) + if shouldSetLegacyPrivacy(rs.bidderInfo, string(coreBidder)) { + setLegacyGDPRFromGPP(reqWrapperCopy, gpp) + setLegacyUSPFromGPP(reqWrapperCopy, gpp) + } + + // remove imps with stored responses so they aren't sent to the bidder + if impResponses, ok := bidderImpWithBidResp[openrtb_ext.BidderName(bidder)]; ok { + removeImpsWithStoredResponses(reqWrapperCopy, impResponses) } - allowedBidderRequests = append(allowedBidderRequests, bidderRequest) + // down convert + info, ok := rs.bidderInfo[bidder] + if !ok || info.OpenRTB == nil || info.OpenRTB.Version != "2.6" { + reqWrapperCopy.Regs = ortb.CloneRegs(reqWrapperCopy.Regs) + if err := openrtb_ext.ConvertDownTo25(reqWrapperCopy); err != nil { + errs = append(errs, err) + continue + } + } + + // sync wrapper + if err := reqWrapperCopy.RebuildRequest(); err != nil { + errs = append(errs, err) + continue + } + + // choose labels + bidderLabels := metrics.AdapterLabels{ + Adapter: coreBidder, + } + if !hadSync && req.BidRequest.App == nil { + bidderLabels.CookieFlag = metrics.CookieFlagNo + } else { + bidderLabels.CookieFlag = metrics.CookieFlagYes + } + if len(reqWrapperCopy.Imp) > 0 { + bidderLabels.Source = auctionReq.LegacyLabels.Source + bidderLabels.RType = auctionReq.LegacyLabels.RType + bidderLabels.PubID = auctionReq.LegacyLabels.PubID + bidderLabels.CookieFlag = auctionReq.LegacyLabels.CookieFlag + bidderLabels.AdapterBids = metrics.AdapterBidPresent + } + + bidderRequest := BidderRequest{ + BidderName: openrtb_ext.BidderName(bidder), + BidderCoreName: coreBidder, + BidRequest: reqWrapperCopy.BidRequest, + IsRequestAlias: isRequestAlias, + BidderStoredResponses: bidderImpWithBidResp[openrtb_ext.BidderName(bidder)], + ImpReplaceImpId: auctionReq.BidderImpReplaceImpID[bidder], + BidderLabels: bidderLabels, + } + bidderRequests = append(bidderRequests, bidderRequest) } return } +// fpdUserEIDExists determines if req fpd config had User.EIDs +func fpdUserEIDExists(req *openrtb_ext.RequestWrapper, fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, bidder string) bool { + fpdToApply, exists := fpd[openrtb_ext.BidderName(bidder)] + if !exists || fpdToApply == nil { + return false + } + if fpdToApply.User == nil { + return false + } + fpdUserEIDs := fpdToApply.User.EIDs + + if len(fpdUserEIDs) == 0 { + return false + } + if req.User == nil { + return true + } + + reqUserEIDs := req.User.EIDs + + if len(reqUserEIDs) != len(fpdUserEIDs) { + return true + } + + // if bidder fpd didn't have user.eids then user.eids will remain the same + // hence we can use the same index to compare elements + for i := range reqUserEIDs { + pReqUserEID := &reqUserEIDs[i] + pFpdUserEID := &fpdUserEIDs[i] + if pReqUserEID != pFpdUserEID { + return true + } + } + return false +} + +// removeImpsWithStoredResponses deletes imps with stored bid resp +func removeImpsWithStoredResponses(req *openrtb_ext.RequestWrapper, impBidResponses map[string]json.RawMessage) { + if len(impBidResponses) == 0 { + return + } + + imps := req.Imp + req.Imp = nil //to indicate this bidder doesn't have real requests + for _, imp := range imps { + if _, ok := impBidResponses[imp.ID]; !ok { + //add real imp back to request + req.Imp = append(req.Imp, imp) + } + } +} + +// PreloadExts ensures all exts have been unmarshalled into wrapper ext objects +func PreloadExts(req *openrtb_ext.RequestWrapper) error { + if req == nil { + return nil + } + if _, err := req.GetRequestExt(); err != nil { + return err + } + if _, err := req.GetUserExt(); err != nil { + return err + } + if _, err := req.GetDeviceExt(); err != nil { + return err + } + if _, err := req.GetRegExt(); err != nil { + return err + } + if _, err := req.GetSiteExt(); err != nil { + return err + } + if _, err := req.GetDOOHExt(); err != nil { + return err + } + if _, err := req.GetSourceExt(); err != nil { + return err + } + return nil +} + func (rs *requestSplitter) isBidderBlockedByPrivacy(r *openrtb_ext.RequestWrapper, activities privacy.ActivityControl, auctionPermissions gdpr.AuctionPermissions, coreBidder, bidderName openrtb_ext.BidderName) bool { // activities control scope := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderName.String()} @@ -261,13 +363,12 @@ func (rs *requestSplitter) isBidderBlockedByPrivacy(r *openrtb_ext.RequestWrappe return false } -func (rs *requestSplitter) applyPrivacy(bidderRequest *BidderRequest, auctionReq AuctionRequest, auctionPermissions gdpr.AuctionPermissions, ccpaEnforcer privacy.PolicyEnforcer, lmt bool, coppa bool) error { - scope := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderRequest.BidderName.String()} +func (rs *requestSplitter) applyPrivacy(reqWrapper *openrtb_ext.RequestWrapper, coreBidderName openrtb_ext.BidderName, bidderName string, auctionReq AuctionRequest, auctionPermissions gdpr.AuctionPermissions, ccpaEnforcer privacy.PolicyEnforcer, lmt bool, coppa bool) error { + scope := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderName} ipConf := privacy.IPConf{IPV6: auctionReq.Account.Privacy.IPv6Config, IPV4: auctionReq.Account.Privacy.IPv4Config} - reqWrapper := &openrtb_ext.RequestWrapper{ - BidRequest: ortb.CloneBidRequestPartial(bidderRequest.BidRequest), - } + bidRequest := ortb.CloneBidRequestPartial(reqWrapper.BidRequest) + reqWrapper.BidRequest = bidRequest passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scope, privacy.NewRequestFromBidRequest(*reqWrapper)) buyerUIDSet := reqWrapper.User != nil && reqWrapper.User.BuyerUID != "" @@ -281,13 +382,13 @@ func (rs *requestSplitter) applyPrivacy(bidderRequest *BidderRequest, auctionReq buyerUIDRemoved = true } - if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) { + if ccpaEnforcer.ShouldEnforce(bidderName) { privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false) buyerUIDRemoved = true } } if buyerUIDSet && buyerUIDRemoved { - rs.me.RecordAdapterBuyerUIDScrubbed(bidderRequest.BidderCoreName) + rs.me.RecordAdapterBuyerUIDScrubbed(coreBidderName) } passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scope, privacy.NewRequestFromBidRequest(*reqWrapper)) @@ -297,7 +398,7 @@ func (rs *requestSplitter) applyPrivacy(bidderRequest *BidderRequest, auctionReq if !auctionPermissions.PassGeo { privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf) } - if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) { + if ccpaEnforcer.ShouldEnforce(bidderName) { privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false) } } @@ -315,7 +416,7 @@ func (rs *requestSplitter) applyPrivacy(bidderRequest *BidderRequest, auctionReq return err } - bidderRequest.BidRequest = reqWrapper.BidRequest + // *bidRequest = *reqWrapper.BidRequest return nil } @@ -389,67 +490,44 @@ func ExtractReqExtBidderParamsMap(bidRequest *openrtb2.BidRequest) (map[string]j return bidderParams, nil } -func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, requestExtParsed *openrtb_ext.ExtRequest, bidderParamsInReqExt map[string]json.RawMessage, cfgABC *openrtb_ext.ExtAlternateBidderCodes) (json.RawMessage, error) { - // Resolve alternatebiddercode for current bidder - var reqABC *openrtb_ext.ExtAlternateBidderCodes - if len(requestExt) != 0 && requestExtParsed != nil && requestExtParsed.Prebid.AlternateBidderCodes != nil { - reqABC = requestExtParsed.Prebid.AlternateBidderCodes - } - alternateBidderCodes := buildRequestExtAlternateBidderCodes(bidder, cfgABC, reqABC) - - if (len(requestExt) == 0 || requestExtParsed == nil) && alternateBidderCodes == nil { - return nil, nil - } - - // Resolve Bidder Params - var bidderParams json.RawMessage - if bidderParamsInReqExt != nil { - bidderParams = bidderParamsInReqExt[bidder] - } - - // Copy Allowed Fields - // Per: https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#prebid-server-ortb2-extension-summary - prebid := openrtb_ext.ExtRequestPrebid{ - BidderParams: bidderParams, - AlternateBidderCodes: alternateBidderCodes, - } - - if requestExtParsed != nil { - prebid.Channel = requestExtParsed.Prebid.Channel - prebid.CurrencyConversions = requestExtParsed.Prebid.CurrencyConversions - prebid.Debug = requestExtParsed.Prebid.Debug - prebid.Integration = requestExtParsed.Prebid.Integration - prebid.MultiBid = buildRequestExtMultiBid(bidder, requestExtParsed.Prebid.MultiBid, alternateBidderCodes) - prebid.Sdk = requestExtParsed.Prebid.Sdk - prebid.Server = requestExtParsed.Prebid.Server - } - - // Marshal New Prebid Object - prebidJson, err := jsonutil.Marshal(prebid) +func buildRequestExtForBidder(bidder string, req *openrtb_ext.RequestWrapper, reqExtBidderParams map[string]json.RawMessage, cfgABC *openrtb_ext.ExtAlternateBidderCodes) error { + reqExt, err := req.GetRequestExt() if err != nil { - return nil, err + return err } + prebid := reqExt.GetPrebid() - // Parse Existing Ext - extMap := make(map[string]json.RawMessage) - if len(requestExt) != 0 { - if err := jsonutil.Unmarshal(requestExt, &extMap); err != nil { - return nil, err - } + // Resolve alternatebiddercode + var reqABC *openrtb_ext.ExtAlternateBidderCodes + if prebid != nil && prebid.AlternateBidderCodes != nil { + reqABC = prebid.AlternateBidderCodes } + alternateBidderCodes := buildRequestExtAlternateBidderCodes(bidder, cfgABC, reqABC) - // Update Ext With Prebid Json - if bytes.Equal(prebidJson, []byte(`{}`)) { - delete(extMap, "prebid") + var prebidNew openrtb_ext.ExtRequestPrebid + if prebid == nil { + prebidNew = openrtb_ext.ExtRequestPrebid{ + BidderParams: reqExtBidderParams[bidder], + AlternateBidderCodes: alternateBidderCodes, + } } else { - extMap["prebid"] = prebidJson + // Copy Allowed Fields + // Per: https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#prebid-server-ortb2-extension-summary + prebidNew = openrtb_ext.ExtRequestPrebid{ + BidderParams: reqExtBidderParams[bidder], + AlternateBidderCodes: alternateBidderCodes, + Channel: prebid.Channel, + CurrencyConversions: prebid.CurrencyConversions, + Debug: prebid.Debug, + Integration: prebid.Integration, + MultiBid: buildRequestExtMultiBid(bidder, prebid.MultiBid, alternateBidderCodes), + Sdk: prebid.Sdk, + Server: prebid.Server, + } } - if len(extMap) > 0 { - return jsonutil.Marshal(extMap) - } else { - return nil, nil - } + reqExt.SetPrebid(&prebidNew) + return nil } func buildRequestExtAlternateBidderCodes(bidder string, accABC *openrtb_ext.ExtAlternateBidderCodes, reqABC *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes { @@ -688,7 +766,7 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map // // In this function, "givenBidder" may or may not be an alias. "coreBidder" must *not* be an alias. // It returns true if a Cookie User Sync existed, and false otherwise. -func prepareUser(req *openrtb2.BidRequest, givenBidder, syncerKey string, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { +func prepareUser(req *openrtb_ext.RequestWrapper, givenBidder, syncerKey string, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { cookieId, hadCookie, _ := usersyncs.GetUID(syncerKey) if id, ok := explicitBuyerUIDs[strings.ToLower(givenBidder)]; ok { @@ -716,42 +794,32 @@ func copyWithBuyerUID(user *openrtb2.User, buyerUID string) *openrtb2.User { return user } -// removeUnpermissionedEids modifies the request to remove any request.user.ext.eids not permissions for the specific bidder -func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, requestExt *openrtb_ext.ExtRequest) error { +// removeUnpermissionedEids modifies the request to remove any request.user.eids not permissions for the specific bidder +func removeUnpermissionedEids(reqWrapper *openrtb_ext.RequestWrapper, bidder string) error { // ensure request might have eids (as much as we can check before unmarshalling) - if request.User == nil || len(request.User.Ext) == 0 { + if reqWrapper.User == nil || len(reqWrapper.User.EIDs) == 0 { return nil } // ensure request has eid permissions to enforce - if requestExt == nil || requestExt.Prebid.Data == nil || len(requestExt.Prebid.Data.EidPermissions) == 0 { - return nil - } - - // low level unmarshal to preserve other request.user.ext values. prebid server is non-destructive. - var userExt map[string]json.RawMessage - if err := jsonutil.Unmarshal(request.User.Ext, &userExt); err != nil { + reqExt, err := reqWrapper.GetRequestExt() + if err != nil { return err } - - eidsJSON, eidsSpecified := userExt["eids"] - if !eidsSpecified { + if reqExt == nil { return nil } - var eids []openrtb2.EID - if err := jsonutil.Unmarshal(eidsJSON, &eids); err != nil { - return err - } - - // exit early if there are no eids (empty array) - if len(eids) == 0 { + reqExtPrebid := reqExt.GetPrebid() + if reqExtPrebid == nil || reqExtPrebid.Data == nil || len(reqExtPrebid.Data.EidPermissions) == 0 { return nil } + eids := reqWrapper.User.EIDs + // translate eid permissions to a map for quick lookup eidRules := make(map[string][]string) - for _, p := range requestExt.Prebid.Data.EidPermissions { + for _, p := range reqExtPrebid.Data.EidPermissions { eidRules[p.Source] = p.Bidders } @@ -779,37 +847,14 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque return nil } - // marshal eidsAllowed back to userExt if len(eidsAllowed) == 0 { - delete(userExt, "eids") + reqWrapper.User.EIDs = nil } else { - eidsRaw, err := jsonutil.Marshal(eidsAllowed) - if err != nil { - return err - } - userExt["eids"] = eidsRaw + reqWrapper.User.EIDs = eidsAllowed } - - // exit early if userExt is empty - if len(userExt) == 0 { - setUserExtWithCopy(request, nil) - return nil - } - - userExtJSON, err := jsonutil.Marshal(userExt) - if err != nil { - return err - } - setUserExtWithCopy(request, userExtJSON) return nil } -func setUserExtWithCopy(request *openrtb2.BidRequest, userExtJSON json.RawMessage) { - userCopy := *request.User - userCopy.Ext = userExtJSON - request.User = &userCopy -} - // resolveBidder returns the known BidderName associated with bidder, if bidder is an alias. If it's not an alias, the bidder is returned. func resolveBidder(bidder string, requestAliases map[string]string) (openrtb_ext.BidderName, bool) { normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidder) @@ -942,14 +987,19 @@ func getExtBidAdjustmentFactors(requestExtPrebid *openrtb_ext.ExtRequestPrebid) return nil } -func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, r BidderRequest) { +func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, + coreBidderName openrtb_ext.BidderName, + bidderName openrtb_ext.BidderName, + isRequestAlias bool, + reqWrapper *openrtb_ext.RequestWrapper, + fpdUserEIDsPresent bool) { if fpd == nil { return } - bidder := r.BidderCoreName - if r.IsRequestAlias { - bidder = r.BidderName + bidder := coreBidderName + if isRequestAlias { + bidder = bidderName } fpdToApply, exists := fpd[bidder] @@ -958,77 +1008,31 @@ func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyD } if fpdToApply.Site != nil { - r.BidRequest.Site = fpdToApply.Site + reqWrapper.Site = fpdToApply.Site } if fpdToApply.App != nil { - r.BidRequest.App = fpdToApply.App + reqWrapper.App = fpdToApply.App } if fpdToApply.User != nil { - //BuyerUID is a value obtained between fpd extraction and fpd application. - //BuyerUID needs to be set back to fpd before applying this fpd to final bidder request - if r.BidRequest.User != nil && len(r.BidRequest.User.BuyerUID) > 0 { - fpdToApply.User.BuyerUID = r.BidRequest.User.BuyerUID - } - r.BidRequest.User = fpdToApply.User - } -} - -func buildBidResponseRequest(req *openrtb2.BidRequest, - bidderImpResponses stored_responses.BidderImpsWithBidResponses, - requestAliases map[string]string, - bidderImpReplaceImpID stored_responses.BidderImpReplaceImpID) map[openrtb_ext.BidderName]BidderRequest { - - bidderToBidderResponse := make(map[openrtb_ext.BidderName]BidderRequest) - - for bidderName, impResps := range bidderImpResponses { - resolvedBidder, isRequestAlias := resolveBidder(string(bidderName), requestAliases) - bidderToBidderResponse[bidderName] = BidderRequest{ - BidRequest: req, - BidderCoreName: resolvedBidder, - BidderName: bidderName, - BidderStoredResponses: impResps, - ImpReplaceImpId: bidderImpReplaceImpID[string(bidderName)], - IsRequestAlias: isRequestAlias, - BidderLabels: metrics.AdapterLabels{Adapter: resolvedBidder}, - } - } - return bidderToBidderResponse -} - -func mergeBidderRequests(allBidderRequests []BidderRequest, bidderNameToBidderReq map[openrtb_ext.BidderName]BidderRequest) []BidderRequest { - if len(allBidderRequests) == 0 && len(bidderNameToBidderReq) == 0 { - return allBidderRequests - } - if len(allBidderRequests) == 0 && len(bidderNameToBidderReq) > 0 { - for _, v := range bidderNameToBidderReq { - allBidderRequests = append(allBidderRequests, v) - } - return allBidderRequests - } else if len(allBidderRequests) > 0 && len(bidderNameToBidderReq) > 0 { - //merge bidder requests with real imps and imps with stored resp - for bn, br := range bidderNameToBidderReq { - found := false - for i, ar := range allBidderRequests { - if ar.BidderName == bn { - //bidder req with real imps and imps with stored resp - allBidderRequests[i].BidderStoredResponses = br.BidderStoredResponses - found = true - break - } + if reqWrapper.User != nil { + if len(reqWrapper.User.BuyerUID) > 0 { + //BuyerUID is a value obtained between fpd extraction and fpd application. + //BuyerUID needs to be set back to fpd before applying this fpd to final bidder request + fpdToApply.User.BuyerUID = reqWrapper.User.BuyerUID } - if !found { - //bidder req with stored bid responses only - br.BidRequest.Imp = nil // to indicate this bidder request has bidder responses only - allBidderRequests = append(allBidderRequests, br) + + // if FPD config didn't have user.eids - use reqWrapper.User.EIDs after removeUnpermissionedEids + if !fpdUserEIDsPresent { + fpdToApply.User.EIDs = reqWrapper.User.EIDs } } + reqWrapper.User = fpdToApply.User } - return allBidderRequests } -func setLegacyGDPRFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) { +func setLegacyGDPRFromGPP(r *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) { if r.Regs != nil && r.Regs.GDPR == nil { if r.Regs.GPPSID != nil { // Set to 0 unless SID exists @@ -1057,13 +1061,12 @@ func setLegacyGDPRFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) { } } } - } -func setLegacyUSPFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) { + +func setLegacyUSPFromGPP(r *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) { if r.Regs == nil { return } - if len(r.Regs.USPrivacy) > 0 || r.Regs.GPPSID == nil { return } @@ -1078,7 +1081,6 @@ func setLegacyUSPFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) { } } } - } func WrapJSONInData(data []byte) []byte { @@ -1139,24 +1141,20 @@ func getPrebidMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { } } -func applyBidAdjustmentToFloor(allBidderRequests []BidderRequest, bidAdjustmentFactors map[string]float64) { - - if len(bidAdjustmentFactors) == 0 { +func applyBidAdjustmentToFloor(req *openrtb_ext.RequestWrapper, bidder string, adjustmentFactors map[string]float64) { + if len(adjustmentFactors) == 0 { return } - for _, bidderRequest := range allBidderRequests { - bidAdjustment := 1.0 - - if bidAdjustemntValue, ok := bidAdjustmentFactors[string(bidderRequest.BidderName)]; ok { - bidAdjustment = bidAdjustemntValue - } + bidAdjustment := 1.0 + if v, ok := adjustmentFactors[bidder]; ok && v != 0.0 { + bidAdjustment = v + } - if bidAdjustment != 1.0 { - for index, imp := range bidderRequest.BidRequest.Imp { - imp.BidFloor = imp.BidFloor / bidAdjustment - bidderRequest.BidRequest.Imp[index] = imp - } + if bidAdjustment != 1.0 { + for index, imp := range req.Imp { + imp.BidFloor = imp.BidFloor / bidAdjustment + req.Imp[index] = imp } } } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 5d2a83d5b49..0d6f094aece 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -4,12 +4,9 @@ import ( "context" "encoding/json" "errors" - "fmt" "sort" "testing" - "github.com/prebid/prebid-server/v2/stored_responses" - gpplib "github.com/prebid/go-gpp" "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v20/openrtb2" @@ -967,7 +964,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { W: ptrutil.ToPtr[int64](300), H: ptrutil.ToPtr[int64](250), }, - Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"},"bidderB":{"placementId":"456"}}}}`), }, }, expectedBidderRequests: map[string]BidderRequest{ @@ -998,7 +995,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { W: ptrutil.ToPtr[int64](300), H: ptrutil.ToPtr[int64](250), }, - Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"},"bidderB":{"placementId":"456"}}}}`), }, { ID: "imp-id2", @@ -1038,7 +1035,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { W: ptrutil.ToPtr[int64](300), H: ptrutil.ToPtr[int64](250), }, - Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"},"bidderB":{"placementId":"456"}}}}`), }, { ID: "imp-id2", @@ -1104,11 +1101,11 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { imps: []openrtb2.Imp{ { ID: "imp-id1", - Ext: json.RawMessage(`"prebid": {}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, { ID: "imp-id2", - Ext: json.RawMessage(`"prebid": {}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, }, expectedBidderRequests: map[string]BidderRequest{ @@ -1279,7 +1276,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { req := newBidRequest(t) req.Ext = test.reqExt req.Regs = &openrtb2.Regs{ - Ext: json.RawMessage(`{"us_privacy":"` + test.ccpaConsent + `"}`), + USPrivacy: test.ccpaConsent, } privacyConfig := config.Privacy{ @@ -1339,32 +1336,32 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { testCases := []struct { - description string - reqExt json.RawMessage - reqRegsExt json.RawMessage - expectError error + description string + reqExt json.RawMessage + reqRegsPrivacy string + expectError error }{ { - description: "Invalid Consent", - reqExt: json.RawMessage(`{"prebid":{"nosale":["*"]}}`), - reqRegsExt: json.RawMessage(`{"us_privacy":"malformed"}`), + description: "Invalid Consent", + reqExt: json.RawMessage(`{"prebid":{"nosale":["*"]}}`), + reqRegsPrivacy: "malformed", expectError: &errortypes.Warning{ Message: "request.regs.ext.us_privacy must contain 4 characters", WarningCode: errortypes.InvalidPrivacyConsentWarningCode, }, }, { - description: "Invalid No Sale Bidders", - reqExt: json.RawMessage(`{"prebid":{"nosale":["*", "another"]}}`), - reqRegsExt: json.RawMessage(`{"us_privacy":"1NYN"}`), - expectError: errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided"), + description: "Invalid No Sale Bidders", + reqExt: json.RawMessage(`{"prebid":{"nosale":["*", "another"]}}`), + reqRegsPrivacy: "1NYN", + expectError: errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided"), }, } for _, test := range testCases { req := newBidRequest(t) req.Ext = test.reqExt - req.Regs = &openrtb2.Regs{Ext: test.reqRegsExt} + req.Regs = &openrtb2.Regs{USPrivacy: test.reqRegsPrivacy} var reqExtStruct openrtb_ext.ExtRequest err := jsonutil.UnmarshalValid(req.Ext, &reqExtStruct) @@ -1480,46 +1477,114 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { testCases := []struct { description string inExt json.RawMessage - inSourceExt json.RawMessage + inSChain *openrtb2.SupplyChain outRequestExt json.RawMessage - outSourceExt json.RawMessage + outSource *openrtb2.Source hasError bool + ortbVersion string }{ { description: "nil", inExt: nil, - inSourceExt: nil, + inSChain: nil, outRequestExt: nil, - outSourceExt: nil, + outSource: &openrtb2.Source{ + TID: "testTID", + SChain: nil, + Ext: nil, + }, }, { - description: "ORTB 2.5 chain at source.ext.schain", - inExt: nil, - inSourceExt: json.RawMessage(`{` + seller1SChain + `}`), + description: "Supply Chain defined in request.Source.supplyChain", + inExt: nil, + inSChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, outRequestExt: nil, - outSourceExt: json.RawMessage(`{` + seller1SChain + `}`), + outSource: &openrtb2.Source{ + TID: "testTID", + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, + ortbVersion: "2.6", }, { - description: "ORTB 2.5 schain at request.ext.prebid.schains", + description: "Supply Chain defined in request.ext.prebid.schains", inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - inSourceExt: nil, + inSChain: nil, outRequestExt: nil, - outSourceExt: json.RawMessage(`{` + seller1SChain + `}`), + outSource: &openrtb2.Source{ + TID: "testTID", + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, + ortbVersion: "2.6", }, { - description: "schainwriter instantation error -- multiple bidder schains in ext.prebid.schains.", - inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), - inSourceExt: json.RawMessage(`{` + seller1SChain + `}`), + description: "schainwriter instantation error -- multiple bidder schains in ext.prebid.schains.", + inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), + inSChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + outRequestExt: nil, - outSourceExt: nil, + outSource: nil, hasError: true, }, } for _, test := range testCases { req := newBidRequest(t) - if test.inSourceExt != nil { - req.Source.Ext = test.inSourceExt + if test.inSChain != nil { + req.Source.SChain = test.inSChain } var extRequest *openrtb_ext.ExtRequest @@ -1548,7 +1613,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { privacyConfig: config.Privacy{}, gdprPermsBuilder: gdprPermissionsBuilder, hostSChainNode: nil, - bidderInfo: config.BidderInfos{}, + bidderInfo: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{Version: test.ortbVersion}}}, } bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{}) @@ -1558,7 +1623,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { } else { result := bidderRequests[0] assert.Nil(t, errs) - assert.Equal(t, test.outSourceExt, result.BidRequest.Source.Ext, test.description+":Source.Ext") + assert.Equal(t, test.outSource, result.BidRequest.Source, test.description+":Source") assert.Equal(t, test.outRequestExt, result.BidRequest.Ext, test.description+":Ext") } } @@ -2291,7 +2356,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { for _, test := range testCases { req := newBidRequest(t) - req.User.Ext = json.RawMessage(`{"consent":"` + test.gdprConsent + `"}`) + req.User.Consent = test.gdprConsent privacyConfig := config.Privacy{} accountConfig := config.Account{} @@ -2473,14 +2538,14 @@ func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) { req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidReq}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, expectRegs: &downgradedRegs, expectUser: &downgradedUser, - bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: false}}}, + bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: false, Version: "2.6"}}}, }, { name: "Supported", req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidReq}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, expectRegs: bidReq.Regs, expectUser: bidReq.User, - bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: true}}}, + bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: true, Version: "2.6"}}}, }, } @@ -2527,145 +2592,146 @@ func TestBuildRequestExtForBidder(t *testing.T) { ) testCases := []struct { - description string + name string requestExt json.RawMessage bidderParams map[string]json.RawMessage alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes expectedJson json.RawMessage }{ { - description: "Nil", + name: "Nil", bidderParams: nil, requestExt: nil, alternateBidderCodes: nil, expectedJson: nil, }, { - description: "Empty", + name: "Empty", bidderParams: nil, alternateBidderCodes: nil, requestExt: json.RawMessage(`{}`), expectedJson: nil, }, { - description: "Prebid - Allowed Fields Only", + name: "Prebid - Allowed Fields Only", bidderParams: nil, requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), }, { - description: "Prebid - Allowed Fields + Bidder Params", + name: "Prebid - Allowed Fields + Bidder Params", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}, "bidderparams":"bar"}}`), }, { - description: "Other", + name: "Other", bidderParams: nil, requestExt: json.RawMessage(`{"other":"foo"}`), expectedJson: json.RawMessage(`{"other":"foo"}`), }, { - description: "Prebid + Other + Bider Params", + name: "Prebid + Other + Bider Params", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}, "bidderparams":"bar"}}`), }, { - description: "Prebid + AlternateBidderCodes in pbs config but current bidder not in AlternateBidderCodes config", + name: "Prebid + AlternateBidderCodes in pbs config but current bidder not in AlternateBidderCodes config", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"bar": {Enabled: true, AllowedBidderCodes: []string{"*"}}}}, requestExt: json.RawMessage(`{"other":"foo"}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`), }, { - description: "Prebid + AlternateBidderCodes in request", + name: "Prebid + AlternateBidderCodes in request", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`), }, { - description: "Prebid + AlternateBidderCodes in request but current bidder not in AlternateBidderCodes config", + name: "Prebid + AlternateBidderCodes in request but current bidder not in AlternateBidderCodes config", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`), }, { - description: "Prebid + AlternateBidderCodes in both pbs config and in the request", + name: "Prebid + AlternateBidderCodes in both pbs config and in the request", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"foo": {Enabled: true, AllowedBidderCodes: []string{"*"}}}}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`), }, { - description: "Prebid + Other + Bider Params + MultiBid.Bidder", + name: "Prebid + Other + Bider Params + MultiBid.Bidder", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo","maxbids":2,"targetbiddercodeprefix":"fmb"}],"bidderparams":"bar"}}`), }, { - description: "Prebid + Other + Bider Params + MultiBid.Bidders", + name: "Prebid + Other + Bider Params + MultiBid.Bidders", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"pubmatic","maxbids":3,"targetbiddercodeprefix":"pubM"},{"bidders":["foo","groupm"],"maxbids":4}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidders":["foo"],"maxbids":4}],"bidderparams":"bar"}}`), }, { - description: "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)", + name: "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo2","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`), }, { - description: "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)", + name: "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo2","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`), }, { - description: "Prebid + AlternateBidderCodes.MultiBid.Bidder", + name: "Prebid + AlternateBidderCodes.MultiBid.Bidder", requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), }, { - description: "Prebid + AlternateBidderCodes.MultiBid.Bidders", + name: "Prebid + AlternateBidderCodes.MultiBid.Bidders", requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic"],"maxbids":4}]}}`), }, { - description: "Prebid + AlternateBidderCodes.MultiBid.Bidder with *", + name: "Prebid + AlternateBidderCodes.MultiBid.Bidder with *", requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), }, { - description: "Prebid + AlternateBidderCodes.MultiBid.Bidders with *", + name: "Prebid + AlternateBidderCodes.MultiBid.Bidders with *", requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic"],"maxbids":4},{"bidders":["groupm"],"maxbids":4}]}}`), }, { - description: "Prebid + AlternateBidderCodes + MultiBid", + name: "Prebid + AlternateBidderCodes + MultiBid", requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"multibid":[{"bidder":"foo3","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}}}}`), }, } for _, test := range testCases { - requestExtParsed := &openrtb_ext.ExtRequest{} - if test.requestExt != nil { - err := jsonutil.UnmarshalValid(test.requestExt, requestExtParsed) - if !assert.NoError(t, err, test.description+":parse_ext") { - continue + t.Run(test.name, func(t *testing.T) { + req := openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: test.requestExt, + }, } - } + err := buildRequestExtForBidder(bidder, &req, test.bidderParams, test.alternateBidderCodes) + assert.NoError(t, req.RebuildRequest()) + assert.NoError(t, err) - actualJson, actualErr := buildRequestExtForBidder(bidder, test.requestExt, requestExtParsed, test.bidderParams, test.alternateBidderCodes) - if len(test.expectedJson) > 0 { - assert.JSONEq(t, string(test.expectedJson), string(actualJson), test.description+":json") - } else { - assert.Equal(t, test.expectedJson, actualJson, test.description+":json") - } - assert.NoError(t, actualErr, test.description+":err") + if len(test.expectedJson) > 0 { + assert.JSONEq(t, string(test.expectedJson), string(req.Ext)) + } else { + assert.Equal(t, test.expectedJson, req.Ext) + } + }) } } @@ -2673,28 +2739,37 @@ func TestBuildRequestExtForBidder_RequestExtParsedNil(t *testing.T) { var ( bidder = "foo" requestExt = json.RawMessage(`{}`) - requestExtParsed *openrtb_ext.ExtRequest bidderParams map[string]json.RawMessage alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes ) - actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes) - assert.Nil(t, actualJson) - assert.NoError(t, actualErr) + req := openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: requestExt, + }, + } + err := buildRequestExtForBidder(bidder, &req, bidderParams, alternateBidderCodes) + assert.NoError(t, req.RebuildRequest()) + assert.Nil(t, req.Ext) + assert.NoError(t, err) } func TestBuildRequestExtForBidder_RequestExtMalformed(t *testing.T) { var ( bidder = "foo" requestExt = json.RawMessage(`malformed`) - requestExtParsed = &openrtb_ext.ExtRequest{} bidderParams map[string]json.RawMessage alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes ) - actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes) - assert.Equal(t, json.RawMessage(nil), actualJson) - assert.EqualError(t, actualErr, "expect { or n, but found m") + req := openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: requestExt, + }, + } + err := buildRequestExtForBidder(bidder, &req, bidderParams, alternateBidderCodes) + assert.NoError(t, req.RebuildRequest()) + assert.EqualError(t, err, "expect { or n, but found m") } // newAdapterAliasBidRequest builds a BidRequest with aliases @@ -2880,193 +2955,112 @@ func TestRemoveUnpermissionedEids(t *testing.T) { bidder := "bidderA" testCases := []struct { - description string - userExt json.RawMessage - eidPermissions []openrtb_ext.ExtRequestPrebidDataEidPermission - expectedUserExt json.RawMessage + description string + userEids []openrtb2.EID + eidPermissions []openrtb_ext.ExtRequestPrebidDataEidPermission + expectedUserEids []openrtb2.EID }{ - { - description: "Extension Nil", - userExt: nil, - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"bidderA"}}, - }, - expectedUserExt: nil, - }, - { - description: "Extension Empty", - userExt: json.RawMessage(`{}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"bidderA"}}, - }, - expectedUserExt: json.RawMessage(`{}`), - }, - { - description: "Extension Empty - Keep Other Data", - userExt: json.RawMessage(`{"other":42}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"bidderA"}}, - }, - expectedUserExt: json.RawMessage(`{"other":42}`), - }, + { description: "Eids Empty", - userExt: json.RawMessage(`{"eids":[]}`), + userEids: []openrtb2.EID{}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"bidderA"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[]}`), + expectedUserEids: []openrtb2.EID{}, }, { - description: "Eids Empty - Keep Other Data", - userExt: json.RawMessage(`{"eids":[],"other":42}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"bidderA"}}, - }, - expectedUserExt: json.RawMessage(`{"eids":[],"other":42}`), - }, - { - description: "Allowed By Nil Permissions", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), - eidPermissions: nil, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + description: "Allowed By Nil Permissions", + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, + eidPermissions: nil, + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { - description: "Allowed By Empty Permissions", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{}, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + description: "Allowed By Empty Permissions", + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, + eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{}, + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { description: "Allowed By Specific Bidder", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"bidderA"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { description: "Allowed By Specific Bidder - Case Insensitive", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"BIDDERA"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { description: "Allowed By All Bidders", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"*"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { description: "Allowed By Lack Of Matching Source", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source2", Bidders: []string{"otherBidder"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), - }, - { - description: "Allowed - Keep Other Data", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"other":42}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"bidderA"}}, - }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"other":42}`), + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { description: "Denied", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"otherBidder"}}, }, - expectedUserExt: nil, + expectedUserEids: nil, }, { - description: "Denied - Keep Other Data", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"otherdata":42}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"otherBidder"}}, + description: "Mix Of Allowed By Specific Bidder, Allowed By Lack Of Matching Source, Denied", + userEids: []openrtb2.EID{ + {Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID1"}}}, + {Source: "source2", UIDs: []openrtb2.UID{{ID: "anyID2"}}}, + {Source: "source3", UIDs: []openrtb2.UID{{ID: "anyID3"}}}, }, - expectedUserExt: json.RawMessage(`{"otherdata":42}`), - }, - { - description: "Mix Of Allowed By Specific Bidder, Allowed By Lack Of Matching Source, Denied, Keep Other Data", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID1"}]},{"source":"source2","uids":[{"id":"anyID2"}]},{"source":"source3","uids":[{"id":"anyID3"}]}],"other":42}`), eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"bidderA"}}, {Source: "source3", Bidders: []string{"otherBidder"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID1"}]},{"source":"source2","uids":[{"id":"anyID2"}]}],"other":42}`), + expectedUserEids: []openrtb2.EID{ + {Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID1"}}}, + {Source: "source2", UIDs: []openrtb2.UID{{ID: "anyID2"}}}, + }, }, } for _, test := range testCases { - request := &openrtb2.BidRequest{ - User: &openrtb2.User{Ext: test.userExt}, - } + t.Run(test.description, func(t *testing.T) { + request := &openrtb2.BidRequest{ + User: &openrtb2.User{EIDs: test.userEids}, + } - requestExt := &openrtb_ext.ExtRequest{ - Prebid: openrtb_ext.ExtRequestPrebid{ + reqWrapper := openrtb_ext.RequestWrapper{BidRequest: request} + re, _ := reqWrapper.GetRequestExt() + re.SetPrebid(&openrtb_ext.ExtRequestPrebid{ Data: &openrtb_ext.ExtRequestPrebidData{ EidPermissions: test.eidPermissions, }, - }, - } - - expectedRequest := &openrtb2.BidRequest{ - User: &openrtb2.User{Ext: test.expectedUserExt}, - } - - resultErr := removeUnpermissionedEids(request, bidder, requestExt) - assert.NoError(t, resultErr, test.description) - assert.Equal(t, expectedRequest, request, test.description) - } -} - -func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) { - testCases := []struct { - description string - userExt json.RawMessage - expectedErr string - }{ - { - description: "Malformed Ext", - userExt: json.RawMessage(`malformed`), - expectedErr: "expect { or n, but found m", - }, - { - description: "Malformed Eid Array Type", - userExt: json.RawMessage(`{"eids":[42]}`), - expectedErr: "cannot unmarshal []openrtb2.EID: expect { or n, but found 4", - }, - { - description: "Malformed Eid Item Type", - userExt: json.RawMessage(`{"eids":[{"source":42,"id":"anyID"}]}`), - expectedErr: "cannot unmarshal openrtb2.EID.Source: expects \" or n, but found 4", - }, - } - - for _, test := range testCases { - request := &openrtb2.BidRequest{ - User: &openrtb2.User{Ext: test.userExt}, - } + }) - requestExt := &openrtb_ext.ExtRequest{ - Prebid: openrtb_ext.ExtRequestPrebid{ - Data: &openrtb_ext.ExtRequestPrebidData{ - EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"*"}}, - }, - }, - }, - } + expectedRequest := &openrtb2.BidRequest{ + User: &openrtb2.User{EIDs: test.expectedUserEids}, + } - resultErr := removeUnpermissionedEids(request, "bidderA", requestExt) - assert.EqualError(t, resultErr, test.expectedErr, test.description) + resultErr := removeUnpermissionedEids(&reqWrapper, bidder) + assert.NoError(t, resultErr, test.description) + assert.Equal(t, expectedRequest, reqWrapper.BidRequest) + }) } } @@ -3184,23 +3178,17 @@ func TestGetDebugInfo(t *testing.T) { func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { testCases := []struct { - description string - request *openrtb2.BidRequest - requestExt *openrtb_ext.ExtRequest + description string + request *openrtb2.BidRequest + eidPermissions []openrtb_ext.ExtRequestPrebidDataEidPermission }{ { description: "Nil User", request: &openrtb2.BidRequest{ User: nil, }, - requestExt: &openrtb_ext.ExtRequest{ - Prebid: openrtb_ext.ExtRequestPrebid{ - Data: &openrtb_ext.ExtRequestPrebidData{ - EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"*"}}, - }, - }, - }, + eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ + {Source: "source1", Bidders: []string{"*"}}, }, }, { @@ -3208,14 +3196,8 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { request: &openrtb2.BidRequest{ User: &openrtb2.User{}, }, - requestExt: &openrtb_ext.ExtRequest{ - Prebid: openrtb_ext.ExtRequestPrebid{ - Data: &openrtb_ext.ExtRequestPrebidData{ - EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"*"}}, - }, - }, - }, + eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ + {Source: "source1", Bidders: []string{"*"}}, }, }, { @@ -3223,27 +3205,25 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { request: &openrtb2.BidRequest{ User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, }, - requestExt: nil, - }, - { - description: "Nil Prebid Data", - request: &openrtb2.BidRequest{ - User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, - }, - requestExt: &openrtb_ext.ExtRequest{ - Prebid: openrtb_ext.ExtRequestPrebid{ - Data: nil, - }, - }, }, } for _, test := range testCases { - requestExpected := *test.request + t.Run(test.description, func(t *testing.T) { + requestExpected := *test.request + reqWrapper := openrtb_ext.RequestWrapper{BidRequest: test.request} + + re, _ := reqWrapper.GetRequestExt() + re.SetPrebid(&openrtb_ext.ExtRequestPrebid{ + Data: &openrtb_ext.ExtRequestPrebidData{ + EidPermissions: test.eidPermissions, + }, + }) - resultErr := removeUnpermissionedEids(test.request, "bidderA", test.requestExt) - assert.NoError(t, resultErr, test.description+":err") - assert.Equal(t, &requestExpected, test.request, test.description+":request") + resultErr := removeUnpermissionedEids(&reqWrapper, "bidderA") + assert.NoError(t, resultErr, test.description+":err") + assert.Equal(t, &requestExpected, reqWrapper.BidRequest, test.description+":request") + }) } } @@ -3278,28 +3258,57 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { }, }.Builder + ortb26enabled := config.BidderInfo{OpenRTB: &config.OpenRTBInfo{Version: "2.6"}} reqSplitter := &requestSplitter{ bidderToSyncerKey: map[string]string{}, me: &metrics.MetricsEngineMock{}, privacyConfig: config.Privacy{}, gdprPermsBuilder: gdprPermissionsBuilder, hostSChainNode: nil, - bidderInfo: config.BidderInfos{}, + bidderInfo: config.BidderInfos{"appnexus": ortb26enabled, "axonix": ortb26enabled}, } bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{}) assert.Nil(t, errs) assert.Len(t, bidderRequests, 2, "Bid request count is not 2") - bidRequestSourceExts := map[openrtb_ext.BidderName]json.RawMessage{} + bidRequestSourceSupplyChain := map[openrtb_ext.BidderName]*openrtb2.SupplyChain{} for _, bidderRequest := range bidderRequests { - bidRequestSourceExts[bidderRequest.BidderName] = bidderRequest.BidRequest.Source.Ext + bidRequestSourceSupplyChain[bidderRequest.BidderName] = bidderRequest.BidRequest.Source.SChain + } + + appnexusSchainsSchainExpected := &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, } - appnexusPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`) - axonixPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}`) - assert.Equal(t, appnexusPrebidSchainsSchain, bidRequestSourceExts["appnexus"], "Incorrect appnexus bid request schain in source.ext") - assert.Equal(t, axonixPrebidSchainsSchain, bidRequestSourceExts["axonix"], "Incorrect axonix bid request schain in source.ext") + axonixSchainsSchainExpected := &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller2.com", + SID: "00002", + RID: "BidRequest2", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + } + + assert.Equal(t, appnexusSchainsSchainExpected, bidRequestSourceSupplyChain["appnexus"], "Incorrect appnexus bid request schain ") + assert.Equal(t, axonixSchainsSchainExpected, bidRequestSourceSupplyChain["axonix"], "Incorrect axonix bid request schain") } func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { @@ -3423,6 +3432,7 @@ func TestApplyFPD(t *testing.T) { inputBidderIsRequestAlias bool inputRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest + fpdUserEIDsExisted bool }{ { description: "fpd-nil", @@ -3544,17 +3554,45 @@ func TestApplyFPD(t *testing.T) { inputRequest: openrtb2.BidRequest{}, expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, }, + { + description: "req.User is defined and had bidder fpd user eids (fpdUserEIDsExisted); bidderFPD.User defined and has EIDs. Expect to see user.EIDs in result request taken from fpd", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source1"}, {Source: "source2"}}}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source3"}, {Source: "source4"}}}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source1"}, {Source: "source2"}}}}, + fpdUserEIDsExisted: true, + }, + { + description: "req.User is defined and doesn't have fpr user eids (fpdUserEIDsExisted); bidderFPD.User defined and has EIDs. Expect to see user.EIDs in result request taken from original req", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source1"}, {Source: "source2"}}}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source3"}, {Source: "source4"}}}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source3"}, {Source: "source4"}}}}, + fpdUserEIDsExisted: false, + }, } for _, testCase := range testCases { - bidderRequest := BidderRequest{ - BidderName: openrtb_ext.BidderName(testCase.inputBidderName), - BidderCoreName: openrtb_ext.BidderName(testCase.inputBidderCoreName), - IsRequestAlias: testCase.inputBidderIsRequestAlias, - BidRequest: &testCase.inputRequest, - } - applyFPD(testCase.inputFpd, bidderRequest) - assert.Equal(t, testCase.expectedRequest, testCase.inputRequest, fmt.Sprintf("incorrect request after applying fpd, testcase %s", testCase.description)) + t.Run(testCase.description, func(t *testing.T) { + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &testCase.inputRequest} + applyFPD( + testCase.inputFpd, + openrtb_ext.BidderName(testCase.inputBidderCoreName), + openrtb_ext.BidderName(testCase.inputBidderName), + testCase.inputBidderIsRequestAlias, + reqWrapper, + testCase.fpdUserEIDsExisted, + ) + assert.Equal(t, &testCase.expectedRequest, reqWrapper.BidRequest) + }) } } @@ -3881,21 +3919,27 @@ func (gs GPPMockSection) Encode(bool) []byte { func TestGdprFromGPP(t *testing.T) { testCases := []struct { name string - initialRequest *openrtb2.BidRequest + initialRequest *openrtb_ext.RequestWrapper gpp gpplib.GppContainer - expectedRequest *openrtb2.BidRequest + expectedRequest *openrtb_ext.RequestWrapper }{ { - name: "Empty", // Empty Request - initialRequest: &openrtb2.BidRequest{}, - gpp: gpplib.GppContainer{}, - expectedRequest: &openrtb2.BidRequest{}, + name: "Empty", // Empty Request + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + gpp: gpplib.GppContainer{}, + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, }, { name: "GDPR_Downgrade", // GDPR from GPP, into empty - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + }, }, }, gpp: gpplib.GppContainer{ @@ -3907,25 +3951,29 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](1), - }, - User: &openrtb2.User{ - Consent: "GDPRConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](1), + }, + User: &openrtb2.User{ + Consent: "GDPRConsent", + }, }, }, }, { name: "GDPR_Downgrade", // GDPR from GPP, into empty legacy, existing objects - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - USPrivacy: "LegacyUSP", - }, - User: &openrtb2.User{ - ID: "1234", + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + USPrivacy: "LegacyUSP", + }, + User: &openrtb2.User{ + ID: "1234", + }, }, }, gpp: gpplib.GppContainer{ @@ -3937,27 +3985,31 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](1), - USPrivacy: "LegacyUSP", - }, - User: &openrtb2.User{ - ID: "1234", - Consent: "GDPRConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](1), + USPrivacy: "LegacyUSP", + }, + User: &openrtb2.User{ + ID: "1234", + Consent: "GDPRConsent", + }, }, }, }, { name: "Downgrade_Blocked_By_Existing", // GDPR from GPP blocked by existing GDPR", - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](1), - }, - User: &openrtb2.User{ - Consent: "LegacyConsent", + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](1), + }, + User: &openrtb2.User{ + Consent: "LegacyConsent", + }, }, }, gpp: gpplib.GppContainer{ @@ -3969,22 +4021,26 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](1), - }, - User: &openrtb2.User{ - Consent: "LegacyConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](1), + }, + User: &openrtb2.User{ + Consent: "LegacyConsent", + }, }, }, }, { name: "Downgrade_Partial", // GDPR from GPP partially blocked by existing GDPR - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](0), + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](0), + }, }, }, gpp: gpplib.GppContainer{ @@ -3996,21 +4052,25 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](0), - }, - User: &openrtb2.User{ - Consent: "GDPRConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](0), + }, + User: &openrtb2.User{ + Consent: "GDPRConsent", + }, }, }, }, { name: "No_GDPR", // Downgrade not possible due to missing GDPR - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + }, }, }, gpp: gpplib.GppContainer{ @@ -4022,18 +4082,22 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, - GDPR: ptrutil.ToPtr[int8](0), + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + GDPR: ptrutil.ToPtr[int8](0), + }, }, }, }, { name: "No_SID", // GDPR from GPP partially blocked by no SID - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + }, }, }, gpp: gpplib.GppContainer{ @@ -4049,19 +4113,23 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, - GDPR: ptrutil.ToPtr[int8](0), - }, - User: &openrtb2.User{ - Consent: "GDPRConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + GDPR: ptrutil.ToPtr[int8](0), + }, + User: &openrtb2.User{ + Consent: "GDPRConsent", + }, }, }, }, { - name: "GDPR_Nil_SID", // GDPR from GPP, into empty, but with nil SID - initialRequest: &openrtb2.BidRequest{}, + name: "GDPR_Nil_SID", // GDPR from GPP, into empty, but with nil SID + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, gpp: gpplib.GppContainer{ SectionTypes: []constants.SectionID{2}, Sections: []gpplib.Section{ @@ -4071,20 +4139,24 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - User: &openrtb2.User{ - Consent: "GDPRConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + Consent: "GDPRConsent", + }, }, }, }, { name: "Downgrade_Nil_SID_Blocked_By_Existing", // GDPR from GPP blocked by existing GDPR, with nil SID", - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GDPR: ptrutil.ToPtr[int8](1), - }, - User: &openrtb2.User{ - Consent: "LegacyConsent", + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GDPR: ptrutil.ToPtr[int8](1), + }, + User: &openrtb2.User{ + Consent: "LegacyConsent", + }, }, }, gpp: gpplib.GppContainer{ @@ -4096,12 +4168,14 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GDPR: ptrutil.ToPtr[int8](1), - }, - User: &openrtb2.User{ - Consent: "LegacyConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GDPR: ptrutil.ToPtr[int8](1), + }, + User: &openrtb2.User{ + Consent: "LegacyConsent", + }, }, }, }, @@ -4118,21 +4192,27 @@ func TestGdprFromGPP(t *testing.T) { func TestPrivacyFromGPP(t *testing.T) { testCases := []struct { name string - initialRequest *openrtb2.BidRequest + initialRequest *openrtb_ext.RequestWrapper gpp gpplib.GppContainer - expectedRequest *openrtb2.BidRequest + expectedRequest *openrtb_ext.RequestWrapper }{ { - name: "Empty", // Empty Request - initialRequest: &openrtb2.BidRequest{}, - gpp: gpplib.GppContainer{}, - expectedRequest: &openrtb2.BidRequest{}, + name: "Empty", // Empty Request + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + gpp: gpplib.GppContainer{}, + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, }, { name: "Privacy_Downgrade", // US Privacy from GPP, into empty - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + }, }, }, gpp: gpplib.GppContainer{ @@ -4144,19 +4224,23 @@ func TestPrivacyFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, - USPrivacy: "USPrivacy", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + USPrivacy: "USPrivacy", + }, }, }, }, { name: "Downgrade_Blocked_By_Existing", // US Privacy from GPP blocked by existing US Privacy - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, - USPrivacy: "LegacyPrivacy", + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + USPrivacy: "LegacyPrivacy", + }, }, }, gpp: gpplib.GppContainer{ @@ -4168,18 +4252,22 @@ func TestPrivacyFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, - USPrivacy: "LegacyPrivacy", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + USPrivacy: "LegacyPrivacy", + }, }, }, }, { name: "No_USPrivacy", // Downgrade not possible due to missing USPrivacy - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + }, }, }, gpp: gpplib.GppContainer{ @@ -4191,17 +4279,21 @@ func TestPrivacyFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + }, }, }, }, { name: "No_SID", // US Privacy from GPP partially blocked by no SID - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + }, }, }, gpp: gpplib.GppContainer{ @@ -4217,9 +4309,11 @@ func TestPrivacyFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + }, }, }, }, @@ -4709,6 +4803,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { privacyConfig config.AccountPrivacy componentName string allow bool + ortbVersion string expectedReqNumber int expectedUser openrtb2.User expectUserScrub bool @@ -4720,6 +4815,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { name: "fetch_bids_request_with_one_bidder_allowed", req: newBidRequest(t), privacyConfig: getFetchBidsActivityConfig("appnexus", true), + ortbVersion: "2.6", expectedReqNumber: 1, expectedUser: expectedUserDefault, expectedDevice: expectedDeviceDefault, @@ -4738,6 +4834,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { name: "transmit_ufpd_allowed", req: newBidRequest(t), privacyConfig: getTransmitUFPDActivityConfig("appnexus", true), + ortbVersion: "2.6", expectedReqNumber: 1, expectedUser: expectedUserDefault, expectedDevice: expectedDeviceDefault, @@ -4779,6 +4876,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { name: "transmit_precise_geo_allowed", req: newBidRequest(t), privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", true), + ortbVersion: "2.6", expectedReqNumber: 1, expectedUser: expectedUserDefault, expectedDevice: expectedDeviceDefault, @@ -4790,6 +4888,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { name: "transmit_precise_geo_deny", req: newBidRequest(t), privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", false), + ortbVersion: "2.6", expectedReqNumber: 1, expectedUser: openrtb2.User{ ID: "our-id", @@ -4822,6 +4921,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { name: "transmit_tid_allowed", req: newBidRequest(t), privacyConfig: getTransmitTIDActivityConfig("appnexus", true), + ortbVersion: "2.6", expectedReqNumber: 1, expectedUser: expectedUserDefault, expectedDevice: expectedDeviceDefault, @@ -4832,6 +4932,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { name: "transmit_tid_deny", req: newBidRequest(t), privacyConfig: getTransmitTIDActivityConfig("appnexus", false), + ortbVersion: "2.6", expectedReqNumber: 1, expectedUser: expectedUserDefault, expectedDevice: expectedDeviceDefault, @@ -4868,7 +4969,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { bidderToSyncerKey: bidderToSyncerKey, me: &metricsMock, hostSChainNode: nil, - bidderInfo: config.BidderInfos{}, + bidderInfo: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{Version: test.ortbVersion}}}, } bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{}) @@ -4942,119 +5043,96 @@ func getTransmitTIDActivityConfig(componentName string, allow bool) config.Accou func TestApplyBidAdjustmentToFloor(t *testing.T) { type args struct { - allBidderRequests []BidderRequest + bidRequestWrapper *openrtb_ext.RequestWrapper + bidderName string bidAdjustmentFactors map[string]float64 } tests := []struct { - name string - args args - expectedAllBidderRequests []BidderRequest + name string + args args + expectedBidRequest *openrtb2.BidRequest }{ { - name: " bidAdjustmentFactor is empty", + name: "bid_adjustment_factor_is_nil", args: args{ - allBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, - }, - BidderName: openrtb_ext.BidderName("appnexus"), - }, - }, - bidAdjustmentFactors: map[string]float64{}, - }, - expectedAllBidderRequests: []BidderRequest{ - { + bidRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, - BidderName: openrtb_ext.BidderName("appnexus"), }, + bidderName: "appnexus", + bidAdjustmentFactors: nil, + }, + expectedBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, }, { - name: "bidAdjustmentFactor not present for request bidder", + name: "bid_adjustment_factor_is_empty", args: args{ - allBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, - }, - BidderName: openrtb_ext.BidderName("appnexus"), + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, }, - bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0}, + bidderName: "appnexus", + bidAdjustmentFactors: map[string]float64{}, }, - expectedAllBidderRequests: []BidderRequest{ - { + expectedBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + }, + { + name: "bid_adjustment_factor_not_present", + args: args{ + bidRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, - BidderName: openrtb_ext.BidderName("appnexus"), }, + bidderName: "appnexus", + bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0}, + }, + expectedBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, }, { - name: "bidAdjustmentFactor present for request bidder", + name: "bid_adjustment_factor_present", args: args{ - allBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, - }, - BidderName: openrtb_ext.BidderName("appnexus"), + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, }, + bidderName: "appnexus", bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0, "appnexus": 0.75}, }, - expectedAllBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}}, - }, - BidderName: openrtb_ext.BidderName("appnexus"), - }, + expectedBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}}, }, }, { - name: "bidAdjustmentFactor present only for appnexus request bidder", + name: "bid_adjustment_factor_present_and_zero", args: args{ - allBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, - }, - BidderName: openrtb_ext.BidderName("appnexus"), - }, - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, - }, - BidderName: openrtb_ext.BidderName("pubmatic"), - }, - }, - bidAdjustmentFactors: map[string]float64{"appnexus": 0.75}, - }, - expectedAllBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}}, - }, - BidderName: openrtb_ext.BidderName("appnexus"), - }, - { + bidRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, - BidderName: openrtb_ext.BidderName("pubmatic"), }, + bidderName: "appnexus", + bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0, "appnexus": 0.0}, + }, + expectedBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - applyBidAdjustmentToFloor(tt.args.allBidderRequests, tt.args.bidAdjustmentFactors) - assert.Equal(t, tt.expectedAllBidderRequests, tt.args.allBidderRequests, tt.name) + applyBidAdjustmentToFloor(tt.args.bidRequestWrapper, tt.args.bidderName, tt.args.bidAdjustmentFactors) + assert.NoError(t, tt.args.bidRequestWrapper.RebuildRequest()) + assert.Equal(t, tt.expectedBidRequest, tt.args.bidRequestWrapper.BidRequest, tt.name) }) } } @@ -5277,24 +5355,85 @@ func TestCopyExtAlternateBidderCodes(t *testing.T) { } } -func TestBuildBidResponseRequestBidderName(t *testing.T) { - bidderImpResponses := stored_responses.BidderImpsWithBidResponses{ - openrtb_ext.BidderName("appnexus"): {"impId1": json.RawMessage(`{}`), "impId2": json.RawMessage(`{}`)}, - openrtb_ext.BidderName("appneXUS"): {"impId3": json.RawMessage(`{}`), "impId4": json.RawMessage(`{}`)}, - } +func TestRemoveImpsWithStoredResponses(t *testing.T) { + bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`) + testCases := []struct { + description string + req *openrtb_ext.RequestWrapper + storedBidResponses map[string]json.RawMessage + expectedImps []openrtb2.Imp + }{ + { + description: "request with imps and stored bid response for this imp", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1"}, + }, + }, + }, + storedBidResponses: map[string]json.RawMessage{ + "imp-id1": bidRespId1, + }, + expectedImps: nil, + }, + { + description: "request with imps and stored bid response for one of these imp", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1"}, + {ID: "imp-id2"}, + }, + }, + }, + storedBidResponses: map[string]json.RawMessage{ + "imp-id1": bidRespId1, + }, + expectedImps: []openrtb2.Imp{ + { + ID: "imp-id2", + }, + }, + }, + { + description: "request with imps and stored bid response for both of these imp", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1"}, + {ID: "imp-id2"}, + }, + }, + }, + storedBidResponses: map[string]json.RawMessage{ + "imp-id1": bidRespId1, + "imp-id2": bidRespId1, + }, + expectedImps: nil, + }, + { + description: "request with imps and no stored bid responses", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1"}, + {ID: "imp-id2"}, + }, + }, + }, + storedBidResponses: nil, - bidderImpReplaceImpID := stored_responses.BidderImpReplaceImpID{ - "appnexus": {"impId1": true, "impId2": false}, - "appneXUS": {"impId3": true, "impId4": false}, + expectedImps: []openrtb2.Imp{ + {ID: "imp-id1"}, + {ID: "imp-id2"}, + }, + }, + } + for _, testCase := range testCases { + request := testCase.req + removeImpsWithStoredResponses(request, testCase.storedBidResponses) + assert.NoError(t, request.RebuildRequest()) + assert.Equal(t, testCase.expectedImps, request.Imp, "incorrect Impressions for testCase %s", testCase.description) } - result := buildBidResponseRequest(nil, bidderImpResponses, nil, bidderImpReplaceImpID) - - resultAppnexus := result["appnexus"] - assert.Equal(t, resultAppnexus.BidderName, openrtb_ext.BidderName("appnexus")) - assert.Equal(t, resultAppnexus.ImpReplaceImpId, map[string]bool{"impId1": true, "impId2": false}) - - resultAppneXUS := result["appneXUS"] - assert.Equal(t, resultAppneXUS.BidderName, openrtb_ext.BidderName("appneXUS")) - assert.Equal(t, resultAppneXUS.ImpReplaceImpId, map[string]bool{"impId3": true, "impId4": false}) - } diff --git a/injector/injector_test.go b/injector/injector_test.go index 2c9dceba154..1a6385c48e9 100644 --- a/injector/injector_test.go +++ b/injector/injector_test.go @@ -35,7 +35,7 @@ var reqWrapper = &openrtb_ext.RequestWrapper{ Device: &openrtb2.Device{ Lmt: ptrutil.ToPtr(int8(1)), }, - User: &openrtb2.User{Ext: []byte(`{"consent":"1" }`)}, + User: &openrtb2.User{Consent: "1", Ext: []byte(`{"consent":"2" }`)}, Ext: []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR1":"value1"}}}`), }, } diff --git a/macros/provider.go b/macros/provider.go index 0b4ef3eacb3..73e5755566a 100644 --- a/macros/provider.go +++ b/macros/provider.go @@ -104,9 +104,8 @@ func (b *MacroProvider) populateRequestMacros(reqWrapper *openrtb_ext.RequestWra } } - userExt, err := reqWrapper.GetUserExt() - if err == nil && userExt != nil && userExt.GetConsent() != nil { - b.macros[MacroKeyConsent] = *userExt.GetConsent() + if reqWrapper.User != nil && len(reqWrapper.User.Consent) > 0 { + b.macros[MacroKeyConsent] = reqWrapper.User.Consent } if reqWrapper.Device != nil && reqWrapper.Device.Lmt != nil { b.macros[MacroKeyLmtTracking] = strconv.Itoa(int(*reqWrapper.Device.Lmt)) diff --git a/macros/provider_test.go b/macros/provider_test.go index 3da56ea7826..23f56605f94 100644 --- a/macros/provider_test.go +++ b/macros/provider_test.go @@ -132,7 +132,7 @@ func TestPopulateRequestMacros(t *testing.T) { args: args{ reqWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ - User: &openrtb2.User{Ext: []byte(`{"consent":"1" }`)}, + User: &openrtb2.User{Consent: "1", Ext: []byte(`{"consent":"2" }`)}, Ext: []byte(`{"prebid":{"integration":"testIntegration"}}`), }, }, @@ -189,7 +189,7 @@ func TestPopulateRequestMacros(t *testing.T) { Device: &openrtb2.Device{ Lmt: &lmt, }, - User: &openrtb2.User{Ext: []byte(`{"consent":"1" }`)}, + User: &openrtb2.User{Consent: "1", Ext: []byte(`{"consent":"2" }`)}, Ext: []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR1":"value1"}}}`), }, }, diff --git a/macros/string_index_based_replacer_test.go b/macros/string_index_based_replacer_test.go index 6e09db1e15a..a51f02fc2f0 100644 --- a/macros/string_index_based_replacer_test.go +++ b/macros/string_index_based_replacer_test.go @@ -116,7 +116,7 @@ var req *openrtb_ext.RequestWrapper = &openrtb_ext.RequestWrapper{ Device: &openrtb2.Device{ Lmt: &lmt, }, - User: &openrtb2.User{Ext: []byte(`{"consent":"yes" }`)}, + User: &openrtb2.User{Consent: "yes", Ext: []byte(`{"consent":"no" }`)}, Ext: []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR1":"value1","CUSTOMMACR2":"value2","CUSTOMMACR3":"value3"}}}`), }, } diff --git a/openrtb_ext/convert_down.go b/openrtb_ext/convert_down.go index e0842978551..bfb6028d8c7 100644 --- a/openrtb_ext/convert_down.go +++ b/openrtb_ext/convert_down.go @@ -31,13 +31,6 @@ func ConvertDownTo25(r *RequestWrapper) error { } } - // Remove fields introduced in OpenRTB 2.6+. The previous OpenRTB 2.5 spec did not specify that - // bidders must tolerate new or unexpected fields. - clear26Fields(r) - clear202211Fields(r) - clear202303Fields(r) - clear202309Fields(r) - return nil } diff --git a/openrtb_ext/convert_down_test.go b/openrtb_ext/convert_down_test.go index 1bf112dcb3a..346ad4816ed 100644 --- a/openrtb_ext/convert_down_test.go +++ b/openrtb_ext/convert_down_test.go @@ -35,50 +35,6 @@ func TestConvertDownTo25(t *testing.T) { User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)}, }, }, - { - name: "2.6-dropped", // integration with clear26Fields - givenRequest: openrtb2.BidRequest{ - ID: "anyID", - CatTax: adcom1.CatTaxIABContent10, - Device: &openrtb2.Device{LangB: "anyLang"}, - }, - expectedRequest: openrtb2.BidRequest{ - ID: "anyID", - Device: &openrtb2.Device{}, - }, - }, - { - name: "2.6-202211-dropped", // integration with clear202211Fields - givenRequest: openrtb2.BidRequest{ - ID: "anyID", - App: &openrtb2.App{InventoryPartnerDomain: "anyDomain"}, - }, - expectedRequest: openrtb2.BidRequest{ - ID: "anyID", - App: &openrtb2.App{}, - }, - }, - { - name: "2.6-202303-dropped", // integration with clear202303Fields - givenRequest: openrtb2.BidRequest{ - ID: "anyID", - Imp: []openrtb2.Imp{{ID: "1", Refresh: &openrtb2.Refresh{Count: ptrutil.ToPtr(1)}}}, - }, - expectedRequest: openrtb2.BidRequest{ - ID: "anyID", - Imp: []openrtb2.Imp{{ID: "1"}}, - }, - }, - { - name: "2.6-202309-dropped", // integration with clear202309Fields - givenRequest: openrtb2.BidRequest{ - ID: "anyID", - ACat: []string{"anyACat"}, - }, - expectedRequest: openrtb2.BidRequest{ - ID: "anyID", - }, - }, { name: "2.6-to-2.5-OtherExtFields", givenRequest: openrtb2.BidRequest{ diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go index a73cfc9241c..58527528ec6 100644 --- a/openrtb_ext/request_wrapper.go +++ b/openrtb_ext/request_wrapper.go @@ -95,6 +95,12 @@ func (rw *RequestWrapper) GetImp() []*ImpWrapper { func (rw *RequestWrapper) SetImp(imps []*ImpWrapper) { rw.impWrappers = imps + imparr := make([]openrtb2.Imp, len(imps)) + for i, iw := range imps { + imparr[i] = *iw.Imp + iw.Imp = &imparr[i] + } + rw.Imp = imparr rw.impWrappersAccessed = true } @@ -239,6 +245,7 @@ func (rw *RequestWrapper) rebuildImp() error { return err } rw.Imp[i] = *rw.impWrappers[i].Imp + rw.impWrappers[i].Imp = &rw.Imp[i] } return nil @@ -392,6 +399,8 @@ func (rw *RequestWrapper) rebuildSourceExt() error { return nil } +// Clone clones the request wrapper exts and the imp wrappers +// the cloned imp wrappers are pointing to the bid request imps func (rw *RequestWrapper) Clone() *RequestWrapper { if rw == nil { return nil @@ -414,6 +423,26 @@ func (rw *RequestWrapper) Clone() *RequestWrapper { return &clone } +func (rw *RequestWrapper) CloneAndClearImpWrappers() *RequestWrapper { + if rw == nil { + return nil + } + rw.impWrappersAccessed = false + + clone := *rw + clone.impWrappers = nil + clone.userExt = rw.userExt.Clone() + clone.deviceExt = rw.deviceExt.Clone() + clone.requestExt = rw.requestExt.Clone() + clone.appExt = rw.appExt.Clone() + clone.regExt = rw.regExt.Clone() + clone.siteExt = rw.siteExt.Clone() + clone.doohExt = rw.doohExt.Clone() + clone.sourceExt = rw.sourceExt.Clone() + + return &clone +} + // --------------------------------------------------------------- // UserExt provides an interface for request.user.ext // --------------------------------------------------------------- diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go index c7892b964af..465e3b71f3a 100644 --- a/openrtb_ext/request_wrapper_test.go +++ b/openrtb_ext/request_wrapper_test.go @@ -198,6 +198,7 @@ func TestRebuildImp(t *testing.T) { request openrtb2.BidRequest requestImpWrapper []*ImpWrapper expectedRequest openrtb2.BidRequest + expectedAccessed bool expectedError string }{ { @@ -217,11 +218,13 @@ func TestRebuildImp(t *testing.T) { request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}}, requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "2"}, impExt: &ImpExt{prebid: prebid, prebidDirty: true}}}, expectedRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "2", Ext: prebidJson}}}, + expectedAccessed: true, }, { description: "One - Accessed - Error", request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}}, requestImpWrapper: []*ImpWrapper{{Imp: nil, impExt: &ImpExt{}}}, + expectedAccessed: true, expectedError: "ImpWrapper RebuildImp called on a nil Imp", }, { @@ -229,6 +232,7 @@ func TestRebuildImp(t *testing.T) { request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}, {ID: "2"}}}, requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "1"}, impExt: &ImpExt{}}, {Imp: &openrtb2.Imp{ID: "2"}, impExt: &ImpExt{prebid: prebid, prebidDirty: true}}}, expectedRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}, {ID: "2", Ext: prebidJson}}}, + expectedAccessed: true, }, } @@ -247,6 +251,20 @@ func TestRebuildImp(t *testing.T) { assert.NoError(t, err, test.description) assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) } + + if test.expectedAccessed && test.expectedError == "" { + bidRequestImps := make(map[string]*openrtb2.Imp, 0) + for i, v := range w.Imp { + bidRequestImps[v.ID] = &w.Imp[i] + } + wrapperImps := make(map[string]*openrtb2.Imp, 0) + for i, v := range w.impWrappers { + wrapperImps[v.ID] = w.impWrappers[i].Imp + } + for k := range bidRequestImps { + assert.Same(t, bidRequestImps[k], wrapperImps[k], test.description) + } + } } } @@ -1866,6 +1884,37 @@ func TestImpWrapperGetImpExt(t *testing.T) { } } +func TestImpWrapperSetImp(t *testing.T) { + origImps := []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1"}, + {ID: "imp2", TagID: "tag2"}, + {ID: "imp3", TagID: "tag3"}, + } + expectedImps := []openrtb2.Imp{ + {ID: "imp1", TagID: "tag4", BidFloor: 0.5}, + {ID: "imp1.1", TagID: "tag2", BidFloor: 0.6}, + {ID: "imp2", TagID: "notag"}, + {ID: "imp3", TagID: "tag3"}, + } + rw := RequestWrapper{BidRequest: &openrtb2.BidRequest{Imp: origImps}} + iw := rw.GetImp() + rw.Imp[0].TagID = "tag4" + rw.Imp[0].BidFloor = 0.5 + iw[1] = &ImpWrapper{Imp: &expectedImps[1]} + *iw[2] = ImpWrapper{Imp: &expectedImps[2]} + iw = append(iw, &ImpWrapper{Imp: &expectedImps[3]}) + + rw.SetImp(iw) + assert.Equal(t, expectedImps, rw.BidRequest.Imp) + iw = rw.GetImp() + // Ensure that the wrapper pointers are in sync. + for i := range rw.BidRequest.Imp { + // Assert the pointers are in sync. + assert.Same(t, &rw.Imp[i], iw[i].Imp) + } + +} + func TestImpExtTid(t *testing.T) { impExt := &ImpExt{} diff --git a/ortb/clone.go b/ortb/clone.go index 1e34795e05a..ff2d16560b8 100644 --- a/ortb/clone.go +++ b/ortb/clone.go @@ -277,3 +277,19 @@ func CloneBidRequestPartial(s *openrtb2.BidRequest) *openrtb2.BidRequest { return &c } + +func CloneRegs(s *openrtb2.Regs) *openrtb2.Regs { + if s == nil { + return nil + } + + // Shallow Copy (Value Fields) + c := *s + + // Deep Copy (Pointers) + c.GDPR = ptrutil.Clone(s.GDPR) + c.GPPSID = slices.Clone(s.GPPSID) + c.Ext = slices.Clone(s.Ext) + + return &c +} diff --git a/ortb/clone_test.go b/ortb/clone_test.go index 21c19f170c6..096f78d51b2 100644 --- a/ortb/clone_test.go +++ b/ortb/clone_test.go @@ -759,3 +759,44 @@ func discoverPointerFields(t reflect.Type) []string { } return fields } + +func TestCloneRegs(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneRegs(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.Regs{} + result := CloneRegs(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + given := &openrtb2.Regs{ + COPPA: 1, + GDPR: ptrutil.ToPtr(int8(0)), + USPrivacy: "1YNN", + GPP: "SomeGPPStrig", + GPPSID: []int8{1, 2, 3}, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneRegs(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.GDPR, result.GDPR, "gdpr") + assert.NotSame(t, given.GPPSID, result.GPPSID, "gppsid[]") + assert.NotSame(t, given.GPPSID[0], result.GPPSID[0], "gppsid[0]") + assert.NotSame(t, given.Ext, result.Ext, "ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Regs{})), + []string{ + "GDPR", + "GPPSID", + "Ext", + }) + }) +} diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go index 265d47e8595..3a0934c5722 100644 --- a/privacy/ccpa/consentwriter.go +++ b/privacy/ccpa/consentwriter.go @@ -2,7 +2,6 @@ package ccpa import ( "github.com/prebid/openrtb/v20/openrtb2" - "github.com/prebid/prebid-server/v2/openrtb_ext" ) // ConsentWriter implements the old PolicyWriter interface for CCPA. @@ -16,16 +15,14 @@ func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { if req == nil { return nil } - reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req} // Set consent string in USPrivacy if c.Consent != "" { - if regsExt, err := reqWrap.GetRegExt(); err == nil { - regsExt.SetUSPrivacy(c.Consent) - } else { - return err + if req.Regs == nil { + req.Regs = &openrtb2.Regs{} } + req.Regs.USPrivacy = c.Consent } - return reqWrap.RebuildRequest() + return nil } diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go index a92400dce53..fe3dab248ef 100644 --- a/privacy/ccpa/consentwriter_test.go +++ b/privacy/ccpa/consentwriter_test.go @@ -75,7 +75,9 @@ func TestConsentWriterLegacy(t *testing.T) { description: "Success", request: &openrtb2.BidRequest{}, expected: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + Regs: &openrtb2.Regs{ + USPrivacy: "anyConsent", + }, }, }, { @@ -83,9 +85,12 @@ func TestConsentWriterLegacy(t *testing.T) { request: &openrtb2.BidRequest{ Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, }, - expectedError: true, + expectedError: false, expected: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, + Regs: &openrtb2.Regs{ + USPrivacy: "anyConsent", + Ext: json.RawMessage(`malformed}`), + }, }, }, } diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index 0b719bf1455..463cf3391d8 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -41,15 +41,8 @@ func ReadFromRequestWrapper(req *openrtb_ext.RequestWrapper, gpp gpplib.GppConta WarningCode: errortypes.InvalidPrivacyConsentWarningCode} } - if consent == "" { - // Read consent from request.regs.ext - regsExt, err := req.GetRegExt() - if err != nil { - return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err) - } - if regsExt != nil { - consent = regsExt.GetUSPrivacy() - } + if consent == "" && req.Regs != nil { + consent = req.Regs.USPrivacy } // Read no sale bidders from request.ext.prebid reqExt, err := req.GetRequestExt() @@ -75,21 +68,19 @@ func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) { // Write mutates an OpenRTB bid request with the CCPA regulatory information. func (p Policy) Write(req *openrtb_ext.RequestWrapper) error { - if req == nil { + if req == nil || req.BidRequest == nil { return nil } - regsExt, err := req.GetRegExt() - if err != nil { - return err - } - reqExt, err := req.GetRequestExt() if err != nil { return err } - regsExt.SetUSPrivacy(p.Consent) + if req.Regs == nil { + req.Regs = &openrtb2.Regs{} + } + req.Regs.USPrivacy = p.Consent setPrebidNoSale(p.NoSaleBidders, reqExt) return nil } diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index 20d9f680ba1..c77b4ddc985 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -23,7 +23,7 @@ func TestReadFromRequestWrapper(t *testing.T) { { description: "Success", request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "ABC"}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -83,26 +83,10 @@ func TestReadFromRequestWrapper(t *testing.T) { NoSaleBidders: []string{"a", "b"}, }, }, - { - description: "Malformed Regs.Ext", - request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, - Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), - }, - expectedError: true, - }, - { - description: "Invalid Regs.Ext Type", - request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123`)}, - Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), - }, - expectedError: true, - }, { description: "Nil Ext", request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "ABC"}, Ext: nil, }, expectedPolicy: Policy{ @@ -113,7 +97,7 @@ func TestReadFromRequestWrapper(t *testing.T) { { description: "Empty Ext", request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "ABC"}, Ext: json.RawMessage(`{}`), }, expectedPolicy: Policy{ @@ -124,7 +108,7 @@ func TestReadFromRequestWrapper(t *testing.T) { { description: "Missing Ext.Prebid No Sale Value", request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "ABC"}, Ext: json.RawMessage(`{"anythingElse":"42"}`), }, expectedPolicy: Policy{ @@ -148,15 +132,6 @@ func TestReadFromRequestWrapper(t *testing.T) { }, expectedError: true, }, - { - description: "Injection Attack", - request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`)}, - }, - expectedPolicy: Policy{ - Consent: "1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", - }, - }, { description: "GPP Success", request: &openrtb2.BidRequest{ @@ -244,7 +219,7 @@ func TestReadFromRequest(t *testing.T) { { description: "Success", request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "ABC"}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -353,7 +328,7 @@ func TestWrite(t *testing.T) { policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, request: &openrtb2.BidRequest{}, expected: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "anyConsent"}, Ext: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), }, }, diff --git a/privacy/gdpr/consentwriter.go b/privacy/gdpr/consentwriter.go index 243a6cf79e9..8269352355d 100644 --- a/privacy/gdpr/consentwriter.go +++ b/privacy/gdpr/consentwriter.go @@ -2,13 +2,12 @@ package gdpr import ( "github.com/prebid/openrtb/v20/openrtb2" - "github.com/prebid/prebid-server/v2/openrtb_ext" ) // ConsentWriter implements the PolicyWriter interface for GDPR TCF. type ConsentWriter struct { - Consent string - RegExtGDPR *int8 + Consent string + GDPR *int8 } // Write mutates an OpenRTB bid request with the GDPR TCF consent. @@ -16,26 +15,19 @@ func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { if req == nil { return nil } - reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req} - if c.RegExtGDPR != nil { - if regsExt, err := reqWrap.GetRegExt(); err == nil { - regsExt.SetGDPR(c.RegExtGDPR) - } else { - return err + if c.GDPR != nil { + if req.Regs == nil { + req.Regs = &openrtb2.Regs{} } + req.Regs.GDPR = c.GDPR } if c.Consent != "" { - if userExt, err := reqWrap.GetUserExt(); err == nil { - userExt.SetConsent(&c.Consent) - } else { - return err + if req.User == nil { + req.User = &openrtb2.User{} } - } - - if err := reqWrap.RebuildRequest(); err != nil { - return err + req.User.Consent = c.Consent } return nil diff --git a/privacy/gdpr/consentwriter_test.go b/privacy/gdpr/consentwriter_test.go index 47f24bc9ecc..436f46dd563 100644 --- a/privacy/gdpr/consentwriter_test.go +++ b/privacy/gdpr/consentwriter_test.go @@ -27,14 +27,14 @@ func TestConsentWriter(t *testing.T) { consent: "anyConsent", request: &openrtb2.BidRequest{}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, + Consent: "anyConsent"}}, }, { description: "Enabled With Nil Request User Ext Object", consent: "anyConsent", request: &openrtb2.BidRequest{User: &openrtb2.User{}}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, + Consent: "anyConsent"}}, }, { description: "Enabled With Existing Request User Ext Object - Doesn't Overwrite", @@ -42,29 +42,25 @@ func TestConsentWriter(t *testing.T) { request: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"existing":"any"}`)}}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, + Consent: "anyConsent", + Ext: json.RawMessage(`{"existing":"any"}`)}}, }, { description: "Enabled With Existing Request User Ext Object - Overwrites", consent: "anyConsent", request: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"existing":"any","consent":"toBeOverwritten"}`)}}, + Consent: "toBeOverwritten", + Ext: json.RawMessage(`{"existing":"any"}`)}}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, - }, - { - description: "Enabled With Existing Malformed Request User Ext Object", - consent: "anyConsent", - request: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`malformed`)}}, - expectedError: true, + Consent: "anyConsent", + Ext: json.RawMessage(`{"existing":"any"}`)}}, }, { description: "Injection Attack With Nil Request User Object", consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", request: &openrtb2.BidRequest{}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), + Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", }}, }, { @@ -72,7 +68,8 @@ func TestConsentWriter(t *testing.T) { consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", request: &openrtb2.BidRequest{User: &openrtb2.User{}}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), + Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", + Ext: nil, }}, }, { @@ -82,7 +79,8 @@ func TestConsentWriter(t *testing.T) { Ext: json.RawMessage(`{"existing":"any"}`), }}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"","existing":"any"}`), + Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", + Ext: json.RawMessage(`{"existing":"any"}`), }}, }, } diff --git a/schain/schainwriter.go b/schain/schainwriter.go index b7ff0b52e95..141987ff4c5 100644 --- a/schain/schainwriter.go +++ b/schain/schainwriter.go @@ -3,7 +3,6 @@ package schain import ( "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/prebid/prebid-server/v2/util/jsonutil" ) // NewSChainWriter creates an ORTB 2.5 schain writer instance @@ -34,9 +33,9 @@ type SChainWriter struct { // Write selects an schain from the multi-schain ORTB 2.5 location (req.ext.prebid.schains) for the specified bidder // and copies it to the ORTB 2.5 location (req.source.ext). If no schain exists for the bidder in the multi-schain // location and no wildcard schain exists, the request is not modified. -func (w SChainWriter) Write(req *openrtb2.BidRequest, bidder string) { +func (w SChainWriter) Write(reqWrapper *openrtb_ext.RequestWrapper, bidder string) { const sChainWildCard = "*" - var selectedSChain *openrtb2.SupplyChain + var selectedSChain openrtb2.SupplyChain wildCardSChain := w.sChainsByBidder[sChainWildCard] bidderSChain := w.sChainsByBidder[bidder] @@ -46,32 +45,27 @@ func (w SChainWriter) Write(req *openrtb2.BidRequest, bidder string) { return } - selectedSChain = &openrtb2.SupplyChain{Ver: "1.0"} + selectedSChain = openrtb2.SupplyChain{Ver: "1.0"} if bidderSChain != nil { - selectedSChain = bidderSChain + selectedSChain = *bidderSChain } else if wildCardSChain != nil { - selectedSChain = wildCardSChain + selectedSChain = *wildCardSChain } - schain := openrtb_ext.ExtRequestPrebidSChain{ - SChain: *selectedSChain, - } - - if req.Source == nil { - req.Source = &openrtb2.Source{} + if reqWrapper.Source == nil { + reqWrapper.Source = &openrtb2.Source{} } else { - sourceCopy := *req.Source - req.Source = &sourceCopy + // Copy Source to avoid shared memory issues. + // Source may be modified differently for different bidders in request + sourceCopy := *reqWrapper.Source + reqWrapper.Source = &sourceCopy } - if w.hostSChainNode != nil { - schain.SChain.Nodes = append(schain.SChain.Nodes, *w.hostSChainNode) - } + reqWrapper.Source.SChain = &selectedSChain - sourceExt, err := jsonutil.Marshal(schain) - if err == nil { - req.Source.Ext = sourceExt + if w.hostSChainNode != nil { + reqWrapper.Source.SChain.Nodes = append(reqWrapper.Source.SChain.Nodes, *w.hostSChainNode) } } diff --git a/schain/schainwriter_test.go b/schain/schainwriter_test.go index d9b1358ffe6..757fc616052 100644 --- a/schain/schainwriter_test.go +++ b/schain/schainwriter_test.go @@ -17,206 +17,348 @@ func TestSChainWriter(t *testing.T) { const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}` const seller3SChain string = `"schain":{"complete":3,"nodes":[{"asi":"directseller3.com","sid":"00003","rid":"BidRequest3","hp":3}],"ver":"3.0"}` const sellerWildCardSChain string = `"schain":{"complete":1,"nodes":[{"asi":"wildcard1.com","sid":"wildcard1","rid":"WildcardReq1","hp":1}],"ver":"1.0"}` - const hostNode string = `{"asi":"pbshostcompany.com","sid":"00001","rid":"BidRequest","hp":1}` const seller1Node string = `{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}` tests := []struct { description string - giveRequest openrtb2.BidRequest + giveRequest *openrtb_ext.RequestWrapper giveBidder string giveHostSChain *openrtb2.SupplyChainNode - wantRequest openrtb2.BidRequest + wantRequest *openrtb_ext.RequestWrapper wantError bool }{ { description: "nil source, nil ext.prebid.schains and empty host schain", - giveRequest: openrtb2.BidRequest{ - Ext: nil, - Source: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: nil, + Source: nil, + }, }, + giveBidder: "appnexus", giveHostSChain: nil, - wantRequest: openrtb2.BidRequest{ - Ext: nil, - Source: nil, + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: nil, + Source: nil, + }, }, }, { - description: "Use source schain -- no bidder schain or wildcard schain in nil ext.prebid.schains", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller2SChain + `}`), + description: "Use source schain -- no bidder schain or wildcard schain in nil ext.prebid.schains - so source.schain is set and unmodified", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Ver: "1.1", + }, + }, }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller2SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Ver: "1.1", + }, + }, }, }, }, { - description: "Use source schain -- no bidder schain or wildcard schain in not nil ext.prebid.schains", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller2SChain + `}`), + description: "Use source schain -- no bidder schain or wildcard schain in not nil ext.prebid.schains - so source.schain is set and unmodified", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Ver: "1.1", + }, + Ext: json.RawMessage(`{"some":"data"}`), + }, }, }, giveBidder: "rubicon", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller2SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Ver: "1.1", + }, + Ext: json.RawMessage(`{"some":"data"}`), + }, }, }, }, { - description: "Use schain for bidder in ext.prebid.schains; ensure other ext.source field values are retained.", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - FD: openrtb2.Int8Ptr(1), - TID: "tid data", - PChain: "pchain data", - Ext: json.RawMessage(`{` + seller2SChain + `}`), + description: "Use schain for bidder in ext.prebid.schains; ensure other source field values are retained.", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: &openrtb2.Source{ + FD: openrtb2.Int8Ptr(1), + TID: "tid data", + PChain: "pchain data", + Ext: json.RawMessage(`{"some":"data"}`), + }, }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - FD: openrtb2.Int8Ptr(1), - TID: "tid data", - PChain: "pchain data", - Ext: json.RawMessage(`{` + seller1SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: &openrtb2.Source{ + FD: openrtb2.Int8Ptr(1), + TID: "tid data", + PChain: "pchain data", + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: json.RawMessage(`{"some":"data"}`), + }, }, }, }, { description: "Use schain for bidder in ext.prebid.schains, nil req.source ", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: nil, + }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller1SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, }, }, }, { description: "Use wildcard schain in ext.prebid.schains.", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), + Source: &openrtb2.Source{ + Ext: nil, + }, }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + sellerWildCardSChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "wildcard1.com", + SID: "wildcard1", + RID: "WildcardReq1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, }, }, }, { description: "Use schain for bidder in ext.prebid.schains instead of wildcard.", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), + Source: &openrtb2.Source{ + Ext: nil, + }, }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller1SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, }, }, }, { description: "Use source schain -- multiple (two) bidder schains in ext.prebid.schains.", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller3SChain + `}`), + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), + Source: &openrtb2.Source{ + Ext: json.RawMessage(`{` + seller3SChain + `}`), + }, }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller3SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), + Source: &openrtb2.Source{ + Ext: json.RawMessage(`{` + seller3SChain + `}`), + }, }, }, wantError: true, }, { description: "Schain in request, host schain defined, source.ext for bidder request should update with appended host schain", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`), - Source: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`), + Source: nil, + }, }, giveBidder: "testbidder", giveHostSChain: &openrtb2.SupplyChainNode{ ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1), }, - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[` + seller1Node + `,` + hostNode + `],"ver":"1.0"}}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + { + ASI: "pbshostcompany.com", + SID: "00001", + RID: "BidRequest", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, }, }, }, { description: "No Schain in request, host schain defined, source.ext for bidder request should have just the host schain", - giveRequest: openrtb2.BidRequest{ - Ext: nil, - Source: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: nil, + Source: nil, + }, }, giveBidder: "testbidder", giveHostSChain: &openrtb2.SupplyChainNode{ ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1), }, - wantRequest: openrtb2.BidRequest{ - Ext: nil, - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":[` + hostNode + `],"ver":"1.0"}}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: nil, + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "pbshostcompany.com", + SID: "00001", + RID: "BidRequest", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, }, }, }, } for _, tt := range tests { - // unmarshal ext to get schains object needed to initialize writer - var reqExt *openrtb_ext.ExtRequest - if tt.giveRequest.Ext != nil { - reqExt = &openrtb_ext.ExtRequest{} - err := jsonutil.UnmarshalValid(tt.giveRequest.Ext, reqExt) - if err != nil { - t.Error("Unable to unmarshal request.ext") + t.Run(tt.description, func(t *testing.T) { + // unmarshal ext to get schains object needed to initialize writer + var reqExt *openrtb_ext.ExtRequest + if tt.giveRequest.Ext != nil { + reqExt = &openrtb_ext.ExtRequest{} + err := jsonutil.UnmarshalValid(tt.giveRequest.Ext, reqExt) + if err != nil { + t.Error("Unable to unmarshal request.ext") + } } - } - writer, err := NewSChainWriter(reqExt, tt.giveHostSChain) + writer, err := NewSChainWriter(reqExt, tt.giveHostSChain) - if tt.wantError { - assert.NotNil(t, err) - assert.Nil(t, writer) - } else { - assert.Nil(t, err) - assert.NotNil(t, writer) + if tt.wantError { + assert.NotNil(t, err) + assert.Nil(t, writer) + } else { + assert.Nil(t, err) + assert.NotNil(t, writer) - writer.Write(&tt.giveRequest, tt.giveBidder) + writer.Write(tt.giveRequest, tt.giveBidder) - assert.Equal(t, tt.wantRequest, tt.giveRequest, tt.description) - } + assert.Equal(t, tt.wantRequest, tt.giveRequest, tt.description) + } + }) } } diff --git a/stored_responses/stored_responses.go b/stored_responses/stored_responses.go index 04dc3decf5a..f7fb79f1c21 100644 --- a/stored_responses/stored_responses.go +++ b/stored_responses/stored_responses.go @@ -22,22 +22,9 @@ type ImpBidderReplaceImpID map[string]map[string]bool type BidderImpReplaceImpID map[string]map[string]bool func InitStoredBidResponses(req *openrtb2.BidRequest, storedBidResponses ImpBidderStoredResp) BidderImpsWithBidResponses { - removeImpsWithStoredResponses(req, storedBidResponses) return buildStoredResp(storedBidResponses) } -// removeImpsWithStoredResponses deletes imps with stored bid resp -func removeImpsWithStoredResponses(req *openrtb2.BidRequest, storedBidResponses ImpBidderStoredResp) { - imps := req.Imp - req.Imp = nil //to indicate this bidder doesn't have real requests - for _, imp := range imps { - if _, ok := storedBidResponses[imp.ID]; !ok { - //add real imp back to request - req.Imp = append(req.Imp, imp) - } - } -} - func buildStoredResp(storedBidResponses ImpBidderStoredResp) BidderImpsWithBidResponses { // bidder -> imp id -> stored bid resp bidderToImpToResponses := BidderImpsWithBidResponses{} diff --git a/stored_responses/stored_responses_test.go b/stored_responses/stored_responses_test.go index 58bba22aae8..626d1433faf 100644 --- a/stored_responses/stored_responses_test.go +++ b/stored_responses/stored_responses_test.go @@ -11,72 +11,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRemoveImpsWithStoredResponses(t *testing.T) { - bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`) - testCases := []struct { - description string - reqIn *openrtb2.BidRequest - storedBidResponses ImpBidderStoredResp - expectedImps []openrtb2.Imp - }{ - { - description: "request with imps and stored bid response for this imp", - reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ - {ID: "imp-id1"}, - }}, - storedBidResponses: ImpBidderStoredResp{ - "imp-id1": {"appnexus": bidRespId1}, - }, - expectedImps: nil, - }, - { - description: "request with imps and stored bid response for one of these imp", - reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ - {ID: "imp-id1"}, - {ID: "imp-id2"}, - }}, - storedBidResponses: ImpBidderStoredResp{ - "imp-id1": {"appnexus": bidRespId1}, - }, - expectedImps: []openrtb2.Imp{ - { - ID: "imp-id2", - }, - }, - }, - { - description: "request with imps and stored bid response for both of these imp", - reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ - {ID: "imp-id1"}, - {ID: "imp-id2"}, - }}, - storedBidResponses: ImpBidderStoredResp{ - "imp-id1": {"appnexus": bidRespId1}, - "imp-id2": {"appnexus": bidRespId1}, - }, - expectedImps: nil, - }, - { - description: "request with imps and no stored bid responses", - reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ - {ID: "imp-id1"}, - {ID: "imp-id2"}, - }}, - storedBidResponses: nil, - - expectedImps: []openrtb2.Imp{ - {ID: "imp-id1"}, - {ID: "imp-id2"}, - }, - }, - } - for _, testCase := range testCases { - request := testCase.reqIn - removeImpsWithStoredResponses(request, testCase.storedBidResponses) - assert.Equal(t, testCase.expectedImps, request.Imp, "incorrect Impressions for testCase %s", testCase.description) - } -} - func TestBuildStoredBidResponses(t *testing.T) { bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`) bidRespId2 := json.RawMessage(`{"id": "resp_id2"}`)