From 686921136188dfda55b357455cc2b04822ae0fe9 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Mon, 20 Nov 2023 13:13:43 -0500 Subject: [PATCH] Adapter Name Case Insensitive: dealTier (#3218) --- .../exemplary/all-ext-case-insensitive.json | 6 +- exchange/exchange.go | 11 +- exchange/exchange_test.go | 234 ++++++++++++++---- openrtb_ext/deal_tier.go | 4 +- openrtb_ext/deal_tier_test.go | 15 ++ 5 files changed, 214 insertions(+), 56 deletions(-) diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json index 20997076af2..67633ab8e1a 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json @@ -51,7 +51,7 @@ ] }, "ext": { - "appnexus": { + "APPnexus": { "placementId": 12883451 }, "districtm": { @@ -109,7 +109,7 @@ "price": 1.01 } ], - "seat": "appnexus" + "seat": "APPnexus" }, { "bid": [ @@ -137,4 +137,4 @@ "nbr": 0 }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/exchange/exchange.go b/exchange/exchange.go index b1818ee2443..211d82fc79c 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -577,15 +577,18 @@ func applyDealSupport(bidRequest *openrtb2.BidRequest, auc *auction, bidCategory for impID, topBidsPerImp := range auc.winningBidsByBidder { impDeal := impDealMap[impID] for bidder, topBidsPerBidder := range topBidsPerImp { - maxBid := bidsToUpdate(multiBid, bidder.String()) - + bidderNormalized, bidderFound := openrtb_ext.NormalizeBidderName(bidder.String()) + if !bidderFound { + continue + } + maxBid := bidsToUpdate(multiBid, bidderNormalized.String()) for i, topBid := range topBidsPerBidder { if i == maxBid { break } if topBid.DealPriority > 0 { - if validateDealTier(impDeal[bidder]) { - updateHbPbCatDur(topBid, impDeal[bidder], bidCategory) + if validateDealTier(impDeal[bidderNormalized]) { + updateHbPbCatDur(topBid, impDeal[bidderNormalized], bidCategory) } else { errs = append(errs, fmt.Errorf("dealTier configuration invalid for bidder '%s', imp ID '%s'", string(bidder), impID)) } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 6567a727010..defe94027a7 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -3390,92 +3390,168 @@ func TestUpdateRejections(t *testing.T) { } func TestApplyDealSupport(t *testing.T) { + type testInput struct { + dealPriority int + impExt json.RawMessage + targ map[string]string + bidderName openrtb_ext.BidderName + } + + type testOutput struct { + hbPbCatDur string + dealErr string + dealTierSatisfied bool + } + testCases := []struct { - description string - dealPriority int - impExt json.RawMessage - targ map[string]string - expectedHbPbCatDur string - expectedDealErr string - expectedDealTierSatisfied bool + description string + in testInput + expected testOutput }{ { - description: "hb_pb_cat_dur should be modified", - dealPriority: 5, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_movies_30s", + description: "hb_pb_cat_dur should be modified", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_movies_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "tier5_movies_30s", + dealErr: "", + dealTierSatisfied: true, }, - expectedHbPbCatDur: "tier5_movies_30s", - expectedDealErr: "", - expectedDealTierSatisfied: true, }, { - description: "hb_pb_cat_dur should not be modified due to priority not exceeding min", - dealPriority: 9, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_medicine_30s", + description: "hb_pb_cat_dur should be modified even with a mixed case bidder in the impExt", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"APPnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_movies_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "tier5_movies_30s", + dealErr: "", + dealTierSatisfied: true, }, - expectedHbPbCatDur: "12.00_medicine_30s", - expectedDealErr: "", - expectedDealTierSatisfied: false, }, { - description: "hb_pb_cat_dur should not be modified due to invalid config", - dealPriority: 5, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_games_30s", + description: "hb_pb_cat_dur should be modified even with a mixed case bidder in the winningBidsByBidder map", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_movies_30s", + }, + bidderName: openrtb_ext.BidderName("APPnexus"), + }, + expected: testOutput{ + hbPbCatDur: "tier5_movies_30s", + dealErr: "", + dealTierSatisfied: true, }, - expectedHbPbCatDur: "12.00_games_30s", - expectedDealErr: "dealTier configuration invalid for bidder 'appnexus', imp ID 'imp_id1'", - expectedDealTierSatisfied: false, }, { - description: "hb_pb_cat_dur should not be modified due to deal priority of 0", - dealPriority: 0, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_auto_30s", + description: "hb_pb_cat_dur should not be modified due to unknown bidder in the winningBidsByBidder map", + in: testInput{ + dealPriority: 9, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_medicine_30s", + }, + bidderName: openrtb_ext.BidderName("unknown"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_medicine_30s", + dealErr: "", + dealTierSatisfied: false, + }, + }, + { + description: "hb_pb_cat_dur should not be modified due to priority not exceeding min", + in: testInput{ + dealPriority: 9, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_medicine_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_medicine_30s", + dealErr: "", + dealTierSatisfied: false, + }, + }, + { + description: "hb_pb_cat_dur should not be modified due to invalid config", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_games_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_games_30s", + dealErr: "dealTier configuration invalid for bidder 'appnexus', imp ID 'imp_id1'", + dealTierSatisfied: false, + }, + }, + { + description: "hb_pb_cat_dur should not be modified due to deal priority of 0", + in: testInput{ + dealPriority: 0, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_auto_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_auto_30s", + dealErr: "", + dealTierSatisfied: false, }, - expectedHbPbCatDur: "12.00_auto_30s", - expectedDealErr: "", - expectedDealTierSatisfied: false, }, } - bidderName := openrtb_ext.BidderName("appnexus") for _, test := range testCases { bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", Imp: []openrtb2.Imp{ { ID: "imp_id1", - Ext: test.impExt, + Ext: test.in.impExt, }, }, } - bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.dealPriority, false, "", 0, "USD", ""} + bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.in.dealPriority, false, "", 0, "USD", ""} bidCategory := map[string]string{ - bid.Bid.ID: test.targ["hb_pb_cat_dur"], + bid.Bid.ID: test.in.targ["hb_pb_cat_dur"], } auc := &auction{ winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp_id1": { - bidderName: {&bid}, + test.in.bidderName: {&bid}, }, }, } dealErrs := applyDealSupport(bidRequest, auc, bidCategory, nil) - assert.Equal(t, test.expectedHbPbCatDur, bidCategory[auc.winningBidsByBidder["imp_id1"][bidderName][0].Bid.ID], test.description) - assert.Equal(t, test.expectedDealTierSatisfied, auc.winningBidsByBidder["imp_id1"][bidderName][0].DealTierSatisfied, "expectedDealTierSatisfied=%v when %v", test.expectedDealTierSatisfied, test.description) - if len(test.expectedDealErr) > 0 { - assert.Containsf(t, dealErrs, errors.New(test.expectedDealErr), "Expected error message not found in deal errors") + assert.Equal(t, test.expected.hbPbCatDur, bidCategory[auc.winningBidsByBidder["imp_id1"][test.in.bidderName][0].Bid.ID], test.description) + assert.Equal(t, test.expected.dealTierSatisfied, auc.winningBidsByBidder["imp_id1"][test.in.bidderName][0].DealTierSatisfied, "expected.dealTierSatisfied=%v when %v", test.expected.dealTierSatisfied, test.description) + if len(test.expected.dealErr) > 0 { + assert.Containsf(t, dealErrs, errors.New(test.expected.dealErr), "Expected error message not found in deal errors") } } } @@ -5733,3 +5809,65 @@ func TestBuildMultiBidMap(t *testing.T) { } } } + +func TestBidsToUpdate(t *testing.T) { + type testInput struct { + multiBid map[string]openrtb_ext.ExtMultiBid + bidder string + } + testCases := []struct { + desc string + in testInput + expected int + }{ + { + desc: "Empty multibid map. Expect openrtb_ext.DefaultBidLimit", + in: testInput{}, + expected: openrtb_ext.DefaultBidLimit, + }, + { + desc: "Empty bidder. Expect openrtb_ext.DefaultBidLimit", + in: testInput{ + multiBid: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidder: "appnexus", + MaxBids: ptrutil.ToPtr(2), + }, + }, + }, + expected: openrtb_ext.DefaultBidLimit, + }, + { + desc: "bidder finds a match in multibid map but TargetBidderCodePrefix is empty. Expect openrtb_ext.DefaultBidLimit", + in: testInput{ + multiBid: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidder: "appnexus", + MaxBids: ptrutil.ToPtr(2), + }, + }, + bidder: "appnexus", + }, + expected: openrtb_ext.DefaultBidLimit, + }, + { + desc: "multibid element with non-empty TargetBidderCodePrefix matches bidder. Expect MaxBids value", + in: testInput{ + multiBid: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidder: "appnexus", + MaxBids: ptrutil.ToPtr(2), + TargetBidderCodePrefix: "aPrefix", + }, + }, + bidder: "appnexus", + }, + expected: 2, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + assert.Equal(t, tc.expected, bidsToUpdate(tc.in.multiBid, tc.in.bidder), tc.desc) + }) + } +} diff --git a/openrtb_ext/deal_tier.go b/openrtb_ext/deal_tier.go index b1d8ee11bc2..bd8d681dbb4 100644 --- a/openrtb_ext/deal_tier.go +++ b/openrtb_ext/deal_tier.go @@ -38,7 +38,9 @@ func ReadDealTiersFromImp(imp openrtb2.Imp) (DealTierBidderMap, error) { } for bidder, param := range impPrebidExt.Prebid.Bidders { if param.DealTier != nil { - dealTiers[BidderName(bidder)] = *param.DealTier + if bidderNormalized, bidderFound := NormalizeBidderName(bidder); bidderFound { + dealTiers[bidderNormalized] = *param.DealTier + } } } diff --git a/openrtb_ext/deal_tier_test.go b/openrtb_ext/deal_tier_test.go index b8607748ca9..22bf5d1890c 100644 --- a/openrtb_ext/deal_tier_test.go +++ b/openrtb_ext/deal_tier_test.go @@ -56,6 +56,16 @@ func TestReadDealTiersFromImp(t *testing.T) { impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}}}`), expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "anyPrefix", MinDealTier: 5}}, }, + { + description: "imp.ext.prebid.bidder - one but it's not found in the Adapter Bidder list", + impExt: json.RawMessage(`{"prebid": {"bidder": {"unknown": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}}}`), + expectedResult: DealTierBidderMap{}, + }, + { + description: "imp.ext.prebid.bidder - one but case is different from the Adapter Bidder list", + impExt: json.RawMessage(`{"prebid": {"bidder": {"APpNExUS": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}}}`), + expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "anyPrefix", MinDealTier: 5}}, + }, { description: "imp.ext.prebid.bidder - one with other params", impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}, "supportdeals": true}, "tid": "1234"}`), @@ -66,6 +76,11 @@ func TestReadDealTiersFromImp(t *testing.T) { impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "appnexusPrefix"}, "placementId": 12345}, "rubicon": {"dealTier": {"minDealTier": 8, "prefix": "rubiconPrefix"}, "placementId": 12345}}}}`), expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "appnexusPrefix", MinDealTier: 5}, BidderRubicon: {Prefix: "rubiconPrefix", MinDealTier: 8}}, }, + { + description: "imp.ext.prebid.bidder - same bidder listed twice but with different case the last one prevails", + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 100, "prefix": "appnexusPrefix"}, "placementId": 12345},"APpNExUS": {"dealTier": {"minDealTier": 5, "prefix": "APpNExUSPrefix"}, "placementId": 12345}}}}`), + expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "APpNExUSPrefix", MinDealTier: 5}}, + }, { description: "imp.ext.prebid.bidder - one without deal tier", impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"placementId": 12345}}}}`),