diff --git a/exchange/exchange.go b/exchange/exchange.go index 0810ad814ab..a8c76a5ebb1 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -515,10 +515,14 @@ func buildMultiBidMap(prebid *openrtb_ext.ExtRequestPrebid) map[string]openrtb_e multiBidMap := make(map[string]openrtb_ext.ExtMultiBid) for _, multiBid := range prebid.MultiBid { if multiBid.Bidder != "" { - multiBidMap[multiBid.Bidder] = *multiBid + if bidderNormalized, bidderFound := openrtb_ext.NormalizeBidderName(multiBid.Bidder); bidderFound { + multiBidMap[string(bidderNormalized)] = *multiBid + } } else { for _, bidder := range multiBid.Bidders { - multiBidMap[bidder] = *multiBid + if bidderNormalized, bidderFound := openrtb_ext.NormalizeBidderName(bidder); bidderFound { + multiBidMap[string(bidderNormalized)] = *multiBid + } } } } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 6e6e8afe76e..e57900cdfee 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2259,8 +2259,13 @@ func TestTimeoutComputation(t *testing.T) { func TestExchangeJSON(t *testing.T) { if specFiles, err := os.ReadDir("./exchangetest"); err == nil { for _, specFile := range specFiles { + if !strings.HasSuffix(specFile.Name(), ".json") { + continue + } + fileName := "./exchangetest/" + specFile.Name() fileDisplayName := "exchange/exchangetest/" + specFile.Name() + t.Run(fileDisplayName, func(t *testing.T) { specData, err := loadFile(fileName) if assert.NoError(t, err, "Failed to load contents of file %s: %v", fileDisplayName, err) { @@ -5750,3 +5755,195 @@ func TestSetSeatNonBid(t *testing.T) { }) } } + +func TestBuildMultiBidMap(t *testing.T) { + type testCase struct { + desc string + inPrebid *openrtb_ext.ExtRequestPrebid + expected map[string]openrtb_ext.ExtMultiBid + } + testGroups := []struct { + groupDesc string + tests []testCase + }{ + { + groupDesc: "Nil or empty tests", + tests: []testCase{ + { + desc: "prebid nil, expect nil map", + inPrebid: nil, + expected: nil, + }, + { + desc: "prebid.MultiBid nil, expect nil map", + inPrebid: &openrtb_ext.ExtRequestPrebid{}, + expected: nil, + }, + { + desc: "not-nil prebid.MultiBid is empty, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{}, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + }, + }, + { + groupDesc: "prebid.MultiBid.Bidder tests", + tests: []testCase{ + { + desc: "Lowercase prebid.MultiBid.Bidder is found in the BidderName list, entry is mapped", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "appnexus"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": {Bidder: "appnexus"}, + }, + }, + { + desc: "Uppercase prebid.MultiBid.Bidder is found in the BidderName list, entry is mapped", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "APPNEXUS"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": {Bidder: "APPNEXUS"}, + }, + }, + { + desc: "Lowercase prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "unknown"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Mixed-case prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "UnknownBidder"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Different-cased prebid.MultiBid.Bidder entries that refer to the same adapter are found in the BidderName list are mapped once", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "AppNexus"}, + {Bidder: "appnexus"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": {Bidder: "appnexus"}, + }, + }, + }, + }, + { + groupDesc: "prebid.MultiBid.Bidders tests", + tests: []testCase{ + { + desc: "Lowercase prebid.MultiBid.Bidder is found in the BidderName list, entry is mapped", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"appnexus"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidders: []string{"appnexus"}, + }, + }, + }, + { + desc: "Lowercase prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"unknown"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Mixed-case prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"UnknownBidder"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Different-cased prebid.MultiBid.Bidder entries that refer to the same adapter are found in the BidderName list are mapped once", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"AppNexus", "appnexus", "UnknownBidder"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidders: []string{"AppNexus", "appnexus", "UnknownBidder"}, + }, + }, + }, + }, + }, + { + groupDesc: "prebid.MultiBid.Bidder and prebid.MultiBid.Bidders entries in tests", + tests: []testCase{ + { + desc: "prebid.MultiBid.Bidder found, ignore entries in prebid.MultiBid.Bidders, even if its unknown", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + { + Bidder: "UnknownBidder", + Bidders: []string{"appnexus", "rubicon", "pubmatic"}, + }, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "prebid.MultiBid.Bidder found in one entry, prebid.MultiBid.Bidders in another. Add all to map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + { + Bidder: "pubmatic", + Bidders: []string{"appnexus", "rubicon", "UnknownBidder"}, + }, + { + Bidders: []string{"UnknownBidder", "appnexus", "rubicon"}, + }, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "pubmatic": { + Bidder: "pubmatic", + Bidders: []string{"appnexus", "rubicon", "UnknownBidder"}, + }, + "appnexus": { + Bidders: []string{"UnknownBidder", "appnexus", "rubicon"}, + }, + "rubicon": { + Bidders: []string{"UnknownBidder", "appnexus", "rubicon"}, + }, + }, + }, + }, + }, + } + for _, group := range testGroups { + for _, tc := range group.tests { + t.Run(group.groupDesc+tc.desc, func(t *testing.T) { + multiBidMap := buildMultiBidMap(tc.inPrebid) + assert.Equal(t, tc.expected, multiBidMap, tc.desc) + }) + } + } +} diff --git a/exchange/exchangetest/multi-bids-mixed-case.json b/exchange/exchangetest/multi-bids-mixed-case.json new file mode 100644 index 00000000000..44630b3068c --- /dev/null +++ b/exchange/exchangetest/multi-bids-mixed-case.json @@ -0,0 +1,449 @@ +{ + "description": "incoming req.ext.prebid.multibid comes with a mixed case bidder name. Expect the same bidder name casing in the outgoing ext.prebid.multibid field", + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": "5890" + }, + "appnexus": { + "placementId": 1 + } + } + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": "5890" + }, + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": true, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + } + }, + "multibid": [ + { + "bidder": "PUBmatic", + "maxbids": 2, + "targetbiddercodeprefix": "pubm" + }, + { + "bidders": [ + "appnexus", + "someBidder" + ], + "maxbids": 2 + } + ] + } + } + } + }, + "outgoingRequests": { + "pubmatic": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "5890" + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "5890" + } + } + } + ], + "ext": { + "prebid": { + "multibid": [ + { + "bidder": "PUBmatic", + "maxbids": 2, + "targetbiddercodeprefix": "pubm" + } + ] + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + }, + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + }, + { + "ortbBid": { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + }, + { + "ortbBid": { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + } + ], + "seat": "pubmatic" + } + ] + } + }, + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "multibid": [ + { + "bidders": [ + "appnexus" + ], + "maxbids": 2 + } + ] + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-1" + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + }, + { + "ortbBid": { + "id": "apn-bid-2", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-2" + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "pubmatic", + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.71, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder": "pubmatic", + "hb_bidder_pubmatic": "pubmatic", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_pubmat": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_pubmat": "/pbcache/endpoint", + "hb_pb": "0.70", + "hb_pb_pubmatic": "0.70", + "hb_size": "200x250", + "hb_size_pubmatic": "200x250" + }, + "targetbiddercode": "pubmatic" + } + } + }, + { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.21, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video" + } + } + }, + { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3", + "ext": { + "origbidcpm": 0.61, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder": "pubmatic", + "hb_bidder_pubmatic": "pubmatic", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_pubmat": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_pubmat": "/pbcache/endpoint", + "hb_pb": "0.60", + "hb_pb_pubmatic": "0.60", + "hb_size": "300x500", + "hb_size_pubmatic": "300x500" + }, + "targetbiddercode": "pubmatic" + } + } + }, + { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4", + "ext": { + "origbidcpm": 0.51, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder_pubm2": "pubm2", + "hb_cache_host_pubm2": "www.pbcserver.com", + "hb_cache_path_pubm2": "/pbcache/endpoint", + "hb_pb_pubm2": "0.50", + "hb_size_pubm2": "200x250" + }, + "targetbiddercode": "pubm2" + } + } + } + ] + }, + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "targetbiddercode": "appnexus", + "type": "banner", + "targeting": { + "hb_bidder_appnexus": "appnexus", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb_appnexus": "0.20", + "hb_size_appnexus": "200x500" + } + } + } + }, + { + "id": "apn-bid-2", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-2", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/utils.go b/exchange/utils.go index a67a87b9c97..70988fbf7b9 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -472,12 +472,12 @@ func buildRequestExtMultiBid(adapter string, reqMultiBid []*openrtb_ext.ExtMulti adapterMultiBid := make([]*openrtb_ext.ExtMultiBid, 0) for _, multiBid := range reqMultiBid { if multiBid.Bidder != "" { - if multiBid.Bidder == adapter || isBidderInExtAlternateBidderCodes(adapter, multiBid.Bidder, adapterABC) { + if strings.ToLower(multiBid.Bidder) == adapter || isBidderInExtAlternateBidderCodes(adapter, strings.ToLower(multiBid.Bidder), adapterABC) { adapterMultiBid = append(adapterMultiBid, multiBid) } } else { for _, bidder := range multiBid.Bidders { - if bidder == adapter || isBidderInExtAlternateBidderCodes(adapter, bidder, adapterABC) { + if strings.ToLower(bidder) == adapter || isBidderInExtAlternateBidderCodes(adapter, strings.ToLower(bidder), adapterABC) { adapterMultiBid = append(adapterMultiBid, &openrtb_ext.ExtMultiBid{ Bidders: []string{bidder}, MaxBids: multiBid.MaxBids,