From 163503183d98c0ed4d21fa573ef7b3f9aa08109b Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Tue, 28 Mar 2023 17:59:51 -0700 Subject: [PATCH 01/27] Initial major implementation, no tests --- config/accounts.go | 33 +++---- endpoints/openrtb2/auction.go | 132 ++++++++++++++++++++++++++ errortypes/code.go | 1 + exchange/bidder.go | 31 +++++- exchange/bidder_test.go | 39 +++++--- exchange/bidder_validate_bids.go | 4 +- exchange/bidder_validate_bids_test.go | 10 +- exchange/exchange.go | 8 +- exchange/exchange_test.go | 6 +- openrtb_ext/request.go | 87 +++++++++++++++++ 10 files changed, 305 insertions(+), 46 deletions(-) diff --git a/config/accounts.go b/config/accounts.go index 2a9deb49be5..ed8bd16ebfc 100644 --- a/config/accounts.go +++ b/config/accounts.go @@ -22,22 +22,23 @@ const ( // Account represents a publisher account configuration type Account struct { - ID string `mapstructure:"id" json:"id"` - Disabled bool `mapstructure:"disabled" json:"disabled"` - CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"` - EventsEnabled bool `mapstructure:"events_enabled" json:"events_enabled"` - CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"` - GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"` - DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"` - DefaultIntegration string `mapstructure:"default_integration" json:"default_integration"` - CookieSync CookieSync `mapstructure:"cookie_sync" json:"cookie_sync"` - Events Events `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725 - TruncateTargetAttribute *int `mapstructure:"truncate_target_attr" json:"truncate_target_attr"` - AlternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes `mapstructure:"alternatebiddercodes" json:"alternatebiddercodes"` - Hooks AccountHooks `mapstructure:"hooks" json:"hooks"` - PriceFloors AccountPriceFloors `mapstructure:"price_floors" json:"price_floors"` - Validations Validations `mapstructure:"validations" json:"validations"` - DefaultBidLimit int `mapstructure:"default_bid_limit" json:"default_bid_limit"` + ID string `mapstructure:"id" json:"id"` + Disabled bool `mapstructure:"disabled" json:"disabled"` + CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"` + EventsEnabled bool `mapstructure:"events_enabled" json:"events_enabled"` + CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"` + GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"` + DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"` + DefaultIntegration string `mapstructure:"default_integration" json:"default_integration"` + CookieSync CookieSync `mapstructure:"cookie_sync" json:"cookie_sync"` + Events Events `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725 + TruncateTargetAttribute *int `mapstructure:"truncate_target_attr" json:"truncate_target_attr"` + AlternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes `mapstructure:"alternatebiddercodes" json:"alternatebiddercodes"` + Hooks AccountHooks `mapstructure:"hooks" json:"hooks"` + PriceFloors AccountPriceFloors `mapstructure:"price_floors" json:"price_floors"` + Validations Validations `mapstructure:"validations" json:"validations"` + DefaultBidLimit int `mapstructure:"default_bid_limit" json:"default_bid_limit"` + BidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments `mapstructure:"bidadjustments" json:"bidadjustments"` } // CookieSync represents the account-level defaults for the cookie sync endpoint. diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 13b27898564..39b859f538a 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "math" "net/http" "net/url" "regexp" @@ -437,6 +438,12 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return } + // Merge Host and Account Bid Adjustment Info + if err := mergeBidAdjustments(req, account.BidAdjustments); err != nil { + errs = []error{err} + return + } + // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). deps.setFieldsImplicitly(httpRequest, req) @@ -855,6 +862,64 @@ func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[str return nil } +func validateBidAdjustments(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) bool { + if bidAdjustments.MediaType.Banner != nil { + if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Banner); !valid { + return false + } + } + if bidAdjustments.MediaType.Video != nil { + if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Video); !valid { + return false + } + } + if bidAdjustments.MediaType.Audio != nil { + if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Audio); !valid { + return false + } + } + if bidAdjustments.MediaType.Native != nil { + if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Native); !valid { + return false + } + } + return true +} + +func validateBidAdjustmentsHelper(bidAdjMap map[string]map[string][]openrtb_ext.Adjustments) bool { + for bidderName, _ := range bidAdjMap { + for dealId, _ := range bidAdjMap[bidderName] { + for _, adjustment := range bidAdjMap[bidderName][dealId] { + if !validateAdjustment(adjustment) { + return false + } + } + } + } + return true +} + +func validateAdjustment(adjustment openrtb_ext.Adjustments) bool { + switch adjustment.AdjType { + case openrtb_ext.AdjTypeCpm: + if adjustment.Currency == "" || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { + return false + } + case openrtb_ext.AdjTypeMultiplier: + if adjustment.Value < 0 || adjustment.Value > 100 { + return false + } + adjustment.Currency = "" + case openrtb_ext.AdjTypeStatic: + if adjustment.Currency == "" || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { + return false + } + default: + return false + } + return true +} + func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error { _, err := schain.BidderToPrebidSChains(sChains) return err @@ -1535,6 +1600,17 @@ func validateRequestExt(req *openrtb_ext.RequestWrapper) []error { reqExt.SetPrebid(prebid) } + if valid := validateBidAdjustments(prebid.BidAdjustments); !valid { + prebid.BidAdjustments = nil + reqExt.SetPrebid(prebid) + if prebid.Debug { + errs = append(errs, &errortypes.Warning{ + WarningCode: errortypes.BidAdjustmentWarningCode, + Message: "Bid Adjustment Was Invalid", + }) + } + } + return errs } @@ -2280,3 +2356,59 @@ func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage func generateStoredBidResponseValidationError(impID string) error { return fmt.Errorf("request validation failed. Stored bid responses are specified for imp %s. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse", impID) } + +func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, account *openrtb_ext.ExtRequestPrebidBidAdjustments) error { + reqExt, err := req.GetRequestExt() + if err != nil { + return err + } + host := reqExt.GetPrebid() + + if host == nil && account != nil { + host.BidAdjustments = account + reqExt.SetPrebid(host) + } + + if host.BidAdjustments.MediaType.Banner != nil && account.MediaType.Banner != nil { + host.BidAdjustments.MediaType.Banner = mergeBidAdjustmentsHelper(host.BidAdjustments.MediaType.Banner, account.MediaType.Banner) + } else if account.MediaType.Banner != nil { + host.BidAdjustments.MediaType.Banner = account.MediaType.Banner + } + + if host.BidAdjustments.MediaType.Video != nil && account.MediaType.Video != nil { + host.BidAdjustments.MediaType.Video = mergeBidAdjustmentsHelper(host.BidAdjustments.MediaType.Video, account.MediaType.Video) + } else if account.MediaType.Video != nil { + host.BidAdjustments.MediaType.Video = account.MediaType.Video + } + + if host.BidAdjustments.MediaType.Native != nil && account.MediaType.Native != nil { + host.BidAdjustments.MediaType.Native = mergeBidAdjustmentsHelper(host.BidAdjustments.MediaType.Native, account.MediaType.Native) + } else if account.MediaType.Native != nil { + host.BidAdjustments.MediaType.Native = account.MediaType.Native + } + + if host.BidAdjustments.MediaType.Audio != nil && account.MediaType.Audio != nil { + host.BidAdjustments.MediaType.Audio = mergeBidAdjustmentsHelper(host.BidAdjustments.MediaType.Audio, account.MediaType.Audio) + } else if account.MediaType.Audio != nil { + host.BidAdjustments.MediaType.Audio = account.MediaType.Audio + } + + reqExt.SetPrebid(host) + return nil +} + +// TODO: Better function name? +func mergeBidAdjustmentsHelper(hostAdjMap map[string]map[string][]openrtb_ext.Adjustments, accountAdjMap map[string]map[string][]openrtb_ext.Adjustments) map[string]map[string][]openrtb_ext.Adjustments { + for bidderName, dealIdToAdjustmentsMap := range accountAdjMap { + if _, ok := hostAdjMap[bidderName]; ok { + for dealID, acctAdjustmentsArray := range accountAdjMap[bidderName] { + if _, okay := hostAdjMap[bidderName][dealID]; !okay { + hostAdjMap[bidderName][dealID] = acctAdjustmentsArray + } + } + } else { + hostAdjMap[bidderName] = dealIdToAdjustmentsMap + } + } + return hostAdjMap +} diff --git a/errortypes/code.go b/errortypes/code.go index e796a05dfc5..6db2e13ddd5 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -25,6 +25,7 @@ const ( DisabledCurrencyConversionWarningCode AlternateBidderCodeWarningCode MultiBidWarningCode + BidAdjustmentWarningCode ) // Coder provides an error or warning code with severity. diff --git a/exchange/bidder.go b/exchange/bidder.go index 125a17149bc..5d4fa31896f 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -56,7 +56,7 @@ type AdaptedBidder interface { // // Any errors will be user-facing in the API. // Error messages should help publishers understand what might account for "bad" bids. - requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor) ([]*entities.PbsOrtbSeatBid, []error) + requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) ([]*entities.PbsOrtbSeatBid, []error) } // bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters @@ -115,7 +115,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) ([]*entities.PbsOrtbSeatBid, []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, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) ([]*entities.PbsOrtbSeatBid, []error) { reject := hookExecutor.ExecuteBidderRequestStage(bidderRequest.BidRequest, string(bidderRequest.BidderName)) if reject != nil { return nil, []error{reject} @@ -331,10 +331,13 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde adjustmentFactor = givenAdjustment } + adjArray := bidAdjustments.GetAdjustmentArray(bidResponse.Bids[i].BidType, bidderRequest.BidderName, bidResponse.Bids[i].Bid.DealID) + originalBidCpm := 0.0 if bidResponse.Bids[i].Bid != nil { originalBidCpm = bidResponse.Bids[i].Bid.Price bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate + bidResponse.Bids[i].Bid.Price = applyAdjustmentArray(adjArray, bidResponse.Bids[i].Bid.Price, bidResponse.Currency, reqInfo) } if _, ok := seatBidMap[bidderName]; !ok { @@ -680,3 +683,27 @@ func compressToGZIP(requestBody []byte) []byte { w.Close() return b.Bytes() } + +// TODO: If adjarray == nil +func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) float64 { + originalBidPrice := bidPrice + + for _, adjustment := range adjArray { + if adjustment.AdjType == openrtb_ext.AdjTypeMultiplier { + bidPrice = bidPrice * adjustment.Value + } else if adjustment.AdjType == openrtb_ext.AdjTypeCpm { + convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, adjustment.Currency) + if err != nil { + return originalBidPrice + } + bidPrice = bidPrice - convertedVal + } else if adjustment.AdjType == openrtb_ext.AdjTypeStatic { + convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, adjustment.Currency) + if err != nil { + return originalBidPrice + } + bidPrice = convertedVal + } + } + return bidPrice +} diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 32057c50314..13631edf248 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -108,7 +108,7 @@ func TestSingleBidder(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Len(t, seatBids, 1) seatBid := seatBids[0] @@ -230,7 +230,7 @@ func TestSingleBidderGzip(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Len(t, seatBids, 1) seatBid := seatBids[0] @@ -330,7 +330,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) expectedHttpCalls := []*openrtb_ext.ExtHttpCall{ { @@ -382,7 +382,7 @@ func TestSetGPCHeader(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -432,7 +432,7 @@ func TestSetGPCHeaderNil(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -502,7 +502,7 @@ func TestMultiBidder(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) if len(seatBids) != 1 { t.Fatalf("SeatBid should exist, because bids exist.") @@ -883,6 +883,7 @@ func TestMultiCurrencies(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, + &openrtb_ext.ExtRequestPrebidBidAdjustments{}, ) assert.Len(t, seatBids, 1) seatBid := seatBids[0] @@ -1042,6 +1043,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, + &openrtb_ext.ExtRequestPrebidBidAdjustments{}, ) assert.Len(t, seatBids, 1) seatBid := seatBids[0] @@ -1220,6 +1222,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, + &openrtb_ext.ExtRequestPrebidBidAdjustments{}, ) assert.Len(t, seatBids, 1) seatBid := seatBids[0] @@ -1537,6 +1540,7 @@ func TestMobileNativeTypes(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, + &openrtb_ext.ExtRequestPrebidBidAdjustments{}, ) assert.Len(t, seatBids, 1) @@ -1656,6 +1660,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, + &openrtb_ext.ExtRequestPrebidBidAdjustments{}, ) assert.Len(t, seatBids, 1) @@ -1770,6 +1775,7 @@ func TestFledge(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, + &openrtb_ext.ExtRequestPrebidBidAdjustments{}, ) assert.Len(t, seatBids, 1) assert.NotNil(t, seatBids[0].FledgeAuctionConfigs) @@ -1793,7 +1799,7 @@ func TestErrorReporting(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - bids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + bids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") } @@ -2025,7 +2031,7 @@ func TestCallRecordAdapterConnections(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - _, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + _, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) @@ -2268,7 +2274,7 @@ func TestRequestBidsWithAdsCertsSigner(t *testing.T) { addCallSignHeader: true, bidAdjustments: bidAdjustments, } - _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Empty(t, errs, "no errors should be returned") } @@ -2492,7 +2498,8 @@ func TestExtraBid(t *testing.T) { }, }, }, - &hookexecution.EmptyHookExecutor{}) + &hookexecution.EmptyHookExecutor{}, + &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Nil(t, errs) assert.Len(t, seatBids, 2) sort.Slice(seatBids, func(i, j int) bool { @@ -2605,7 +2612,8 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { }, }, }, - &hookexecution.EmptyHookExecutor{}) + &hookexecution.EmptyHookExecutor{}, + &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Equal(t, wantErrs, errs) assert.Len(t, seatBids, 2) assert.ElementsMatch(t, wantSeatBids, seatBids) @@ -2715,7 +2723,8 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { }, }, }, - &hookexecution.EmptyHookExecutor{}) + &hookexecution.EmptyHookExecutor{}, + &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Nil(t, errs) assert.Len(t, seatBids, 2) sort.Slice(seatBids, func(i, j int) bool { @@ -2827,7 +2836,8 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { }, }, }, - &hookexecution.EmptyHookExecutor{}) + &hookexecution.EmptyHookExecutor{}, + &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Nil(t, errs) assert.Len(t, seatBids, 2) sort.Slice(seatBids, func(i, j int) bool { @@ -2951,7 +2961,8 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { }, }, }, - &hookexecution.EmptyHookExecutor{}) + &hookexecution.EmptyHookExecutor{}, + &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Nil(t, errs) assert.Len(t, seatBids, 2) sort.Slice(seatBids, func(i, j int) bool { diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index b3886941be6..b97b8ec32f1 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -31,8 +31,8 @@ type validatedBidder struct { bidder AdaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor) ([]*entities.PbsOrtbSeatBid, []error) { - seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor) +func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) ([]*entities.PbsOrtbSeatBid, []error) { + seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor, bidAdjustments) for _, seatBid := range seatBids { if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 { errs = append(errs, validationErrors...) diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index 08516a7d2e4..68adad5ddaa 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -65,7 +65,7 @@ func TestAllValidBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, 4) assert.Len(t, errs, 0) @@ -135,7 +135,7 @@ func TestAllBadBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, 0) assert.Len(t, errs, 7) @@ -216,7 +216,7 @@ func TestMixedBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, 3) assert.Len(t, errs, 5) @@ -345,7 +345,7 @@ func TestCurrencyBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}) + seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, expectedValidBids) assert.Len(t, errs, expectedErrs) @@ -357,6 +357,6 @@ type mockAdaptedBidder struct { errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) ([]*entities.PbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) ([]*entities.PbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/exchange.go b/exchange/exchange.go index 9438d71ffaf..e168d702bac 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -324,8 +324,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } else if r.Account.AlternateBidderCodes != nil { alternateBidderCodes = *r.Account.AlternateBidderCodes } - - adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExt.Prebid.Experiment, r.HookExecutor) + adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExt.Prebid.Experiment, r.HookExecutor, requestExt.Prebid.BidAdjustments) } var auc *auction @@ -559,7 +558,8 @@ func (e *exchange) getAllBids( headerDebugAllowed bool, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, experiment *openrtb_ext.Experiment, - hookExecutor hookexecution.StageExecutor) ( + hookExecutor hookexecution.StageExecutor, + upgradedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) ( map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, *openrtb_ext.Fledge, @@ -597,7 +597,7 @@ func (e *exchange) getAllBids( addCallSignHeader: isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]), bidAdjustments: bidAdjustments, } - seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor) + seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, upgradedBidAdjustments) // Add in time reporting elapsed := time.Since(start) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 44aa3cc6fc4..deff53dec44 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -5037,7 +5037,7 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (seatBids []*entities.PbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) (seatBids []*entities.PbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok { if expectedRequest != nil { if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) { @@ -5095,7 +5095,7 @@ type capturingRequestBidder struct { req *openrtb2.BidRequest } -func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (seatBid []*entities.PbsOrtbSeatBid, errs []error) { +func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) (seatBid []*entities.PbsOrtbSeatBid, errs []error) { b.req = bidderRequest.BidRequest return []*entities.PbsOrtbSeatBid{{}}, nil } @@ -5202,7 +5202,7 @@ func (e *emptyUsersync) HasAnyLiveSyncs() bool { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (posb []*entities.PbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) (posb []*entities.PbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 2edabcb2c23..fca90cc2f92 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -30,6 +30,14 @@ const NativeExchangeSpecificLowerBound = 500 const MaxDecimalFigures int = 15 +const AdjTypeCpm string = "cpm" + +const AdjTypeMultiplier string = "multiplier" + +const AdjTypeStatic string = "static" + +const AdjWildCard string = "*" + // ExtRequest defines the contract for bidrequest.ext type ExtRequest struct { Prebid ExtRequestPrebid `json:"prebid"` @@ -75,6 +83,8 @@ type ExtRequestPrebid struct { MultiBid []*ExtMultiBid `json:"multibid,omitempty"` MultiBidMap map[string]ExtMultiBid `json:"-"` + + BidAdjustments *ExtRequestPrebidBidAdjustments `json:"bidadjustments,omitempty"` } // Experiment defines if experimental features are available for the request @@ -141,6 +151,28 @@ type ExtRequestPrebidCacheVAST struct { ReturnCreative *bool `json:"returnCreative,omitempty"` } +type ExtRequestPrebidBidAdjustments struct { + MediaType *MediaType `json:"mediatype,omitempty"` +} + +// How the map is strucutred +// Map #1 -- Key: BidderName, Value: Another Map +// Map #2 -- Key: DealID, Value: Adjustments Array +// Overall: BidderName maps to a DealID that maps to an Adjustments Array +type MediaType struct { + Banner map[string]map[string][]Adjustments `json:"banner,omitempty"` + Video map[string]map[string][]Adjustments `json:"video,omitempty"` + Audio map[string]map[string][]Adjustments `json:"audio,omitempty"` + Native map[string]map[string][]Adjustments `json:"native,omitempty"` + WildCard map[string]map[string][]Adjustments `json:"*,omitempty"` +} + +type Adjustments struct { + AdjType string `json:"adjtype,omitempty"` + Value float64 `json:"value,omitempty"` + Currency string `json:"currency,omitempty"` +} + // ExtRequestTargeting defines the contract for bidrequest.ext.prebid.targeting type ExtRequestTargeting struct { PriceGranularity *PriceGranularity `json:"pricegranularity,omitempty"` @@ -306,3 +338,58 @@ func (m ExtMultiBid) String() string { } return fmt.Sprintf("{Bidder:%s, Bidders:%v, MaxBids:%s, TargetBidderCodePrefix:%s}", m.Bidder, m.Bidders, maxBid, m.TargetBidderCodePrefix) } + +func (bidAdj *ExtRequestPrebidBidAdjustments) GetAdjustmentArray(bidType BidType, bidderName BidderName, dealID string) []Adjustments { + if bidAdj.MediaType.Banner != nil && bidType == BidTypeBanner { + if adjArray := getAdjustmentArrayHelper(bidAdj.MediaType.Banner, bidderName.String(), dealID); adjArray != nil { + return adjArray + } + } + if bidAdj.MediaType.Video != nil && bidType == BidTypeVideo { + if adjArray := getAdjustmentArrayHelper(bidAdj.MediaType.Video, bidderName.String(), dealID); adjArray != nil { + return adjArray + } + } + if bidAdj.MediaType.Audio != nil && bidType == BidTypeAudio { + if adjArray := getAdjustmentArrayHelper(bidAdj.MediaType.Audio, bidderName.String(), dealID); adjArray != nil { + return adjArray + } + + } + if bidAdj.MediaType.Native != nil && bidType == BidTypeNative { + if adjArray := getAdjustmentArrayHelper(bidAdj.MediaType.Native, bidderName.String(), dealID); adjArray != nil { + return adjArray + } + } + if bidAdj.MediaType.WildCard != nil { + if adjArray := getAdjustmentArrayHelper(bidAdj.MediaType.WildCard, bidderName.String(), dealID); adjArray != nil { + return adjArray + } + } + return nil +} + +// TODO: Better function name? +func getAdjustmentArrayHelper(bidAdjMap map[string]map[string][]Adjustments, bidderName string, dealID string) []Adjustments { + + // Priority For Returning Adjustment Array + // #1: Matched bidderName and dealID + // #2: Matched bidderName and WildCard dealID field + // #3: Wildcard bidder field and matched DealID + // #4: Wildcard bidder and wildcard dealID + + if _, ok := bidAdjMap[bidderName]; ok { + if _, ok := bidAdjMap[bidderName][dealID]; ok { + return bidAdjMap[bidderName][dealID] + } else if _, ok := bidAdjMap[bidderName][AdjWildCard]; ok { + return bidAdjMap[bidderName][AdjWildCard] + } + } else if _, ok := bidAdjMap[AdjWildCard]; ok { + if _, ok := bidAdjMap[AdjWildCard][dealID]; ok { + return bidAdjMap[AdjWildCard][dealID] + } else if _, ok := bidAdjMap[AdjWildCard][AdjWildCard]; ok { + return bidAdjMap[AdjWildCard][AdjWildCard] + } + } + return nil +} From b942b42808a3f462c86409673c100706fd0e4f1c Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Wed, 29 Mar 2023 17:38:38 -0700 Subject: [PATCH 02/27] Move/add merge/validation to HoldAuction --- endpoints/openrtb2/auction.go | 123 +--------------------------------- exchange/exchange.go | 69 ++++++++++++++++++- openrtb_ext/request.go | 59 ++++++++++++++++ 3 files changed, 128 insertions(+), 123 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 39b859f538a..6122bb0a3a2 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "math" "net/http" "net/url" "regexp" @@ -438,12 +437,6 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return } - // Merge Host and Account Bid Adjustment Info - if err := mergeBidAdjustments(req, account.BidAdjustments); err != nil { - errs = []error{err} - return - } - // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). deps.setFieldsImplicitly(httpRequest, req) @@ -862,64 +855,6 @@ func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[str return nil } -func validateBidAdjustments(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) bool { - if bidAdjustments.MediaType.Banner != nil { - if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Banner); !valid { - return false - } - } - if bidAdjustments.MediaType.Video != nil { - if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Video); !valid { - return false - } - } - if bidAdjustments.MediaType.Audio != nil { - if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Audio); !valid { - return false - } - } - if bidAdjustments.MediaType.Native != nil { - if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Native); !valid { - return false - } - } - return true -} - -func validateBidAdjustmentsHelper(bidAdjMap map[string]map[string][]openrtb_ext.Adjustments) bool { - for bidderName, _ := range bidAdjMap { - for dealId, _ := range bidAdjMap[bidderName] { - for _, adjustment := range bidAdjMap[bidderName][dealId] { - if !validateAdjustment(adjustment) { - return false - } - } - } - } - return true -} - -func validateAdjustment(adjustment openrtb_ext.Adjustments) bool { - switch adjustment.AdjType { - case openrtb_ext.AdjTypeCpm: - if adjustment.Currency == "" || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { - return false - } - case openrtb_ext.AdjTypeMultiplier: - if adjustment.Value < 0 || adjustment.Value > 100 { - return false - } - adjustment.Currency = "" - case openrtb_ext.AdjTypeStatic: - if adjustment.Currency == "" || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { - return false - } - default: - return false - } - return true -} - func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error { _, err := schain.BidderToPrebidSChains(sChains) return err @@ -1600,7 +1535,7 @@ func validateRequestExt(req *openrtb_ext.RequestWrapper) []error { reqExt.SetPrebid(prebid) } - if valid := validateBidAdjustments(prebid.BidAdjustments); !valid { + if valid := prebid.BidAdjustments.ValidateBidAdjustments(); !valid { prebid.BidAdjustments = nil reqExt.SetPrebid(prebid) if prebid.Debug { @@ -2356,59 +2291,3 @@ func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage func generateStoredBidResponseValidationError(impID string) error { return fmt.Errorf("request validation failed. Stored bid responses are specified for imp %s. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse", impID) } - -func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, account *openrtb_ext.ExtRequestPrebidBidAdjustments) error { - reqExt, err := req.GetRequestExt() - if err != nil { - return err - } - host := reqExt.GetPrebid() - - if host == nil && account != nil { - host.BidAdjustments = account - reqExt.SetPrebid(host) - } - - if host.BidAdjustments.MediaType.Banner != nil && account.MediaType.Banner != nil { - host.BidAdjustments.MediaType.Banner = mergeBidAdjustmentsHelper(host.BidAdjustments.MediaType.Banner, account.MediaType.Banner) - } else if account.MediaType.Banner != nil { - host.BidAdjustments.MediaType.Banner = account.MediaType.Banner - } - - if host.BidAdjustments.MediaType.Video != nil && account.MediaType.Video != nil { - host.BidAdjustments.MediaType.Video = mergeBidAdjustmentsHelper(host.BidAdjustments.MediaType.Video, account.MediaType.Video) - } else if account.MediaType.Video != nil { - host.BidAdjustments.MediaType.Video = account.MediaType.Video - } - - if host.BidAdjustments.MediaType.Native != nil && account.MediaType.Native != nil { - host.BidAdjustments.MediaType.Native = mergeBidAdjustmentsHelper(host.BidAdjustments.MediaType.Native, account.MediaType.Native) - } else if account.MediaType.Native != nil { - host.BidAdjustments.MediaType.Native = account.MediaType.Native - } - - if host.BidAdjustments.MediaType.Audio != nil && account.MediaType.Audio != nil { - host.BidAdjustments.MediaType.Audio = mergeBidAdjustmentsHelper(host.BidAdjustments.MediaType.Audio, account.MediaType.Audio) - } else if account.MediaType.Audio != nil { - host.BidAdjustments.MediaType.Audio = account.MediaType.Audio - } - - reqExt.SetPrebid(host) - return nil -} - -// TODO: Better function name? -func mergeBidAdjustmentsHelper(hostAdjMap map[string]map[string][]openrtb_ext.Adjustments, accountAdjMap map[string]map[string][]openrtb_ext.Adjustments) map[string]map[string][]openrtb_ext.Adjustments { - for bidderName, dealIdToAdjustmentsMap := range accountAdjMap { - if _, ok := hostAdjMap[bidderName]; ok { - for dealID, acctAdjustmentsArray := range accountAdjMap[bidderName] { - if _, okay := hostAdjMap[bidderName][dealID]; !okay { - hostAdjMap[bidderName][dealID] = acctAdjustmentsArray - } - } - } else { - hostAdjMap[bidderName] = dealIdToAdjustmentsMap - } - } - return hostAdjMap -} diff --git a/exchange/exchange.go b/exchange/exchange.go index e168d702bac..905577ccbfe 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -274,6 +274,20 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } } + mergedBidAdj, err := mergeBidAdjustments(r.BidRequestWrapper, r.Account.BidAdjustments) + if err != nil { + return nil, err + } + if valid := mergedBidAdj.ValidateBidAdjustments(); !valid { + mergedBidAdj = nil + if requestExt.Prebid.Debug { + errs = append(errs, &errortypes.Warning{ + WarningCode: errortypes.BidAdjustmentWarningCode, + Message: "Bid Adjustment Was Invalid", + }) + } + } + bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExt) recordImpMetrics(r.BidRequestWrapper.BidRequest, e.me) @@ -324,7 +338,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } else if r.Account.AlternateBidderCodes != nil { alternateBidderCodes = *r.Account.AlternateBidderCodes } - adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExt.Prebid.Experiment, r.HookExecutor, requestExt.Prebid.BidAdjustments) + adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExt.Prebid.Experiment, r.HookExecutor, mergedBidAdj) } var auc *auction @@ -1434,3 +1448,56 @@ func setErrorMessageSecureMarkup(validationType string) string { } return "" } + +func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, account *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { + reqExt, err := req.GetRequestExt() + if err != nil { + return nil, err + } + extPrebid := reqExt.GetPrebid() + + if extPrebid == nil && account != nil { + return account, nil + } + + if extPrebid.BidAdjustments.MediaType.Banner != nil && account.MediaType.Banner != nil { + extPrebid.BidAdjustments.MediaType.Banner = mergeBidAdjustmentsHelper(extPrebid.BidAdjustments.MediaType.Banner, account.MediaType.Banner) + } else if account.MediaType.Banner != nil { + extPrebid.BidAdjustments.MediaType.Banner = account.MediaType.Banner + } + + if extPrebid.BidAdjustments.MediaType.Video != nil && account.MediaType.Video != nil { + extPrebid.BidAdjustments.MediaType.Video = mergeBidAdjustmentsHelper(extPrebid.BidAdjustments.MediaType.Video, account.MediaType.Video) + } else if account.MediaType.Video != nil { + extPrebid.BidAdjustments.MediaType.Video = account.MediaType.Video + } + + if extPrebid.BidAdjustments.MediaType.Native != nil && account.MediaType.Native != nil { + extPrebid.BidAdjustments.MediaType.Native = mergeBidAdjustmentsHelper(extPrebid.BidAdjustments.MediaType.Native, account.MediaType.Native) + } else if account.MediaType.Native != nil { + extPrebid.BidAdjustments.MediaType.Native = account.MediaType.Native + } + + if extPrebid.BidAdjustments.MediaType.Audio != nil && account.MediaType.Audio != nil { + extPrebid.BidAdjustments.MediaType.Audio = mergeBidAdjustmentsHelper(extPrebid.BidAdjustments.MediaType.Audio, account.MediaType.Audio) + } else if account.MediaType.Audio != nil { + extPrebid.BidAdjustments.MediaType.Audio = account.MediaType.Audio + } + return extPrebid.BidAdjustments, nil +} + +// TODO: Better function name? +func mergeBidAdjustmentsHelper(hostAdjMap map[string]map[string][]openrtb_ext.Adjustments, accountAdjMap map[string]map[string][]openrtb_ext.Adjustments) map[string]map[string][]openrtb_ext.Adjustments { + for bidderName, dealIdToAdjustmentsMap := range accountAdjMap { + if _, ok := hostAdjMap[bidderName]; ok { + for dealID, acctAdjustmentsArray := range accountAdjMap[bidderName] { + if _, okay := hostAdjMap[bidderName][dealID]; !okay { + hostAdjMap[bidderName][dealID] = acctAdjustmentsArray + } + } + } else { + hostAdjMap[bidderName] = dealIdToAdjustmentsMap + } + } + return hostAdjMap +} diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index fca90cc2f92..13449ab8b52 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -3,6 +3,7 @@ package openrtb_ext import ( "encoding/json" "fmt" + "math" "github.com/prebid/openrtb/v17/openrtb2" ) @@ -393,3 +394,61 @@ func getAdjustmentArrayHelper(bidAdjMap map[string]map[string][]Adjustments, bid } return nil } + +func (bidAdjustments ExtRequestPrebidBidAdjustments) ValidateBidAdjustments() bool { + if bidAdjustments.MediaType.Banner != nil { + if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Banner); !valid { + return false + } + } + if bidAdjustments.MediaType.Video != nil { + if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Video); !valid { + return false + } + } + if bidAdjustments.MediaType.Audio != nil { + if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Audio); !valid { + return false + } + } + if bidAdjustments.MediaType.Native != nil { + if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Native); !valid { + return false + } + } + return true +} + +func validateBidAdjustmentsHelper(bidAdjMap map[string]map[string][]Adjustments) bool { + for bidderName, _ := range bidAdjMap { + for dealId, _ := range bidAdjMap[bidderName] { + for _, adjustment := range bidAdjMap[bidderName][dealId] { + if !validateAdjustment(adjustment) { + return false + } + } + } + } + return true +} + +func validateAdjustment(adjustment Adjustments) bool { + switch adjustment.AdjType { + case AdjTypeCpm: + if adjustment.Currency == "" || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { + return false + } + case AdjTypeMultiplier: + if adjustment.Value < 0 || adjustment.Value > 100 { + return false + } + adjustment.Currency = "" + case AdjTypeStatic: + if adjustment.Currency == "" || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { + return false + } + default: + return false + } + return true +} From 1302f8640a247d5b1d28bc1bc4a02ea7ebebf481 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Sun, 2 Apr 2023 19:21:53 -0700 Subject: [PATCH 03/27] Bid Adj Testing --- endpoints/openrtb2/auction.go | 2 +- .../exemplary/bidadjustments-cpm.json | 92 +++++++ .../exemplary/bidadjustments-simple.json | 75 +++++ .../exemplary/bidadjustments-static.json | 92 +++++++ endpoints/openrtb2/test_utils.go | 8 +- exchange/bidder.go | 9 +- exchange/bidder_test.go | 104 +++++-- exchange/exchange.go | 8 +- exchange/exchange_test.go | 131 +++++++++ openrtb_ext/request.go | 18 +- openrtb_ext/request_test.go | 257 ++++++++++++++++++ 11 files changed, 760 insertions(+), 36 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 6122bb0a3a2..e0aa8a40af1 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1541,7 +1541,7 @@ func validateRequestExt(req *openrtb_ext.RequestWrapper) []error { if prebid.Debug { errs = append(errs, &errortypes.Warning{ WarningCode: errortypes.BidAdjustmentWarningCode, - Message: "Bid Adjustment Was Invalid", + Message: "Bid Adjustment From Request Was Invalid", }) } } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json new file mode 100644 index 00000000000..d60b0ffd162 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json @@ -0,0 +1,92 @@ +{ + "description": "Bid Adjustment Test with CPM, and WildCard Preference Testing", + "config": { + "mockBidders": [ + {"bidderName": "appnexus", "currency": "USD", "price": 20.00} + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 2.0 + } + }, + "usepbsrates": false + }, + "bidadjustments": { + "mediatype": { + "banner": { + "appnexus": { + "*": [ + { + "adjtype": "cpm", + "value": 5.0, + "currency": "EUR" + } + ] + }, + "*": { + "*": [ + { + "adjtype": "multiplier", + "value": 3.0 + } + ] + } + } + } + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 17.5, + "ext": {"prebid":{"type":"banner"}} + } + ], + "seat": "appnexus" + } + ], + "bidid":"test bid id", + "cur":"USD", + "nbr":0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json new file mode 100644 index 00000000000..e63222de756 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json @@ -0,0 +1,75 @@ +{ + "description": "Bid Adjustment Test With One Adjustment", + "config": { + "mockBidders": [ + {"bidderName": "appnexus", "currency": "USD", "price": 2.00, "dealid": "some-deal-id"} + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "bidadjustments": { + "mediatype": { + "banner": { + "appnexus": { + "some-deal-id": [ + { + "adjtype": "multiplier", + "value": 2.0 + } + ] + } + } + } + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 4.0, + "ext": {"prebid":{"type":"banner"}} + } + ], + "seat": "appnexus" + } + ], + "bidid":"test bid id", + "cur":"USD", + "nbr":0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json new file mode 100644 index 00000000000..b1dc165bf56 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json @@ -0,0 +1,92 @@ +{ + "description": "Bid Adjustment with Static and WildCard testing", + "config": { + "mockBidders": [ + {"bidderName": "appnexus", "currency": "USD", "price": 2.00, "dealid": "some-deal-id"} + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 2.0 + } + }, + "usepbsrates": false + }, + "bidadjustments": { + "mediatype": { + "banner": { + "*": { + "some-deal-id": [ + { + "adjtype": "static", + "value": 5.0, + "currency": "EUR" + } + ] + }, + "some-bidder-name": { + "*": [ + { + "adjtype": "multiplier", + "value": 2.0 + } + ] + } + } + } + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 2.5, + "ext": {"prebid":{"type":"banner"}} + } + ], + "seat": "appnexus" + } + ], + "bidid":"test bid id", + "cur":"USD", + "nbr":0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index 89227b5a5d3..c81644f0032 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -936,6 +936,7 @@ type mockBidderHandler struct { BidderName string `json:"bidderName"` Currency string `json:"currency"` Price float64 `json:"price"` + DealId string `json:"dealid"` } func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { @@ -970,9 +971,10 @@ func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { { Bid: []openrtb2.Bid{ { - ID: b.BidderName + "-bid", - ImpID: openrtb2Request.Imp[0].ID, - Price: b.Price, + ID: b.BidderName + "-bid", + ImpID: openrtb2Request.Imp[0].ID, + Price: b.Price, + DealID: b.DealId, }, }, Seat: b.BidderName, diff --git a/exchange/bidder.go b/exchange/bidder.go index 5d4fa31896f..b35fee8802b 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -331,7 +331,10 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde adjustmentFactor = givenAdjustment } - adjArray := bidAdjustments.GetAdjustmentArray(bidResponse.Bids[i].BidType, bidderRequest.BidderName, bidResponse.Bids[i].Bid.DealID) + adjArray := []openrtb_ext.Adjustments{} + if bidAdjustments != nil { + adjArray = bidAdjustments.GetAdjustmentArray(bidResponse.Bids[i].BidType, bidderRequest.BidderName, bidResponse.Bids[i].Bid.DealID) + } originalBidCpm := 0.0 if bidResponse.Bids[i].Bid != nil { @@ -692,13 +695,13 @@ func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, if adjustment.AdjType == openrtb_ext.AdjTypeMultiplier { bidPrice = bidPrice * adjustment.Value } else if adjustment.AdjType == openrtb_ext.AdjTypeCpm { - convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, adjustment.Currency) + convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, *adjustment.Currency) if err != nil { return originalBidPrice } bidPrice = bidPrice - convertedVal } else if adjustment.AdjType == openrtb_ext.AdjTypeStatic { - convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, adjustment.Currency) + convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, *adjustment.Currency) if err != nil { return originalBidPrice } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 13631edf248..0b1b6e24537 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -108,7 +108,7 @@ func TestSingleBidder(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) seatBid := seatBids[0] @@ -230,7 +230,7 @@ func TestSingleBidderGzip(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) seatBid := seatBids[0] @@ -330,7 +330,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) expectedHttpCalls := []*openrtb_ext.ExtHttpCall{ { @@ -382,7 +382,7 @@ func TestSetGPCHeader(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -432,7 +432,7 @@ func TestSetGPCHeaderNil(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -502,7 +502,7 @@ func TestMultiBidder(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) if len(seatBids) != 1 { t.Fatalf("SeatBid should exist, because bids exist.") @@ -883,7 +883,7 @@ func TestMultiCurrencies(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, - &openrtb_ext.ExtRequestPrebidBidAdjustments{}, + nil, ) assert.Len(t, seatBids, 1) seatBid := seatBids[0] @@ -1043,7 +1043,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, - &openrtb_ext.ExtRequestPrebidBidAdjustments{}, + nil, ) assert.Len(t, seatBids, 1) seatBid := seatBids[0] @@ -1222,7 +1222,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, - &openrtb_ext.ExtRequestPrebidBidAdjustments{}, + nil, ) assert.Len(t, seatBids, 1) seatBid := seatBids[0] @@ -1540,7 +1540,7 @@ func TestMobileNativeTypes(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, - &openrtb_ext.ExtRequestPrebidBidAdjustments{}, + nil, ) assert.Len(t, seatBids, 1) @@ -1660,7 +1660,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, - &openrtb_ext.ExtRequestPrebidBidAdjustments{}, + nil, ) assert.Len(t, seatBids, 1) @@ -1775,7 +1775,7 @@ func TestFledge(t *testing.T) { }, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, - &openrtb_ext.ExtRequestPrebidBidAdjustments{}, + nil, ) assert.Len(t, seatBids, 1) assert.NotNil(t, seatBids[0].FledgeAuctionConfigs) @@ -1799,7 +1799,7 @@ func TestErrorReporting(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - bids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + bids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") } @@ -2031,7 +2031,7 @@ func TestCallRecordAdapterConnections(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - _, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + _, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) @@ -2274,7 +2274,7 @@ func TestRequestBidsWithAdsCertsSigner(t *testing.T) { addCallSignHeader: true, bidAdjustments: bidAdjustments, } - _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Empty(t, errs, "no errors should be returned") } @@ -2499,7 +2499,7 @@ func TestExtraBid(t *testing.T) { }, }, &hookexecution.EmptyHookExecutor{}, - &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + nil) assert.Nil(t, errs) assert.Len(t, seatBids, 2) sort.Slice(seatBids, func(i, j int) bool { @@ -2613,7 +2613,7 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { }, }, &hookexecution.EmptyHookExecutor{}, - &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + nil) assert.Equal(t, wantErrs, errs) assert.Len(t, seatBids, 2) assert.ElementsMatch(t, wantSeatBids, seatBids) @@ -2724,7 +2724,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { }, }, &hookexecution.EmptyHookExecutor{}, - &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + nil) assert.Nil(t, errs) assert.Len(t, seatBids, 2) sort.Slice(seatBids, func(i, j int) bool { @@ -2837,7 +2837,7 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { }, }, &hookexecution.EmptyHookExecutor{}, - &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + nil) assert.Nil(t, errs) assert.Len(t, seatBids, 2) sort.Slice(seatBids, func(i, j int) bool { @@ -2962,7 +2962,7 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { }, }, &hookexecution.EmptyHookExecutor{}, - &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + nil) assert.Nil(t, errs) assert.Len(t, seatBids, 2) sort.Slice(seatBids, func(i, j int) bool { @@ -2970,3 +2970,67 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { }) assert.Equal(t, wantSeatBids, seatBids) } + +func TestApplyAdjustmentArray(t *testing.T) { + var ( + givenFrom string = "EUR" + givenTo string = "USA" + ) + + testCases := []struct { + name string + givenAdjustments []openrtb_ext.Adjustments + setMock func(m *mock.Mock) + givenBidPrice float64 + expectedBidPrice float64 + }{ + { + name: "CPM adj type, value after currency conversion should be subtracted from given bid price", + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: &givenTo}}, + givenBidPrice: 10.0, + setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(2.5, nil) }, + expectedBidPrice: 7.5, + }, + { + name: "Static adj type, value after currency conversion should be the bid price", + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: "static", Value: 4.0, Currency: &givenTo}}, + givenBidPrice: 10.0, + setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(5.0, nil) }, + expectedBidPrice: 20.0, + }, + { + name: "Multiplier adj type with no currency conversion", + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 3.0}}, + givenBidPrice: 10.0, + expectedBidPrice: 30.0, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + reqInfo := adapters.ExtraRequestInfo{} + if test.givenAdjustments[0].Currency != nil { + mockConversions := &mockConversions{} + test.setMock(&mockConversions.Mock) + reqInfo = adapters.NewExtraRequestInfo(mockConversions) + } + + bidPrice := applyAdjustmentArray(test.givenAdjustments, test.givenBidPrice, givenFrom, &reqInfo) + assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") + }) + } +} + +type mockConversions struct { + mock.Mock +} + +func (m mockConversions) GetRate(from string, to string) (float64, error) { + args := m.Called(from, to) + return args.Get(0).(float64), args.Error(1) +} + +func (m mockConversions) GetRates() *map[string]map[string]float64 { + args := m.Called() + return args.Get(0).(*map[string]map[string]float64) +} diff --git a/exchange/exchange.go b/exchange/exchange.go index 905577ccbfe..668d75697ae 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -283,7 +283,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * if requestExt.Prebid.Debug { errs = append(errs, &errortypes.Warning{ WarningCode: errortypes.BidAdjustmentWarningCode, - Message: "Bid Adjustment Was Invalid", + Message: "Bid Adjustment Was Invalid After Merge", }) } } @@ -1456,9 +1456,15 @@ func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, account *openrtb_ext.E } extPrebid := reqExt.GetPrebid() + if extPrebid == nil && account == nil { + return nil, nil + } if extPrebid == nil && account != nil { return account, nil } + if extPrebid != nil && account == nil { + return extPrebid.BidAdjustments, nil + } if extPrebid.BidAdjustments.MediaType.Banner != nil && account.MediaType.Banner != nil { extPrebid.BidAdjustments.MediaType.Banner = mergeBidAdjustmentsHelper(extPrebid.BidAdjustments.MediaType.Banner, account.MediaType.Banner) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index deff53dec44..cc691e96da7 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -5352,6 +5352,137 @@ func TestModulesCanBeExecutedForMultipleBiddersSimultaneously(t *testing.T) { } } +func TestMergeBidAdjustments(t *testing.T) { + currency := "USD" + + testCases := []struct { + name string + givenRequestWrapper *openrtb_ext.RequestWrapper + givenAccount *config.Account + expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments + }{ + { + name: "Different Bidder Names for Bid Adjustments Present in Request and Account", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + { + name: "Same Bidder Name and DealIDs for Bid Adjustments Present in Request and Account. Request should take precedence", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + }, + { + name: "Same Bidder Name, different DealIDs for Bid Adjustments Present in Request and Account.", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "static", "value": 3.00, "currency": "USD"}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 3.00, Currency: ¤cy}}, + "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + { + name: "Different bidder names, request comes with CPM bid adjustment", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{"adjtype": "cpm", "value": 0.18, "currency": "USD"}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 0.18, Currency: ¤cy}}, + }, + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + mergedBidAdj, err := mergeBidAdjustments(test.givenRequestWrapper, test.givenAccount.BidAdjustments) + assert.NoError(t, err, "Unexpected error recieved") + assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) + }) + } +} + type TestApplyHookMutationsBuilder struct { hooks.EmptyPlanBuilder } diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 13449ab8b52..769abed0385 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -171,7 +171,7 @@ type MediaType struct { type Adjustments struct { AdjType string `json:"adjtype,omitempty"` Value float64 `json:"value,omitempty"` - Currency string `json:"currency,omitempty"` + Currency *string `json:"currency,omitempty"` } // ExtRequestTargeting defines the contract for bidrequest.ext.prebid.targeting @@ -378,7 +378,6 @@ func getAdjustmentArrayHelper(bidAdjMap map[string]map[string][]Adjustments, bid // #2: Matched bidderName and WildCard dealID field // #3: Wildcard bidder field and matched DealID // #4: Wildcard bidder and wildcard dealID - if _, ok := bidAdjMap[bidderName]; ok { if _, ok := bidAdjMap[bidderName][dealID]; ok { return bidAdjMap[bidderName][dealID] @@ -395,7 +394,10 @@ func getAdjustmentArrayHelper(bidAdjMap map[string]map[string][]Adjustments, bid return nil } -func (bidAdjustments ExtRequestPrebidBidAdjustments) ValidateBidAdjustments() bool { +func (bidAdjustments *ExtRequestPrebidBidAdjustments) ValidateBidAdjustments() bool { + if bidAdjustments == nil { + return true + } if bidAdjustments.MediaType.Banner != nil { if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Banner); !valid { return false @@ -420,8 +422,8 @@ func (bidAdjustments ExtRequestPrebidBidAdjustments) ValidateBidAdjustments() bo } func validateBidAdjustmentsHelper(bidAdjMap map[string]map[string][]Adjustments) bool { - for bidderName, _ := range bidAdjMap { - for dealId, _ := range bidAdjMap[bidderName] { + for bidderName := range bidAdjMap { + for dealId := range bidAdjMap[bidderName] { for _, adjustment := range bidAdjMap[bidderName][dealId] { if !validateAdjustment(adjustment) { return false @@ -435,16 +437,16 @@ func validateBidAdjustmentsHelper(bidAdjMap map[string]map[string][]Adjustments) func validateAdjustment(adjustment Adjustments) bool { switch adjustment.AdjType { case AdjTypeCpm: - if adjustment.Currency == "" || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { + if adjustment.Currency == nil || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { return false } case AdjTypeMultiplier: if adjustment.Value < 0 || adjustment.Value > 100 { return false } - adjustment.Currency = "" + adjustment.Currency = nil case AdjTypeStatic: - if adjustment.Currency == "" || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { + if adjustment.Currency == nil || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { return false } default: diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go index 8d1ecee22b3..0a86730990e 100644 --- a/openrtb_ext/request_test.go +++ b/openrtb_ext/request_test.go @@ -141,3 +141,260 @@ var validGranularityTests []granularityTestData = []granularityTestData{ }, }, } + +func TestValidateBidAdjustments(t *testing.T) { + currency := "USD" + + testCases := []struct { + name string + givenBidAdjustments *ExtRequestPrebidBidAdjustments + expected bool + }{ + { + name: "Valid single bid adjustment multiplier", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Banner: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + expected: true, + }, + { + name: "Invalid bid adjustment value, negative", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Banner: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "multiplier", Value: -1.0}}, + }, + }, + }, + }, + expected: false, + }, + { + name: "Invalid bid adjustment value, too big", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Banner: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "multiplier", Value: 200}}, + }, + }, + }, + }, + expected: false, + }, + { + name: "Valid bid adjustment cpm", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Banner: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ¤cy}}, + }, + }, + }, + }, + expected: true, + }, + { + name: "Invalid CPM bid adjustment, no currency given", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Banner: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: nil}}, + }, + }, + }, + }, + expected: false, + }, + { + name: "Invalid CPM bid adjustment, negative value", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Banner: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "cpm", Value: -1.0, Currency: ¤cy}}, + }, + }, + }, + }, + expected: false, + }, + { + name: "Valid static bid adjustment", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Banner: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: ¤cy}}, + }, + }, + }, + }, + expected: true, + }, + { + name: "Invalid static bid adjustment, no currency", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Banner: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: nil}}, + }, + }, + }, + }, + expected: false, + }, + { + name: "Invalid static bid adjustment, negative value", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Banner: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "static", Value: -1.0, Currency: ¤cy}}, + }, + }, + }, + }, + expected: false, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actual := test.givenBidAdjustments.ValidateBidAdjustments() + assert.Equal(t, test.expected, actual, "Boolean didn't match") + }) + } +} + +func TestGetAdjustmentArray(t *testing.T) { + currency := "USD" + + testCases := []struct { + name string + givenBidAdjustments *ExtRequestPrebidBidAdjustments + givenBidType BidType + givenBidderName BidderName + givenDealId string + expected []Adjustments + }{ + { + name: "One bid adjustment, should return same adjustment", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Banner: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + givenBidType: BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + { + name: "Multiple bid adjs, WildCard MediaType, non WildCard should have precedence", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Audio: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: ¤cy}}, + }, + }, + WildCard: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ¤cy}}, + }, + }, + }, + }, + givenBidType: BidTypeAudio, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []Adjustments{{AdjType: "static", Value: 1.0, Currency: ¤cy}}, + }, + { + name: "Single bid adj, Deal ID doesn't match, but wildcard present, should return given bid adj", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Native: map[string]map[string][]Adjustments{ + "bidderA": { + "*": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ¤cy}}, + }, + }, + }, + }, + givenBidType: BidTypeNative, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ¤cy}}, + }, + { + name: "Single bid adj, Not matched bidder, but WildCard, should return given bid adj", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + Video: map[string]map[string][]Adjustments{ + "*": { + "dealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + givenBidType: BidTypeVideo, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + { + name: "WildCard bidder and dealId, should return given bid adj", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + WildCard: map[string]map[string][]Adjustments{ + "*": { + "*": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + givenBidType: BidTypeVideo, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + { + name: "WildCard bidder, but dealId doesn't match given, should return nil", + givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ + MediaType: &MediaType{ + WildCard: map[string]map[string][]Adjustments{ + "bidderB": { + "diffDealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + givenBidType: BidTypeVideo, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: nil, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + adjArray := test.givenBidAdjustments.GetAdjustmentArray(test.givenBidType, test.givenBidderName, test.givenDealId) + assert.Equal(t, test.expected, adjArray, "Adjustment Array doesn't match") + }) + } +} From 14c79ecf24b9e52b38d3f2101442bda1569e232f Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Sun, 2 Apr 2023 19:35:44 -0700 Subject: [PATCH 04/27] Remove unused value from merge --- openrtb_ext/request.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 0edfe0ae221..ddb0276042d 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -83,8 +83,6 @@ type ExtRequestPrebid struct { // any other value or an empty string disables trace output at all. Trace string `json:"trace,omitempty"` - MultiBidMap map[string]ExtMultiBid `json:"-"` - BidAdjustments *ExtRequestPrebidBidAdjustments `json:"bidadjustments,omitempty"` } From d055f96e4d3e97b768b2219167142ba838a83a4c Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Mon, 3 Apr 2023 16:55:36 -0700 Subject: [PATCH 05/27] Fix failed tests post merge --- .../valid-whole/exemplary/bidadjustments-cpm.json | 2 +- .../valid-whole/exemplary/bidadjustments-simple.json | 2 +- .../valid-whole/exemplary/bidadjustments-static.json | 2 +- errortypes/code.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json index d60b0ffd162..e33684ae20b 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json @@ -78,7 +78,7 @@ "id": "appnexus-bid", "impid": "some-impression-id", "price": 17.5, - "ext": {"prebid":{"type":"banner"}} + "ext": {"origbidcpm": 20, "origbidcur": "USD", "prebid": {"meta": {"adaptercode": "appnexus"}, "type": "banner"}} } ], "seat": "appnexus" diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json index e63222de756..437dd82f280 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json @@ -61,7 +61,7 @@ "id": "appnexus-bid", "impid": "some-impression-id", "price": 4.0, - "ext": {"prebid":{"type":"banner"}} + "ext": {"origbidcpm": 2, "origbidcur": "USD", "prebid": {"meta": {"adaptercode": "appnexus"}, "type": "banner"}} } ], "seat": "appnexus" diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json index b1dc165bf56..85ececef243 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json @@ -78,7 +78,7 @@ "id": "appnexus-bid", "impid": "some-impression-id", "price": 2.5, - "ext": {"prebid":{"type":"banner"}} + "ext": {"origbidcpm": 2, "origbidcur": "USD", "prebid": {"meta": {"adaptercode": "appnexus"}, "type": "banner"}} } ], "seat": "appnexus" diff --git a/errortypes/code.go b/errortypes/code.go index e9279e6e8b0..99afb02add9 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -25,8 +25,8 @@ const ( DisabledCurrencyConversionWarningCode AlternateBidderCodeWarningCode MultiBidWarningCode - BidAdjustmentWarningCode AdServerTargetingWarningCode + BidAdjustmentWarningCode ) // Coder provides an error or warning code with severity. From 4ff5ede824f852c734110851d7ff30a598273ef5 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Tue, 4 Apr 2023 13:05:05 -0700 Subject: [PATCH 06/27] Better function names --- exchange/exchange.go | 11 +++++------ openrtb_ext/request.go | 34 ++++++++++++++++------------------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index bc241638cc9..3a33ea61cb6 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -1537,33 +1537,32 @@ func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, account *openrtb_ext.E } if extPrebid.BidAdjustments.MediaType.Banner != nil && account.MediaType.Banner != nil { - extPrebid.BidAdjustments.MediaType.Banner = mergeBidAdjustmentsHelper(extPrebid.BidAdjustments.MediaType.Banner, account.MediaType.Banner) + extPrebid.BidAdjustments.MediaType.Banner = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Banner, account.MediaType.Banner) } else if account.MediaType.Banner != nil { extPrebid.BidAdjustments.MediaType.Banner = account.MediaType.Banner } if extPrebid.BidAdjustments.MediaType.Video != nil && account.MediaType.Video != nil { - extPrebid.BidAdjustments.MediaType.Video = mergeBidAdjustmentsHelper(extPrebid.BidAdjustments.MediaType.Video, account.MediaType.Video) + extPrebid.BidAdjustments.MediaType.Video = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Video, account.MediaType.Video) } else if account.MediaType.Video != nil { extPrebid.BidAdjustments.MediaType.Video = account.MediaType.Video } if extPrebid.BidAdjustments.MediaType.Native != nil && account.MediaType.Native != nil { - extPrebid.BidAdjustments.MediaType.Native = mergeBidAdjustmentsHelper(extPrebid.BidAdjustments.MediaType.Native, account.MediaType.Native) + extPrebid.BidAdjustments.MediaType.Native = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Native, account.MediaType.Native) } else if account.MediaType.Native != nil { extPrebid.BidAdjustments.MediaType.Native = account.MediaType.Native } if extPrebid.BidAdjustments.MediaType.Audio != nil && account.MediaType.Audio != nil { - extPrebid.BidAdjustments.MediaType.Audio = mergeBidAdjustmentsHelper(extPrebid.BidAdjustments.MediaType.Audio, account.MediaType.Audio) + extPrebid.BidAdjustments.MediaType.Audio = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Audio, account.MediaType.Audio) } else if account.MediaType.Audio != nil { extPrebid.BidAdjustments.MediaType.Audio = account.MediaType.Audio } return extPrebid.BidAdjustments, nil } -// TODO: Better function name? -func mergeBidAdjustmentsHelper(hostAdjMap map[string]map[string][]openrtb_ext.Adjustments, accountAdjMap map[string]map[string][]openrtb_ext.Adjustments) map[string]map[string][]openrtb_ext.Adjustments { +func mergeAdjustmentsForMediaType(hostAdjMap map[string]map[string][]openrtb_ext.Adjustments, accountAdjMap map[string]map[string][]openrtb_ext.Adjustments) map[string]map[string][]openrtb_ext.Adjustments { for bidderName, dealIdToAdjustmentsMap := range accountAdjMap { if _, ok := hostAdjMap[bidderName]; ok { for dealID, acctAdjustmentsArray := range accountAdjMap[bidderName] { diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 7de1410b94b..4bce61fb7f8 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -347,42 +347,40 @@ func (m ExtMultiBid) String() string { func (bidAdj *ExtRequestPrebidBidAdjustments) GetAdjustmentArray(bidType BidType, bidderName BidderName, dealID string) []Adjustments { if bidAdj.MediaType.Banner != nil && bidType == BidTypeBanner { - if adjArray := getAdjustmentArrayHelper(bidAdj.MediaType.Banner, bidderName.String(), dealID); adjArray != nil { + if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Banner, bidderName.String(), dealID); adjArray != nil { return adjArray } } if bidAdj.MediaType.Video != nil && bidType == BidTypeVideo { - if adjArray := getAdjustmentArrayHelper(bidAdj.MediaType.Video, bidderName.String(), dealID); adjArray != nil { + if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Video, bidderName.String(), dealID); adjArray != nil { return adjArray } } if bidAdj.MediaType.Audio != nil && bidType == BidTypeAudio { - if adjArray := getAdjustmentArrayHelper(bidAdj.MediaType.Audio, bidderName.String(), dealID); adjArray != nil { + if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Audio, bidderName.String(), dealID); adjArray != nil { return adjArray } } if bidAdj.MediaType.Native != nil && bidType == BidTypeNative { - if adjArray := getAdjustmentArrayHelper(bidAdj.MediaType.Native, bidderName.String(), dealID); adjArray != nil { + if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Native, bidderName.String(), dealID); adjArray != nil { return adjArray } } if bidAdj.MediaType.WildCard != nil { - if adjArray := getAdjustmentArrayHelper(bidAdj.MediaType.WildCard, bidderName.String(), dealID); adjArray != nil { + if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.WildCard, bidderName.String(), dealID); adjArray != nil { return adjArray } } return nil } -// TODO: Better function name? -func getAdjustmentArrayHelper(bidAdjMap map[string]map[string][]Adjustments, bidderName string, dealID string) []Adjustments { - - // Priority For Returning Adjustment Array - // #1: Matched bidderName and dealID - // #2: Matched bidderName and WildCard dealID field - // #3: Wildcard bidder field and matched DealID - // #4: Wildcard bidder and wildcard dealID +// Priority For Returning Adjustment Array Based on Passed BidderName and DealID +// #1: Are able to match bidderName and dealID +// #2: Are able to match bidderName and dealID field is WildCard +// #3: Bidder field is WildCard and are able to match DealID +// #4: Wildcard bidder and wildcard dealID +func getAdjustmentArrayForMediaType(bidAdjMap map[string]map[string][]Adjustments, bidderName string, dealID string) []Adjustments { if _, ok := bidAdjMap[bidderName]; ok { if _, ok := bidAdjMap[bidderName][dealID]; ok { return bidAdjMap[bidderName][dealID] @@ -404,29 +402,29 @@ func (bidAdjustments *ExtRequestPrebidBidAdjustments) ValidateBidAdjustments() b return true } if bidAdjustments.MediaType.Banner != nil { - if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Banner); !valid { + if valid := findAndValidateAdjustment(bidAdjustments.MediaType.Banner); !valid { return false } } if bidAdjustments.MediaType.Video != nil { - if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Video); !valid { + if valid := findAndValidateAdjustment(bidAdjustments.MediaType.Video); !valid { return false } } if bidAdjustments.MediaType.Audio != nil { - if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Audio); !valid { + if valid := findAndValidateAdjustment(bidAdjustments.MediaType.Audio); !valid { return false } } if bidAdjustments.MediaType.Native != nil { - if valid := validateBidAdjustmentsHelper(bidAdjustments.MediaType.Native); !valid { + if valid := findAndValidateAdjustment(bidAdjustments.MediaType.Native); !valid { return false } } return true } -func validateBidAdjustmentsHelper(bidAdjMap map[string]map[string][]Adjustments) bool { +func findAndValidateAdjustment(bidAdjMap map[string]map[string][]Adjustments) bool { for bidderName := range bidAdjMap { for dealId := range bidAdjMap[bidderName] { for _, adjustment := range bidAdjMap[bidderName][dealId] { From 9b2e52511230f6e65055615c01e5bfc2ad4406e4 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Tue, 11 Apr 2023 15:44:55 -0700 Subject: [PATCH 07/27] Address feedback --- endpoints/openrtb2/auction.go | 10 +++---- exchange/bidder.go | 7 ++++- exchange/bidder_test.go | 20 ++++++++++--- exchange/exchange.go | 54 +++++++++++++++++------------------ exchange/exchange_test.go | 2 +- openrtb_ext/request.go | 33 ++++++++------------- openrtb_ext/request_test.go | 11 +++++-- 7 files changed, 74 insertions(+), 63 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 3c252411b61..93c184523fb 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1552,12 +1552,10 @@ func validateRequestExt(req *openrtb_ext.RequestWrapper) []error { if valid := prebid.BidAdjustments.ValidateBidAdjustments(); !valid { prebid.BidAdjustments = nil reqExt.SetPrebid(prebid) - if prebid.Debug { - errs = append(errs, &errortypes.Warning{ - WarningCode: errortypes.BidAdjustmentWarningCode, - Message: "Bid Adjustment From Request Was Invalid", - }) - } + errs = append(errs, &errortypes.Warning{ + WarningCode: errortypes.BidAdjustmentWarningCode, + Message: "Bid Adjustment From Request Was Invalid", + }) } return errs diff --git a/exchange/bidder.go b/exchange/bidder.go index b35fee8802b..889e7543133 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -687,8 +687,10 @@ func compressToGZIP(requestBody []byte) []byte { return b.Bytes() } -// TODO: If adjarray == nil func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) float64 { + if adjArray == nil { + return bidPrice + } originalBidPrice := bidPrice for _, adjustment := range adjArray { @@ -708,5 +710,8 @@ func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, bidPrice = convertedVal } } + if bidPrice <= 0 { + return originalBidPrice + } return bidPrice } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 0b1b6e24537..933118a3a91 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -2986,30 +2986,42 @@ func TestApplyAdjustmentArray(t *testing.T) { }{ { name: "CPM adj type, value after currency conversion should be subtracted from given bid price", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: &givenTo}}, + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: &givenTo}}, givenBidPrice: 10.0, setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(2.5, nil) }, expectedBidPrice: 7.5, }, { name: "Static adj type, value after currency conversion should be the bid price", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: "static", Value: 4.0, Currency: &givenTo}}, + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeStatic, Value: 4.0, Currency: &givenTo}}, givenBidPrice: 10.0, setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(5.0, nil) }, expectedBidPrice: 20.0, }, { name: "Multiplier adj type with no currency conversion", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 3.0}}, + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeMultiplier, Value: 3.0}}, givenBidPrice: 10.0, expectedBidPrice: 30.0, }, + { + name: "Bid price after conversions is equal or less than 0, should return original bid price instead", + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeMultiplier, Value: -1.0}}, + givenBidPrice: 10.0, + expectedBidPrice: 10.0, + }, + { + name: "Nil adjustment array", + givenAdjustments: nil, + givenBidPrice: 10.0, + expectedBidPrice: 10.0, + }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { reqInfo := adapters.ExtraRequestInfo{} - if test.givenAdjustments[0].Currency != nil { + if test.givenAdjustments != nil && test.givenAdjustments[0].Currency != nil { mockConversions := &mockConversions{} test.setMock(&mockConversions.Mock) reqInfo = adapters.NewExtraRequestInfo(mockConversions) diff --git a/exchange/exchange.go b/exchange/exchange.go index 3a33ea61cb6..b49733b2b81 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -1519,60 +1519,60 @@ func setErrorMessageSecureMarkup(validationType string) string { return "" } -func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, account *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { +func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { reqExt, err := req.GetRequestExt() if err != nil { return nil, err } extPrebid := reqExt.GetPrebid() - if extPrebid == nil && account == nil { + if extPrebid == nil && acctBidAdjs == nil { return nil, nil } - if extPrebid == nil && account != nil { - return account, nil + if extPrebid == nil && acctBidAdjs != nil { + return acctBidAdjs, nil } - if extPrebid != nil && account == nil { + if extPrebid != nil && acctBidAdjs == nil { return extPrebid.BidAdjustments, nil } - if extPrebid.BidAdjustments.MediaType.Banner != nil && account.MediaType.Banner != nil { - extPrebid.BidAdjustments.MediaType.Banner = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Banner, account.MediaType.Banner) - } else if account.MediaType.Banner != nil { - extPrebid.BidAdjustments.MediaType.Banner = account.MediaType.Banner + if extPrebid.BidAdjustments.MediaType.Banner != nil && acctBidAdjs.MediaType.Banner != nil { + extPrebid.BidAdjustments.MediaType.Banner = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Banner, acctBidAdjs.MediaType.Banner) + } else if acctBidAdjs.MediaType.Banner != nil { + extPrebid.BidAdjustments.MediaType.Banner = acctBidAdjs.MediaType.Banner } - if extPrebid.BidAdjustments.MediaType.Video != nil && account.MediaType.Video != nil { - extPrebid.BidAdjustments.MediaType.Video = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Video, account.MediaType.Video) - } else if account.MediaType.Video != nil { - extPrebid.BidAdjustments.MediaType.Video = account.MediaType.Video + if extPrebid.BidAdjustments.MediaType.Video != nil && acctBidAdjs.MediaType.Video != nil { + extPrebid.BidAdjustments.MediaType.Video = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Video, acctBidAdjs.MediaType.Video) + } else if acctBidAdjs.MediaType.Video != nil { + extPrebid.BidAdjustments.MediaType.Video = acctBidAdjs.MediaType.Video } - if extPrebid.BidAdjustments.MediaType.Native != nil && account.MediaType.Native != nil { - extPrebid.BidAdjustments.MediaType.Native = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Native, account.MediaType.Native) - } else if account.MediaType.Native != nil { - extPrebid.BidAdjustments.MediaType.Native = account.MediaType.Native + if extPrebid.BidAdjustments.MediaType.Native != nil && acctBidAdjs.MediaType.Native != nil { + extPrebid.BidAdjustments.MediaType.Native = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Native, acctBidAdjs.MediaType.Native) + } else if acctBidAdjs.MediaType.Native != nil { + extPrebid.BidAdjustments.MediaType.Native = acctBidAdjs.MediaType.Native } - if extPrebid.BidAdjustments.MediaType.Audio != nil && account.MediaType.Audio != nil { - extPrebid.BidAdjustments.MediaType.Audio = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Audio, account.MediaType.Audio) - } else if account.MediaType.Audio != nil { - extPrebid.BidAdjustments.MediaType.Audio = account.MediaType.Audio + if extPrebid.BidAdjustments.MediaType.Audio != nil && acctBidAdjs.MediaType.Audio != nil { + extPrebid.BidAdjustments.MediaType.Audio = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Audio, acctBidAdjs.MediaType.Audio) + } else if acctBidAdjs.MediaType.Audio != nil { + extPrebid.BidAdjustments.MediaType.Audio = acctBidAdjs.MediaType.Audio } return extPrebid.BidAdjustments, nil } -func mergeAdjustmentsForMediaType(hostAdjMap map[string]map[string][]openrtb_ext.Adjustments, accountAdjMap map[string]map[string][]openrtb_ext.Adjustments) map[string]map[string][]openrtb_ext.Adjustments { +func mergeAdjustmentsForMediaType(reqAdjMap map[string]map[string][]openrtb_ext.Adjustments, accountAdjMap map[string]map[string][]openrtb_ext.Adjustments) map[string]map[string][]openrtb_ext.Adjustments { for bidderName, dealIdToAdjustmentsMap := range accountAdjMap { - if _, ok := hostAdjMap[bidderName]; ok { + if _, ok := reqAdjMap[bidderName]; ok { for dealID, acctAdjustmentsArray := range accountAdjMap[bidderName] { - if _, okay := hostAdjMap[bidderName][dealID]; !okay { - hostAdjMap[bidderName][dealID] = acctAdjustmentsArray + if _, okay := reqAdjMap[bidderName][dealID]; !okay { + reqAdjMap[bidderName][dealID] = acctAdjustmentsArray } } } else { - hostAdjMap[bidderName] = dealIdToAdjustmentsMap + reqAdjMap[bidderName] = dealIdToAdjustmentsMap } } - return hostAdjMap + return reqAdjMap } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 4b069cf3cf7..c93e000e6e1 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -5580,7 +5580,7 @@ func TestMergeBidAdjustments(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { mergedBidAdj, err := mergeBidAdjustments(test.givenRequestWrapper, test.givenAccount.BidAdjustments) - assert.NoError(t, err, "Unexpected error recieved") + assert.NoError(t, err, "Unexpected error received") assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) }) } diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 4bce61fb7f8..31904664c0a 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -31,13 +31,12 @@ const NativeExchangeSpecificLowerBound = 500 const MaxDecimalFigures int = 15 -const AdjTypeCpm string = "cpm" - -const AdjTypeMultiplier string = "multiplier" - -const AdjTypeStatic string = "static" - -const AdjWildCard string = "*" +const ( + AdjTypeCpm = "cpm" + AdjTypeMultiplier = "multiplier" + AdjTypeStatic = "static" + AdjWildCard = "*" +) // ExtRequest defines the contract for bidrequest.ext type ExtRequest struct { @@ -402,24 +401,16 @@ func (bidAdjustments *ExtRequestPrebidBidAdjustments) ValidateBidAdjustments() b return true } if bidAdjustments.MediaType.Banner != nil { - if valid := findAndValidateAdjustment(bidAdjustments.MediaType.Banner); !valid { - return false - } + return findAndValidateAdjustment(bidAdjustments.MediaType.Banner) } if bidAdjustments.MediaType.Video != nil { - if valid := findAndValidateAdjustment(bidAdjustments.MediaType.Video); !valid { - return false - } + return findAndValidateAdjustment(bidAdjustments.MediaType.Video) } if bidAdjustments.MediaType.Audio != nil { - if valid := findAndValidateAdjustment(bidAdjustments.MediaType.Audio); !valid { - return false - } + return findAndValidateAdjustment(bidAdjustments.MediaType.Audio) } if bidAdjustments.MediaType.Native != nil { - if valid := findAndValidateAdjustment(bidAdjustments.MediaType.Native); !valid { - return false - } + return findAndValidateAdjustment(bidAdjustments.MediaType.Native) } return true } @@ -444,12 +435,12 @@ func validateAdjustment(adjustment Adjustments) bool { return false } case AdjTypeMultiplier: - if adjustment.Value < 0 || adjustment.Value > 100 { + if adjustment.Value <= 0 || adjustment.Value > 100 { return false } adjustment.Currency = nil case AdjTypeStatic: - if adjustment.Currency == nil || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { + if adjustment.Currency == nil || adjustment.Value <= 0 || adjustment.Value > math.MaxFloat64 { return false } default: diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go index 0a86730990e..0311b1ea3e2 100644 --- a/openrtb_ext/request_test.go +++ b/openrtb_ext/request_test.go @@ -167,7 +167,7 @@ func TestValidateBidAdjustments(t *testing.T) { name: "Invalid bid adjustment value, negative", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ MediaType: &MediaType{ - Banner: map[string]map[string][]Adjustments{ + Video: map[string]map[string][]Adjustments{ "bidderA": { "dealId": []Adjustments{{AdjType: "multiplier", Value: -1.0}}, }, @@ -180,7 +180,7 @@ func TestValidateBidAdjustments(t *testing.T) { name: "Invalid bid adjustment value, too big", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ MediaType: &MediaType{ - Banner: map[string]map[string][]Adjustments{ + Audio: map[string]map[string][]Adjustments{ "bidderA": { "dealId": []Adjustments{{AdjType: "multiplier", Value: 200}}, }, @@ -193,7 +193,7 @@ func TestValidateBidAdjustments(t *testing.T) { name: "Valid bid adjustment cpm", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ MediaType: &MediaType{ - Banner: map[string]map[string][]Adjustments{ + Native: map[string]map[string][]Adjustments{ "bidderA": { "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ¤cy}}, }, @@ -267,6 +267,11 @@ func TestValidateBidAdjustments(t *testing.T) { }, expected: false, }, + { + name: "Nil Bid Adjustment", + givenBidAdjustments: nil, + expected: true, + }, } for _, test := range testCases { From fb2ab84991746ed1c544e3c254d5390520a39acd Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Wed, 12 Apr 2023 13:17:19 -0700 Subject: [PATCH 08/27] Tweak to validation --- openrtb_ext/request.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 31904664c0a..e193f85cd4f 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -431,20 +431,18 @@ func findAndValidateAdjustment(bidAdjMap map[string]map[string][]Adjustments) bo func validateAdjustment(adjustment Adjustments) bool { switch adjustment.AdjType { case AdjTypeCpm: - if adjustment.Currency == nil || adjustment.Value < 0 || adjustment.Value > math.MaxFloat64 { - return false + if adjustment.Currency != nil && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { + return true } case AdjTypeMultiplier: - if adjustment.Value <= 0 || adjustment.Value > 100 { - return false + if adjustment.Value >= 0 && adjustment.Value < 100 { + return true } adjustment.Currency = nil case AdjTypeStatic: - if adjustment.Currency == nil || adjustment.Value <= 0 || adjustment.Value > math.MaxFloat64 { - return false + if adjustment.Currency != nil && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { + return true } - default: - return false } - return true + return false } From 4659a9ad5e722e804ce891cfa7635bf50a498514 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Fri, 14 Apr 2023 15:23:20 -0700 Subject: [PATCH 09/27] Combine adjustment functions, round bid price --- exchange/bidder.go | 23 +++++++++---- exchange/bidder_test.go | 70 +++++++++++++++++++++++++++++++++++++-- exchange/exchange.go | 16 ++++++--- exchange/exchange_test.go | 65 ++++++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 14 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index 889e7543133..9ee576d474f 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "math" "net/http" "net/http/httptrace" "regexp" @@ -74,6 +75,8 @@ const ( Gzip string = "GZIP" ) +const amountOfDecimalPlaces float64 = 4.0 + // AdaptBidder converts an adapters.Bidder into an exchange.AdaptedBidder. // // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter" @@ -331,16 +334,11 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde adjustmentFactor = givenAdjustment } - adjArray := []openrtb_ext.Adjustments{} - if bidAdjustments != nil { - adjArray = bidAdjustments.GetAdjustmentArray(bidResponse.Bids[i].BidType, bidderRequest.BidderName, bidResponse.Bids[i].Bid.DealID) - } - originalBidCpm := 0.0 if bidResponse.Bids[i].Bid != nil { originalBidCpm = bidResponse.Bids[i].Bid.Price bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate - bidResponse.Bids[i].Bid.Price = applyAdjustmentArray(adjArray, bidResponse.Bids[i].Bid.Price, bidResponse.Currency, reqInfo) + bidResponse.Bids[i].Bid.Price = getAndApplyAdjustmentArray(bidAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, bidResponse.Currency, reqInfo) } if _, ok := seatBidMap[bidderName]; !ok { @@ -713,5 +711,16 @@ func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, if bidPrice <= 0 { return originalBidPrice } - return bidPrice + roundTo := math.Pow(10, amountOfDecimalPlaces) + return math.Round(bidPrice*roundTo) / roundTo // Returns Bid Price rounded to 4 decimal places +} + +func getAndApplyAdjustmentArray(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) float64 { + adjArray := []openrtb_ext.Adjustments{} + if bidAdjustments != nil { + adjArray = bidAdjustments.GetAdjustmentArray(bidInfo.BidType, bidderName, bidInfo.Bid.DealID) + } else { + return bidInfo.Bid.Price + } + return applyAdjustmentArray(adjArray, bidInfo.Bid.Price, currency, reqInfo) } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 933118a3a91..d28cfa79241 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -2985,11 +2985,11 @@ func TestApplyAdjustmentArray(t *testing.T) { expectedBidPrice float64 }{ { - name: "CPM adj type, value after currency conversion should be subtracted from given bid price", + name: "CPM adj type, value after currency conversion should be subtracted from given bid price. Price should round to 4 decimal places", givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: &givenTo}}, - givenBidPrice: 10.0, + givenBidPrice: 10.58687, setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(2.5, nil) }, - expectedBidPrice: 7.5, + expectedBidPrice: 8.0869, }, { name: "Static adj type, value after currency conversion should be the bid price", @@ -3033,6 +3033,70 @@ func TestApplyAdjustmentArray(t *testing.T) { } } +func TestGetAndApplyAdjustmentArray(t *testing.T) { + var ( + givenFrom string = "EUR" + givenTo string = "USA" + ) + + testCases := []struct { + name string + givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments + givenBidderName openrtb_ext.BidderName + givenBidInfo *adapters.TypedBid + setMock func(m *mock.Mock) + expectedBidPrice float64 + }{ + { + name: "Valid Bid Adjustments, CPM adj type, function should Get and Apply the adjustment properly", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + BidType: openrtb_ext.BidTypeBanner, + }, + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: &givenTo}}, + }, + }, + }, + }, + givenBidderName: "bidderA", + setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(2.5, nil) }, + expectedBidPrice: 7.5, + }, + { + name: "Nil adjustment array, expect no change to the bid price", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + }, + BidType: openrtb_ext.BidTypeBanner, + }, + givenBidAdjustments: nil, + expectedBidPrice: 10.0, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + reqInfo := adapters.ExtraRequestInfo{} + if test.givenBidAdjustments != nil { + mockConversions := &mockConversions{} + test.setMock(&mockConversions.Mock) + reqInfo = adapters.NewExtraRequestInfo(mockConversions) + } + + bidPrice := getAndApplyAdjustmentArray(test.givenBidAdjustments, test.givenBidInfo, test.givenBidderName, givenFrom, &reqInfo) + assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") + }) + } +} + type mockConversions struct { mock.Mock } diff --git a/exchange/exchange.go b/exchange/exchange.go index b49733b2b81..c8ed661b445 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -277,13 +277,10 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * r.FirstPartyData = resolvedFPD } - mergedBidAdj, err := mergeBidAdjustments(r.BidRequestWrapper, r.Account.BidAdjustments) + mergedBidAdj, err := processBidAdjustments(r.BidRequestWrapper, r.Account.BidAdjustments) if err != nil { return nil, err } - if valid := mergedBidAdj.ValidateBidAdjustments(); !valid { - mergedBidAdj = nil - } bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExtPrebid) @@ -1576,3 +1573,14 @@ func mergeAdjustmentsForMediaType(reqAdjMap map[string]map[string][]openrtb_ext. } return reqAdjMap } + +func processBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { + mergedBidAdj, err := mergeBidAdjustments(req, acctBidAdjs) + if err != nil { + return nil, err + } + if valid := mergedBidAdj.ValidateBidAdjustments(); !valid { + mergedBidAdj = nil + } + return mergedBidAdj, err +} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index c93e000e6e1..d1d56de6ab3 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -5586,6 +5586,71 @@ func TestMergeBidAdjustments(t *testing.T) { } } +func TestProcessBidAdjustments(t *testing.T) { + testCases := []struct { + name string + givenRequestWrapper *openrtb_ext.RequestWrapper + givenAccount *config.Account + expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments + }{ + { + name: "Valid Request and Account Adjustments with different bidder names, should properly merge", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + { + name: "Invalid Request Adjustment, Expect Nil Merged Adjustments", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 200}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: nil, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + mergedBidAdj, err := processBidAdjustments(test.givenRequestWrapper, test.givenAccount.BidAdjustments) + assert.NoError(t, err, "Unexpected error received") + assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) + }) + } +} + type TestApplyHookMutationsBuilder struct { hooks.EmptyPlanBuilder } From 0c688fd2668c248b2a283ed62b35ec11a2f9ef70 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Mon, 17 Apr 2023 15:05:42 -0700 Subject: [PATCH 10/27] Moved exchange adjustment functions/tests --- exchange/bidadjustments.go | 120 ++++++++++ exchange/bidadjustments_test.go | 405 ++++++++++++++++++++++++++++++++ exchange/bidder.go | 43 ---- exchange/bidder_test.go | 140 ----------- exchange/exchange.go | 69 ------ exchange/exchange_test.go | 196 ---------------- 6 files changed, 525 insertions(+), 448 deletions(-) create mode 100644 exchange/bidadjustments.go create mode 100644 exchange/bidadjustments_test.go diff --git a/exchange/bidadjustments.go b/exchange/bidadjustments.go new file mode 100644 index 00000000000..7cba088e3e3 --- /dev/null +++ b/exchange/bidadjustments.go @@ -0,0 +1,120 @@ +package exchange + +import ( + "math" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const roundTo float64 = 10000 // Rounds to 4 Decimal Places + +func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) float64 { + if adjArray == nil { + return bidPrice + } + originalBidPrice := bidPrice + + for _, adjustment := range adjArray { + if adjustment.AdjType == openrtb_ext.AdjTypeMultiplier { + bidPrice = bidPrice * adjustment.Value + } else if adjustment.AdjType == openrtb_ext.AdjTypeCpm { + convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, *adjustment.Currency) + if err != nil { + return originalBidPrice + } + bidPrice = bidPrice - convertedVal + } else if adjustment.AdjType == openrtb_ext.AdjTypeStatic { + convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, *adjustment.Currency) + if err != nil { + return originalBidPrice + } + bidPrice = convertedVal + } + } + roundedBidPrice := math.Round(bidPrice*roundTo) / roundTo // Returns Bid Price rounded to 4 decimal places + + if roundedBidPrice <= 0 { + return originalBidPrice + } + return roundedBidPrice +} + +func getAndApplyAdjustmentArray(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) float64 { + adjArray := []openrtb_ext.Adjustments{} + if bidAdjustments != nil { + adjArray = bidAdjustments.GetAdjustmentArray(bidInfo.BidType, bidderName, bidInfo.Bid.DealID) + } else { + return bidInfo.Bid.Price + } + return applyAdjustmentArray(adjArray, bidInfo.Bid.Price, currency, reqInfo) +} + +func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { + reqExt, err := req.GetRequestExt() + if err != nil { + return nil, err + } + extPrebid := reqExt.GetPrebid() + + if extPrebid == nil && acctBidAdjs == nil { + return nil, nil + } + if extPrebid == nil && acctBidAdjs != nil { + return acctBidAdjs, nil + } + if extPrebid != nil && acctBidAdjs == nil { + return extPrebid.BidAdjustments, nil + } + + if extPrebid.BidAdjustments.MediaType.Banner != nil && acctBidAdjs.MediaType.Banner != nil { + extPrebid.BidAdjustments.MediaType.Banner = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Banner, acctBidAdjs.MediaType.Banner) + } else if acctBidAdjs.MediaType.Banner != nil { + extPrebid.BidAdjustments.MediaType.Banner = acctBidAdjs.MediaType.Banner + } + + if extPrebid.BidAdjustments.MediaType.Video != nil && acctBidAdjs.MediaType.Video != nil { + extPrebid.BidAdjustments.MediaType.Video = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Video, acctBidAdjs.MediaType.Video) + } else if acctBidAdjs.MediaType.Video != nil { + extPrebid.BidAdjustments.MediaType.Video = acctBidAdjs.MediaType.Video + } + + if extPrebid.BidAdjustments.MediaType.Native != nil && acctBidAdjs.MediaType.Native != nil { + extPrebid.BidAdjustments.MediaType.Native = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Native, acctBidAdjs.MediaType.Native) + } else if acctBidAdjs.MediaType.Native != nil { + extPrebid.BidAdjustments.MediaType.Native = acctBidAdjs.MediaType.Native + } + + if extPrebid.BidAdjustments.MediaType.Audio != nil && acctBidAdjs.MediaType.Audio != nil { + extPrebid.BidAdjustments.MediaType.Audio = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Audio, acctBidAdjs.MediaType.Audio) + } else if acctBidAdjs.MediaType.Audio != nil { + extPrebid.BidAdjustments.MediaType.Audio = acctBidAdjs.MediaType.Audio + } + return extPrebid.BidAdjustments, nil +} + +func mergeAdjustmentsForMediaType(reqAdjMap map[string]map[string][]openrtb_ext.Adjustments, accountAdjMap map[string]map[string][]openrtb_ext.Adjustments) map[string]map[string][]openrtb_ext.Adjustments { + for bidderName, dealIdToAdjustmentsMap := range accountAdjMap { + if _, ok := reqAdjMap[bidderName]; ok { + for dealID, acctAdjustmentsArray := range accountAdjMap[bidderName] { + if _, okay := reqAdjMap[bidderName][dealID]; !okay { + reqAdjMap[bidderName][dealID] = acctAdjustmentsArray + } + } + } else { + reqAdjMap[bidderName] = dealIdToAdjustmentsMap + } + } + return reqAdjMap +} + +func processBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { + mergedBidAdj, err := mergeBidAdjustments(req, acctBidAdjs) + if err != nil { + return nil, err + } + if valid := mergedBidAdj.ValidateBidAdjustments(); !valid { + mergedBidAdj = nil + } + return mergedBidAdj, err +} diff --git a/exchange/bidadjustments_test.go b/exchange/bidadjustments_test.go new file mode 100644 index 00000000000..7cc97a86a8a --- /dev/null +++ b/exchange/bidadjustments_test.go @@ -0,0 +1,405 @@ +package exchange + +import ( + "testing" + + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestApplyAdjustmentArray(t *testing.T) { + var ( + givenFrom string = "EUR" + givenTo string = "USA" + ) + + testCases := []struct { + name string + givenAdjustments []openrtb_ext.Adjustments + setMock func(m *mock.Mock) + givenBidPrice float64 + expectedBidPrice float64 + }{ + { + name: "CPM adj type, value after currency conversion should be subtracted from given bid price. Price should round to 4 decimal places", + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: &givenTo}}, + givenBidPrice: 10.58687, + setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(2.5, nil) }, + expectedBidPrice: 8.0869, + }, + { + name: "Static adj type, value after currency conversion should be the bid price", + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeStatic, Value: 4.0, Currency: &givenTo}}, + givenBidPrice: 10.0, + setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(5.0, nil) }, + expectedBidPrice: 20.0, + }, + { + name: "Multiplier adj type with no currency conversion", + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeMultiplier, Value: 3.0}}, + givenBidPrice: 10.0, + expectedBidPrice: 30.0, + }, + { + name: "Bid price after conversions is equal or less than 0, should return original bid price instead", + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeMultiplier, Value: -1.0}}, + givenBidPrice: 10.0, + expectedBidPrice: 10.0, + }, + { + name: "Nil adjustment array", + givenAdjustments: nil, + givenBidPrice: 10.0, + expectedBidPrice: 10.0, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + reqInfo := adapters.ExtraRequestInfo{} + if test.givenAdjustments != nil && test.givenAdjustments[0].Currency != nil { + mockConversions := &mockConversions{} + test.setMock(&mockConversions.Mock) + reqInfo = adapters.NewExtraRequestInfo(mockConversions) + } + + bidPrice := applyAdjustmentArray(test.givenAdjustments, test.givenBidPrice, givenFrom, &reqInfo) + assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") + }) + } +} + +func TestGetAndApplyAdjustmentArray(t *testing.T) { + var ( + givenFrom string = "EUR" + givenTo string = "USA" + ) + + testCases := []struct { + name string + givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments + givenBidderName openrtb_ext.BidderName + givenBidInfo *adapters.TypedBid + setMock func(m *mock.Mock) + expectedBidPrice float64 + }{ + { + name: "Valid Bid Adjustments, CPM adj type, function should Get and Apply the adjustment properly", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + BidType: openrtb_ext.BidTypeBanner, + }, + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: &givenTo}}, + }, + }, + }, + }, + givenBidderName: "bidderA", + setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(2.5, nil) }, + expectedBidPrice: 7.5, + }, + { + name: "Nil adjustment array, expect no change to the bid price", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + }, + BidType: openrtb_ext.BidTypeBanner, + }, + givenBidAdjustments: nil, + expectedBidPrice: 10.0, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + reqInfo := adapters.ExtraRequestInfo{} + if test.givenBidAdjustments != nil { + mockConversions := &mockConversions{} + test.setMock(&mockConversions.Mock) + reqInfo = adapters.NewExtraRequestInfo(mockConversions) + } + + bidPrice := getAndApplyAdjustmentArray(test.givenBidAdjustments, test.givenBidInfo, test.givenBidderName, givenFrom, &reqInfo) + assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") + }) + } +} + +type mockConversions struct { + mock.Mock +} + +func (m mockConversions) GetRate(from string, to string) (float64, error) { + args := m.Called(from, to) + return args.Get(0).(float64), args.Error(1) +} + +func (m mockConversions) GetRates() *map[string]map[string]float64 { + args := m.Called() + return args.Get(0).(*map[string]map[string]float64) +} + +func TestMergeBidAdjustments(t *testing.T) { + currency := "USD" + + testCases := []struct { + name string + givenRequestWrapper *openrtb_ext.RequestWrapper + givenAccount *config.Account + expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments + }{ + { + name: "Different Bidder Names for Bid Adjustments Present in Request and Account", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + { + name: "Same Bidder Name and DealIDs for Bid Adjustments Present in Request and Account. Request should take precedence", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"audio":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Audio: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Audio: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + }, + { + name: "Same Bidder Name, different DealIDs for Bid Adjustments Present in Request and Account.", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video":{"bidderA":{"dealId":[{ "adjtype": "static", "value": 3.00, "currency": "USD"}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Video: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Video: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 3.00, Currency: ¤cy}}, + "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + { + name: "Different bidder names, request comes with CPM bid adjustment", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"native":{"bidderA":{"dealId":[{"adjtype": "cpm", "value": 0.18, "currency": "USD"}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Native: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Native: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 0.18, Currency: ¤cy}}, + }, + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + { + name: "Account has Banner Adjustment, Request has Video Adjustment", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + Video: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + }, + { + name: "Request has nil ExtPrebid, Account has Banner Adjustment", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"ext":{"bidder": {}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + mergedBidAdj, err := mergeBidAdjustments(test.givenRequestWrapper, test.givenAccount.BidAdjustments) + assert.NoError(t, err, "Unexpected error received") + assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) + }) + } +} + +func TestProcessBidAdjustments(t *testing.T) { + testCases := []struct { + name string + givenRequestWrapper *openrtb_ext.RequestWrapper + givenAccount *config.Account + expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments + }{ + { + name: "Valid Request and Account Adjustments with different bidder names, should properly merge", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + { + name: "Invalid Request Adjustment, Expect Nil Merged Adjustments", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 200}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: &openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: nil, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + mergedBidAdj, err := processBidAdjustments(test.givenRequestWrapper, test.givenAccount.BidAdjustments) + assert.NoError(t, err, "Unexpected error received") + assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) + }) + } +} diff --git a/exchange/bidder.go b/exchange/bidder.go index 9ee576d474f..343fe2fecdc 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io" - "math" "net/http" "net/http/httptrace" "regexp" @@ -75,8 +74,6 @@ const ( Gzip string = "GZIP" ) -const amountOfDecimalPlaces float64 = 4.0 - // AdaptBidder converts an adapters.Bidder into an exchange.AdaptedBidder. // // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter" @@ -684,43 +681,3 @@ func compressToGZIP(requestBody []byte) []byte { w.Close() return b.Bytes() } - -func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) float64 { - if adjArray == nil { - return bidPrice - } - originalBidPrice := bidPrice - - for _, adjustment := range adjArray { - if adjustment.AdjType == openrtb_ext.AdjTypeMultiplier { - bidPrice = bidPrice * adjustment.Value - } else if adjustment.AdjType == openrtb_ext.AdjTypeCpm { - convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, *adjustment.Currency) - if err != nil { - return originalBidPrice - } - bidPrice = bidPrice - convertedVal - } else if adjustment.AdjType == openrtb_ext.AdjTypeStatic { - convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, *adjustment.Currency) - if err != nil { - return originalBidPrice - } - bidPrice = convertedVal - } - } - if bidPrice <= 0 { - return originalBidPrice - } - roundTo := math.Pow(10, amountOfDecimalPlaces) - return math.Round(bidPrice*roundTo) / roundTo // Returns Bid Price rounded to 4 decimal places -} - -func getAndApplyAdjustmentArray(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) float64 { - adjArray := []openrtb_ext.Adjustments{} - if bidAdjustments != nil { - adjArray = bidAdjustments.GetAdjustmentArray(bidInfo.BidType, bidderName, bidInfo.Bid.DealID) - } else { - return bidInfo.Bid.Price - } - return applyAdjustmentArray(adjArray, bidInfo.Bid.Price, currency, reqInfo) -} diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index d28cfa79241..544b7fe5cce 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -2970,143 +2970,3 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { }) assert.Equal(t, wantSeatBids, seatBids) } - -func TestApplyAdjustmentArray(t *testing.T) { - var ( - givenFrom string = "EUR" - givenTo string = "USA" - ) - - testCases := []struct { - name string - givenAdjustments []openrtb_ext.Adjustments - setMock func(m *mock.Mock) - givenBidPrice float64 - expectedBidPrice float64 - }{ - { - name: "CPM adj type, value after currency conversion should be subtracted from given bid price. Price should round to 4 decimal places", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: &givenTo}}, - givenBidPrice: 10.58687, - setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(2.5, nil) }, - expectedBidPrice: 8.0869, - }, - { - name: "Static adj type, value after currency conversion should be the bid price", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeStatic, Value: 4.0, Currency: &givenTo}}, - givenBidPrice: 10.0, - setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(5.0, nil) }, - expectedBidPrice: 20.0, - }, - { - name: "Multiplier adj type with no currency conversion", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeMultiplier, Value: 3.0}}, - givenBidPrice: 10.0, - expectedBidPrice: 30.0, - }, - { - name: "Bid price after conversions is equal or less than 0, should return original bid price instead", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeMultiplier, Value: -1.0}}, - givenBidPrice: 10.0, - expectedBidPrice: 10.0, - }, - { - name: "Nil adjustment array", - givenAdjustments: nil, - givenBidPrice: 10.0, - expectedBidPrice: 10.0, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - reqInfo := adapters.ExtraRequestInfo{} - if test.givenAdjustments != nil && test.givenAdjustments[0].Currency != nil { - mockConversions := &mockConversions{} - test.setMock(&mockConversions.Mock) - reqInfo = adapters.NewExtraRequestInfo(mockConversions) - } - - bidPrice := applyAdjustmentArray(test.givenAdjustments, test.givenBidPrice, givenFrom, &reqInfo) - assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") - }) - } -} - -func TestGetAndApplyAdjustmentArray(t *testing.T) { - var ( - givenFrom string = "EUR" - givenTo string = "USA" - ) - - testCases := []struct { - name string - givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments - givenBidderName openrtb_ext.BidderName - givenBidInfo *adapters.TypedBid - setMock func(m *mock.Mock) - expectedBidPrice float64 - }{ - { - name: "Valid Bid Adjustments, CPM adj type, function should Get and Apply the adjustment properly", - givenBidInfo: &adapters.TypedBid{ - Bid: &openrtb2.Bid{ - Price: 10.0, - DealID: "dealId", - }, - BidType: openrtb_ext.BidTypeBanner, - }, - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: &givenTo}}, - }, - }, - }, - }, - givenBidderName: "bidderA", - setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(2.5, nil) }, - expectedBidPrice: 7.5, - }, - { - name: "Nil adjustment array, expect no change to the bid price", - givenBidInfo: &adapters.TypedBid{ - Bid: &openrtb2.Bid{ - Price: 10.0, - }, - BidType: openrtb_ext.BidTypeBanner, - }, - givenBidAdjustments: nil, - expectedBidPrice: 10.0, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - reqInfo := adapters.ExtraRequestInfo{} - if test.givenBidAdjustments != nil { - mockConversions := &mockConversions{} - test.setMock(&mockConversions.Mock) - reqInfo = adapters.NewExtraRequestInfo(mockConversions) - } - - bidPrice := getAndApplyAdjustmentArray(test.givenBidAdjustments, test.givenBidInfo, test.givenBidderName, givenFrom, &reqInfo) - assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") - }) - } -} - -type mockConversions struct { - mock.Mock -} - -func (m mockConversions) GetRate(from string, to string) (float64, error) { - args := m.Called(from, to) - return args.Get(0).(float64), args.Error(1) -} - -func (m mockConversions) GetRates() *map[string]map[string]float64 { - args := m.Called() - return args.Get(0).(*map[string]map[string]float64) -} diff --git a/exchange/exchange.go b/exchange/exchange.go index c8ed661b445..7c7ff716cab 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -1515,72 +1515,3 @@ func setErrorMessageSecureMarkup(validationType string) string { } return "" } - -func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { - reqExt, err := req.GetRequestExt() - if err != nil { - return nil, err - } - extPrebid := reqExt.GetPrebid() - - if extPrebid == nil && acctBidAdjs == nil { - return nil, nil - } - if extPrebid == nil && acctBidAdjs != nil { - return acctBidAdjs, nil - } - if extPrebid != nil && acctBidAdjs == nil { - return extPrebid.BidAdjustments, nil - } - - if extPrebid.BidAdjustments.MediaType.Banner != nil && acctBidAdjs.MediaType.Banner != nil { - extPrebid.BidAdjustments.MediaType.Banner = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Banner, acctBidAdjs.MediaType.Banner) - } else if acctBidAdjs.MediaType.Banner != nil { - extPrebid.BidAdjustments.MediaType.Banner = acctBidAdjs.MediaType.Banner - } - - if extPrebid.BidAdjustments.MediaType.Video != nil && acctBidAdjs.MediaType.Video != nil { - extPrebid.BidAdjustments.MediaType.Video = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Video, acctBidAdjs.MediaType.Video) - } else if acctBidAdjs.MediaType.Video != nil { - extPrebid.BidAdjustments.MediaType.Video = acctBidAdjs.MediaType.Video - } - - if extPrebid.BidAdjustments.MediaType.Native != nil && acctBidAdjs.MediaType.Native != nil { - extPrebid.BidAdjustments.MediaType.Native = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Native, acctBidAdjs.MediaType.Native) - } else if acctBidAdjs.MediaType.Native != nil { - extPrebid.BidAdjustments.MediaType.Native = acctBidAdjs.MediaType.Native - } - - if extPrebid.BidAdjustments.MediaType.Audio != nil && acctBidAdjs.MediaType.Audio != nil { - extPrebid.BidAdjustments.MediaType.Audio = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Audio, acctBidAdjs.MediaType.Audio) - } else if acctBidAdjs.MediaType.Audio != nil { - extPrebid.BidAdjustments.MediaType.Audio = acctBidAdjs.MediaType.Audio - } - return extPrebid.BidAdjustments, nil -} - -func mergeAdjustmentsForMediaType(reqAdjMap map[string]map[string][]openrtb_ext.Adjustments, accountAdjMap map[string]map[string][]openrtb_ext.Adjustments) map[string]map[string][]openrtb_ext.Adjustments { - for bidderName, dealIdToAdjustmentsMap := range accountAdjMap { - if _, ok := reqAdjMap[bidderName]; ok { - for dealID, acctAdjustmentsArray := range accountAdjMap[bidderName] { - if _, okay := reqAdjMap[bidderName][dealID]; !okay { - reqAdjMap[bidderName][dealID] = acctAdjustmentsArray - } - } - } else { - reqAdjMap[bidderName] = dealIdToAdjustmentsMap - } - } - return reqAdjMap -} - -func processBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { - mergedBidAdj, err := mergeBidAdjustments(req, acctBidAdjs) - if err != nil { - return nil, err - } - if valid := mergedBidAdj.ValidateBidAdjustments(); !valid { - mergedBidAdj = nil - } - return mergedBidAdj, err -} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index d1d56de6ab3..7dac9bdab45 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -5455,202 +5455,6 @@ func TestModulesCanBeExecutedForMultipleBiddersSimultaneously(t *testing.T) { } } -func TestMergeBidAdjustments(t *testing.T) { - currency := "USD" - - testCases := []struct { - name string - givenRequestWrapper *openrtb_ext.RequestWrapper - givenAccount *config.Account - expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments - }{ - { - name: "Different Bidder Names for Bid Adjustments Present in Request and Account", - givenRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, - }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - { - name: "Same Bidder Name and DealIDs for Bid Adjustments Present in Request and Account. Request should take precedence", - givenRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, - }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - }, - }, - }, - }, - { - name: "Same Bidder Name, different DealIDs for Bid Adjustments Present in Request and Account.", - givenRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "static", "value": 3.00, "currency": "USD"}]}}}}}}`)}, - }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 3.00, Currency: ¤cy}}, - "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - { - name: "Different bidder names, request comes with CPM bid adjustment", - givenRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{"adjtype": "cpm", "value": 0.18, "currency": "USD"}]}}}}}}`)}, - }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 0.18, Currency: ¤cy}}, - }, - "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - mergedBidAdj, err := mergeBidAdjustments(test.givenRequestWrapper, test.givenAccount.BidAdjustments) - assert.NoError(t, err, "Unexpected error received") - assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) - }) - } -} - -func TestProcessBidAdjustments(t *testing.T) { - testCases := []struct { - name string - givenRequestWrapper *openrtb_ext.RequestWrapper - givenAccount *config.Account - expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments - }{ - { - name: "Valid Request and Account Adjustments with different bidder names, should properly merge", - givenRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, - }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - { - name: "Invalid Request Adjustment, Expect Nil Merged Adjustments", - givenRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 200}]}}}}}}`)}, - }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - expectedBidAdjustments: nil, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - mergedBidAdj, err := processBidAdjustments(test.givenRequestWrapper, test.givenAccount.BidAdjustments) - assert.NoError(t, err, "Unexpected error received") - assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) - }) - } -} - type TestApplyHookMutationsBuilder struct { hooks.EmptyPlanBuilder } From 05a132fad254aa904f36010ad9995461ce989b8c Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Tue, 18 Apr 2023 10:54:35 -0700 Subject: [PATCH 11/27] Address module error --- exchange/bidadjustments_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchange/bidadjustments_test.go b/exchange/bidadjustments_test.go index 7cc97a86a8a..b9548917646 100644 --- a/exchange/bidadjustments_test.go +++ b/exchange/bidadjustments_test.go @@ -3,7 +3,7 @@ package exchange import ( "testing" - "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" From 3d4a341baa7db4f93af07f4b3c248cce365c7eb1 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Wed, 19 Apr 2023 17:22:40 -0700 Subject: [PATCH 12/27] Address half of feedback, remove ptrs,update tests --- endpoints/openrtb2/auction.go | 2 +- endpoints/openrtb2/test_utils.go | 4 +- exchange/bidadjustments.go | 4 +- exchange/bidadjustments_test.go | 46 +++++++++++----------- openrtb_ext/request.go | 26 ++++++------- openrtb_ext/request_test.go | 65 ++++++++++++++++---------------- 6 files changed, 73 insertions(+), 74 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 2e974afa977..306ad6c681e 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1555,7 +1555,7 @@ func validateRequestExt(req *openrtb_ext.RequestWrapper) []error { reqExt.SetPrebid(prebid) errs = append(errs, &errortypes.Warning{ WarningCode: errortypes.BidAdjustmentWarningCode, - Message: "Bid Adjustment From Request Was Invalid", + Message: "bid adjustment from request was invalid", }) } diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index 6d38049aa80..7df9565a4d4 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -936,7 +936,7 @@ type mockBidderHandler struct { BidderName string `json:"bidderName"` Currency string `json:"currency"` Price float64 `json:"price"` - DealId string `json:"dealid"` + DealID string `json:"dealid"` } func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { @@ -974,7 +974,7 @@ func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { ID: b.BidderName + "-bid", ImpID: openrtb2Request.Imp[0].ID, Price: b.Price, - DealID: b.DealId, + DealID: b.DealID, }, }, Seat: b.BidderName, diff --git a/exchange/bidadjustments.go b/exchange/bidadjustments.go index 7cba088e3e3..0caef1fee5e 100644 --- a/exchange/bidadjustments.go +++ b/exchange/bidadjustments.go @@ -19,13 +19,13 @@ func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, if adjustment.AdjType == openrtb_ext.AdjTypeMultiplier { bidPrice = bidPrice * adjustment.Value } else if adjustment.AdjType == openrtb_ext.AdjTypeCpm { - convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, *adjustment.Currency) + convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, adjustment.Currency) if err != nil { return originalBidPrice } bidPrice = bidPrice - convertedVal } else if adjustment.AdjType == openrtb_ext.AdjTypeStatic { - convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, *adjustment.Currency) + convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, adjustment.Currency) if err != nil { return originalBidPrice } diff --git a/exchange/bidadjustments_test.go b/exchange/bidadjustments_test.go index b9548917646..2659dddedfd 100644 --- a/exchange/bidadjustments_test.go +++ b/exchange/bidadjustments_test.go @@ -26,14 +26,14 @@ func TestApplyAdjustmentArray(t *testing.T) { }{ { name: "CPM adj type, value after currency conversion should be subtracted from given bid price. Price should round to 4 decimal places", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: &givenTo}}, + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: givenTo}}, givenBidPrice: 10.58687, setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(2.5, nil) }, expectedBidPrice: 8.0869, }, { name: "Static adj type, value after currency conversion should be the bid price", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeStatic, Value: 4.0, Currency: &givenTo}}, + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeStatic, Value: 4.0, Currency: givenTo}}, givenBidPrice: 10.0, setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(5.0, nil) }, expectedBidPrice: 20.0, @@ -61,7 +61,7 @@ func TestApplyAdjustmentArray(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { reqInfo := adapters.ExtraRequestInfo{} - if test.givenAdjustments != nil && test.givenAdjustments[0].Currency != nil { + if test.givenAdjustments != nil && test.givenAdjustments[0].Currency != "" { mockConversions := &mockConversions{} test.setMock(&mockConversions.Mock) reqInfo = adapters.NewExtraRequestInfo(mockConversions) @@ -97,10 +97,10 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, }, givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: &givenTo}}, + "dealId": []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: givenTo}}, }, }, }, @@ -152,8 +152,6 @@ func (m mockConversions) GetRates() *map[string]map[string]float64 { } func TestMergeBidAdjustments(t *testing.T) { - currency := "USD" - testCases := []struct { name string givenRequestWrapper *openrtb_ext.RequestWrapper @@ -167,7 +165,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderB": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, @@ -177,7 +175,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderA": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, @@ -196,7 +194,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Audio: map[string]map[string][]openrtb_ext.Adjustments{ "bidderA": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, @@ -206,7 +204,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Audio: map[string]map[string][]openrtb_ext.Adjustments{ "bidderA": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, @@ -222,7 +220,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Video: map[string]map[string][]openrtb_ext.Adjustments{ "bidderA": { "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, @@ -232,10 +230,10 @@ func TestMergeBidAdjustments(t *testing.T) { }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Video: map[string]map[string][]openrtb_ext.Adjustments{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 3.00, Currency: ¤cy}}, + "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 3.00, Currency: "USD"}}, "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, }, }, @@ -249,7 +247,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Native: map[string]map[string][]openrtb_ext.Adjustments{ "bidderB": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, @@ -259,10 +257,10 @@ func TestMergeBidAdjustments(t *testing.T) { }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Native: map[string]map[string][]openrtb_ext.Adjustments{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 0.18, Currency: ¤cy}}, + "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 0.18, Currency: "USD"}}, }, "bidderB": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, @@ -278,7 +276,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderB": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, @@ -288,7 +286,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderB": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, @@ -309,7 +307,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderB": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, @@ -319,7 +317,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderB": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, @@ -353,7 +351,7 @@ func TestProcessBidAdjustments(t *testing.T) { }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderB": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, @@ -363,7 +361,7 @@ func TestProcessBidAdjustments(t *testing.T) { }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderA": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, @@ -382,7 +380,7 @@ func TestProcessBidAdjustments(t *testing.T) { }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: &openrtb_ext.MediaType{ + MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderB": { "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index ca6652b71c5..4ff32bf743a 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -159,7 +159,7 @@ type ExtRequestPrebidCacheVAST struct { } type ExtRequestPrebidBidAdjustments struct { - MediaType *MediaType `json:"mediatype,omitempty"` + MediaType MediaType `json:"mediatype,omitempty"` } // How the map is strucutred @@ -177,7 +177,7 @@ type MediaType struct { type Adjustments struct { AdjType string `json:"adjtype,omitempty"` Value float64 `json:"value,omitempty"` - Currency *string `json:"currency,omitempty"` + Currency string `json:"currency,omitempty"` } // ExtRequestTargeting defines the contract for bidrequest.ext.prebid.targeting @@ -402,17 +402,17 @@ func (bidAdjustments *ExtRequestPrebidBidAdjustments) ValidateBidAdjustments() b if bidAdjustments == nil { return true } - if bidAdjustments.MediaType.Banner != nil { - return findAndValidateAdjustment(bidAdjustments.MediaType.Banner) + if bidAdjustments.MediaType.Banner != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Banner) { + return false } - if bidAdjustments.MediaType.Video != nil { - return findAndValidateAdjustment(bidAdjustments.MediaType.Video) + if bidAdjustments.MediaType.Audio != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Audio) { + return false } - if bidAdjustments.MediaType.Audio != nil { - return findAndValidateAdjustment(bidAdjustments.MediaType.Audio) + if bidAdjustments.MediaType.Video != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Video) { + return false } - if bidAdjustments.MediaType.Native != nil { - return findAndValidateAdjustment(bidAdjustments.MediaType.Native) + if bidAdjustments.MediaType.Native != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Native) { + return false } return true } @@ -433,16 +433,16 @@ func findAndValidateAdjustment(bidAdjMap map[string]map[string][]Adjustments) bo func validateAdjustment(adjustment Adjustments) bool { switch adjustment.AdjType { case AdjTypeCpm: - if adjustment.Currency != nil && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { + if adjustment.Currency != "" && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { return true } case AdjTypeMultiplier: if adjustment.Value >= 0 && adjustment.Value < 100 { return true } - adjustment.Currency = nil + adjustment.Currency = "" case AdjTypeStatic: - if adjustment.Currency != nil && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { + if adjustment.Currency != "" && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { return true } } diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go index 0311b1ea3e2..68b5fb302d4 100644 --- a/openrtb_ext/request_test.go +++ b/openrtb_ext/request_test.go @@ -143,8 +143,6 @@ var validGranularityTests []granularityTestData = []granularityTestData{ } func TestValidateBidAdjustments(t *testing.T) { - currency := "USD" - testCases := []struct { name string givenBidAdjustments *ExtRequestPrebidBidAdjustments @@ -153,7 +151,7 @@ func TestValidateBidAdjustments(t *testing.T) { { name: "Valid single bid adjustment multiplier", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ Banner: map[string]map[string][]Adjustments{ "bidderA": { "dealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, @@ -164,9 +162,14 @@ func TestValidateBidAdjustments(t *testing.T) { expected: true, }, { - name: "Invalid bid adjustment value, negative", + name: "Valid banner bid adjustment, invalid video bid adjustment, negative value", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ + Banner: map[string]map[string][]Adjustments{ + "bidderA": { + "dealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, Video: map[string]map[string][]Adjustments{ "bidderA": { "dealId": []Adjustments{{AdjType: "multiplier", Value: -1.0}}, @@ -179,7 +182,7 @@ func TestValidateBidAdjustments(t *testing.T) { { name: "Invalid bid adjustment value, too big", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ Audio: map[string]map[string][]Adjustments{ "bidderA": { "dealId": []Adjustments{{AdjType: "multiplier", Value: 200}}, @@ -192,10 +195,10 @@ func TestValidateBidAdjustments(t *testing.T) { { name: "Valid bid adjustment cpm", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ Native: map[string]map[string][]Adjustments{ "bidderA": { - "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ¤cy}}, + "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, }, }, }, @@ -205,10 +208,10 @@ func TestValidateBidAdjustments(t *testing.T) { { name: "Invalid CPM bid adjustment, no currency given", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ Banner: map[string]map[string][]Adjustments{ "bidderA": { - "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: nil}}, + "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ""}}, }, }, }, @@ -218,10 +221,10 @@ func TestValidateBidAdjustments(t *testing.T) { { name: "Invalid CPM bid adjustment, negative value", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ - Banner: map[string]map[string][]Adjustments{ + MediaType: MediaType{ + Native: map[string]map[string][]Adjustments{ "bidderA": { - "dealId": []Adjustments{{AdjType: "cpm", Value: -1.0, Currency: ¤cy}}, + "dealId": []Adjustments{{AdjType: "cpm", Value: -1.0, Currency: "USD"}}, }, }, }, @@ -231,10 +234,10 @@ func TestValidateBidAdjustments(t *testing.T) { { name: "Valid static bid adjustment", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ Banner: map[string]map[string][]Adjustments{ "bidderA": { - "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: ¤cy}}, + "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, }, }, }, @@ -244,10 +247,10 @@ func TestValidateBidAdjustments(t *testing.T) { { name: "Invalid static bid adjustment, no currency", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ Banner: map[string]map[string][]Adjustments{ "bidderA": { - "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: nil}}, + "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: ""}}, }, }, }, @@ -257,10 +260,10 @@ func TestValidateBidAdjustments(t *testing.T) { { name: "Invalid static bid adjustment, negative value", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ Banner: map[string]map[string][]Adjustments{ "bidderA": { - "dealId": []Adjustments{{AdjType: "static", Value: -1.0, Currency: ¤cy}}, + "dealId": []Adjustments{{AdjType: "static", Value: -1.0, Currency: "USD"}}, }, }, }, @@ -283,8 +286,6 @@ func TestValidateBidAdjustments(t *testing.T) { } func TestGetAdjustmentArray(t *testing.T) { - currency := "USD" - testCases := []struct { name string givenBidAdjustments *ExtRequestPrebidBidAdjustments @@ -296,7 +297,7 @@ func TestGetAdjustmentArray(t *testing.T) { { name: "One bid adjustment, should return same adjustment", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ Banner: map[string]map[string][]Adjustments{ "bidderA": { "dealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, @@ -312,15 +313,15 @@ func TestGetAdjustmentArray(t *testing.T) { { name: "Multiple bid adjs, WildCard MediaType, non WildCard should have precedence", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ Audio: map[string]map[string][]Adjustments{ "bidderA": { - "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: ¤cy}}, + "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, }, }, WildCard: map[string]map[string][]Adjustments{ "bidderA": { - "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ¤cy}}, + "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, }, }, }, @@ -328,15 +329,15 @@ func TestGetAdjustmentArray(t *testing.T) { givenBidType: BidTypeAudio, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []Adjustments{{AdjType: "static", Value: 1.0, Currency: ¤cy}}, + expected: []Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, }, { name: "Single bid adj, Deal ID doesn't match, but wildcard present, should return given bid adj", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ Native: map[string]map[string][]Adjustments{ "bidderA": { - "*": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ¤cy}}, + "*": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, }, }, }, @@ -344,12 +345,12 @@ func TestGetAdjustmentArray(t *testing.T) { givenBidType: BidTypeNative, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ¤cy}}, + expected: []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, }, { name: "Single bid adj, Not matched bidder, but WildCard, should return given bid adj", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ Video: map[string]map[string][]Adjustments{ "*": { "dealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, @@ -365,7 +366,7 @@ func TestGetAdjustmentArray(t *testing.T) { { name: "WildCard bidder and dealId, should return given bid adj", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ WildCard: map[string]map[string][]Adjustments{ "*": { "*": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, @@ -381,7 +382,7 @@ func TestGetAdjustmentArray(t *testing.T) { { name: "WildCard bidder, but dealId doesn't match given, should return nil", givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: &MediaType{ + MediaType: MediaType{ WildCard: map[string]map[string][]Adjustments{ "bidderB": { "diffDealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, From fdf5edc84da38dc9bb94e9ca3b0b3653051dcd18 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Thu, 20 Apr 2023 14:38:04 -0700 Subject: [PATCH 13/27] Updates to adj array apply --- .../exemplary/bidadjustments-cpm.json | 2 +- .../exemplary/bidadjustments-static.json | 6 +- exchange/bidadjustments.go | 24 +++---- exchange/bidadjustments_test.go | 69 ++++++++++++++----- exchange/bidder.go | 4 +- 5 files changed, 71 insertions(+), 34 deletions(-) diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json index e33684ae20b..aec5b9d2468 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json @@ -77,7 +77,7 @@ { "id": "appnexus-bid", "impid": "some-impression-id", - "price": 17.5, + "price": 10.0, "ext": {"origbidcpm": 20, "origbidcur": "USD", "prebid": {"meta": {"adaptercode": "appnexus"}, "type": "banner"}} } ], diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json index 85ececef243..34ca842869d 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json @@ -50,7 +50,7 @@ "some-deal-id": [ { "adjtype": "static", - "value": 5.0, + "value": 10.0, "currency": "EUR" } ] @@ -77,7 +77,7 @@ { "id": "appnexus-bid", "impid": "some-impression-id", - "price": 2.5, + "price": 10.0, "ext": {"origbidcpm": 2, "origbidcur": "USD", "prebid": {"meta": {"adaptercode": "appnexus"}, "type": "banner"}} } ], @@ -85,7 +85,7 @@ } ], "bidid":"test bid id", - "cur":"USD", + "cur":"EUR", "nbr":0 }, "expectedReturnCode": 200 diff --git a/exchange/bidadjustments.go b/exchange/bidadjustments.go index 0caef1fee5e..74b59d27806 100644 --- a/exchange/bidadjustments.go +++ b/exchange/bidadjustments.go @@ -9,43 +9,41 @@ import ( const roundTo float64 = 10000 // Rounds to 4 Decimal Places -func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) float64 { +func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { if adjArray == nil { - return bidPrice + return bidPrice, currency } originalBidPrice := bidPrice + originalCurrency := currency for _, adjustment := range adjArray { if adjustment.AdjType == openrtb_ext.AdjTypeMultiplier { bidPrice = bidPrice * adjustment.Value } else if adjustment.AdjType == openrtb_ext.AdjTypeCpm { - convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, adjustment.Currency) + convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, adjustment.Currency, currency) // Convert Adjustment to Bid Currency if err != nil { - return originalBidPrice + return originalBidPrice, currency } bidPrice = bidPrice - convertedVal } else if adjustment.AdjType == openrtb_ext.AdjTypeStatic { - convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, currency, adjustment.Currency) - if err != nil { - return originalBidPrice - } - bidPrice = convertedVal + bidPrice = adjustment.Value + currency = adjustment.Currency } } roundedBidPrice := math.Round(bidPrice*roundTo) / roundTo // Returns Bid Price rounded to 4 decimal places if roundedBidPrice <= 0 { - return originalBidPrice + return originalBidPrice, originalCurrency } - return roundedBidPrice + return roundedBidPrice, currency } -func getAndApplyAdjustmentArray(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) float64 { +func getAndApplyAdjustmentArray(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { adjArray := []openrtb_ext.Adjustments{} if bidAdjustments != nil { adjArray = bidAdjustments.GetAdjustmentArray(bidInfo.BidType, bidderName, bidInfo.Bid.DealID) } else { - return bidInfo.Bid.Price + return bidInfo.Bid.Price, currency } return applyAdjustmentArray(adjArray, bidInfo.Bid.Price, currency, reqInfo) } diff --git a/exchange/bidadjustments_test.go b/exchange/bidadjustments_test.go index 2659dddedfd..676ced9c6a5 100644 --- a/exchange/bidadjustments_test.go +++ b/exchange/bidadjustments_test.go @@ -13,8 +13,8 @@ import ( func TestApplyAdjustmentArray(t *testing.T) { var ( - givenFrom string = "EUR" - givenTo string = "USA" + adjCur string = "EUR" + bidCur string = "USA" ) testCases := []struct { @@ -23,60 +23,70 @@ func TestApplyAdjustmentArray(t *testing.T) { setMock func(m *mock.Mock) givenBidPrice float64 expectedBidPrice float64 + expectedCurrency string }{ { name: "CPM adj type, value after currency conversion should be subtracted from given bid price. Price should round to 4 decimal places", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: givenTo}}, + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: adjCur}}, givenBidPrice: 10.58687, - setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(2.5, nil) }, + setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, expectedBidPrice: 8.0869, + expectedCurrency: bidCur, }, { - name: "Static adj type, value after currency conversion should be the bid price", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeStatic, Value: 4.0, Currency: givenTo}}, + name: "Static adj type, that static value should be the bid price, currency should be updated as well", + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeStatic, Value: 4.0, Currency: adjCur}}, givenBidPrice: 10.0, - setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(5.0, nil) }, - expectedBidPrice: 20.0, + setMock: nil, + expectedBidPrice: 4.0, + expectedCurrency: adjCur, }, { name: "Multiplier adj type with no currency conversion", givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeMultiplier, Value: 3.0}}, givenBidPrice: 10.0, + setMock: nil, expectedBidPrice: 30.0, + expectedCurrency: bidCur, }, { name: "Bid price after conversions is equal or less than 0, should return original bid price instead", givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeMultiplier, Value: -1.0}}, givenBidPrice: 10.0, + setMock: nil, expectedBidPrice: 10.0, + expectedCurrency: bidCur, }, { name: "Nil adjustment array", givenAdjustments: nil, givenBidPrice: 10.0, + setMock: nil, expectedBidPrice: 10.0, + expectedCurrency: bidCur, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { reqInfo := adapters.ExtraRequestInfo{} - if test.givenAdjustments != nil && test.givenAdjustments[0].Currency != "" { + if test.setMock != nil { mockConversions := &mockConversions{} test.setMock(&mockConversions.Mock) reqInfo = adapters.NewExtraRequestInfo(mockConversions) } - bidPrice := applyAdjustmentArray(test.givenAdjustments, test.givenBidPrice, givenFrom, &reqInfo) + bidPrice, currencyAfterAdjustment := applyAdjustmentArray(test.givenAdjustments, test.givenBidPrice, bidCur, &reqInfo) assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") + assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") }) } } func TestGetAndApplyAdjustmentArray(t *testing.T) { var ( - givenFrom string = "EUR" - givenTo string = "USA" + adjCur string = "EUR" + bidCur string = "USA" ) testCases := []struct { @@ -86,6 +96,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { givenBidInfo *adapters.TypedBid setMock func(m *mock.Mock) expectedBidPrice float64 + expectedCurrency string }{ { name: "Valid Bid Adjustments, CPM adj type, function should Get and Apply the adjustment properly", @@ -100,14 +111,38 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: givenTo}}, + "dealId": []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: adjCur}}, }, }, }, }, givenBidderName: "bidderA", - setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USA").Return(2.5, nil) }, + setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, expectedBidPrice: 7.5, + expectedCurrency: bidCur, + }, + { + name: "Valid Bid Adjustments, static adj type, function should Get and Apply the adjustment properly", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + BidType: openrtb_ext.BidTypeBanner, + }, + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeStatic, Value: 5.0, Currency: adjCur}}, + }, + }, + }, + }, + givenBidderName: "bidderA", + setMock: nil, + expectedBidPrice: 5.0, + expectedCurrency: adjCur, }, { name: "Nil adjustment array, expect no change to the bid price", @@ -119,20 +154,22 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { }, givenBidAdjustments: nil, expectedBidPrice: 10.0, + expectedCurrency: bidCur, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { reqInfo := adapters.ExtraRequestInfo{} - if test.givenBidAdjustments != nil { + if test.setMock != nil { mockConversions := &mockConversions{} test.setMock(&mockConversions.Mock) reqInfo = adapters.NewExtraRequestInfo(mockConversions) } - bidPrice := getAndApplyAdjustmentArray(test.givenBidAdjustments, test.givenBidInfo, test.givenBidderName, givenFrom, &reqInfo) + bidPrice, currencyAfterAdjustment := getAndApplyAdjustmentArray(test.givenBidAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo) assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") + assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") }) } } diff --git a/exchange/bidder.go b/exchange/bidder.go index 66a8c1ecd68..5bb8d1f7aee 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -332,10 +332,11 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde } originalBidCpm := 0.0 + currencyAfterAdjustments := "" if bidResponse.Bids[i].Bid != nil { originalBidCpm = bidResponse.Bids[i].Bid.Price bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate - bidResponse.Bids[i].Bid.Price = getAndApplyAdjustmentArray(bidAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, bidResponse.Currency, reqInfo) + bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = getAndApplyAdjustmentArray(bidAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo) } if _, ok := seatBidMap[bidderName]; !ok { @@ -358,6 +359,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde OriginalBidCPM: originalBidCpm, OriginalBidCur: bidResponse.Currency, }) + seatBidMap[bidderName].Currency = currencyAfterAdjustments } } else { // If no conversions found, do not handle the bid From b7567942d5a8da97e63982ac1c892e5a6b754bed Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Thu, 20 Apr 2023 15:46:18 -0700 Subject: [PATCH 14/27] Move bid adjustment logic to own package --- .../bidadjustments.go | 23 +- .../bidadjustments_test.go | 18 +- bidadjustments/get.go | 55 ++++ bidadjustments/get_test.go | 128 +++++++++ bidadjustments/validate.go | 58 ++++ bidadjustments/validate_test.go | 151 ++++++++++ endpoints/openrtb2/auction.go | 3 +- exchange/bidder.go | 3 +- exchange/exchange.go | 3 +- openrtb_ext/request.go | 111 -------- openrtb_ext/request_test.go | 263 ------------------ 11 files changed, 422 insertions(+), 394 deletions(-) rename {exchange => bidadjustments}/bidadjustments.go (88%) rename {exchange => bidadjustments}/bidadjustments_test.go (94%) create mode 100644 bidadjustments/get.go create mode 100644 bidadjustments/get_test.go create mode 100644 bidadjustments/validate.go create mode 100644 bidadjustments/validate_test.go diff --git a/exchange/bidadjustments.go b/bidadjustments/bidadjustments.go similarity index 88% rename from exchange/bidadjustments.go rename to bidadjustments/bidadjustments.go index 74b59d27806..1ac869879af 100644 --- a/exchange/bidadjustments.go +++ b/bidadjustments/bidadjustments.go @@ -1,4 +1,4 @@ -package exchange +package bidadjustments import ( "math" @@ -7,6 +7,13 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) +const ( + AdjTypeCpm = "cpm" + AdjTypeMultiplier = "multiplier" + AdjTypeStatic = "static" + AdjWildCard = "*" +) + const roundTo float64 = 10000 // Rounds to 4 Decimal Places func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { @@ -17,15 +24,15 @@ func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, originalCurrency := currency for _, adjustment := range adjArray { - if adjustment.AdjType == openrtb_ext.AdjTypeMultiplier { + if adjustment.AdjType == AdjTypeMultiplier { bidPrice = bidPrice * adjustment.Value - } else if adjustment.AdjType == openrtb_ext.AdjTypeCpm { + } else if adjustment.AdjType == AdjTypeCpm { convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, adjustment.Currency, currency) // Convert Adjustment to Bid Currency if err != nil { return originalBidPrice, currency } bidPrice = bidPrice - convertedVal - } else if adjustment.AdjType == openrtb_ext.AdjTypeStatic { + } else if adjustment.AdjType == AdjTypeStatic { bidPrice = adjustment.Value currency = adjustment.Currency } @@ -38,10 +45,10 @@ func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, return roundedBidPrice, currency } -func getAndApplyAdjustmentArray(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { +func GetAndApplyAdjustmentArray(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { adjArray := []openrtb_ext.Adjustments{} if bidAdjustments != nil { - adjArray = bidAdjustments.GetAdjustmentArray(bidInfo.BidType, bidderName, bidInfo.Bid.DealID) + adjArray = GetAdjustmentArray(bidAdjustments, bidInfo.BidType, bidderName, bidInfo.Bid.DealID) } else { return bidInfo.Bid.Price, currency } @@ -106,12 +113,12 @@ func mergeAdjustmentsForMediaType(reqAdjMap map[string]map[string][]openrtb_ext. return reqAdjMap } -func processBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { +func ProcessBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { mergedBidAdj, err := mergeBidAdjustments(req, acctBidAdjs) if err != nil { return nil, err } - if valid := mergedBidAdj.ValidateBidAdjustments(); !valid { + if !Validate(mergedBidAdj) { mergedBidAdj = nil } return mergedBidAdj, err diff --git a/exchange/bidadjustments_test.go b/bidadjustments/bidadjustments_test.go similarity index 94% rename from exchange/bidadjustments_test.go rename to bidadjustments/bidadjustments_test.go index 676ced9c6a5..405e47ba0ed 100644 --- a/exchange/bidadjustments_test.go +++ b/bidadjustments/bidadjustments_test.go @@ -1,4 +1,4 @@ -package exchange +package bidadjustments import ( "testing" @@ -27,7 +27,7 @@ func TestApplyAdjustmentArray(t *testing.T) { }{ { name: "CPM adj type, value after currency conversion should be subtracted from given bid price. Price should round to 4 decimal places", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: adjCur}}, + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 1.0, Currency: adjCur}}, givenBidPrice: 10.58687, setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, expectedBidPrice: 8.0869, @@ -35,7 +35,7 @@ func TestApplyAdjustmentArray(t *testing.T) { }, { name: "Static adj type, that static value should be the bid price, currency should be updated as well", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeStatic, Value: 4.0, Currency: adjCur}}, + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: AdjTypeStatic, Value: 4.0, Currency: adjCur}}, givenBidPrice: 10.0, setMock: nil, expectedBidPrice: 4.0, @@ -43,7 +43,7 @@ func TestApplyAdjustmentArray(t *testing.T) { }, { name: "Multiplier adj type with no currency conversion", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeMultiplier, Value: 3.0}}, + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 3.0}}, givenBidPrice: 10.0, setMock: nil, expectedBidPrice: 30.0, @@ -51,7 +51,7 @@ func TestApplyAdjustmentArray(t *testing.T) { }, { name: "Bid price after conversions is equal or less than 0, should return original bid price instead", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeMultiplier, Value: -1.0}}, + givenAdjustments: []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: -1.0}}, givenBidPrice: 10.0, setMock: nil, expectedBidPrice: 10.0, @@ -111,7 +111,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeCpm, Value: 1.0, Currency: adjCur}}, + "dealId": []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 1.0, Currency: adjCur}}, }, }, }, @@ -134,7 +134,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { MediaType: openrtb_ext.MediaType{ Banner: map[string]map[string][]openrtb_ext.Adjustments{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: openrtb_ext.AdjTypeStatic, Value: 5.0, Currency: adjCur}}, + "dealId": []openrtb_ext.Adjustments{{AdjType: AdjTypeStatic, Value: 5.0, Currency: adjCur}}, }, }, }, @@ -167,7 +167,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { reqInfo = adapters.NewExtraRequestInfo(mockConversions) } - bidPrice, currencyAfterAdjustment := getAndApplyAdjustmentArray(test.givenBidAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo) + bidPrice, currencyAfterAdjustment := GetAndApplyAdjustmentArray(test.givenBidAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo) assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") }) @@ -432,7 +432,7 @@ func TestProcessBidAdjustments(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - mergedBidAdj, err := processBidAdjustments(test.givenRequestWrapper, test.givenAccount.BidAdjustments) + mergedBidAdj, err := ProcessBidAdjustments(test.givenRequestWrapper, test.givenAccount.BidAdjustments) assert.NoError(t, err, "Unexpected error received") assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) }) diff --git a/bidadjustments/get.go b/bidadjustments/get.go new file mode 100644 index 00000000000..eae1a64037d --- /dev/null +++ b/bidadjustments/get.go @@ -0,0 +1,55 @@ +package bidadjustments + +import "github.com/prebid/prebid-server/openrtb_ext" + +func GetAdjustmentArray(bidAdj *openrtb_ext.ExtRequestPrebidBidAdjustments, bidType openrtb_ext.BidType, bidderName openrtb_ext.BidderName, dealID string) []openrtb_ext.Adjustments { + if bidAdj.MediaType.Banner != nil && bidType == openrtb_ext.BidTypeBanner { + if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Banner, bidderName.String(), dealID); adjArray != nil { + return adjArray + } + } + if bidAdj.MediaType.Video != nil && bidType == openrtb_ext.BidTypeVideo { + if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Video, bidderName.String(), dealID); adjArray != nil { + return adjArray + } + } + if bidAdj.MediaType.Audio != nil && bidType == openrtb_ext.BidTypeAudio { + if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Audio, bidderName.String(), dealID); adjArray != nil { + return adjArray + } + + } + if bidAdj.MediaType.Native != nil && bidType == openrtb_ext.BidTypeNative { + if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Native, bidderName.String(), dealID); adjArray != nil { + return adjArray + } + } + if bidAdj.MediaType.WildCard != nil { + if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.WildCard, bidderName.String(), dealID); adjArray != nil { + return adjArray + } + } + return nil +} + +// Priority For Returning Adjustment Array Based on Passed BidderName and DealID +// #1: Are able to match bidderName and dealID +// #2: Are able to match bidderName and dealID field is WildCard +// #3: Bidder field is WildCard and are able to match DealID +// #4: Wildcard bidder and wildcard dealID +func getAdjustmentArrayForMediaType(bidAdjMap map[string]map[string][]openrtb_ext.Adjustments, bidderName string, dealID string) []openrtb_ext.Adjustments { + if _, ok := bidAdjMap[bidderName]; ok { + if _, ok := bidAdjMap[bidderName][dealID]; ok { + return bidAdjMap[bidderName][dealID] + } else if _, ok := bidAdjMap[bidderName][AdjWildCard]; ok { + return bidAdjMap[bidderName][AdjWildCard] + } + } else if _, ok := bidAdjMap[AdjWildCard]; ok { + if _, ok := bidAdjMap[AdjWildCard][dealID]; ok { + return bidAdjMap[AdjWildCard][dealID] + } else if _, ok := bidAdjMap[AdjWildCard][AdjWildCard]; ok { + return bidAdjMap[AdjWildCard][AdjWildCard] + } + } + return nil +} diff --git a/bidadjustments/get_test.go b/bidadjustments/get_test.go new file mode 100644 index 00000000000..8d11b16b918 --- /dev/null +++ b/bidadjustments/get_test.go @@ -0,0 +1,128 @@ +package bidadjustments + +import ( + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestGetAdjustmentArray(t *testing.T) { + testCases := []struct { + name string + givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments + givenBidType openrtb_ext.BidType + givenBidderName openrtb_ext.BidderName + givenDealId string + expected []openrtb_ext.Adjustments + }{ + { + name: "One bid adjustment, should return same adjustment", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + { + name: "Multiple bid adjs, WildCard MediaType, non WildCard should have precedence", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Audio: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, + }, + }, + WildCard: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, + }, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeAudio, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, + }, + { + name: "Single bid adj, Deal ID doesn't match, but wildcard present, should return given bid adj", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Native: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "*": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, + }, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeNative, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, + }, + { + name: "Single bid adj, Not matched bidder, but WildCard, should return given bid adj", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Video: map[string]map[string][]openrtb_ext.Adjustments{ + "*": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeVideo, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + { + name: "WildCard bidder and dealId, should return given bid adj", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + WildCard: map[string]map[string][]openrtb_ext.Adjustments{ + "*": { + "*": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeVideo, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + { + name: "WildCard bidder, but dealId doesn't match given, should return nil", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + WildCard: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeVideo, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: nil, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + adjArray := GetAdjustmentArray(test.givenBidAdjustments, test.givenBidType, test.givenBidderName, test.givenDealId) + assert.Equal(t, test.expected, adjArray, "Adjustment Array doesn't match") + }) + } +} diff --git a/bidadjustments/validate.go b/bidadjustments/validate.go new file mode 100644 index 00000000000..fca728c2a41 --- /dev/null +++ b/bidadjustments/validate.go @@ -0,0 +1,58 @@ +package bidadjustments + +import ( + "math" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func Validate(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) bool { + if bidAdjustments == nil { + return true + } + if bidAdjustments.MediaType.Banner != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Banner) { + return false + } + if bidAdjustments.MediaType.Audio != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Audio) { + return false + } + if bidAdjustments.MediaType.Video != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Video) { + return false + } + if bidAdjustments.MediaType.Native != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Native) { + return false + } + return true +} + +func findAndValidateAdjustment(bidAdjMap map[string]map[string][]openrtb_ext.Adjustments) bool { + for bidderName := range bidAdjMap { + for dealId := range bidAdjMap[bidderName] { + for _, adjustment := range bidAdjMap[bidderName][dealId] { + if !validateAdjustment(adjustment) { + return false + } + } + } + } + return true +} + +func validateAdjustment(adjustment openrtb_ext.Adjustments) bool { + switch adjustment.AdjType { + case AdjTypeCpm: + if adjustment.Currency != "" && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { + return true + } + case AdjTypeMultiplier: + if adjustment.Value >= 0 && adjustment.Value < 100 { + return true + } + adjustment.Currency = "" + case AdjTypeStatic: + if adjustment.Currency != "" && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { + return true + } + } + return false +} diff --git a/bidadjustments/validate_test.go b/bidadjustments/validate_test.go new file mode 100644 index 00000000000..33f7d3d25fb --- /dev/null +++ b/bidadjustments/validate_test.go @@ -0,0 +1,151 @@ +package bidadjustments + +import ( + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestValidateBidAdjustments(t *testing.T) { + testCases := []struct { + name string + givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments + expected bool + }{ + { + name: "Valid single bid adjustment multiplier", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + expected: true, + }, + { + name: "Valid banner bid adjustment, invalid video bid adjustment, negative value", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + Video: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: -1.0}}, + }, + }, + }, + }, + expected: false, + }, + { + name: "Invalid bid adjustment value, too big", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Audio: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 200}}, + }, + }, + }, + }, + expected: false, + }, + { + name: "Valid bid adjustment cpm", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Native: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, + }, + }, + }, + }, + expected: true, + }, + { + name: "Invalid CPM bid adjustment, no currency given", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ""}}, + }, + }, + }, + }, + expected: false, + }, + { + name: "Invalid CPM bid adjustment, negative value", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Native: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: -1.0, Currency: "USD"}}, + }, + }, + }, + }, + expected: false, + }, + { + name: "Valid static bid adjustment", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, + }, + }, + }, + }, + expected: true, + }, + { + name: "Invalid static bid adjustment, no currency", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 1.0, Currency: ""}}, + }, + }, + }, + }, + expected: false, + }, + { + name: "Invalid static bid adjustment, negative value", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: -1.0, Currency: "USD"}}, + }, + }, + }, + }, + expected: false, + }, + { + name: "Nil Bid Adjustment", + givenBidAdjustments: nil, + expected: true, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actual := Validate(test.givenBidAdjustments) + assert.Equal(t, test.expected, actual, "Boolean didn't match") + }) + } +} diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 306ad6c681e..cf18fb11508 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -24,6 +24,7 @@ import ( nativeRequests "github.com/prebid/openrtb/v19/native1/request" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/prebid-server/bidadjustments" "github.com/prebid/prebid-server/hooks" "github.com/prebid/prebid-server/ortb" "golang.org/x/net/publicsuffix" @@ -1550,7 +1551,7 @@ func validateRequestExt(req *openrtb_ext.RequestWrapper) []error { reqExt.SetPrebid(prebid) } - if valid := prebid.BidAdjustments.ValidateBidAdjustments(); !valid { + if !bidadjustments.Validate(prebid.BidAdjustments) { prebid.BidAdjustments = nil reqExt.SetPrebid(prebid) errs = append(errs, &errortypes.Warning{ diff --git a/exchange/bidder.go b/exchange/bidder.go index 5bb8d1f7aee..1b3c0f400f8 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -16,6 +16,7 @@ import ( "time" "github.com/golang/glog" + "github.com/prebid/prebid-server/bidadjustments" "github.com/prebid/prebid-server/config/util" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/exchange/entities" @@ -336,7 +337,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if bidResponse.Bids[i].Bid != nil { originalBidCpm = bidResponse.Bids[i].Bid.Price bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate - bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = getAndApplyAdjustmentArray(bidAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo) + bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustments.GetAndApplyAdjustmentArray(bidAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo) } if _, ok := seatBidMap[bidderName]; !ok { diff --git a/exchange/exchange.go b/exchange/exchange.go index 4fb2203dd84..32eb2eb73df 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -16,6 +16,7 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adservertargeting" + "github.com/prebid/prebid-server/bidadjustments" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" @@ -290,7 +291,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * r.FirstPartyData = resolvedFPD } - mergedBidAdj, err := processBidAdjustments(r.BidRequestWrapper, r.Account.BidAdjustments) + mergedBidAdj, err := bidadjustments.ProcessBidAdjustments(r.BidRequestWrapper, r.Account.BidAdjustments) if err != nil { return nil, err } diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 4ff32bf743a..96999110499 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -3,7 +3,6 @@ package openrtb_ext import ( "encoding/json" "fmt" - "math" "github.com/prebid/openrtb/v19/openrtb2" ) @@ -31,13 +30,6 @@ const NativeExchangeSpecificLowerBound = 500 const MaxDecimalFigures int = 15 -const ( - AdjTypeCpm = "cpm" - AdjTypeMultiplier = "multiplier" - AdjTypeStatic = "static" - AdjWildCard = "*" -) - // ExtRequest defines the contract for bidrequest.ext type ExtRequest struct { Prebid ExtRequestPrebid `json:"prebid"` @@ -345,106 +337,3 @@ func (m ExtMultiBid) String() string { } return fmt.Sprintf("{Bidder:%s, Bidders:%v, MaxBids:%s, TargetBidderCodePrefix:%s}", m.Bidder, m.Bidders, maxBid, m.TargetBidderCodePrefix) } - -func (bidAdj *ExtRequestPrebidBidAdjustments) GetAdjustmentArray(bidType BidType, bidderName BidderName, dealID string) []Adjustments { - if bidAdj.MediaType.Banner != nil && bidType == BidTypeBanner { - if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Banner, bidderName.String(), dealID); adjArray != nil { - return adjArray - } - } - if bidAdj.MediaType.Video != nil && bidType == BidTypeVideo { - if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Video, bidderName.String(), dealID); adjArray != nil { - return adjArray - } - } - if bidAdj.MediaType.Audio != nil && bidType == BidTypeAudio { - if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Audio, bidderName.String(), dealID); adjArray != nil { - return adjArray - } - - } - if bidAdj.MediaType.Native != nil && bidType == BidTypeNative { - if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Native, bidderName.String(), dealID); adjArray != nil { - return adjArray - } - } - if bidAdj.MediaType.WildCard != nil { - if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.WildCard, bidderName.String(), dealID); adjArray != nil { - return adjArray - } - } - return nil -} - -// Priority For Returning Adjustment Array Based on Passed BidderName and DealID -// #1: Are able to match bidderName and dealID -// #2: Are able to match bidderName and dealID field is WildCard -// #3: Bidder field is WildCard and are able to match DealID -// #4: Wildcard bidder and wildcard dealID -func getAdjustmentArrayForMediaType(bidAdjMap map[string]map[string][]Adjustments, bidderName string, dealID string) []Adjustments { - if _, ok := bidAdjMap[bidderName]; ok { - if _, ok := bidAdjMap[bidderName][dealID]; ok { - return bidAdjMap[bidderName][dealID] - } else if _, ok := bidAdjMap[bidderName][AdjWildCard]; ok { - return bidAdjMap[bidderName][AdjWildCard] - } - } else if _, ok := bidAdjMap[AdjWildCard]; ok { - if _, ok := bidAdjMap[AdjWildCard][dealID]; ok { - return bidAdjMap[AdjWildCard][dealID] - } else if _, ok := bidAdjMap[AdjWildCard][AdjWildCard]; ok { - return bidAdjMap[AdjWildCard][AdjWildCard] - } - } - return nil -} - -func (bidAdjustments *ExtRequestPrebidBidAdjustments) ValidateBidAdjustments() bool { - if bidAdjustments == nil { - return true - } - if bidAdjustments.MediaType.Banner != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Banner) { - return false - } - if bidAdjustments.MediaType.Audio != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Audio) { - return false - } - if bidAdjustments.MediaType.Video != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Video) { - return false - } - if bidAdjustments.MediaType.Native != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Native) { - return false - } - return true -} - -func findAndValidateAdjustment(bidAdjMap map[string]map[string][]Adjustments) bool { - for bidderName := range bidAdjMap { - for dealId := range bidAdjMap[bidderName] { - for _, adjustment := range bidAdjMap[bidderName][dealId] { - if !validateAdjustment(adjustment) { - return false - } - } - } - } - return true -} - -func validateAdjustment(adjustment Adjustments) bool { - switch adjustment.AdjType { - case AdjTypeCpm: - if adjustment.Currency != "" && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { - return true - } - case AdjTypeMultiplier: - if adjustment.Value >= 0 && adjustment.Value < 100 { - return true - } - adjustment.Currency = "" - case AdjTypeStatic: - if adjustment.Currency != "" && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { - return true - } - } - return false -} diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go index 68b5fb302d4..8d1ecee22b3 100644 --- a/openrtb_ext/request_test.go +++ b/openrtb_ext/request_test.go @@ -141,266 +141,3 @@ var validGranularityTests []granularityTestData = []granularityTestData{ }, }, } - -func TestValidateBidAdjustments(t *testing.T) { - testCases := []struct { - name string - givenBidAdjustments *ExtRequestPrebidBidAdjustments - expected bool - }{ - { - name: "Valid single bid adjustment multiplier", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Banner: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - }, - }, - }, - expected: true, - }, - { - name: "Valid banner bid adjustment, invalid video bid adjustment, negative value", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Banner: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - }, - Video: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "multiplier", Value: -1.0}}, - }, - }, - }, - }, - expected: false, - }, - { - name: "Invalid bid adjustment value, too big", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Audio: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "multiplier", Value: 200}}, - }, - }, - }, - }, - expected: false, - }, - { - name: "Valid bid adjustment cpm", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Native: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, - }, - }, - }, - }, - expected: true, - }, - { - name: "Invalid CPM bid adjustment, no currency given", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Banner: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ""}}, - }, - }, - }, - }, - expected: false, - }, - { - name: "Invalid CPM bid adjustment, negative value", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Native: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "cpm", Value: -1.0, Currency: "USD"}}, - }, - }, - }, - }, - expected: false, - }, - { - name: "Valid static bid adjustment", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Banner: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, - }, - }, - }, - }, - expected: true, - }, - { - name: "Invalid static bid adjustment, no currency", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Banner: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: ""}}, - }, - }, - }, - }, - expected: false, - }, - { - name: "Invalid static bid adjustment, negative value", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Banner: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "static", Value: -1.0, Currency: "USD"}}, - }, - }, - }, - }, - expected: false, - }, - { - name: "Nil Bid Adjustment", - givenBidAdjustments: nil, - expected: true, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - actual := test.givenBidAdjustments.ValidateBidAdjustments() - assert.Equal(t, test.expected, actual, "Boolean didn't match") - }) - } -} - -func TestGetAdjustmentArray(t *testing.T) { - testCases := []struct { - name string - givenBidAdjustments *ExtRequestPrebidBidAdjustments - givenBidType BidType - givenBidderName BidderName - givenDealId string - expected []Adjustments - }{ - { - name: "One bid adjustment, should return same adjustment", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Banner: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - }, - }, - }, - givenBidType: BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - { - name: "Multiple bid adjs, WildCard MediaType, non WildCard should have precedence", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Audio: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, - }, - }, - WildCard: map[string]map[string][]Adjustments{ - "bidderA": { - "dealId": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, - }, - }, - }, - }, - givenBidType: BidTypeAudio, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, - }, - { - name: "Single bid adj, Deal ID doesn't match, but wildcard present, should return given bid adj", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Native: map[string]map[string][]Adjustments{ - "bidderA": { - "*": []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, - }, - }, - }, - }, - givenBidType: BidTypeNative, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, - }, - { - name: "Single bid adj, Not matched bidder, but WildCard, should return given bid adj", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - Video: map[string]map[string][]Adjustments{ - "*": { - "dealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - }, - }, - }, - givenBidType: BidTypeVideo, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - { - name: "WildCard bidder and dealId, should return given bid adj", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - WildCard: map[string]map[string][]Adjustments{ - "*": { - "*": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - }, - }, - }, - givenBidType: BidTypeVideo, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - { - name: "WildCard bidder, but dealId doesn't match given, should return nil", - givenBidAdjustments: &ExtRequestPrebidBidAdjustments{ - MediaType: MediaType{ - WildCard: map[string]map[string][]Adjustments{ - "bidderB": { - "diffDealId": []Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, - }, - }, - }, - givenBidType: BidTypeVideo, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: nil, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - adjArray := test.givenBidAdjustments.GetAdjustmentArray(test.givenBidType, test.givenBidderName, test.givenDealId) - assert.Equal(t, test.expected, adjArray, "Adjustment Array doesn't match") - }) - } -} From 7d6b4fc8d3bdf1775cce8d956745669ff2abc65a Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Sun, 23 Apr 2023 14:45:16 -0700 Subject: [PATCH 15/27] Big changes to GetAdjustmentArray, minor to merge --- bidadjustments/bidadjustments.go | 43 +++-- bidadjustments/bidadjustments_test.go | 228 ++++++++++++++++++++++---- bidadjustments/get.go | 78 ++++----- bidadjustments/get_test.go | 221 +++++++++++++++++-------- bidadjustments/validate.go | 3 + bidadjustments/validate_test.go | 15 +- exchange/bidder.go | 6 +- exchange/bidder_validate_bids.go | 4 +- exchange/bidder_validate_bids_test.go | 10 +- exchange/exchange.go | 7 +- exchange/exchange_test.go | 6 +- 11 files changed, 457 insertions(+), 164 deletions(-) diff --git a/bidadjustments/bidadjustments.go b/bidadjustments/bidadjustments.go index 1ac869879af..6d3adc8f00c 100644 --- a/bidadjustments/bidadjustments.go +++ b/bidadjustments/bidadjustments.go @@ -11,7 +11,8 @@ const ( AdjTypeCpm = "cpm" AdjTypeMultiplier = "multiplier" AdjTypeStatic = "static" - AdjWildCard = "*" + WildCard = "*" + PipeDelimiter = "|" ) const roundTo float64 = 10000 // Rounds to 4 Decimal Places @@ -45,16 +46,6 @@ func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, return roundedBidPrice, currency } -func GetAndApplyAdjustmentArray(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { - adjArray := []openrtb_ext.Adjustments{} - if bidAdjustments != nil { - adjArray = GetAdjustmentArray(bidAdjustments, bidInfo.BidType, bidderName, bidInfo.Bid.DealID) - } else { - return bidInfo.Bid.Price, currency - } - return applyAdjustmentArray(adjArray, bidInfo.Bid.Price, currency, reqInfo) -} - func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { reqExt, err := req.GetRequestExt() if err != nil { @@ -95,6 +86,12 @@ func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_e } else if acctBidAdjs.MediaType.Audio != nil { extPrebid.BidAdjustments.MediaType.Audio = acctBidAdjs.MediaType.Audio } + + if extPrebid.BidAdjustments.MediaType.WildCard != nil && acctBidAdjs.MediaType.WildCard != nil { + extPrebid.BidAdjustments.MediaType.WildCard = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.WildCard, acctBidAdjs.MediaType.WildCard) + } else if acctBidAdjs.MediaType.WildCard != nil { + extPrebid.BidAdjustments.MediaType.WildCard = acctBidAdjs.MediaType.WildCard + } return extPrebid.BidAdjustments, nil } @@ -123,3 +120,27 @@ func ProcessBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb } return mergedBidAdj, err } + +func GenerateMap(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) map[string][]openrtb_ext.Adjustments { + if bidAdjustments == nil { + return nil + } + ruleToAdjustmentMap := make(map[string][]openrtb_ext.Adjustments) + ruleToAdjustmentMap = generateMapForMediaType(bidAdjustments.MediaType.Banner, string(openrtb_ext.BidTypeBanner), ruleToAdjustmentMap) + ruleToAdjustmentMap = generateMapForMediaType(bidAdjustments.MediaType.Video, string(openrtb_ext.BidTypeVideo), ruleToAdjustmentMap) + ruleToAdjustmentMap = generateMapForMediaType(bidAdjustments.MediaType.Audio, string(openrtb_ext.BidTypeAudio), ruleToAdjustmentMap) + ruleToAdjustmentMap = generateMapForMediaType(bidAdjustments.MediaType.Native, string(openrtb_ext.BidTypeNative), ruleToAdjustmentMap) + ruleToAdjustmentMap = generateMapForMediaType(bidAdjustments.MediaType.WildCard, WildCard, ruleToAdjustmentMap) + + return ruleToAdjustmentMap +} + +func generateMapForMediaType(bidAdj map[string]map[string][]openrtb_ext.Adjustments, mediaType string, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustments) map[string][]openrtb_ext.Adjustments { + for bidderName := range bidAdj { + for dealID, adjArray := range bidAdj[bidderName] { + rule := mediaType + PipeDelimiter + bidderName + PipeDelimiter + dealID + ruleToAdjustmentMap[rule] = adjArray + } + } + return ruleToAdjustmentMap +} diff --git a/bidadjustments/bidadjustments_test.go b/bidadjustments/bidadjustments_test.go index 405e47ba0ed..745ac5e50a6 100644 --- a/bidadjustments/bidadjustments_test.go +++ b/bidadjustments/bidadjustments_test.go @@ -90,16 +90,16 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { ) testCases := []struct { - name string - givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments - givenBidderName openrtb_ext.BidderName - givenBidInfo *adapters.TypedBid - setMock func(m *mock.Mock) - expectedBidPrice float64 - expectedCurrency string + name string + givenRuleToAdjustments map[string][]openrtb_ext.Adjustments + givenBidderName openrtb_ext.BidderName + givenBidInfo *adapters.TypedBid + setMock func(m *mock.Mock) + expectedBidPrice float64 + expectedCurrency string }{ { - name: "Valid Bid Adjustments, CPM adj type, function should Get and Apply the adjustment properly", + name: "CPM adjustment type should be chosen, function should Get and Apply the adjustment properly", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 10.0, @@ -107,12 +107,18 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { }, BidType: openrtb_ext.BidTypeBanner, }, - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 1.0, Currency: adjCur}}, - }, + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "banner|bidderA|dealId": { + { + AdjType: AdjTypeCpm, + Value: 1.0, + Currency: adjCur, + }, + }, + "banner|bidderA|*": { + { + AdjType: AdjTypeMultiplier, + Value: 2.0, }, }, }, @@ -122,7 +128,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { expectedCurrency: bidCur, }, { - name: "Valid Bid Adjustments, static adj type, function should Get and Apply the adjustment properly", + name: "Static adj type should be chosen, function should Get and Apply the adjustment properly", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 10.0, @@ -130,31 +136,71 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { }, BidType: openrtb_ext.BidTypeBanner, }, - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: AdjTypeStatic, Value: 5.0, Currency: adjCur}}, - }, + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "*|bidderA|dealId": { + { + AdjType: AdjTypeCpm, + Value: 1.0, + Currency: adjCur, + }, + }, + "banner|bidderA|*": { + { + AdjType: AdjTypeStatic, + Value: 2.0, + Currency: adjCur, }, }, }, givenBidderName: "bidderA", setMock: nil, - expectedBidPrice: 5.0, + expectedBidPrice: 2.0, expectedCurrency: adjCur, }, { - name: "Nil adjustment array, expect no change to the bid price", + name: "Multiplier adj type should be chosen, function should Get and Apply the adjustment properly", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ - Price: 10.0, + Price: 10.0, + DealID: "dealId", }, BidType: openrtb_ext.BidTypeBanner, }, - givenBidAdjustments: nil, - expectedBidPrice: 10.0, - expectedCurrency: bidCur, + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "*|*|dealId": { + { + AdjType: AdjTypeCpm, + Value: 1.0, + Currency: adjCur, + }, + }, + "banner|*|*": { + { + AdjType: AdjTypeMultiplier, + Value: 2.0, + Currency: adjCur, + }, + }, + }, + givenBidderName: "bidderA", + setMock: nil, + expectedBidPrice: 20.0, + expectedCurrency: bidCur, + }, + { + name: "Nil rule to adjustments map, function should return provided bid info", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + BidType: openrtb_ext.BidTypeBanner, + }, + givenRuleToAdjustments: nil, + givenBidderName: "bidderA", + setMock: nil, + expectedBidPrice: 10.0, + expectedCurrency: bidCur, }, } @@ -167,7 +213,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { reqInfo = adapters.NewExtraRequestInfo(mockConversions) } - bidPrice, currencyAfterAdjustment := GetAndApplyAdjustmentArray(test.givenBidAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo) + bidPrice, currencyAfterAdjustment := GetAndApplyAdjustmentArray(test.givenRuleToAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo) assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") }) @@ -363,6 +409,37 @@ func TestMergeBidAdjustments(t *testing.T) { }, }, }, + { + name: "Account has Wildcard Adjustment, Request has Video Adjustment", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + WildCard: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + WildCard: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderB": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + }, + }, + Video: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + }, } for _, test := range testCases { @@ -374,6 +451,101 @@ func TestMergeBidAdjustments(t *testing.T) { } } +func TestGenerateMap(t *testing.T) { + testCases := []struct { + name string + givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments + expectedMap map[string][]openrtb_ext.Adjustments + }{ + { + name: "Simple example, only one adjustment given", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + }, + }, + }, + }, + expectedMap: map[string][]openrtb_ext.Adjustments{ + "banner|bidderA|dealId": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, + }, + }, + }, + }, + { + name: "Many adjustments given", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 1.1}}, + }, + "*": { + "diffDealId": []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 1.1, Currency: "USD"}}, + "*": []openrtb_ext.Adjustments{{AdjType: AdjTypeStatic, Value: 5.0, Currency: "USD"}}, + }, + }, + Video: map[string]map[string][]openrtb_ext.Adjustments{ + "*": { + "*": []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 1.1}, {AdjType: AdjTypeCpm, Value: 0.18, Currency: "USD"}}, + }, + }, + }, + }, + expectedMap: map[string][]openrtb_ext.Adjustments{ + "banner|bidderA|dealId": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, + }, + }, + "banner|*|diffDealId": { + { + AdjType: AdjTypeCpm, + Value: 1.1, + Currency: "USD", + }, + }, + "banner|*|*": { + { + AdjType: AdjTypeStatic, + Value: 5.0, + Currency: "USD", + }, + }, + "video|*|*": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, + }, + { + AdjType: AdjTypeCpm, + Value: 0.18, + Currency: "USD", + }, + }, + }, + }, + { + name: "Nil adjustments given", + givenBidAdjustments: nil, + expectedMap: nil, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + ruleToAdjustmentMap := GenerateMap(test.givenBidAdjustments) + assert.Equal(t, test.expectedMap, ruleToAdjustmentMap) + }) + } +} + func TestProcessBidAdjustments(t *testing.T) { testCases := []struct { name string diff --git a/bidadjustments/get.go b/bidadjustments/get.go index eae1a64037d..95ccce85981 100644 --- a/bidadjustments/get.go +++ b/bidadjustments/get.go @@ -1,54 +1,44 @@ package bidadjustments -import "github.com/prebid/prebid-server/openrtb_ext" +import ( + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" +) -func GetAdjustmentArray(bidAdj *openrtb_ext.ExtRequestPrebidBidAdjustments, bidType openrtb_ext.BidType, bidderName openrtb_ext.BidderName, dealID string) []openrtb_ext.Adjustments { - if bidAdj.MediaType.Banner != nil && bidType == openrtb_ext.BidTypeBanner { - if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Banner, bidderName.String(), dealID); adjArray != nil { - return adjArray - } - } - if bidAdj.MediaType.Video != nil && bidType == openrtb_ext.BidTypeVideo { - if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Video, bidderName.String(), dealID); adjArray != nil { - return adjArray - } +func GetAndApplyAdjustmentArray(ruleToAdjustments map[string][]openrtb_ext.Adjustments, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { + adjArray := []openrtb_ext.Adjustments{} + if ruleToAdjustments != nil { + adjArray = getAdjustmentArray(ruleToAdjustments, string(bidInfo.BidType), string(bidderName), bidInfo.Bid.DealID) + } else { + return bidInfo.Bid.Price, currency } - if bidAdj.MediaType.Audio != nil && bidType == openrtb_ext.BidTypeAudio { - if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Audio, bidderName.String(), dealID); adjArray != nil { - return adjArray - } + return applyAdjustmentArray(adjArray, bidInfo.Bid.Price, currency, reqInfo) +} +// Given the bid response information of bidType, bidderName, and dealID, we create the same format of combinations that's present in the key of the ruleToAdjustments map +// There's a max of 8 combinations that can be made with those pieces of bid information, so after discussion with team member, we decided to generate each combo, and check if it's present in the map +// The order of the array is ordered by priority from highest to lowest, so as soon as we find a match, that's the adjustment array we want to return +func getAdjustmentArray(ruleToAdjustments map[string][]openrtb_ext.Adjustments, bidType string, bidderName string, dealID string) []openrtb_ext.Adjustments { + var priorityRules []string + if dealID != "" { + priorityRules = append(priorityRules, bidType+PipeDelimiter+bidderName+PipeDelimiter+dealID) + priorityRules = append(priorityRules, bidType+PipeDelimiter+bidderName+PipeDelimiter+WildCard) + priorityRules = append(priorityRules, bidType+PipeDelimiter+WildCard+PipeDelimiter+dealID) + priorityRules = append(priorityRules, WildCard+PipeDelimiter+bidderName+PipeDelimiter+dealID) + priorityRules = append(priorityRules, bidType+PipeDelimiter+WildCard+PipeDelimiter+WildCard) + priorityRules = append(priorityRules, WildCard+PipeDelimiter+bidderName+PipeDelimiter+WildCard) + priorityRules = append(priorityRules, WildCard+PipeDelimiter+WildCard+PipeDelimiter+dealID) + priorityRules = append(priorityRules, WildCard+PipeDelimiter+WildCard+PipeDelimiter+WildCard) + } else { + priorityRules = append(priorityRules, bidType+PipeDelimiter+bidderName+PipeDelimiter+WildCard) + priorityRules = append(priorityRules, bidType+PipeDelimiter+WildCard+PipeDelimiter+WildCard) + priorityRules = append(priorityRules, WildCard+PipeDelimiter+bidderName+PipeDelimiter+WildCard) + priorityRules = append(priorityRules, WildCard+PipeDelimiter+WildCard+PipeDelimiter+WildCard) } - if bidAdj.MediaType.Native != nil && bidType == openrtb_ext.BidTypeNative { - if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.Native, bidderName.String(), dealID); adjArray != nil { - return adjArray - } - } - if bidAdj.MediaType.WildCard != nil { - if adjArray := getAdjustmentArrayForMediaType(bidAdj.MediaType.WildCard, bidderName.String(), dealID); adjArray != nil { - return adjArray - } - } - return nil -} -// Priority For Returning Adjustment Array Based on Passed BidderName and DealID -// #1: Are able to match bidderName and dealID -// #2: Are able to match bidderName and dealID field is WildCard -// #3: Bidder field is WildCard and are able to match DealID -// #4: Wildcard bidder and wildcard dealID -func getAdjustmentArrayForMediaType(bidAdjMap map[string]map[string][]openrtb_ext.Adjustments, bidderName string, dealID string) []openrtb_ext.Adjustments { - if _, ok := bidAdjMap[bidderName]; ok { - if _, ok := bidAdjMap[bidderName][dealID]; ok { - return bidAdjMap[bidderName][dealID] - } else if _, ok := bidAdjMap[bidderName][AdjWildCard]; ok { - return bidAdjMap[bidderName][AdjWildCard] - } - } else if _, ok := bidAdjMap[AdjWildCard]; ok { - if _, ok := bidAdjMap[AdjWildCard][dealID]; ok { - return bidAdjMap[AdjWildCard][dealID] - } else if _, ok := bidAdjMap[AdjWildCard][AdjWildCard]; ok { - return bidAdjMap[AdjWildCard][AdjWildCard] + for _, rule := range priorityRules { + if _, ok := ruleToAdjustments[rule]; ok { + return ruleToAdjustments[rule] } } return nil diff --git a/bidadjustments/get_test.go b/bidadjustments/get_test.go index 8d11b16b918..a56b49f7341 100644 --- a/bidadjustments/get_test.go +++ b/bidadjustments/get_test.go @@ -9,119 +9,212 @@ import ( func TestGetAdjustmentArray(t *testing.T) { testCases := []struct { - name string - givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments - givenBidType openrtb_ext.BidType - givenBidderName openrtb_ext.BidderName - givenDealId string - expected []openrtb_ext.Adjustments + name string + givenRuleToAdjustments map[string][]openrtb_ext.Adjustments + givenBidType openrtb_ext.BidType + givenBidderName openrtb_ext.BidderName + givenDealId string + expected []openrtb_ext.Adjustments }{ { - name: "One bid adjustment, should return same adjustment", - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, + name: "Priority #1 should be chosen", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "banner|bidderA|dealId": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, + }, + }, + "banner|bidderA|*": { + { + AdjType: AdjTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 1.1}}, + }, + { + name: "Priority #2 should be chosen", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "banner|bidderA|*": { + { + AdjType: AdjTypeStatic, + Value: 5.0, + }, + }, + "banner|*|dealId": { + { + AdjType: AdjTypeMultiplier, + Value: 2.0, }, }, }, givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeStatic, Value: 5.0}}, }, { - name: "Multiple bid adjs, WildCard MediaType, non WildCard should have precedence", - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Audio: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, - }, + name: "Priority #3 should be chosen", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "banner|*|dealId": { + { + AdjType: AdjTypeCpm, + Value: 3.0, + Currency: "USD", }, - WildCard: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, - }, + }, + "*|bidderA|dealId": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, }, }, }, - givenBidType: openrtb_ext.BidTypeAudio, + givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, + expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 3.0, Currency: "USD"}}, }, { - name: "Single bid adj, Deal ID doesn't match, but wildcard present, should return given bid adj", - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Native: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderA": { - "*": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, - }, + name: "Priority #4 should be chosen", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "*|bidderA|dealId": { + { + AdjType: AdjTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "banner|*|*": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, }, }, }, - givenBidType: openrtb_ext.BidTypeNative, + givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, + expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 3.0, Currency: "USD"}}, }, { - name: "Single bid adj, Not matched bidder, but WildCard, should return given bid adj", - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Video: map[string]map[string][]openrtb_ext.Adjustments{ - "*": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, + name: "Priority #5 should be chosen", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "banner|*|*": { + { + AdjType: AdjTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "*|bidderA|*": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, }, }, }, - givenBidType: openrtb_ext.BidTypeVideo, + givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 3.0, Currency: "USD"}}, }, { - name: "WildCard bidder and dealId, should return given bid adj", - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - WildCard: map[string]map[string][]openrtb_ext.Adjustments{ - "*": { - "*": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, + name: "Priority #6 should be chosen", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "*|bidderA|*": { + { + AdjType: AdjTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "*|*|dealId": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, }, }, }, - givenBidType: openrtb_ext.BidTypeVideo, + givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 3.0, Currency: "USD"}}, }, { - name: "WildCard bidder, but dealId doesn't match given, should return nil", - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - WildCard: map[string]map[string][]openrtb_ext.Adjustments{ - "bidderB": { - "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, - }, + name: "Priority #7 should be chosen", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "*|*|dealId": { + { + AdjType: AdjTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "*|*|*": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, }, }, }, - givenBidType: openrtb_ext.BidTypeVideo, + givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "dealId", - expected: nil, + expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority #8 should be chosen, given the provided info doesn't match the other provided rules", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "*|*|*": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, + }, + }, + "banner|bidderA|dealId": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderB", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 1.1}}, + }, + { + name: "No dealID given, should choose correct rule", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + "banner|bidderA|*": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, + }, + }, + "banner|*|*": { + { + AdjType: AdjTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "", + expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 1.1}}, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - adjArray := GetAdjustmentArray(test.givenBidAdjustments, test.givenBidType, test.givenBidderName, test.givenDealId) + adjArray := getAdjustmentArray(test.givenRuleToAdjustments, string(test.givenBidType), string(test.givenBidderName), test.givenDealId) assert.Equal(t, test.expected, adjArray, "Adjustment Array doesn't match") }) } diff --git a/bidadjustments/validate.go b/bidadjustments/validate.go index fca728c2a41..33f6f8f6074 100644 --- a/bidadjustments/validate.go +++ b/bidadjustments/validate.go @@ -22,6 +22,9 @@ func Validate(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) bool { if bidAdjustments.MediaType.Native != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Native) { return false } + if bidAdjustments.MediaType.WildCard != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.WildCard) { + return false + } return true } diff --git a/bidadjustments/validate_test.go b/bidadjustments/validate_test.go index 33f7d3d25fb..163c25fd47b 100644 --- a/bidadjustments/validate_test.go +++ b/bidadjustments/validate_test.go @@ -45,7 +45,7 @@ func TestValidateBidAdjustments(t *testing.T) { expected: false, }, { - name: "Invalid bid adjustment value, too big", + name: "Invalid bid adjustment value, too large", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ Audio: map[string]map[string][]openrtb_ext.Adjustments{ @@ -135,6 +135,19 @@ func TestValidateBidAdjustments(t *testing.T) { }, expected: false, }, + { + name: "Invalid wildcard adjustment, negative value", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + WildCard: map[string]map[string][]openrtb_ext.Adjustments{ + "bidderA": { + "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: -1.0, Currency: "USD"}}, + }, + }, + }, + }, + expected: false, + }, { name: "Nil Bid Adjustment", givenBidAdjustments: nil, diff --git a/exchange/bidder.go b/exchange/bidder.go index 1b3c0f400f8..04a0115534d 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -57,7 +57,7 @@ type AdaptedBidder interface { // // Any errors will be user-facing in the API. // Error messages should help publishers understand what might account for "bad" bids. - requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) ([]*entities.PbsOrtbSeatBid, []error) + 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 map[string][]openrtb_ext.Adjustments) ([]*entities.PbsOrtbSeatBid, []error) } // bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters @@ -116,7 +116,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, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) ([]*entities.PbsOrtbSeatBid, []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 map[string][]openrtb_ext.Adjustments) ([]*entities.PbsOrtbSeatBid, []error) { reject := hookExecutor.ExecuteBidderRequestStage(bidderRequest.BidRequest, string(bidderRequest.BidderName)) if reject != nil { return nil, []error{reject} @@ -337,7 +337,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if bidResponse.Bids[i].Bid != nil { originalBidCpm = bidResponse.Bids[i].Bid.Price bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate - bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustments.GetAndApplyAdjustmentArray(bidAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo) + bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustments.GetAndApplyAdjustmentArray(ruleToAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo) } if _, ok := seatBidMap[bidderName]; !ok { diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index 156868850ef..5e464d87de7 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -31,8 +31,8 @@ type validatedBidder struct { bidder AdaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) ([]*entities.PbsOrtbSeatBid, []error) { - seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor, bidAdjustments) +func (v *validatedBidder) 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 map[string][]openrtb_ext.Adjustments) ([]*entities.PbsOrtbSeatBid, []error) { + seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor, ruleToAdjustments) for _, seatBid := range seatBids { if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 { errs = append(errs, validationErrors...) diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index 542eab2b863..a7fa4bb4fe8 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -65,7 +65,7 @@ func TestAllValidBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, 4) assert.Len(t, errs, 0) @@ -135,7 +135,7 @@ func TestAllBadBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, 0) assert.Len(t, errs, 7) @@ -216,7 +216,7 @@ func TestMixedBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, 3) assert.Len(t, errs, 5) @@ -345,7 +345,7 @@ func TestCurrencyBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, &openrtb_ext.ExtRequestPrebidBidAdjustments{}) + seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, expectedValidBids) assert.Len(t, errs, expectedErrs) @@ -357,6 +357,6 @@ type mockAdaptedBidder struct { errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) ([]*entities.PbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustments) ([]*entities.PbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/exchange.go b/exchange/exchange.go index 32eb2eb73df..b773ad3b7cf 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -295,6 +295,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * if err != nil { return nil, err } + ruleToAdjustments := bidadjustments.GenerateMap(mergedBidAdj) bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExtPrebid) @@ -353,7 +354,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } else if r.Account.AlternateBidderCodes != nil { alternateBidderCodes = *r.Account.AlternateBidderCodes } - adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, mergedBidAdj) + adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, ruleToAdjustments) } var auc *auction @@ -615,7 +616,7 @@ func (e *exchange) getAllBids( alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, experiment *openrtb_ext.Experiment, hookExecutor hookexecution.StageExecutor, - upgradedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) ( + ruleToAdjustments map[string][]openrtb_ext.Adjustments) ( map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, *openrtb_ext.Fledge, @@ -653,7 +654,7 @@ func (e *exchange) getAllBids( addCallSignHeader: isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]), bidAdjustments: bidAdjustments, } - seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, upgradedBidAdjustments) + seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, ruleToAdjustments) // Add in time reporting elapsed := time.Since(start) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index ae6b0eddee4..5f3b776e6b3 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -5313,7 +5313,7 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) (seatBids []*entities.PbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustments) (seatBids []*entities.PbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok { if expectedRequest != nil { if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) { @@ -5371,7 +5371,7 @@ type capturingRequestBidder struct { req *openrtb2.BidRequest } -func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) (seatBid []*entities.PbsOrtbSeatBid, errs []error) { +func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustments) (seatBid []*entities.PbsOrtbSeatBid, errs []error) { b.req = bidderRequest.BidRequest return []*entities.PbsOrtbSeatBid{{}}, nil } @@ -5478,7 +5478,7 @@ func (e *emptyUsersync) HasAnyLiveSyncs() bool { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) (posb []*entities.PbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustments) (posb []*entities.PbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } From 6e1763938857358e7dbee303a3e4659ea2bc297b Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Mon, 24 Apr 2023 12:46:30 -0700 Subject: [PATCH 16/27] Address variety of feedback --- .../bidadjustment.go | 77 +++--- .../bidadjustment_test.go | 208 ++++++++--------- bidadjustment/get.go | 47 ++++ bidadjustment/get_test.go | 221 ++++++++++++++++++ {bidadjustments => bidadjustment}/validate.go | 15 +- .../validate_test.go | 68 +++--- bidadjustments/get.go | 45 ---- bidadjustments/get_test.go | 221 ------------------ endpoints/openrtb2/auction.go | 4 +- .../exemplary/bidadjustments-cpm.json | 89 ++++--- .../exemplary/bidadjustments-simple.json | 72 +++--- .../exemplary/bidadjustments-static.json | 90 ++++--- exchange/bidder.go | 8 +- exchange/bidder_validate_bids.go | 2 +- exchange/bidder_validate_bids_test.go | 2 +- exchange/exchange.go | 8 +- exchange/exchange_test.go | 6 +- openrtb_ext/request.go | 21 +- 18 files changed, 623 insertions(+), 581 deletions(-) rename bidadjustments/bidadjustments.go => bidadjustment/bidadjustment.go (66%) rename bidadjustments/bidadjustments_test.go => bidadjustment/bidadjustment_test.go (62%) create mode 100644 bidadjustment/get.go create mode 100644 bidadjustment/get_test.go rename {bidadjustments => bidadjustment}/validate.go (82%) rename {bidadjustments => bidadjustment}/validate_test.go (52%) delete mode 100644 bidadjustments/get.go delete mode 100644 bidadjustments/get_test.go diff --git a/bidadjustments/bidadjustments.go b/bidadjustment/bidadjustment.go similarity index 66% rename from bidadjustments/bidadjustments.go rename to bidadjustment/bidadjustment.go index 6d3adc8f00c..3e2b8284ace 100644 --- a/bidadjustments/bidadjustments.go +++ b/bidadjustment/bidadjustment.go @@ -1,4 +1,4 @@ -package bidadjustments +package bidadjustment import ( "math" @@ -8,37 +8,38 @@ import ( ) const ( - AdjTypeCpm = "cpm" - AdjTypeMultiplier = "multiplier" - AdjTypeStatic = "static" - WildCard = "*" - PipeDelimiter = "|" + AdjustmentTypeCpm = "cpm" + AdjustmentTypeMultiplier = "multiplier" + AdjustmentTypeStatic = "static" + WildCard = "*" + Delimiter = "|" ) -const roundTo float64 = 10000 // Rounds to 4 Decimal Places +const pricePrecision float64 = 10000 // Rounds to 4 Decimal Places -func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { - if adjArray == nil { +func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { + if adjustments == nil { return bidPrice, currency } originalBidPrice := bidPrice originalCurrency := currency - for _, adjustment := range adjArray { - if adjustment.AdjType == AdjTypeMultiplier { + for _, adjustment := range adjustments { + switch adjustment.Type { + case AdjustmentTypeMultiplier: bidPrice = bidPrice * adjustment.Value - } else if adjustment.AdjType == AdjTypeCpm { + case AdjustmentTypeCpm: convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, adjustment.Currency, currency) // Convert Adjustment to Bid Currency if err != nil { return originalBidPrice, currency } bidPrice = bidPrice - convertedVal - } else if adjustment.AdjType == AdjTypeStatic { + case AdjustmentTypeStatic: bidPrice = adjustment.Value currency = adjustment.Currency } } - roundedBidPrice := math.Round(bidPrice*roundTo) / roundTo // Returns Bid Price rounded to 4 decimal places + roundedBidPrice := math.Round(bidPrice*pricePrecision) / pricePrecision if roundedBidPrice <= 0 { return originalBidPrice, originalCurrency @@ -46,7 +47,18 @@ func applyAdjustmentArray(adjArray []openrtb_ext.Adjustments, bidPrice float64, return roundedBidPrice, currency } -func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { +func Process(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { + mergedBidAdj, err := merge(req, acctBidAdjs) + if err != nil { + return nil, err + } + if !Validate(mergedBidAdj) { + mergedBidAdj = nil + } + return mergedBidAdj, err +} + +func merge(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { reqExt, err := req.GetRequestExt() if err != nil { return nil, err @@ -95,7 +107,7 @@ func mergeBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_e return extPrebid.BidAdjustments, nil } -func mergeAdjustmentsForMediaType(reqAdjMap map[string]map[string][]openrtb_ext.Adjustments, accountAdjMap map[string]map[string][]openrtb_ext.Adjustments) map[string]map[string][]openrtb_ext.Adjustments { +func mergeAdjustmentsForMediaType(reqAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID, accountAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID) map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID { for bidderName, dealIdToAdjustmentsMap := range accountAdjMap { if _, ok := reqAdjMap[bidderName]; ok { for dealID, acctAdjustmentsArray := range accountAdjMap[bidderName] { @@ -110,36 +122,25 @@ func mergeAdjustmentsForMediaType(reqAdjMap map[string]map[string][]openrtb_ext. return reqAdjMap } -func ProcessBidAdjustments(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { - mergedBidAdj, err := mergeBidAdjustments(req, acctBidAdjs) - if err != nil { - return nil, err - } - if !Validate(mergedBidAdj) { - mergedBidAdj = nil - } - return mergedBidAdj, err -} - -func GenerateMap(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) map[string][]openrtb_ext.Adjustments { +func GenerateMap(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) map[string][]openrtb_ext.Adjustment { if bidAdjustments == nil { return nil } - ruleToAdjustmentMap := make(map[string][]openrtb_ext.Adjustments) - ruleToAdjustmentMap = generateMapForMediaType(bidAdjustments.MediaType.Banner, string(openrtb_ext.BidTypeBanner), ruleToAdjustmentMap) - ruleToAdjustmentMap = generateMapForMediaType(bidAdjustments.MediaType.Video, string(openrtb_ext.BidTypeVideo), ruleToAdjustmentMap) - ruleToAdjustmentMap = generateMapForMediaType(bidAdjustments.MediaType.Audio, string(openrtb_ext.BidTypeAudio), ruleToAdjustmentMap) - ruleToAdjustmentMap = generateMapForMediaType(bidAdjustments.MediaType.Native, string(openrtb_ext.BidTypeNative), ruleToAdjustmentMap) - ruleToAdjustmentMap = generateMapForMediaType(bidAdjustments.MediaType.WildCard, WildCard, ruleToAdjustmentMap) + ruleToAdjustmentMap := make(map[string][]openrtb_ext.Adjustment) + ruleToAdjustmentMap = populateMapForMediaType(bidAdjustments.MediaType.Banner, string(openrtb_ext.BidTypeBanner), ruleToAdjustmentMap) + ruleToAdjustmentMap = populateMapForMediaType(bidAdjustments.MediaType.Video, string(openrtb_ext.BidTypeVideo), ruleToAdjustmentMap) + ruleToAdjustmentMap = populateMapForMediaType(bidAdjustments.MediaType.Audio, string(openrtb_ext.BidTypeAudio), ruleToAdjustmentMap) + ruleToAdjustmentMap = populateMapForMediaType(bidAdjustments.MediaType.Native, string(openrtb_ext.BidTypeNative), ruleToAdjustmentMap) + ruleToAdjustmentMap = populateMapForMediaType(bidAdjustments.MediaType.WildCard, WildCard, ruleToAdjustmentMap) return ruleToAdjustmentMap } -func generateMapForMediaType(bidAdj map[string]map[string][]openrtb_ext.Adjustments, mediaType string, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustments) map[string][]openrtb_ext.Adjustments { +func populateMapForMediaType(bidAdj map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID, mediaType string, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) map[string][]openrtb_ext.Adjustment { for bidderName := range bidAdj { - for dealID, adjArray := range bidAdj[bidderName] { - rule := mediaType + PipeDelimiter + bidderName + PipeDelimiter + dealID - ruleToAdjustmentMap[rule] = adjArray + for dealID, adjustments := range bidAdj[bidderName] { + rule := mediaType + Delimiter + string(bidderName) + Delimiter + dealID + ruleToAdjustmentMap[rule] = adjustments } } return ruleToAdjustmentMap diff --git a/bidadjustments/bidadjustments_test.go b/bidadjustment/bidadjustment_test.go similarity index 62% rename from bidadjustments/bidadjustments_test.go rename to bidadjustment/bidadjustment_test.go index 745ac5e50a6..37d4cb96abf 100644 --- a/bidadjustments/bidadjustments_test.go +++ b/bidadjustment/bidadjustment_test.go @@ -1,4 +1,4 @@ -package bidadjustments +package bidadjustment import ( "testing" @@ -19,46 +19,46 @@ func TestApplyAdjustmentArray(t *testing.T) { testCases := []struct { name string - givenAdjustments []openrtb_ext.Adjustments + givenAdjustments []openrtb_ext.Adjustment setMock func(m *mock.Mock) givenBidPrice float64 expectedBidPrice float64 expectedCurrency string }{ { - name: "CPM adj type, value after currency conversion should be subtracted from given bid price. Price should round to 4 decimal places", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 1.0, Currency: adjCur}}, + name: "CpmAdjustment", + givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.0, Currency: adjCur}}, givenBidPrice: 10.58687, setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, expectedBidPrice: 8.0869, expectedCurrency: bidCur, }, { - name: "Static adj type, that static value should be the bid price, currency should be updated as well", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: AdjTypeStatic, Value: 4.0, Currency: adjCur}}, + name: "StaticAdjustment", + givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 4.0, Currency: adjCur}}, givenBidPrice: 10.0, setMock: nil, expectedBidPrice: 4.0, expectedCurrency: adjCur, }, { - name: "Multiplier adj type with no currency conversion", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 3.0}}, + name: "MultiplierAdjustment", + givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 3.0}}, givenBidPrice: 10.0, setMock: nil, expectedBidPrice: 30.0, expectedCurrency: bidCur, }, { - name: "Bid price after conversions is equal or less than 0, should return original bid price instead", - givenAdjustments: []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: -1.0}}, + name: "ReturnOriginalPrice", + givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: -1.0}}, givenBidPrice: 10.0, setMock: nil, expectedBidPrice: 10.0, expectedCurrency: bidCur, }, { - name: "Nil adjustment array", + name: "NilAdjustment", givenAdjustments: nil, givenBidPrice: 10.0, setMock: nil, @@ -76,7 +76,7 @@ func TestApplyAdjustmentArray(t *testing.T) { reqInfo = adapters.NewExtraRequestInfo(mockConversions) } - bidPrice, currencyAfterAdjustment := applyAdjustmentArray(test.givenAdjustments, test.givenBidPrice, bidCur, &reqInfo) + bidPrice, currencyAfterAdjustment := apply(test.givenAdjustments, test.givenBidPrice, bidCur, &reqInfo) assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") }) @@ -91,7 +91,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { testCases := []struct { name string - givenRuleToAdjustments map[string][]openrtb_ext.Adjustments + givenRuleToAdjustments map[string][]openrtb_ext.Adjustment givenBidderName openrtb_ext.BidderName givenBidInfo *adapters.TypedBid setMock func(m *mock.Mock) @@ -99,7 +99,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { expectedCurrency string }{ { - name: "CPM adjustment type should be chosen, function should Get and Apply the adjustment properly", + name: "CpmAdjustment", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 10.0, @@ -107,18 +107,18 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { }, BidType: openrtb_ext.BidTypeBanner, }, - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "banner|bidderA|dealId": { { - AdjType: AdjTypeCpm, + Type: AdjustmentTypeCpm, Value: 1.0, Currency: adjCur, }, }, "banner|bidderA|*": { { - AdjType: AdjTypeMultiplier, - Value: 2.0, + Type: AdjustmentTypeMultiplier, + Value: 2.0, }, }, }, @@ -128,7 +128,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { expectedCurrency: bidCur, }, { - name: "Static adj type should be chosen, function should Get and Apply the adjustment properly", + name: "StaticAdjustment", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 10.0, @@ -136,17 +136,17 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { }, BidType: openrtb_ext.BidTypeBanner, }, - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "*|bidderA|dealId": { { - AdjType: AdjTypeCpm, + Type: AdjustmentTypeCpm, Value: 1.0, Currency: adjCur, }, }, "banner|bidderA|*": { { - AdjType: AdjTypeStatic, + Type: AdjustmentTypeStatic, Value: 2.0, Currency: adjCur, }, @@ -158,7 +158,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { expectedCurrency: adjCur, }, { - name: "Multiplier adj type should be chosen, function should Get and Apply the adjustment properly", + name: "MultiplierAdjustment", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 10.0, @@ -166,17 +166,17 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { }, BidType: openrtb_ext.BidTypeBanner, }, - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "*|*|dealId": { { - AdjType: AdjTypeCpm, + Type: AdjustmentTypeCpm, Value: 1.0, Currency: adjCur, }, }, "banner|*|*": { { - AdjType: AdjTypeMultiplier, + Type: AdjustmentTypeMultiplier, Value: 2.0, Currency: adjCur, }, @@ -188,7 +188,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { expectedCurrency: bidCur, }, { - name: "Nil rule to adjustments map, function should return provided bid info", + name: "NilMap", givenBidInfo: &adapters.TypedBid{ Bid: &openrtb2.Bid{ Price: 10.0, @@ -213,7 +213,7 @@ func TestGetAndApplyAdjustmentArray(t *testing.T) { reqInfo = adapters.NewExtraRequestInfo(mockConversions) } - bidPrice, currencyAfterAdjustment := GetAndApplyAdjustmentArray(test.givenRuleToAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo) + bidPrice, currencyAfterAdjustment := GetAndApplyAdjustments(test.givenRuleToAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo) assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") }) @@ -242,16 +242,16 @@ func TestMergeBidAdjustments(t *testing.T) { expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments }{ { - name: "Different Bidder Names for Bid Adjustments Present in Request and Account", + name: "DiffBidderNames", givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, @@ -259,28 +259,28 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, }, }, { - name: "Same Bidder Name and DealIDs for Bid Adjustments Present in Request and Account. Request should take precedence", + name: "RequestTakesPrecedence", givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"audio":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Audio: map[string]map[string][]openrtb_ext.Adjustments{ + Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, @@ -288,25 +288,25 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Audio: map[string]map[string][]openrtb_ext.Adjustments{ + Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, }, }, }, }, { - name: "Same Bidder Name, different DealIDs for Bid Adjustments Present in Request and Account.", + name: "DiffDealIds", givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video":{"bidderA":{"dealId":[{ "adjtype": "static", "value": 3.00, "currency": "USD"}]}}}}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Video: map[string]map[string][]openrtb_ext.Adjustments{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "diffDealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, @@ -314,26 +314,26 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Video: map[string]map[string][]openrtb_ext.Adjustments{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 3.00, Currency: "USD"}}, - "diffDealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "static", Value: 3.00, Currency: "USD"}}, + "diffDealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, }, }, { - name: "Different bidder names, request comes with CPM bid adjustment", + name: "DiffBidderNamesCpm", givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"native":{"bidderA":{"dealId":[{"adjtype": "cpm", "value": 0.18, "currency": "USD"}]}}}}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Native: map[string]map[string][]openrtb_ext.Adjustments{ + Native: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, @@ -341,28 +341,28 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Native: map[string]map[string][]openrtb_ext.Adjustments{ + Native: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 0.18, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: "cpm", Value: 0.18, Currency: "USD"}}, }, "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, }, }, { - name: "Account has Banner Adjustment, Request has Video Adjustment", + name: "ReqAdjVideoAcctAdjBanner", givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, @@ -370,30 +370,30 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, - Video: map[string]map[string][]openrtb_ext.Adjustments{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, }, }, }, }, { - name: "Request has nil ExtPrebid, Account has Banner Adjustment", + name: "RequestNilPrebid", givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"ext":{"bidder": {}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, @@ -401,25 +401,25 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, }, }, { - name: "Account has Wildcard Adjustment, Request has Video Adjustment", + name: "AcctWildCardRequestVideo", givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - WildCard: map[string]map[string][]openrtb_ext.Adjustments{ + WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, @@ -427,14 +427,14 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - WildCard: map[string]map[string][]openrtb_ext.Adjustments{ + WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, - Video: map[string]map[string][]openrtb_ext.Adjustments{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, }, }, @@ -444,7 +444,7 @@ func TestMergeBidAdjustments(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - mergedBidAdj, err := mergeBidAdjustments(test.givenRequestWrapper, test.givenAccount.BidAdjustments) + mergedBidAdj, err := merge(test.givenRequestWrapper, test.givenAccount.BidAdjustments) assert.NoError(t, err, "Unexpected error received") assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) }) @@ -455,76 +455,76 @@ func TestGenerateMap(t *testing.T) { testCases := []struct { name string givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments - expectedMap map[string][]openrtb_ext.Adjustments + expectedMap map[string][]openrtb_ext.Adjustment }{ { - name: "Simple example, only one adjustment given", + name: "OneAdjustment", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, }, }, }, - expectedMap: map[string][]openrtb_ext.Adjustments{ + expectedMap: map[string][]openrtb_ext.Adjustment{ "banner|bidderA|dealId": { { - AdjType: AdjTypeMultiplier, - Value: 1.1, + Type: AdjustmentTypeMultiplier, + Value: 1.1, }, }, }, }, { - name: "Many adjustments given", + name: "MultipleAdjustments", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, "*": { - "diffDealId": []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 1.1, Currency: "USD"}}, - "*": []openrtb_ext.Adjustments{{AdjType: AdjTypeStatic, Value: 5.0, Currency: "USD"}}, + "diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.1, Currency: "USD"}}, + "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD"}}, }, }, - Video: map[string]map[string][]openrtb_ext.Adjustments{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "*": { - "*": []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 1.1}, {AdjType: AdjTypeCpm, Value: 0.18, Currency: "USD"}}, + "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}, {Type: AdjustmentTypeCpm, Value: 0.18, Currency: "USD"}}, }, }, }, }, - expectedMap: map[string][]openrtb_ext.Adjustments{ + expectedMap: map[string][]openrtb_ext.Adjustment{ "banner|bidderA|dealId": { { - AdjType: AdjTypeMultiplier, - Value: 1.1, + Type: AdjustmentTypeMultiplier, + Value: 1.1, }, }, "banner|*|diffDealId": { { - AdjType: AdjTypeCpm, + Type: AdjustmentTypeCpm, Value: 1.1, Currency: "USD", }, }, "banner|*|*": { { - AdjType: AdjTypeStatic, + Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD", }, }, "video|*|*": { { - AdjType: AdjTypeMultiplier, - Value: 1.1, + Type: AdjustmentTypeMultiplier, + Value: 1.1, }, { - AdjType: AdjTypeCpm, + Type: AdjustmentTypeCpm, Value: 0.18, Currency: "USD", }, @@ -532,7 +532,7 @@ func TestGenerateMap(t *testing.T) { }, }, { - name: "Nil adjustments given", + name: "NilAdjustments", givenBidAdjustments: nil, expectedMap: nil, }, @@ -554,16 +554,16 @@ func TestProcessBidAdjustments(t *testing.T) { expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments }{ { - name: "Valid Request and Account Adjustments with different bidder names, should properly merge", + name: "ValidReqAndAcctAdjustments", givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, @@ -571,28 +571,28 @@ func TestProcessBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, }, }, { - name: "Invalid Request Adjustment, Expect Nil Merged Adjustments", + name: "InvalidReqAdjustment", givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 200}]}}}}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, }, @@ -604,7 +604,7 @@ func TestProcessBidAdjustments(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - mergedBidAdj, err := ProcessBidAdjustments(test.givenRequestWrapper, test.givenAccount.BidAdjustments) + mergedBidAdj, err := Process(test.givenRequestWrapper, test.givenAccount.BidAdjustments) assert.NoError(t, err, "Unexpected error received") assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) }) diff --git a/bidadjustment/get.go b/bidadjustment/get.go new file mode 100644 index 00000000000..71fe4a0c990 --- /dev/null +++ b/bidadjustment/get.go @@ -0,0 +1,47 @@ +package bidadjustment + +import ( + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const maxNumOfCombos = 8 + +func GetAndApplyAdjustments(ruleToAdjustments map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { + adjustments := []openrtb_ext.Adjustment{} + if ruleToAdjustments != nil { + adjustments = get(ruleToAdjustments, string(bidInfo.BidType), string(bidderName), bidInfo.Bid.DealID) + } else { + return bidInfo.Bid.Price, currency + } + return apply(adjustments, bidInfo.Bid.Price, currency, reqInfo) +} + +// get() should return the highest priority slice of adjustments from the map that we can match with the given bid info +// given the bid info, we create the same format of combinations that's present in the key of the ruleToAdjustments map +// the slice is ordered by priority from highest to lowest, as soon as we find a match, we return that slice +func get(ruleToAdjustments map[string][]openrtb_ext.Adjustment, bidType, bidderName, dealID string) []openrtb_ext.Adjustment { + priorityRules := [maxNumOfCombos]string{} + if dealID != "" { + priorityRules[0] = bidType + Delimiter + bidderName + Delimiter + dealID + priorityRules[1] = bidType + Delimiter + bidderName + Delimiter + WildCard + priorityRules[2] = bidType + Delimiter + WildCard + Delimiter + dealID + priorityRules[3] = WildCard + Delimiter + bidderName + Delimiter + dealID + priorityRules[4] = bidType + Delimiter + WildCard + Delimiter + WildCard + priorityRules[5] = WildCard + Delimiter + bidderName + Delimiter + WildCard + priorityRules[6] = WildCard + Delimiter + WildCard + Delimiter + dealID + priorityRules[7] = WildCard + Delimiter + WildCard + Delimiter + WildCard + } else { + priorityRules[0] = bidType + Delimiter + bidderName + Delimiter + WildCard + priorityRules[1] = bidType + Delimiter + WildCard + Delimiter + WildCard + priorityRules[2] = WildCard + Delimiter + bidderName + Delimiter + WildCard + priorityRules[3] = WildCard + Delimiter + WildCard + Delimiter + WildCard + } + + for _, rule := range priorityRules { + if _, ok := ruleToAdjustments[rule]; ok { + return ruleToAdjustments[rule] + } + } + return nil +} diff --git a/bidadjustment/get_test.go b/bidadjustment/get_test.go new file mode 100644 index 00000000000..0026854a241 --- /dev/null +++ b/bidadjustment/get_test.go @@ -0,0 +1,221 @@ +package bidadjustment + +import ( + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestGetAdjustmentArray(t *testing.T) { + testCases := []struct { + name string + givenRuleToAdjustments map[string][]openrtb_ext.Adjustment + givenBidType openrtb_ext.BidType + givenBidderName openrtb_ext.BidderName + givenDealId string + expected []openrtb_ext.Adjustment + }{ + { + name: "Priority1", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|bidderA|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + "banner|bidderA|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + { + name: "Priority2", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|bidderA|*": { + { + Type: AdjustmentTypeStatic, + Value: 5.0, + }, + }, + "banner|*|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0}}, + }, + { + name: "Priority3", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|*|dealId": { + { + Type: AdjustmentTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "*|bidderA|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority4", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|bidderA|dealId": { + { + Type: AdjustmentTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "banner|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority5", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|*|*": { + { + Type: AdjustmentTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "*|bidderA|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority6", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|bidderA|*": { + { + Type: AdjustmentTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "*|*|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority7", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|dealId": { + { + Type: AdjustmentTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "*|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority8", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + "banner|bidderA|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderB", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + { + name: "NoDealID", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|bidderA|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + "banner|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + adjArray := get(test.givenRuleToAdjustments, string(test.givenBidType), string(test.givenBidderName), test.givenDealId) + assert.Equal(t, test.expected, adjArray, "Adjustment Array doesn't match") + }) + } +} diff --git a/bidadjustments/validate.go b/bidadjustment/validate.go similarity index 82% rename from bidadjustments/validate.go rename to bidadjustment/validate.go index 33f6f8f6074..a7c4f8cb7c9 100644 --- a/bidadjustments/validate.go +++ b/bidadjustment/validate.go @@ -1,4 +1,4 @@ -package bidadjustments +package bidadjustment import ( "math" @@ -28,7 +28,7 @@ func Validate(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) bool { return true } -func findAndValidateAdjustment(bidAdjMap map[string]map[string][]openrtb_ext.Adjustments) bool { +func findAndValidateAdjustment(bidAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID) bool { for bidderName := range bidAdjMap { for dealId := range bidAdjMap[bidderName] { for _, adjustment := range bidAdjMap[bidderName][dealId] { @@ -41,18 +41,17 @@ func findAndValidateAdjustment(bidAdjMap map[string]map[string][]openrtb_ext.Adj return true } -func validateAdjustment(adjustment openrtb_ext.Adjustments) bool { - switch adjustment.AdjType { - case AdjTypeCpm: +func validateAdjustment(adjustment openrtb_ext.Adjustment) bool { + switch adjustment.Type { + case AdjustmentTypeCpm: if adjustment.Currency != "" && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { return true } - case AdjTypeMultiplier: + case AdjustmentTypeMultiplier: if adjustment.Value >= 0 && adjustment.Value < 100 { return true } - adjustment.Currency = "" - case AdjTypeStatic: + case AdjustmentTypeStatic: if adjustment.Currency != "" && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { return true } diff --git a/bidadjustments/validate_test.go b/bidadjustment/validate_test.go similarity index 52% rename from bidadjustments/validate_test.go rename to bidadjustment/validate_test.go index 163c25fd47b..16bb179a1c1 100644 --- a/bidadjustments/validate_test.go +++ b/bidadjustment/validate_test.go @@ -1,4 +1,4 @@ -package bidadjustments +package bidadjustment import ( "testing" @@ -14,12 +14,12 @@ func TestValidateBidAdjustments(t *testing.T) { expected bool }{ { - name: "Valid single bid adjustment multiplier", + name: "ValidMultiplierAdjustment", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, }, }, @@ -27,17 +27,17 @@ func TestValidateBidAdjustments(t *testing.T) { expected: true, }, { - name: "Valid banner bid adjustment, invalid video bid adjustment, negative value", + name: "InvalidAdjustmentNegative", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, }, - Video: map[string]map[string][]openrtb_ext.Adjustments{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: -1.0}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: -1.0}}, }, }, }, @@ -45,12 +45,12 @@ func TestValidateBidAdjustments(t *testing.T) { expected: false, }, { - name: "Invalid bid adjustment value, too large", + name: "InvalidAdjustmentTooLarge", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Audio: map[string]map[string][]openrtb_ext.Adjustments{ + Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "multiplier", Value: 200}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 200}}, }, }, }, @@ -58,12 +58,12 @@ func TestValidateBidAdjustments(t *testing.T) { expected: false, }, { - name: "Valid bid adjustment cpm", + name: "ValidCpmAdjustment", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Native: map[string]map[string][]openrtb_ext.Adjustments{ + Native: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.0, Currency: "USD"}}, }, }, }, @@ -71,12 +71,12 @@ func TestValidateBidAdjustments(t *testing.T) { expected: true, }, { - name: "Invalid CPM bid adjustment, no currency given", + name: "InvalidCpmAdjustmentNoCurrency", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: 1.0, Currency: ""}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.0, Currency: ""}}, }, }, }, @@ -84,12 +84,12 @@ func TestValidateBidAdjustments(t *testing.T) { expected: false, }, { - name: "Invalid CPM bid adjustment, negative value", + name: "InvalidAdjustmentNegative", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Native: map[string]map[string][]openrtb_ext.Adjustments{ + Native: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "cpm", Value: -1.0, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: -1.0, Currency: "USD"}}, }, }, }, @@ -97,12 +97,12 @@ func TestValidateBidAdjustments(t *testing.T) { expected: false, }, { - name: "Valid static bid adjustment", + name: "ValidStaticAdjustment", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 1.0, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: "static", Value: 1.0, Currency: "USD"}}, }, }, }, @@ -110,12 +110,12 @@ func TestValidateBidAdjustments(t *testing.T) { expected: true, }, { - name: "Invalid static bid adjustment, no currency", + name: "InvalidStaticAdjustmentNoCurrency", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: 1.0, Currency: ""}}, + "dealId": []openrtb_ext.Adjustment{{Type: "static", Value: 1.0, Currency: ""}}, }, }, }, @@ -123,12 +123,12 @@ func TestValidateBidAdjustments(t *testing.T) { expected: false, }, { - name: "Invalid static bid adjustment, negative value", + name: "InvalidStaticAdjustmentNegative", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[string]map[string][]openrtb_ext.Adjustments{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: -1.0, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: "static", Value: -1.0, Currency: "USD"}}, }, }, }, @@ -136,12 +136,12 @@ func TestValidateBidAdjustments(t *testing.T) { expected: false, }, { - name: "Invalid wildcard adjustment, negative value", + name: "InvalidWildcardAdjustmentNegative", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - WildCard: map[string]map[string][]openrtb_ext.Adjustments{ + WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustments{{AdjType: "static", Value: -1.0, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: "static", Value: -1.0, Currency: "USD"}}, }, }, }, @@ -149,7 +149,7 @@ func TestValidateBidAdjustments(t *testing.T) { expected: false, }, { - name: "Nil Bid Adjustment", + name: "NilAdjustment", givenBidAdjustments: nil, expected: true, }, diff --git a/bidadjustments/get.go b/bidadjustments/get.go deleted file mode 100644 index 95ccce85981..00000000000 --- a/bidadjustments/get.go +++ /dev/null @@ -1,45 +0,0 @@ -package bidadjustments - -import ( - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func GetAndApplyAdjustmentArray(ruleToAdjustments map[string][]openrtb_ext.Adjustments, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { - adjArray := []openrtb_ext.Adjustments{} - if ruleToAdjustments != nil { - adjArray = getAdjustmentArray(ruleToAdjustments, string(bidInfo.BidType), string(bidderName), bidInfo.Bid.DealID) - } else { - return bidInfo.Bid.Price, currency - } - return applyAdjustmentArray(adjArray, bidInfo.Bid.Price, currency, reqInfo) -} - -// Given the bid response information of bidType, bidderName, and dealID, we create the same format of combinations that's present in the key of the ruleToAdjustments map -// There's a max of 8 combinations that can be made with those pieces of bid information, so after discussion with team member, we decided to generate each combo, and check if it's present in the map -// The order of the array is ordered by priority from highest to lowest, so as soon as we find a match, that's the adjustment array we want to return -func getAdjustmentArray(ruleToAdjustments map[string][]openrtb_ext.Adjustments, bidType string, bidderName string, dealID string) []openrtb_ext.Adjustments { - var priorityRules []string - if dealID != "" { - priorityRules = append(priorityRules, bidType+PipeDelimiter+bidderName+PipeDelimiter+dealID) - priorityRules = append(priorityRules, bidType+PipeDelimiter+bidderName+PipeDelimiter+WildCard) - priorityRules = append(priorityRules, bidType+PipeDelimiter+WildCard+PipeDelimiter+dealID) - priorityRules = append(priorityRules, WildCard+PipeDelimiter+bidderName+PipeDelimiter+dealID) - priorityRules = append(priorityRules, bidType+PipeDelimiter+WildCard+PipeDelimiter+WildCard) - priorityRules = append(priorityRules, WildCard+PipeDelimiter+bidderName+PipeDelimiter+WildCard) - priorityRules = append(priorityRules, WildCard+PipeDelimiter+WildCard+PipeDelimiter+dealID) - priorityRules = append(priorityRules, WildCard+PipeDelimiter+WildCard+PipeDelimiter+WildCard) - } else { - priorityRules = append(priorityRules, bidType+PipeDelimiter+bidderName+PipeDelimiter+WildCard) - priorityRules = append(priorityRules, bidType+PipeDelimiter+WildCard+PipeDelimiter+WildCard) - priorityRules = append(priorityRules, WildCard+PipeDelimiter+bidderName+PipeDelimiter+WildCard) - priorityRules = append(priorityRules, WildCard+PipeDelimiter+WildCard+PipeDelimiter+WildCard) - } - - for _, rule := range priorityRules { - if _, ok := ruleToAdjustments[rule]; ok { - return ruleToAdjustments[rule] - } - } - return nil -} diff --git a/bidadjustments/get_test.go b/bidadjustments/get_test.go deleted file mode 100644 index a56b49f7341..00000000000 --- a/bidadjustments/get_test.go +++ /dev/null @@ -1,221 +0,0 @@ -package bidadjustments - -import ( - "testing" - - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" -) - -func TestGetAdjustmentArray(t *testing.T) { - testCases := []struct { - name string - givenRuleToAdjustments map[string][]openrtb_ext.Adjustments - givenBidType openrtb_ext.BidType - givenBidderName openrtb_ext.BidderName - givenDealId string - expected []openrtb_ext.Adjustments - }{ - { - name: "Priority #1 should be chosen", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ - "banner|bidderA|dealId": { - { - AdjType: AdjTypeMultiplier, - Value: 1.1, - }, - }, - "banner|bidderA|*": { - { - AdjType: AdjTypeMultiplier, - Value: 2.0, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 1.1}}, - }, - { - name: "Priority #2 should be chosen", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ - "banner|bidderA|*": { - { - AdjType: AdjTypeStatic, - Value: 5.0, - }, - }, - "banner|*|dealId": { - { - AdjType: AdjTypeMultiplier, - Value: 2.0, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeStatic, Value: 5.0}}, - }, - { - name: "Priority #3 should be chosen", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ - "banner|*|dealId": { - { - AdjType: AdjTypeCpm, - Value: 3.0, - Currency: "USD", - }, - }, - "*|bidderA|dealId": { - { - AdjType: AdjTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 3.0, Currency: "USD"}}, - }, - { - name: "Priority #4 should be chosen", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ - "*|bidderA|dealId": { - { - AdjType: AdjTypeCpm, - Value: 3.0, - Currency: "USD", - }, - }, - "banner|*|*": { - { - AdjType: AdjTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 3.0, Currency: "USD"}}, - }, - { - name: "Priority #5 should be chosen", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ - "banner|*|*": { - { - AdjType: AdjTypeCpm, - Value: 3.0, - Currency: "USD", - }, - }, - "*|bidderA|*": { - { - AdjType: AdjTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 3.0, Currency: "USD"}}, - }, - { - name: "Priority #6 should be chosen", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ - "*|bidderA|*": { - { - AdjType: AdjTypeCpm, - Value: 3.0, - Currency: "USD", - }, - }, - "*|*|dealId": { - { - AdjType: AdjTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 3.0, Currency: "USD"}}, - }, - { - name: "Priority #7 should be chosen", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ - "*|*|dealId": { - { - AdjType: AdjTypeCpm, - Value: 3.0, - Currency: "USD", - }, - }, - "*|*|*": { - { - AdjType: AdjTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeCpm, Value: 3.0, Currency: "USD"}}, - }, - { - name: "Priority #8 should be chosen, given the provided info doesn't match the other provided rules", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ - "*|*|*": { - { - AdjType: AdjTypeMultiplier, - Value: 1.1, - }, - }, - "banner|bidderA|dealId": { - { - AdjType: AdjTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderB", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 1.1}}, - }, - { - name: "No dealID given, should choose correct rule", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustments{ - "banner|bidderA|*": { - { - AdjType: AdjTypeMultiplier, - Value: 1.1, - }, - }, - "banner|*|*": { - { - AdjType: AdjTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "", - expected: []openrtb_ext.Adjustments{{AdjType: AdjTypeMultiplier, Value: 1.1}}, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - adjArray := getAdjustmentArray(test.givenRuleToAdjustments, string(test.givenBidType), string(test.givenBidderName), test.givenDealId) - assert.Equal(t, test.expected, adjArray, "Adjustment Array doesn't match") - }) - } -} diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 37f54e63d1f..52e8fa959d9 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -25,7 +25,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/bidadjustments" + "github.com/prebid/prebid-server/bidadjustment" "github.com/prebid/prebid-server/hooks" "github.com/prebid/prebid-server/ortb" "golang.org/x/net/publicsuffix" @@ -1555,7 +1555,7 @@ func validateRequestExt(req *openrtb_ext.RequestWrapper) []error { reqExt.SetPrebid(prebid) } - if !bidadjustments.Validate(prebid.BidAdjustments) { + if !bidadjustment.Validate(prebid.BidAdjustments) { prebid.BidAdjustments = nil reqExt.SetPrebid(prebid) errs = append(errs, &errortypes.Warning{ diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json index aec5b9d2468..db0a06e6ad9 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json @@ -2,7 +2,11 @@ "description": "Bid Adjustment Test with CPM, and WildCard Preference Testing", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 20.00} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 20.00 + } ] }, "mockBidRequest": { @@ -43,50 +47,59 @@ }, "usepbsrates": false }, - "bidadjustments": { - "mediatype": { - "banner": { - "appnexus": { - "*": [ - { - "adjtype": "cpm", - "value": 5.0, - "currency": "EUR" - } - ] - }, - "*": { - "*": [ - { - "adjtype": "multiplier", - "value": 3.0 - } - ] - } + "bidadjustments": { + "mediatype": { + "banner": { + "appnexus": { + "*": [ + { + "adjtype": "cpm", + "value": 5.0, + "currency": "EUR" } + ] + }, + "*": { + "*": [ + { + "adjtype": "multiplier", + "value": 3.0 + } + ] } + } } + } } } }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 10.0, - "ext": {"origbidcpm": 20, "origbidcur": "USD", "prebid": {"meta": {"adaptercode": "appnexus"}, "type": "banner"}} + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 10.0, + "ext": { + "origbidcpm": 20, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } } - ], - "seat": "appnexus" - } - ], - "bidid":"test bid id", - "cur":"USD", - "nbr":0 + } + ], + "seat": "appnexus" + } + ], + "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/bidadjustments-simple.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json index 437dd82f280..72a8dca9bbc 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json @@ -2,7 +2,12 @@ "description": "Bid Adjustment Test With One Adjustment", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 2.00, "dealid": "some-deal-id"} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 2.00, + "dealid": "some-deal-id" + } ] }, "mockBidRequest": { @@ -35,41 +40,50 @@ "tmax": 500, "ext": { "prebid": { - "bidadjustments": { - "mediatype": { - "banner": { - "appnexus": { - "some-deal-id": [ - { - "adjtype": "multiplier", - "value": 2.0 - } - ] - } + "bidadjustments": { + "mediatype": { + "banner": { + "appnexus": { + "some-deal-id": [ + { + "adjtype": "multiplier", + "value": 2.0 } + ] } + } } + } } } }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 4.0, - "ext": {"origbidcpm": 2, "origbidcur": "USD", "prebid": {"meta": {"adaptercode": "appnexus"}, "type": "banner"}} + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 4.0, + "ext": { + "origbidcpm": 2, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } } - ], - "seat": "appnexus" - } - ], - "bidid":"test bid id", - "cur":"USD", - "nbr":0 + } + ], + "seat": "appnexus" + } + ], + "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/bidadjustments-static.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json index 34ca842869d..9969c47ef9b 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json @@ -2,7 +2,12 @@ "description": "Bid Adjustment with Static and WildCard testing", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 2.00, "dealid": "some-deal-id"} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 2.00, + "dealid": "some-deal-id" + } ] }, "mockBidRequest": { @@ -43,50 +48,59 @@ }, "usepbsrates": false }, - "bidadjustments": { - "mediatype": { - "banner": { - "*": { - "some-deal-id": [ - { - "adjtype": "static", - "value": 10.0, - "currency": "EUR" - } - ] - }, - "some-bidder-name": { - "*": [ - { - "adjtype": "multiplier", - "value": 2.0 - } - ] - } + "bidadjustments": { + "mediatype": { + "banner": { + "*": { + "some-deal-id": [ + { + "adjtype": "static", + "value": 10.0, + "currency": "EUR" } + ] + }, + "some-bidder-name": { + "*": [ + { + "adjtype": "multiplier", + "value": 2.0 + } + ] } + } } + } } } }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 10.0, - "ext": {"origbidcpm": 2, "origbidcur": "USD", "prebid": {"meta": {"adaptercode": "appnexus"}, "type": "banner"}} + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 10.0, + "ext": { + "origbidcpm": 2, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } } - ], - "seat": "appnexus" - } - ], - "bidid":"test bid id", - "cur":"EUR", - "nbr":0 + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "EUR", + "nbr": 0 }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/exchange/bidder.go b/exchange/bidder.go index b3897a3b83f..ec0ba6cbe63 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -16,7 +16,7 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/bidadjustments" + "github.com/prebid/prebid-server/bidadjustment" "github.com/prebid/prebid-server/config/util" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/exchange/entities" @@ -57,7 +57,7 @@ type AdaptedBidder interface { // // Any errors will be user-facing in the API. // Error messages should help publishers understand what might account for "bad" bids. - 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 map[string][]openrtb_ext.Adjustments) ([]*entities.PbsOrtbSeatBid, []error) + 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 map[string][]openrtb_ext.Adjustment) ([]*entities.PbsOrtbSeatBid, []error) } // bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters @@ -116,7 +116,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 map[string][]openrtb_ext.Adjustments) ([]*entities.PbsOrtbSeatBid, []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 map[string][]openrtb_ext.Adjustment) ([]*entities.PbsOrtbSeatBid, []error) { reject := hookExecutor.ExecuteBidderRequestStage(bidderRequest.BidRequest, string(bidderRequest.BidderName)) if reject != nil { return nil, []error{reject} @@ -340,7 +340,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if bidResponse.Bids[i].Bid != nil { originalBidCpm = bidResponse.Bids[i].Bid.Price bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate - bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustments.GetAndApplyAdjustmentArray(ruleToAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo) + bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustment.GetAndApplyAdjustments(ruleToAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo) } if _, ok := seatBidMap[bidderName]; !ok { diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index 5e464d87de7..4d9682b934f 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -31,7 +31,7 @@ type validatedBidder struct { bidder AdaptedBidder } -func (v *validatedBidder) 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 map[string][]openrtb_ext.Adjustments) ([]*entities.PbsOrtbSeatBid, []error) { +func (v *validatedBidder) 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 map[string][]openrtb_ext.Adjustment) ([]*entities.PbsOrtbSeatBid, []error) { seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor, ruleToAdjustments) for _, seatBid := range seatBids { if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 { diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index a7fa4bb4fe8..4d4203eb319 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -357,6 +357,6 @@ type mockAdaptedBidder struct { errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustments) ([]*entities.PbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) ([]*entities.PbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/exchange.go b/exchange/exchange.go index a2da62b43d0..5b13ee336ce 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -16,7 +16,7 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adservertargeting" - "github.com/prebid/prebid-server/bidadjustments" + "github.com/prebid/prebid-server/bidadjustment" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" @@ -298,11 +298,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog r.FirstPartyData = resolvedFPD } - mergedBidAdj, err := bidadjustments.ProcessBidAdjustments(r.BidRequestWrapper, r.Account.BidAdjustments) + mergedBidAdj, err := bidadjustment.Process(r.BidRequestWrapper, r.Account.BidAdjustments) if err != nil { return nil, err } - ruleToAdjustments := bidadjustments.GenerateMap(mergedBidAdj) + ruleToAdjustments := bidadjustment.GenerateMap(mergedBidAdj) bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExtPrebid) @@ -636,7 +636,7 @@ func (e *exchange) getAllBids( experiment *openrtb_ext.Experiment, hookExecutor hookexecution.StageExecutor, pbsRequestStartTime time.Time, - ruleToAdjustments map[string][]openrtb_ext.Adjustments) ( + ruleToAdjustments map[string][]openrtb_ext.Adjustment) ( map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, *openrtb_ext.Fledge, diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 2abce1f915e..f19a53814b0 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -5313,7 +5313,7 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustments) (seatBids []*entities.PbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) (seatBids []*entities.PbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok { if expectedRequest != nil { if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) { @@ -5371,7 +5371,7 @@ type capturingRequestBidder struct { req *openrtb2.BidRequest } -func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustments) (seatBid []*entities.PbsOrtbSeatBid, errs []error) { +func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) (seatBid []*entities.PbsOrtbSeatBid, errs []error) { b.req = bidderRequest.BidRequest return []*entities.PbsOrtbSeatBid{{}}, nil } @@ -5478,7 +5478,7 @@ func (e *emptyUsersync) HasAnyLiveSyncs() bool { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustments) (posb []*entities.PbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) (posb []*entities.PbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 96999110499..be5b9de37bc 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -154,20 +154,19 @@ type ExtRequestPrebidBidAdjustments struct { MediaType MediaType `json:"mediatype,omitempty"` } -// How the map is strucutred -// Map #1 -- Key: BidderName, Value: Another Map -// Map #2 -- Key: DealID, Value: Adjustments Array -// Overall: BidderName maps to a DealID that maps to an Adjustments Array +type AdjusmentsByDealID map[string][]Adjustment + +// BidderName maps to a DealID that maps to the Adjustments type MediaType struct { - Banner map[string]map[string][]Adjustments `json:"banner,omitempty"` - Video map[string]map[string][]Adjustments `json:"video,omitempty"` - Audio map[string]map[string][]Adjustments `json:"audio,omitempty"` - Native map[string]map[string][]Adjustments `json:"native,omitempty"` - WildCard map[string]map[string][]Adjustments `json:"*,omitempty"` + Banner map[BidderName]AdjusmentsByDealID `json:"banner,omitempty"` + Video map[BidderName]AdjusmentsByDealID `json:"video,omitempty"` + Audio map[BidderName]AdjusmentsByDealID `json:"audio,omitempty"` + Native map[BidderName]AdjusmentsByDealID `json:"native,omitempty"` + WildCard map[BidderName]AdjusmentsByDealID `json:"*,omitempty"` } -type Adjustments struct { - AdjType string `json:"adjtype,omitempty"` +type Adjustment struct { + Type string `json:"adjtype,omitempty"` Value float64 `json:"value,omitempty"` Currency string `json:"currency,omitempty"` } From 816d3e23938f4fce5157fb20c5d4128b0adf6e25 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Mon, 24 Apr 2023 13:02:14 -0700 Subject: [PATCH 17/27] Update to not return map unnecessarily --- bidadjustment/bidadjustment.go | 22 +++++++++------------- bidadjustment/bidadjustment_test.go | 7 ++++--- exchange/exchange.go | 3 ++- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/bidadjustment/bidadjustment.go b/bidadjustment/bidadjustment.go index 3e2b8284ace..06ead67d9b3 100644 --- a/bidadjustment/bidadjustment.go +++ b/bidadjustment/bidadjustment.go @@ -18,7 +18,7 @@ const ( const pricePrecision float64 = 10000 // Rounds to 4 Decimal Places func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { - if adjustments == nil { + if adjustments == nil || len(adjustments) == 0 { return bidPrice, currency } originalBidPrice := bidPrice @@ -122,26 +122,22 @@ func mergeAdjustmentsForMediaType(reqAdjMap map[openrtb_ext.BidderName]openrtb_e return reqAdjMap } -func GenerateMap(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) map[string][]openrtb_ext.Adjustment { +func PopulateMap(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, ruleToAdjustments map[string][]openrtb_ext.Adjustment) { if bidAdjustments == nil { - return nil + return } - ruleToAdjustmentMap := make(map[string][]openrtb_ext.Adjustment) - ruleToAdjustmentMap = populateMapForMediaType(bidAdjustments.MediaType.Banner, string(openrtb_ext.BidTypeBanner), ruleToAdjustmentMap) - ruleToAdjustmentMap = populateMapForMediaType(bidAdjustments.MediaType.Video, string(openrtb_ext.BidTypeVideo), ruleToAdjustmentMap) - ruleToAdjustmentMap = populateMapForMediaType(bidAdjustments.MediaType.Audio, string(openrtb_ext.BidTypeAudio), ruleToAdjustmentMap) - ruleToAdjustmentMap = populateMapForMediaType(bidAdjustments.MediaType.Native, string(openrtb_ext.BidTypeNative), ruleToAdjustmentMap) - ruleToAdjustmentMap = populateMapForMediaType(bidAdjustments.MediaType.WildCard, WildCard, ruleToAdjustmentMap) - - return ruleToAdjustmentMap + populateMapForMediaType(bidAdjustments.MediaType.Banner, string(openrtb_ext.BidTypeBanner), ruleToAdjustments) + populateMapForMediaType(bidAdjustments.MediaType.Video, string(openrtb_ext.BidTypeVideo), ruleToAdjustments) + populateMapForMediaType(bidAdjustments.MediaType.Audio, string(openrtb_ext.BidTypeAudio), ruleToAdjustments) + populateMapForMediaType(bidAdjustments.MediaType.Native, string(openrtb_ext.BidTypeNative), ruleToAdjustments) + populateMapForMediaType(bidAdjustments.MediaType.WildCard, WildCard, ruleToAdjustments) } -func populateMapForMediaType(bidAdj map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID, mediaType string, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) map[string][]openrtb_ext.Adjustment { +func populateMapForMediaType(bidAdj map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID, mediaType string, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) { for bidderName := range bidAdj { for dealID, adjustments := range bidAdj[bidderName] { rule := mediaType + Delimiter + string(bidderName) + Delimiter + dealID ruleToAdjustmentMap[rule] = adjustments } } - return ruleToAdjustmentMap } diff --git a/bidadjustment/bidadjustment_test.go b/bidadjustment/bidadjustment_test.go index 37d4cb96abf..6c69a7b57f1 100644 --- a/bidadjustment/bidadjustment_test.go +++ b/bidadjustment/bidadjustment_test.go @@ -534,14 +534,15 @@ func TestGenerateMap(t *testing.T) { { name: "NilAdjustments", givenBidAdjustments: nil, - expectedMap: nil, + expectedMap: map[string][]openrtb_ext.Adjustment{}, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - ruleToAdjustmentMap := GenerateMap(test.givenBidAdjustments) - assert.Equal(t, test.expectedMap, ruleToAdjustmentMap) + ruleToAdjustments := make(map[string][]openrtb_ext.Adjustment) + PopulateMap(test.givenBidAdjustments, ruleToAdjustments) + assert.Equal(t, test.expectedMap, ruleToAdjustments) }) } } diff --git a/exchange/exchange.go b/exchange/exchange.go index 5b13ee336ce..86a45647597 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -302,7 +302,8 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog if err != nil { return nil, err } - ruleToAdjustments := bidadjustment.GenerateMap(mergedBidAdj) + ruleToAdjustments := make(map[string][]openrtb_ext.Adjustment) + bidadjustment.PopulateMap(mergedBidAdj, ruleToAdjustments) bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExtPrebid) From a4fcab88824e248c1334d61ebf3b2fdf0907c596 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Tue, 25 Apr 2023 16:17:43 -0700 Subject: [PATCH 18/27] Add more test cases for validation --- bidadjustment/bidadjustment.go | 44 ++--- bidadjustment/bidadjustment_test.go | 46 ++--- bidadjustment/validate.go | 12 +- bidadjustment/validate_test.go | 252 ++++++++++++++++++++++------ exchange/exchange.go | 2 +- openrtb_ext/request.go | 12 +- 6 files changed, 252 insertions(+), 116 deletions(-) diff --git a/bidadjustment/bidadjustment.go b/bidadjustment/bidadjustment.go index 06ead67d9b3..66391d649b3 100644 --- a/bidadjustment/bidadjustment.go +++ b/bidadjustment/bidadjustment.go @@ -47,7 +47,7 @@ func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency stri return roundedBidPrice, currency } -func Process(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { +func Merge(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { mergedBidAdj, err := merge(req, acctBidAdjs) if err != nil { return nil, err @@ -75,39 +75,23 @@ func merge(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestP return extPrebid.BidAdjustments, nil } - if extPrebid.BidAdjustments.MediaType.Banner != nil && acctBidAdjs.MediaType.Banner != nil { - extPrebid.BidAdjustments.MediaType.Banner = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Banner, acctBidAdjs.MediaType.Banner) - } else if acctBidAdjs.MediaType.Banner != nil { - extPrebid.BidAdjustments.MediaType.Banner = acctBidAdjs.MediaType.Banner - } - - if extPrebid.BidAdjustments.MediaType.Video != nil && acctBidAdjs.MediaType.Video != nil { - extPrebid.BidAdjustments.MediaType.Video = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Video, acctBidAdjs.MediaType.Video) - } else if acctBidAdjs.MediaType.Video != nil { - extPrebid.BidAdjustments.MediaType.Video = acctBidAdjs.MediaType.Video - } + extPrebid.BidAdjustments.MediaType.Banner = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Banner, acctBidAdjs.MediaType.Banner) + extPrebid.BidAdjustments.MediaType.Video = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Video, acctBidAdjs.MediaType.Video) + extPrebid.BidAdjustments.MediaType.Native = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Native, acctBidAdjs.MediaType.Native) + extPrebid.BidAdjustments.MediaType.Audio = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Audio, acctBidAdjs.MediaType.Audio) + extPrebid.BidAdjustments.MediaType.WildCard = mergeForMediaType(extPrebid.BidAdjustments.MediaType.WildCard, acctBidAdjs.MediaType.WildCard) - if extPrebid.BidAdjustments.MediaType.Native != nil && acctBidAdjs.MediaType.Native != nil { - extPrebid.BidAdjustments.MediaType.Native = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Native, acctBidAdjs.MediaType.Native) - } else if acctBidAdjs.MediaType.Native != nil { - extPrebid.BidAdjustments.MediaType.Native = acctBidAdjs.MediaType.Native - } + return extPrebid.BidAdjustments, nil +} - if extPrebid.BidAdjustments.MediaType.Audio != nil && acctBidAdjs.MediaType.Audio != nil { - extPrebid.BidAdjustments.MediaType.Audio = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.Audio, acctBidAdjs.MediaType.Audio) - } else if acctBidAdjs.MediaType.Audio != nil { - extPrebid.BidAdjustments.MediaType.Audio = acctBidAdjs.MediaType.Audio +func mergeForMediaType(reqAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID, accountAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID) map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID { + if reqAdjMap != nil && accountAdjMap == nil { + return reqAdjMap } - - if extPrebid.BidAdjustments.MediaType.WildCard != nil && acctBidAdjs.MediaType.WildCard != nil { - extPrebid.BidAdjustments.MediaType.WildCard = mergeAdjustmentsForMediaType(extPrebid.BidAdjustments.MediaType.WildCard, acctBidAdjs.MediaType.WildCard) - } else if acctBidAdjs.MediaType.WildCard != nil { - extPrebid.BidAdjustments.MediaType.WildCard = acctBidAdjs.MediaType.WildCard + if reqAdjMap == nil && accountAdjMap != nil { + return accountAdjMap } - return extPrebid.BidAdjustments, nil -} -func mergeAdjustmentsForMediaType(reqAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID, accountAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID) map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID { for bidderName, dealIdToAdjustmentsMap := range accountAdjMap { if _, ok := reqAdjMap[bidderName]; ok { for dealID, acctAdjustmentsArray := range accountAdjMap[bidderName] { @@ -133,7 +117,7 @@ func PopulateMap(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, rul populateMapForMediaType(bidAdjustments.MediaType.WildCard, WildCard, ruleToAdjustments) } -func populateMapForMediaType(bidAdj map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID, mediaType string, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) { +func populateMapForMediaType(bidAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID, mediaType string, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) { for bidderName := range bidAdj { for dealID, adjustments := range bidAdj[bidderName] { rule := mediaType + Delimiter + string(bidderName) + Delimiter + dealID diff --git a/bidadjustment/bidadjustment_test.go b/bidadjustment/bidadjustment_test.go index 6c69a7b57f1..443bf34b196 100644 --- a/bidadjustment/bidadjustment_test.go +++ b/bidadjustment/bidadjustment_test.go @@ -249,7 +249,7 @@ func TestMergeBidAdjustments(t *testing.T) { givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, @@ -259,7 +259,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, @@ -278,7 +278,7 @@ func TestMergeBidAdjustments(t *testing.T) { givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, @@ -288,7 +288,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, @@ -304,7 +304,7 @@ func TestMergeBidAdjustments(t *testing.T) { givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "diffDealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, @@ -314,7 +314,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: "static", Value: 3.00, Currency: "USD"}}, "diffDealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, @@ -331,7 +331,7 @@ func TestMergeBidAdjustments(t *testing.T) { givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Native: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, @@ -341,7 +341,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Native: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: "cpm", Value: 0.18, Currency: "USD"}}, }, @@ -360,7 +360,7 @@ func TestMergeBidAdjustments(t *testing.T) { givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, @@ -370,12 +370,12 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, @@ -391,7 +391,7 @@ func TestMergeBidAdjustments(t *testing.T) { givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, @@ -401,7 +401,7 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, @@ -417,7 +417,7 @@ func TestMergeBidAdjustments(t *testing.T) { givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, @@ -427,12 +427,12 @@ func TestMergeBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, }, - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, @@ -461,7 +461,7 @@ func TestGenerateMap(t *testing.T) { name: "OneAdjustment", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, @@ -481,7 +481,7 @@ func TestGenerateMap(t *testing.T) { name: "MultipleAdjustments", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, @@ -490,7 +490,7 @@ func TestGenerateMap(t *testing.T) { "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD"}}, }, }, - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "*": { "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}, {Type: AdjustmentTypeCpm, Value: 0.18, Currency: "USD"}}, }, @@ -562,7 +562,7 @@ func TestProcessBidAdjustments(t *testing.T) { givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, @@ -572,7 +572,7 @@ func TestProcessBidAdjustments(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, }, @@ -591,7 +591,7 @@ func TestProcessBidAdjustments(t *testing.T) { givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, }, @@ -605,7 +605,7 @@ func TestProcessBidAdjustments(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - mergedBidAdj, err := Process(test.givenRequestWrapper, test.givenAccount.BidAdjustments) + mergedBidAdj, err := Merge(test.givenRequestWrapper, test.givenAccount.BidAdjustments) assert.NoError(t, err, "Unexpected error received") assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) }) diff --git a/bidadjustment/validate.go b/bidadjustment/validate.go index a7c4f8cb7c9..c7a543d8bcc 100644 --- a/bidadjustment/validate.go +++ b/bidadjustment/validate.go @@ -10,25 +10,25 @@ func Validate(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) bool { if bidAdjustments == nil { return true } - if bidAdjustments.MediaType.Banner != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Banner) { + if !validateForMediaType(bidAdjustments.MediaType.Banner) { return false } - if bidAdjustments.MediaType.Audio != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Audio) { + if !validateForMediaType(bidAdjustments.MediaType.Audio) { return false } - if bidAdjustments.MediaType.Video != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Video) { + if !validateForMediaType(bidAdjustments.MediaType.Video) { return false } - if bidAdjustments.MediaType.Native != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.Native) { + if !validateForMediaType(bidAdjustments.MediaType.Native) { return false } - if bidAdjustments.MediaType.WildCard != nil && !findAndValidateAdjustment(bidAdjustments.MediaType.WildCard) { + if !validateForMediaType(bidAdjustments.MediaType.WildCard) { return false } return true } -func findAndValidateAdjustment(bidAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID) bool { +func validateForMediaType(bidAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID) bool { for bidderName := range bidAdjMap { for dealId := range bidAdjMap[bidderName] { for _, adjustment := range bidAdjMap[bidderName][dealId] { diff --git a/bidadjustment/validate_test.go b/bidadjustment/validate_test.go index 16bb179a1c1..db50297ed03 100644 --- a/bidadjustment/validate_test.go +++ b/bidadjustment/validate_test.go @@ -7,17 +7,17 @@ import ( "github.com/stretchr/testify/assert" ) -func TestValidateBidAdjustments(t *testing.T) { +func TestValidate(t *testing.T) { testCases := []struct { name string givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments expected bool }{ { - name: "ValidMultiplierAdjustment", + name: "OneAdjustmentValid", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, @@ -27,30 +27,35 @@ func TestValidateBidAdjustments(t *testing.T) { expected: true, }, { - name: "InvalidAdjustmentNegative", + name: "MultipleAdjustmentsValid", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, }, - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: -1.0}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 3.0, Currency: "USD"}}, }, }, }, }, - expected: false, + expected: true, }, { - name: "InvalidAdjustmentTooLarge", + name: "MixOfValidandInvalid", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 200}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + }, + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: ""}}, }, }, }, @@ -58,25 +63,25 @@ func TestValidateBidAdjustments(t *testing.T) { expected: false, }, { - name: "ValidCpmAdjustment", + name: "WildCardInvalid", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Native: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.0, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: -1.1, Currency: "USD"}}, }, }, }, }, - expected: true, + expected: false, }, { - name: "InvalidCpmAdjustmentNoCurrency", + name: "AudioInvalid", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.0, Currency: ""}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0, Currency: ""}}, }, }, }, @@ -84,12 +89,12 @@ func TestValidateBidAdjustments(t *testing.T) { expected: false, }, { - name: "InvalidAdjustmentNegative", + name: "NativeInvalid", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Native: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: -1.0, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: -1.1, Currency: "USD"}}, }, }, }, @@ -97,67 +102,214 @@ func TestValidateBidAdjustments(t *testing.T) { expected: false, }, { - name: "ValidStaticAdjustment", + name: "BannerInvalid", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "static", Value: 1.0, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 150}}, }, }, }, }, + expected: false, + }, + { + name: "EmptyBidAdjustments", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{}, + expected: true, + }, + { + name: "NilBidAdjustments", + givenBidAdjustments: nil, + expected: true, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actual := Validate(test.givenBidAdjustments) + assert.Equal(t, test.expected, actual, "Boolean didn't match") + }) + } +} + +func TestValidateForMediaType(t *testing.T) { + testCases := []struct { + name string + givenBidAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID + expected bool + }{ + { + name: "OneAdjustmentValid", + givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + }, expected: true, }, { - name: "InvalidStaticAdjustmentNoCurrency", - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "static", Value: 1.0, Currency: ""}}, - }, - }, + name: "OneAdjustmentInvalid", + givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: -1.1}}, }, }, expected: false, }, { - name: "InvalidStaticAdjustmentNegative", - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "static", Value: -1.0, Currency: "USD"}}, - }, + name: "MultipleAdjustmentsValid", + givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{ + {Type: AdjustmentTypeMultiplier, Value: 1.1}, + {Type: AdjustmentTypeStatic, Value: 3.0, Currency: "USD"}, + }, + }, + }, + expected: true, + }, + { + name: "MultipleAdjustmentsInvalid", + givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{ + {Type: AdjustmentTypeMultiplier, Value: -1.1}, + {Type: AdjustmentTypeCpm, Value: -3.0, Currency: "USD"}, }, }, }, expected: false, }, { - name: "InvalidWildcardAdjustmentNegative", - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjusmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "static", Value: -1.0, Currency: "USD"}}, - }, + name: "MultipleDealIdsValid", + givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{ + {Type: AdjustmentTypeStatic, Value: 3.0, Currency: "USD"}, + }, + "diffDealId": []openrtb_ext.Adjustment{ + {Type: AdjustmentTypeMultiplier, Value: 1.1}, + }, + }, + }, + expected: true, + }, + { + name: "MultipleBiddersValid", + givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{ + {Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD"}, }, }, + "bidderB": { + "dealId": []openrtb_ext.Adjustment{ + {Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}, + }, + }, + }, + expected: true, + }, + { + name: "NilMediaTypeMap", + givenBidAdjMap: nil, + expected: true, + }, + { + name: "NilBidderMap", + givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": nil, + }, + expected: true, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actual := validateForMediaType(test.givenBidAdjMap) + assert.Equal(t, test.expected, actual, "Boolean didn't match") + }) + } +} + +func TestValidateAdjustment(t *testing.T) { + testCases := []struct { + name string + givenAdjustment openrtb_ext.Adjustment + expected bool + }{ + { + name: "ValidCpm", + givenAdjustment: openrtb_ext.Adjustment{ + Type: AdjustmentTypeCpm, + Value: 5.0, + Currency: "USD", + }, + expected: true, + }, + { + name: "ValidMultiplier", + givenAdjustment: openrtb_ext.Adjustment{ + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + expected: true, + }, + { + name: "ValidStatic", + givenAdjustment: openrtb_ext.Adjustment{ + Type: AdjustmentTypeStatic, + Value: 3.0, + Currency: "USD", + }, + expected: true, + }, + { + name: "InvalidCpm", + givenAdjustment: openrtb_ext.Adjustment{ + Type: AdjustmentTypeCpm, + Value: 5.0, + Currency: "", }, expected: false, }, { - name: "NilAdjustment", - givenBidAdjustments: nil, - expected: true, + name: "InvalidMultiplier", + givenAdjustment: openrtb_ext.Adjustment{ + Type: AdjustmentTypeMultiplier, + Value: 200, + }, + expected: false, + }, + { + name: "InvalidStatic", + givenAdjustment: openrtb_ext.Adjustment{ + Type: AdjustmentTypeStatic, + Value: -3.0, + Currency: "USD", + }, + expected: false, + }, + { + name: "InvalidAdjType", + givenAdjustment: openrtb_ext.Adjustment{ + Type: "Invalid", + Value: 1.0, + }, + expected: false, + }, + { + name: "EmptyAdjustment", + givenAdjustment: openrtb_ext.Adjustment{}, + expected: false, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - actual := Validate(test.givenBidAdjustments) + actual := validateAdjustment(test.givenAdjustment) assert.Equal(t, test.expected, actual, "Boolean didn't match") }) } diff --git a/exchange/exchange.go b/exchange/exchange.go index 86a45647597..f5b03b9e4c3 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -298,7 +298,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog r.FirstPartyData = resolvedFPD } - mergedBidAdj, err := bidadjustment.Process(r.BidRequestWrapper, r.Account.BidAdjustments) + mergedBidAdj, err := bidadjustment.Merge(r.BidRequestWrapper, r.Account.BidAdjustments) if err != nil { return nil, err } diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index be5b9de37bc..973b563b666 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -154,15 +154,15 @@ type ExtRequestPrebidBidAdjustments struct { MediaType MediaType `json:"mediatype,omitempty"` } -type AdjusmentsByDealID map[string][]Adjustment +type AdjustmentsByDealID map[string][]Adjustment // BidderName maps to a DealID that maps to the Adjustments type MediaType struct { - Banner map[BidderName]AdjusmentsByDealID `json:"banner,omitempty"` - Video map[BidderName]AdjusmentsByDealID `json:"video,omitempty"` - Audio map[BidderName]AdjusmentsByDealID `json:"audio,omitempty"` - Native map[BidderName]AdjusmentsByDealID `json:"native,omitempty"` - WildCard map[BidderName]AdjusmentsByDealID `json:"*,omitempty"` + Banner map[BidderName]AdjustmentsByDealID `json:"banner,omitempty"` + Video map[BidderName]AdjustmentsByDealID `json:"video,omitempty"` + Audio map[BidderName]AdjustmentsByDealID `json:"audio,omitempty"` + Native map[BidderName]AdjustmentsByDealID `json:"native,omitempty"` + WildCard map[BidderName]AdjustmentsByDealID `json:"*,omitempty"` } type Adjustment struct { From 20cd02ab2020195230e461e9e44b2d5c9c1bf9df Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Wed, 26 Apr 2023 15:03:42 -0700 Subject: [PATCH 19/27] Renaming, updating bidadjustment package files --- bidadjustment/{get.go => apply.go} | 43 +- bidadjustment/apply_test.go | 447 +++++++++++++++++ .../{bidadjustment.go => build_rules.go} | 73 +-- ...adjustment_test.go => build_rules_test.go} | 459 +++++------------- bidadjustment/get_test.go | 221 --------- exchange/bidder.go | 2 +- exchange/exchange.go | 6 +- 7 files changed, 626 insertions(+), 625 deletions(-) rename bidadjustment/{get.go => apply.go} (58%) create mode 100644 bidadjustment/apply_test.go rename bidadjustment/{bidadjustment.go => build_rules.go} (52%) rename bidadjustment/{bidadjustment_test.go => build_rules_test.go} (67%) delete mode 100644 bidadjustment/get_test.go diff --git a/bidadjustment/get.go b/bidadjustment/apply.go similarity index 58% rename from bidadjustment/get.go rename to bidadjustment/apply.go index 71fe4a0c990..e2cd4ca2963 100644 --- a/bidadjustment/get.go +++ b/bidadjustment/apply.go @@ -1,13 +1,24 @@ package bidadjustment import ( + "math" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/openrtb_ext" ) +const ( + AdjustmentTypeCpm = "cpm" + AdjustmentTypeMultiplier = "multiplier" + AdjustmentTypeStatic = "static" + WildCard = "*" + Delimiter = "|" +) + const maxNumOfCombos = 8 +const pricePrecision float64 = 10000 // Rounds to 4 Decimal Places -func GetAndApplyAdjustments(ruleToAdjustments map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { +func Apply(ruleToAdjustments map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { adjustments := []openrtb_ext.Adjustment{} if ruleToAdjustments != nil { adjustments = get(ruleToAdjustments, string(bidInfo.BidType), string(bidderName), bidInfo.Bid.DealID) @@ -17,6 +28,36 @@ func GetAndApplyAdjustments(ruleToAdjustments map[string][]openrtb_ext.Adjustmen return apply(adjustments, bidInfo.Bid.Price, currency, reqInfo) } +func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { + if adjustments == nil || len(adjustments) == 0 { + return bidPrice, currency + } + originalBidPrice := bidPrice + originalCurrency := currency + + for _, adjustment := range adjustments { + switch adjustment.Type { + case AdjustmentTypeMultiplier: + bidPrice = bidPrice * adjustment.Value + case AdjustmentTypeCpm: + convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, adjustment.Currency, currency) // Convert Adjustment to Bid Currency + if err != nil { + return originalBidPrice, currency + } + bidPrice = bidPrice - convertedVal + case AdjustmentTypeStatic: + bidPrice = adjustment.Value + currency = adjustment.Currency + } + } + roundedBidPrice := math.Round(bidPrice*pricePrecision) / pricePrecision + + if roundedBidPrice <= 0 { + return originalBidPrice, originalCurrency + } + return roundedBidPrice, currency +} + // get() should return the highest priority slice of adjustments from the map that we can match with the given bid info // given the bid info, we create the same format of combinations that's present in the key of the ruleToAdjustments map // the slice is ordered by priority from highest to lowest, as soon as we find a match, we return that slice diff --git a/bidadjustment/apply_test.go b/bidadjustment/apply_test.go new file mode 100644 index 00000000000..c61f3f8b0cd --- /dev/null +++ b/bidadjustment/apply_test.go @@ -0,0 +1,447 @@ +package bidadjustment + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestGetAndApply(t *testing.T) { + var ( + adjCur string = "EUR" + bidCur string = "USA" + ) + + testCases := []struct { + name string + givenRuleToAdjustments map[string][]openrtb_ext.Adjustment + givenBidderName openrtb_ext.BidderName + givenBidInfo *adapters.TypedBid + setMock func(m *mock.Mock) + expectedBidPrice float64 + expectedCurrency string + }{ + { + name: "CpmAdjustment", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + BidType: openrtb_ext.BidTypeBanner, + }, + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|bidderA|dealId": { + { + Type: AdjustmentTypeCpm, + Value: 1.0, + Currency: adjCur, + }, + }, + "banner|bidderA|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidderName: "bidderA", + setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, + expectedBidPrice: 7.5, + expectedCurrency: bidCur, + }, + { + name: "StaticAdjustment", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + BidType: openrtb_ext.BidTypeBanner, + }, + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|bidderA|dealId": { + { + Type: AdjustmentTypeCpm, + Value: 1.0, + Currency: adjCur, + }, + }, + "banner|bidderA|*": { + { + Type: AdjustmentTypeStatic, + Value: 2.0, + Currency: adjCur, + }, + }, + }, + givenBidderName: "bidderA", + setMock: nil, + expectedBidPrice: 2.0, + expectedCurrency: adjCur, + }, + { + name: "MultiplierAdjustment", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + BidType: openrtb_ext.BidTypeBanner, + }, + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|dealId": { + { + Type: AdjustmentTypeCpm, + Value: 1.0, + Currency: adjCur, + }, + }, + "banner|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + Currency: adjCur, + }, + }, + }, + givenBidderName: "bidderA", + setMock: nil, + expectedBidPrice: 20.0, + expectedCurrency: bidCur, + }, + { + name: "NilMap", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + BidType: openrtb_ext.BidTypeBanner, + }, + givenRuleToAdjustments: nil, + givenBidderName: "bidderA", + setMock: nil, + expectedBidPrice: 10.0, + expectedCurrency: bidCur, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + reqInfo := adapters.ExtraRequestInfo{} + if test.setMock != nil { + mockConversions := &mockConversions{} + test.setMock(&mockConversions.Mock) + reqInfo = adapters.NewExtraRequestInfo(mockConversions) + } + + bidPrice, currencyAfterAdjustment := Apply(test.givenRuleToAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo) + assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") + assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") + }) + } +} + +type mockConversions struct { + mock.Mock +} + +func (m mockConversions) GetRate(from string, to string) (float64, error) { + args := m.Called(from, to) + return args.Get(0).(float64), args.Error(1) +} + +func (m mockConversions) GetRates() *map[string]map[string]float64 { + args := m.Called() + return args.Get(0).(*map[string]map[string]float64) +} + +func TestApply(t *testing.T) { + var ( + adjCur string = "EUR" + bidCur string = "USA" + ) + + testCases := []struct { + name string + givenAdjustments []openrtb_ext.Adjustment + setMock func(m *mock.Mock) + givenBidPrice float64 + expectedBidPrice float64 + expectedCurrency string + }{ + { + name: "CpmAdjustment", + givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.0, Currency: adjCur}}, + givenBidPrice: 10.58687, + setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, + expectedBidPrice: 8.0869, + expectedCurrency: bidCur, + }, + { + name: "StaticAdjustment", + givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 4.0, Currency: adjCur}}, + givenBidPrice: 10.0, + setMock: nil, + expectedBidPrice: 4.0, + expectedCurrency: adjCur, + }, + { + name: "MultiplierAdjustment", + givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 3.0}}, + givenBidPrice: 10.0, + setMock: nil, + expectedBidPrice: 30.0, + expectedCurrency: bidCur, + }, + { + name: "ReturnOriginalPrice", + givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: -1.0}}, + givenBidPrice: 10.0, + setMock: nil, + expectedBidPrice: 10.0, + expectedCurrency: bidCur, + }, + { + name: "NilAdjustment", + givenAdjustments: nil, + givenBidPrice: 10.0, + setMock: nil, + expectedBidPrice: 10.0, + expectedCurrency: bidCur, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + reqInfo := adapters.ExtraRequestInfo{} + if test.setMock != nil { + mockConversions := &mockConversions{} + test.setMock(&mockConversions.Mock) + reqInfo = adapters.NewExtraRequestInfo(mockConversions) + } + + bidPrice, currencyAfterAdjustment := apply(test.givenAdjustments, test.givenBidPrice, bidCur, &reqInfo) + assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") + assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") + }) + } +} + +func TestGet(t *testing.T) { + testCases := []struct { + name string + givenRuleToAdjustments map[string][]openrtb_ext.Adjustment + givenBidType openrtb_ext.BidType + givenBidderName openrtb_ext.BidderName + givenDealId string + expected []openrtb_ext.Adjustment + }{ + { + name: "Priority1", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|bidderA|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + "banner|bidderA|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + { + name: "Priority2", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|bidderA|*": { + { + Type: AdjustmentTypeStatic, + Value: 5.0, + }, + }, + "banner|*|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0}}, + }, + { + name: "Priority3", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|*|dealId": { + { + Type: AdjustmentTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "*|bidderA|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority4", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|bidderA|dealId": { + { + Type: AdjustmentTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "banner|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority5", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|*|*": { + { + Type: AdjustmentTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "*|bidderA|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority6", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|bidderA|*": { + { + Type: AdjustmentTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "*|*|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority7", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|dealId": { + { + Type: AdjustmentTypeCpm, + Value: 3.0, + Currency: "USD", + }, + }, + "*|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + }, + { + name: "Priority8", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + "banner|bidderA|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderB", + givenDealId: "dealId", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + { + name: "NoDealID", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|bidderA|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + "banner|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeBanner, + givenBidderName: "bidderA", + givenDealId: "", + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + adjArray := get(test.givenRuleToAdjustments, string(test.givenBidType), string(test.givenBidderName), test.givenDealId) + assert.Equal(t, test.expected, adjArray, "Adjustment Array doesn't match") + }) + } +} diff --git a/bidadjustment/bidadjustment.go b/bidadjustment/build_rules.go similarity index 52% rename from bidadjustment/bidadjustment.go rename to bidadjustment/build_rules.go index 66391d649b3..d519a3c665f 100644 --- a/bidadjustment/bidadjustment.go +++ b/bidadjustment/build_rules.go @@ -1,50 +1,29 @@ package bidadjustment import ( - "math" - - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/openrtb_ext" ) -const ( - AdjustmentTypeCpm = "cpm" - AdjustmentTypeMultiplier = "multiplier" - AdjustmentTypeStatic = "static" - WildCard = "*" - Delimiter = "|" -) - -const pricePrecision float64 = 10000 // Rounds to 4 Decimal Places - -func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { - if adjustments == nil || len(adjustments) == 0 { - return bidPrice, currency +// BuildRules() will populate the rules map with a rule that's a combination of the mediaType, bidderName, and dealId for a particular adjustment +// The result will be a map that'll map a given rule with its adjustment +func BuildRules(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, rules map[string][]openrtb_ext.Adjustment) { + if bidAdjustments == nil { + return } - originalBidPrice := bidPrice - originalCurrency := currency + buildRulesForMediaType(string(openrtb_ext.BidTypeBanner), bidAdjustments.MediaType.Banner, rules) + buildRulesForMediaType(string(openrtb_ext.BidTypeVideo), bidAdjustments.MediaType.Video, rules) + buildRulesForMediaType(string(openrtb_ext.BidTypeAudio), bidAdjustments.MediaType.Audio, rules) + buildRulesForMediaType(string(openrtb_ext.BidTypeNative), bidAdjustments.MediaType.Native, rules) + buildRulesForMediaType(WildCard, bidAdjustments.MediaType.WildCard, rules) +} - for _, adjustment := range adjustments { - switch adjustment.Type { - case AdjustmentTypeMultiplier: - bidPrice = bidPrice * adjustment.Value - case AdjustmentTypeCpm: - convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, adjustment.Currency, currency) // Convert Adjustment to Bid Currency - if err != nil { - return originalBidPrice, currency - } - bidPrice = bidPrice - convertedVal - case AdjustmentTypeStatic: - bidPrice = adjustment.Value - currency = adjustment.Currency +func buildRulesForMediaType(mediaType string, rulesByBidder map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID, rules map[string][]openrtb_ext.Adjustment) { + for bidderName := range rulesByBidder { + for dealID, adjustments := range rulesByBidder[bidderName] { + rule := mediaType + Delimiter + string(bidderName) + Delimiter + dealID + rules[rule] = adjustments } } - roundedBidPrice := math.Round(bidPrice*pricePrecision) / pricePrecision - - if roundedBidPrice <= 0 { - return originalBidPrice, originalCurrency - } - return roundedBidPrice, currency } func Merge(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { @@ -105,23 +84,3 @@ func mergeForMediaType(reqAdjMap map[openrtb_ext.BidderName]openrtb_ext.Adjustme } return reqAdjMap } - -func PopulateMap(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, ruleToAdjustments map[string][]openrtb_ext.Adjustment) { - if bidAdjustments == nil { - return - } - populateMapForMediaType(bidAdjustments.MediaType.Banner, string(openrtb_ext.BidTypeBanner), ruleToAdjustments) - populateMapForMediaType(bidAdjustments.MediaType.Video, string(openrtb_ext.BidTypeVideo), ruleToAdjustments) - populateMapForMediaType(bidAdjustments.MediaType.Audio, string(openrtb_ext.BidTypeAudio), ruleToAdjustments) - populateMapForMediaType(bidAdjustments.MediaType.Native, string(openrtb_ext.BidTypeNative), ruleToAdjustments) - populateMapForMediaType(bidAdjustments.MediaType.WildCard, WildCard, ruleToAdjustments) -} - -func populateMapForMediaType(bidAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID, mediaType string, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) { - for bidderName := range bidAdj { - for dealID, adjustments := range bidAdj[bidderName] { - rule := mediaType + Delimiter + string(bidderName) + Delimiter + dealID - ruleToAdjustmentMap[rule] = adjustments - } - } -} diff --git a/bidadjustment/bidadjustment_test.go b/bidadjustment/build_rules_test.go similarity index 67% rename from bidadjustment/bidadjustment_test.go rename to bidadjustment/build_rules_test.go index 443bf34b196..8cc8e53da6a 100644 --- a/bidadjustment/bidadjustment_test.go +++ b/bidadjustment/build_rules_test.go @@ -4,237 +4,173 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) -func TestApplyAdjustmentArray(t *testing.T) { - var ( - adjCur string = "EUR" - bidCur string = "USA" - ) - +func TestBuildRules(t *testing.T) { testCases := []struct { - name string - givenAdjustments []openrtb_ext.Adjustment - setMock func(m *mock.Mock) - givenBidPrice float64 - expectedBidPrice float64 - expectedCurrency string - }{ - { - name: "CpmAdjustment", - givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.0, Currency: adjCur}}, - givenBidPrice: 10.58687, - setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, - expectedBidPrice: 8.0869, - expectedCurrency: bidCur, - }, - { - name: "StaticAdjustment", - givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 4.0, Currency: adjCur}}, - givenBidPrice: 10.0, - setMock: nil, - expectedBidPrice: 4.0, - expectedCurrency: adjCur, - }, - { - name: "MultiplierAdjustment", - givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 3.0}}, - givenBidPrice: 10.0, - setMock: nil, - expectedBidPrice: 30.0, - expectedCurrency: bidCur, - }, - { - name: "ReturnOriginalPrice", - givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: -1.0}}, - givenBidPrice: 10.0, - setMock: nil, - expectedBidPrice: 10.0, - expectedCurrency: bidCur, - }, - { - name: "NilAdjustment", - givenAdjustments: nil, - givenBidPrice: 10.0, - setMock: nil, - expectedBidPrice: 10.0, - expectedCurrency: bidCur, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - reqInfo := adapters.ExtraRequestInfo{} - if test.setMock != nil { - mockConversions := &mockConversions{} - test.setMock(&mockConversions.Mock) - reqInfo = adapters.NewExtraRequestInfo(mockConversions) - } - - bidPrice, currencyAfterAdjustment := apply(test.givenAdjustments, test.givenBidPrice, bidCur, &reqInfo) - assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") - assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") - }) - } -} - -func TestGetAndApplyAdjustmentArray(t *testing.T) { - var ( - adjCur string = "EUR" - bidCur string = "USA" - ) - - testCases := []struct { - name string - givenRuleToAdjustments map[string][]openrtb_ext.Adjustment - givenBidderName openrtb_ext.BidderName - givenBidInfo *adapters.TypedBid - setMock func(m *mock.Mock) - expectedBidPrice float64 - expectedCurrency string + name string + givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments + expectedMap map[string][]openrtb_ext.Adjustment }{ { - name: "CpmAdjustment", - givenBidInfo: &adapters.TypedBid{ - Bid: &openrtb2.Bid{ - Price: 10.0, - DealID: "dealId", + name: "OneAdjustment", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, + }, + }, }, - BidType: openrtb_ext.BidTypeBanner, }, - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + expectedMap: map[string][]openrtb_ext.Adjustment{ "banner|bidderA|dealId": { - { - Type: AdjustmentTypeCpm, - Value: 1.0, - Currency: adjCur, - }, - }, - "banner|bidderA|*": { { Type: AdjustmentTypeMultiplier, - Value: 2.0, + Value: 1.1, }, }, }, - givenBidderName: "bidderA", - setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, - expectedBidPrice: 7.5, - expectedCurrency: bidCur, }, { - name: "StaticAdjustment", - givenBidInfo: &adapters.TypedBid{ - Bid: &openrtb2.Bid{ - Price: 10.0, - DealID: "dealId", + name: "MultipleAdjustments", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + "*": { + "diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.1, Currency: "USD"}}, + "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD"}}, + }, + }, + Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "*": { + "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}, {Type: AdjustmentTypeCpm, Value: 0.18, Currency: "USD"}}, + }, + }, }, - BidType: openrtb_ext.BidTypeBanner, }, - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|bidderA|dealId": { + expectedMap: map[string][]openrtb_ext.Adjustment{ + "banner|bidderA|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + "banner|*|diffDealId": { { Type: AdjustmentTypeCpm, - Value: 1.0, - Currency: adjCur, + Value: 1.1, + Currency: "USD", }, }, - "banner|bidderA|*": { + "banner|*|*": { { Type: AdjustmentTypeStatic, - Value: 2.0, - Currency: adjCur, + Value: 5.0, + Currency: "USD", + }, + }, + "video|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + { + Type: AdjustmentTypeCpm, + Value: 0.18, + Currency: "USD", }, }, }, - givenBidderName: "bidderA", - setMock: nil, - expectedBidPrice: 2.0, - expectedCurrency: adjCur, }, { - name: "MultiplierAdjustment", - givenBidInfo: &adapters.TypedBid{ - Bid: &openrtb2.Bid{ - Price: 10.0, - DealID: "dealId", - }, - BidType: openrtb_ext.BidTypeBanner, + name: "NilAdjustments", + givenBidAdjustments: nil, + expectedMap: map[string][]openrtb_ext.Adjustment{}, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + ruleToAdjustments := make(map[string][]openrtb_ext.Adjustment) + BuildRules(test.givenBidAdjustments, ruleToAdjustments) + assert.Equal(t, test.expectedMap, ruleToAdjustments) + }) + } +} + +func TestMergeAndValidate(t *testing.T) { + testCases := []struct { + name string + givenRequestWrapper *openrtb_ext.RequestWrapper + givenAccount *config.Account + expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments + }{ + { + name: "ValidReqAndAcctAdjustments", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|*|dealId": { - { - Type: AdjustmentTypeCpm, - Value: 1.0, - Currency: adjCur, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderB": { + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + }, + }, }, }, - "banner|*|*": { - { - Type: AdjustmentTypeMultiplier, - Value: 2.0, - Currency: adjCur, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, + }, + "bidderB": { + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + }, }, }, }, - givenBidderName: "bidderA", - setMock: nil, - expectedBidPrice: 20.0, - expectedCurrency: bidCur, }, { - name: "NilMap", - givenBidInfo: &adapters.TypedBid{ - Bid: &openrtb2.Bid{ - Price: 10.0, - DealID: "dealId", + name: "InvalidReqAdjustment", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 200}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderB": { + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + }, + }, + }, }, - BidType: openrtb_ext.BidTypeBanner, }, - givenRuleToAdjustments: nil, - givenBidderName: "bidderA", - setMock: nil, - expectedBidPrice: 10.0, - expectedCurrency: bidCur, + expectedBidAdjustments: nil, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - reqInfo := adapters.ExtraRequestInfo{} - if test.setMock != nil { - mockConversions := &mockConversions{} - test.setMock(&mockConversions.Mock) - reqInfo = adapters.NewExtraRequestInfo(mockConversions) - } - - bidPrice, currencyAfterAdjustment := GetAndApplyAdjustments(test.givenRuleToAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo) - assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") - assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") + mergedBidAdj, err := Merge(test.givenRequestWrapper, test.givenAccount.BidAdjustments) + assert.NoError(t, err, "Unexpected error received") + assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) }) } } -type mockConversions struct { - mock.Mock -} - -func (m mockConversions) GetRate(from string, to string) (float64, error) { - args := m.Called(from, to) - return args.Get(0).(float64), args.Error(1) -} - -func (m mockConversions) GetRates() *map[string]map[string]float64 { - args := m.Called() - return args.Get(0).(*map[string]map[string]float64) -} - -func TestMergeBidAdjustments(t *testing.T) { +func TestMerge(t *testing.T) { testCases := []struct { name string givenRequestWrapper *openrtb_ext.RequestWrapper @@ -450,164 +386,3 @@ func TestMergeBidAdjustments(t *testing.T) { }) } } - -func TestGenerateMap(t *testing.T) { - testCases := []struct { - name string - givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments - expectedMap map[string][]openrtb_ext.Adjustment - }{ - { - name: "OneAdjustment", - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, - }, - }, - }, - }, - expectedMap: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|dealId": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - }, - }, - { - name: "MultipleAdjustments", - givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, - }, - "*": { - "diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.1, Currency: "USD"}}, - "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD"}}, - }, - }, - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "*": { - "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}, {Type: AdjustmentTypeCpm, Value: 0.18, Currency: "USD"}}, - }, - }, - }, - }, - expectedMap: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|dealId": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - "banner|*|diffDealId": { - { - Type: AdjustmentTypeCpm, - Value: 1.1, - Currency: "USD", - }, - }, - "banner|*|*": { - { - Type: AdjustmentTypeStatic, - Value: 5.0, - Currency: "USD", - }, - }, - "video|*|*": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - { - Type: AdjustmentTypeCpm, - Value: 0.18, - Currency: "USD", - }, - }, - }, - }, - { - name: "NilAdjustments", - givenBidAdjustments: nil, - expectedMap: map[string][]openrtb_ext.Adjustment{}, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - ruleToAdjustments := make(map[string][]openrtb_ext.Adjustment) - PopulateMap(test.givenBidAdjustments, ruleToAdjustments) - assert.Equal(t, test.expectedMap, ruleToAdjustments) - }) - } -} - -func TestProcessBidAdjustments(t *testing.T) { - testCases := []struct { - name string - givenRequestWrapper *openrtb_ext.RequestWrapper - givenAccount *config.Account - expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments - }{ - { - name: "ValidReqAndAcctAdjustments", - givenRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, - }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, - }, - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - { - name: "InvalidReqAdjustment", - givenRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 200}]}}}}}}`)}, - }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, - }, - }, - }, - }, - }, - expectedBidAdjustments: nil, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - mergedBidAdj, err := Merge(test.givenRequestWrapper, test.givenAccount.BidAdjustments) - assert.NoError(t, err, "Unexpected error received") - assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) - }) - } -} diff --git a/bidadjustment/get_test.go b/bidadjustment/get_test.go deleted file mode 100644 index 0026854a241..00000000000 --- a/bidadjustment/get_test.go +++ /dev/null @@ -1,221 +0,0 @@ -package bidadjustment - -import ( - "testing" - - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" -) - -func TestGetAdjustmentArray(t *testing.T) { - testCases := []struct { - name string - givenRuleToAdjustments map[string][]openrtb_ext.Adjustment - givenBidType openrtb_ext.BidType - givenBidderName openrtb_ext.BidderName - givenDealId string - expected []openrtb_ext.Adjustment - }{ - { - name: "Priority1", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|dealId": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - "banner|bidderA|*": { - { - Type: AdjustmentTypeMultiplier, - Value: 2.0, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, - }, - { - name: "Priority2", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|*": { - { - Type: AdjustmentTypeStatic, - Value: 5.0, - }, - }, - "banner|*|dealId": { - { - Type: AdjustmentTypeMultiplier, - Value: 2.0, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0}}, - }, - { - name: "Priority3", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "banner|*|dealId": { - { - Type: AdjustmentTypeCpm, - Value: 3.0, - Currency: "USD", - }, - }, - "*|bidderA|dealId": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, - }, - { - name: "Priority4", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|bidderA|dealId": { - { - Type: AdjustmentTypeCpm, - Value: 3.0, - Currency: "USD", - }, - }, - "banner|*|*": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, - }, - { - name: "Priority5", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "banner|*|*": { - { - Type: AdjustmentTypeCpm, - Value: 3.0, - Currency: "USD", - }, - }, - "*|bidderA|*": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, - }, - { - name: "Priority6", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|bidderA|*": { - { - Type: AdjustmentTypeCpm, - Value: 3.0, - Currency: "USD", - }, - }, - "*|*|dealId": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, - }, - { - name: "Priority7", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|*|dealId": { - { - Type: AdjustmentTypeCpm, - Value: 3.0, - Currency: "USD", - }, - }, - "*|*|*": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, - }, - { - name: "Priority8", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "*|*|*": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - "banner|bidderA|dealId": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderB", - givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, - }, - { - name: "NoDealID", - givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ - "banner|bidderA|*": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - "banner|*|*": { - { - Type: AdjustmentTypeMultiplier, - Value: 1.1, - }, - }, - }, - givenBidType: openrtb_ext.BidTypeBanner, - givenBidderName: "bidderA", - givenDealId: "", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - adjArray := get(test.givenRuleToAdjustments, string(test.givenBidType), string(test.givenBidderName), test.givenDealId) - assert.Equal(t, test.expected, adjArray, "Adjustment Array doesn't match") - }) - } -} diff --git a/exchange/bidder.go b/exchange/bidder.go index ec0ba6cbe63..552d0ef5476 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -340,7 +340,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if bidResponse.Bids[i].Bid != nil { originalBidCpm = bidResponse.Bids[i].Bid.Price bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate - bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustment.GetAndApplyAdjustments(ruleToAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo) + bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustment.Apply(ruleToAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo) } if _, ok := seatBidMap[bidderName]; !ok { diff --git a/exchange/exchange.go b/exchange/exchange.go index f5b03b9e4c3..0772028cca6 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -302,8 +302,8 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog if err != nil { return nil, err } - ruleToAdjustments := make(map[string][]openrtb_ext.Adjustment) - bidadjustment.PopulateMap(mergedBidAdj, ruleToAdjustments) + bidAdjustmentRules := make(map[string][]openrtb_ext.Adjustment) + bidadjustment.BuildRules(mergedBidAdj, bidAdjustmentRules) bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExtPrebid) @@ -363,7 +363,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog } else if r.Account.AlternateBidderCodes != nil { alternateBidderCodes = *r.Account.AlternateBidderCodes } - adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, ruleToAdjustments) + adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, bidAdjustmentRules) r.MakeBidsTimeInfo = buildMakeBidsTimeInfoMap(adapterExtra) } From 7b8b5cb3bc18d6a8ce3c2f3524bdc633e5f209be Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Wed, 26 Apr 2023 15:42:22 -0700 Subject: [PATCH 20/27] Add nil checks to validation, update tests, rename --- bidadjustment/apply.go | 6 ++--- bidadjustment/validate.go | 14 ++++++++---- bidadjustment/validate_test.go | 41 +++++++++++++++++++++------------- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/bidadjustment/apply.go b/bidadjustment/apply.go index e2cd4ca2963..3d604bb9119 100644 --- a/bidadjustment/apply.go +++ b/bidadjustment/apply.go @@ -18,10 +18,10 @@ const ( const maxNumOfCombos = 8 const pricePrecision float64 = 10000 // Rounds to 4 Decimal Places -func Apply(ruleToAdjustments map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { +func Apply(rules map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { adjustments := []openrtb_ext.Adjustment{} - if ruleToAdjustments != nil { - adjustments = get(ruleToAdjustments, string(bidInfo.BidType), string(bidderName), bidInfo.Bid.DealID) + if rules != nil { + adjustments = get(rules, string(bidInfo.BidType), string(bidderName), bidInfo.Bid.DealID) } else { return bidInfo.Bid.Price, currency } diff --git a/bidadjustment/validate.go b/bidadjustment/validate.go index c7a543d8bcc..c388219ded4 100644 --- a/bidadjustment/validate.go +++ b/bidadjustment/validate.go @@ -28,10 +28,16 @@ func Validate(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) bool { return true } -func validateForMediaType(bidAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID) bool { - for bidderName := range bidAdjMap { - for dealId := range bidAdjMap[bidderName] { - for _, adjustment := range bidAdjMap[bidderName][dealId] { +func validateForMediaType(bidAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID) bool { + for bidderName := range bidAdj { + if bidAdj[bidderName] == nil { + return false + } + for dealId := range bidAdj[bidderName] { + if bidAdj[bidderName][dealId] == nil { + return false + } + for _, adjustment := range bidAdj[bidderName][dealId] { if !validateAdjustment(adjustment) { return false } diff --git a/bidadjustment/validate_test.go b/bidadjustment/validate_test.go index db50297ed03..f50ee3e2a00 100644 --- a/bidadjustment/validate_test.go +++ b/bidadjustment/validate_test.go @@ -136,13 +136,13 @@ func TestValidate(t *testing.T) { func TestValidateForMediaType(t *testing.T) { testCases := []struct { - name string - givenBidAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID - expected bool + name string + givenBidAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID + expected bool }{ { name: "OneAdjustmentValid", - givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, @@ -151,7 +151,7 @@ func TestValidateForMediaType(t *testing.T) { }, { name: "OneAdjustmentInvalid", - givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: -1.1}}, }, @@ -160,7 +160,7 @@ func TestValidateForMediaType(t *testing.T) { }, { name: "MultipleAdjustmentsValid", - givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{ {Type: AdjustmentTypeMultiplier, Value: 1.1}, @@ -172,7 +172,7 @@ func TestValidateForMediaType(t *testing.T) { }, { name: "MultipleAdjustmentsInvalid", - givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{ {Type: AdjustmentTypeMultiplier, Value: -1.1}, @@ -184,7 +184,7 @@ func TestValidateForMediaType(t *testing.T) { }, { name: "MultipleDealIdsValid", - givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{ {Type: AdjustmentTypeStatic, Value: 3.0, Currency: "USD"}, @@ -198,7 +198,7 @@ func TestValidateForMediaType(t *testing.T) { }, { name: "MultipleBiddersValid", - givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{ {Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD"}, @@ -213,22 +213,31 @@ func TestValidateForMediaType(t *testing.T) { expected: true, }, { - name: "NilMediaTypeMap", - givenBidAdjMap: nil, - expected: true, + name: "NilBidAdj", + givenBidAdj: nil, + expected: true, }, { - name: "NilBidderMap", - givenBidAdjMap: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + name: "NilBidderToAdjustmentsByDealID", + givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": nil, }, - expected: true, + expected: false, + }, + { + name: "NilDealIdToAdjustments", + givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": nil, + }, + }, + expected: false, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - actual := validateForMediaType(test.givenBidAdjMap) + actual := validateForMediaType(test.givenBidAdj) assert.Equal(t, test.expected, actual, "Boolean didn't match") }) } From 3ee193152193e2b11d595d130c60eaf7288f3298 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Wed, 26 Apr 2023 20:40:16 -0700 Subject: [PATCH 21/27] Video instream/outstream support --- bidadjustment/apply.go | 14 ++--- bidadjustment/apply_test.go | 14 ++--- bidadjustment/build_rules.go | 53 +++++++++-------- bidadjustment/build_rules_test.go | 94 +++++++++++++++++++++---------- bidadjustment/validate.go | 5 +- bidadjustment/validate_test.go | 17 +++++- exchange/bidder.go | 20 ++++++- exchange/bidder_test.go | 54 ++++++++++++++++++ openrtb_ext/request.go | 11 ++-- 9 files changed, 207 insertions(+), 75 deletions(-) diff --git a/bidadjustment/apply.go b/bidadjustment/apply.go index 3d604bb9119..3876b80c4ac 100644 --- a/bidadjustment/apply.go +++ b/bidadjustment/apply.go @@ -18,10 +18,10 @@ const ( const maxNumOfCombos = 8 const pricePrecision float64 = 10000 // Rounds to 4 Decimal Places -func Apply(rules map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { +func Apply(rules map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo, bidType string) (float64, string) { adjustments := []openrtb_ext.Adjustment{} - if rules != nil { - adjustments = get(rules, string(bidInfo.BidType), string(bidderName), bidInfo.Bid.DealID) + if len(rules) > 0 { + adjustments = get(rules, bidType, string(bidderName), bidInfo.Bid.DealID) } else { return bidInfo.Bid.Price, currency } @@ -29,7 +29,7 @@ func Apply(rules map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid } func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { - if adjustments == nil || len(adjustments) == 0 { + if len(adjustments) == 0 { return bidPrice, currency } originalBidPrice := bidPrice @@ -61,7 +61,7 @@ func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency stri // get() should return the highest priority slice of adjustments from the map that we can match with the given bid info // given the bid info, we create the same format of combinations that's present in the key of the ruleToAdjustments map // the slice is ordered by priority from highest to lowest, as soon as we find a match, we return that slice -func get(ruleToAdjustments map[string][]openrtb_ext.Adjustment, bidType, bidderName, dealID string) []openrtb_ext.Adjustment { +func get(rules map[string][]openrtb_ext.Adjustment, bidType, bidderName, dealID string) []openrtb_ext.Adjustment { priorityRules := [maxNumOfCombos]string{} if dealID != "" { priorityRules[0] = bidType + Delimiter + bidderName + Delimiter + dealID @@ -80,8 +80,8 @@ func get(ruleToAdjustments map[string][]openrtb_ext.Adjustment, bidType, bidderN } for _, rule := range priorityRules { - if _, ok := ruleToAdjustments[rule]; ok { - return ruleToAdjustments[rule] + if _, ok := rules[rule]; ok { + return rules[rule] } } return nil diff --git a/bidadjustment/apply_test.go b/bidadjustment/apply_test.go index c61f3f8b0cd..1b1d08294c7 100644 --- a/bidadjustment/apply_test.go +++ b/bidadjustment/apply_test.go @@ -21,6 +21,7 @@ func TestGetAndApply(t *testing.T) { givenRuleToAdjustments map[string][]openrtb_ext.Adjustment givenBidderName openrtb_ext.BidderName givenBidInfo *adapters.TypedBid + givenBidType string setMock func(m *mock.Mock) expectedBidPrice float64 expectedCurrency string @@ -32,8 +33,8 @@ func TestGetAndApply(t *testing.T) { Price: 10.0, DealID: "dealId", }, - BidType: openrtb_ext.BidTypeBanner, }, + givenBidType: string(openrtb_ext.BidTypeBanner), givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "banner|bidderA|dealId": { { @@ -61,8 +62,8 @@ func TestGetAndApply(t *testing.T) { Price: 10.0, DealID: "dealId", }, - BidType: openrtb_ext.BidTypeBanner, }, + givenBidType: string(openrtb_ext.BidTypeBanner), givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "*|bidderA|dealId": { { @@ -91,8 +92,8 @@ func TestGetAndApply(t *testing.T) { Price: 10.0, DealID: "dealId", }, - BidType: openrtb_ext.BidTypeBanner, }, + givenBidType: VideoInstream, givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "*|*|dealId": { { @@ -101,7 +102,7 @@ func TestGetAndApply(t *testing.T) { Currency: adjCur, }, }, - "banner|*|*": { + "video-instream|*|*": { { Type: AdjustmentTypeMultiplier, Value: 2.0, @@ -121,8 +122,8 @@ func TestGetAndApply(t *testing.T) { Price: 10.0, DealID: "dealId", }, - BidType: openrtb_ext.BidTypeBanner, }, + givenBidType: string(openrtb_ext.BidTypeBanner), givenRuleToAdjustments: nil, givenBidderName: "bidderA", setMock: nil, @@ -139,8 +140,7 @@ func TestGetAndApply(t *testing.T) { test.setMock(&mockConversions.Mock) reqInfo = adapters.NewExtraRequestInfo(mockConversions) } - - bidPrice, currencyAfterAdjustment := Apply(test.givenRuleToAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo) + bidPrice, currencyAfterAdjustment := Apply(test.givenRuleToAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo, test.givenBidType) assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") }) diff --git a/bidadjustment/build_rules.go b/bidadjustment/build_rules.go index d519a3c665f..cbbe792eaef 100644 --- a/bidadjustment/build_rules.go +++ b/bidadjustment/build_rules.go @@ -4,6 +4,11 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) +const ( + VideoInstream = "video-instream" + VideoOutstream = "video-outstream" +) + // BuildRules() will populate the rules map with a rule that's a combination of the mediaType, bidderName, and dealId for a particular adjustment // The result will be a map that'll map a given rule with its adjustment func BuildRules(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, rules map[string][]openrtb_ext.Adjustment) { @@ -11,9 +16,10 @@ func BuildRules(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, rule return } buildRulesForMediaType(string(openrtb_ext.BidTypeBanner), bidAdjustments.MediaType.Banner, rules) - buildRulesForMediaType(string(openrtb_ext.BidTypeVideo), bidAdjustments.MediaType.Video, rules) buildRulesForMediaType(string(openrtb_ext.BidTypeAudio), bidAdjustments.MediaType.Audio, rules) buildRulesForMediaType(string(openrtb_ext.BidTypeNative), bidAdjustments.MediaType.Native, rules) + buildRulesForMediaType(VideoInstream, bidAdjustments.MediaType.VideoInstream, rules) + buildRulesForMediaType(VideoOutstream, bidAdjustments.MediaType.VideoOutstream, rules) buildRulesForMediaType(WildCard, bidAdjustments.MediaType.WildCard, rules) } @@ -37,50 +43,51 @@ func Merge(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestP return mergedBidAdj, err } -func merge(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { +func merge(req *openrtb_ext.RequestWrapper, acct *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { reqExt, err := req.GetRequestExt() if err != nil { return nil, err } extPrebid := reqExt.GetPrebid() - if extPrebid == nil && acctBidAdjs == nil { + if extPrebid == nil && acct == nil { return nil, nil } - if extPrebid == nil && acctBidAdjs != nil { - return acctBidAdjs, nil + if extPrebid == nil && acct != nil { + return acct, nil } - if extPrebid != nil && acctBidAdjs == nil { + if extPrebid != nil && acct == nil { return extPrebid.BidAdjustments, nil } - extPrebid.BidAdjustments.MediaType.Banner = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Banner, acctBidAdjs.MediaType.Banner) - extPrebid.BidAdjustments.MediaType.Video = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Video, acctBidAdjs.MediaType.Video) - extPrebid.BidAdjustments.MediaType.Native = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Native, acctBidAdjs.MediaType.Native) - extPrebid.BidAdjustments.MediaType.Audio = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Audio, acctBidAdjs.MediaType.Audio) - extPrebid.BidAdjustments.MediaType.WildCard = mergeForMediaType(extPrebid.BidAdjustments.MediaType.WildCard, acctBidAdjs.MediaType.WildCard) + extPrebid.BidAdjustments.MediaType.Banner = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Banner, acct.MediaType.Banner) + extPrebid.BidAdjustments.MediaType.Native = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Native, acct.MediaType.Native) + extPrebid.BidAdjustments.MediaType.Audio = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Audio, acct.MediaType.Audio) + extPrebid.BidAdjustments.MediaType.VideoInstream = mergeForMediaType(extPrebid.BidAdjustments.MediaType.VideoInstream, acct.MediaType.VideoInstream) + extPrebid.BidAdjustments.MediaType.VideoOutstream = mergeForMediaType(extPrebid.BidAdjustments.MediaType.VideoOutstream, acct.MediaType.VideoOutstream) + extPrebid.BidAdjustments.MediaType.WildCard = mergeForMediaType(extPrebid.BidAdjustments.MediaType.WildCard, acct.MediaType.WildCard) return extPrebid.BidAdjustments, nil } -func mergeForMediaType(reqAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID, accountAdjMap map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID) map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID { - if reqAdjMap != nil && accountAdjMap == nil { - return reqAdjMap +func mergeForMediaType(reqAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID, acctAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID) map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID { + if reqAdj != nil && acctAdj == nil { + return reqAdj } - if reqAdjMap == nil && accountAdjMap != nil { - return accountAdjMap + if reqAdj == nil && acctAdj != nil { + return acctAdj } - for bidderName, dealIdToAdjustmentsMap := range accountAdjMap { - if _, ok := reqAdjMap[bidderName]; ok { - for dealID, acctAdjustmentsArray := range accountAdjMap[bidderName] { - if _, okay := reqAdjMap[bidderName][dealID]; !okay { - reqAdjMap[bidderName][dealID] = acctAdjustmentsArray + for bidderName, dealIdToAdjustmentsMap := range acctAdj { + if _, ok := reqAdj[bidderName]; ok { + for dealID, acctAdjustmentsArray := range acctAdj[bidderName] { + if _, okay := reqAdj[bidderName][dealID]; !okay { + reqAdj[bidderName][dealID] = acctAdjustmentsArray } } } else { - reqAdjMap[bidderName] = dealIdToAdjustmentsMap + reqAdj[bidderName] = dealIdToAdjustmentsMap } } - return reqAdjMap + return reqAdj } diff --git a/bidadjustment/build_rules_test.go b/bidadjustment/build_rules_test.go index 8cc8e53da6a..3d8e8d034e7 100644 --- a/bidadjustment/build_rules_test.go +++ b/bidadjustment/build_rules_test.go @@ -21,7 +21,7 @@ func TestBuildRules(t *testing.T) { MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, }, }, @@ -48,11 +48,16 @@ func TestBuildRules(t *testing.T) { "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD"}}, }, }, - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "*": { "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}, {Type: AdjustmentTypeCpm, Value: 0.18, Currency: "USD"}}, }, }, + VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderB": { + "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 0.25, Currency: "USD"}}, + }, + }, }, }, expectedMap: map[string][]openrtb_ext.Adjustment{ @@ -76,7 +81,7 @@ func TestBuildRules(t *testing.T) { Currency: "USD", }, }, - "video|*|*": { + "video-instream|*|*": { { Type: AdjustmentTypeMultiplier, Value: 1.1, @@ -87,6 +92,13 @@ func TestBuildRules(t *testing.T) { Currency: "USD", }, }, + "video-outstream|bidderB|*": { + { + Type: AdjustmentTypeStatic, + Value: 0.25, + Currency: "USD", + }, + }, }, }, { @@ -187,7 +199,7 @@ func TestMerge(t *testing.T) { MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, }, @@ -197,10 +209,10 @@ func TestMerge(t *testing.T) { MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, }, @@ -216,7 +228,7 @@ func TestMerge(t *testing.T) { MediaType: openrtb_ext.MediaType{ Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, }, @@ -226,7 +238,7 @@ func TestMerge(t *testing.T) { MediaType: openrtb_ext.MediaType{ Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, }, }, @@ -235,14 +247,14 @@ func TestMerge(t *testing.T) { { name: "DiffDealIds", givenRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video":{"bidderA":{"dealId":[{ "adjtype": "static", "value": 3.00, "currency": "USD"}]}}}}}}`)}, + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video-instream":{"bidderA":{"dealId":[{ "adjtype": "static", "value": 3.00, "currency": "USD"}]}}}}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "diffDealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, }, @@ -250,10 +262,10 @@ func TestMerge(t *testing.T) { }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "static", Value: 3.00, Currency: "USD"}}, - "diffDealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 3.00, Currency: "USD"}}, + "diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, }, @@ -269,7 +281,7 @@ func TestMerge(t *testing.T) { MediaType: openrtb_ext.MediaType{ Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, }, @@ -279,10 +291,10 @@ func TestMerge(t *testing.T) { MediaType: openrtb_ext.MediaType{ Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "cpm", Value: 0.18, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 0.18, Currency: "USD"}}, }, "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, }, @@ -291,14 +303,14 @@ func TestMerge(t *testing.T) { { name: "ReqAdjVideoAcctAdjBanner", givenRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video-outstream":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, }, @@ -308,12 +320,12 @@ func TestMerge(t *testing.T) { MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, }, }, @@ -329,7 +341,7 @@ func TestMerge(t *testing.T) { MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, }, @@ -339,7 +351,7 @@ func TestMerge(t *testing.T) { MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, }, @@ -348,14 +360,14 @@ func TestMerge(t *testing.T) { { name: "AcctWildCardRequestVideo", givenRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video-instream":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, givenAccount: &config.Account{ BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, }, @@ -365,12 +377,36 @@ func TestMerge(t *testing.T) { MediaType: openrtb_ext.MediaType{ WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, }, }, - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, + }, + }, + }, + }, + }, + { + name: "NilReqExtPrebidAndAcctBidAdj", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"ext":{"bidder": {}}}`)}, + }, + givenAccount: &config.Account{}, + expectedBidAdjustments: nil, + }, + { + name: "NilAcctBidAdj", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + givenAccount: &config.Account{}, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, }, }, diff --git a/bidadjustment/validate.go b/bidadjustment/validate.go index c388219ded4..06d10d1c760 100644 --- a/bidadjustment/validate.go +++ b/bidadjustment/validate.go @@ -16,7 +16,10 @@ func Validate(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) bool { if !validateForMediaType(bidAdjustments.MediaType.Audio) { return false } - if !validateForMediaType(bidAdjustments.MediaType.Video) { + if !validateForMediaType(bidAdjustments.MediaType.VideoInstream) { + return false + } + if !validateForMediaType(bidAdjustments.MediaType.VideoOutstream) { return false } if !validateForMediaType(bidAdjustments.MediaType.Native) { diff --git a/bidadjustment/validate_test.go b/bidadjustment/validate_test.go index f50ee3e2a00..a7f3d7abde7 100644 --- a/bidadjustment/validate_test.go +++ b/bidadjustment/validate_test.go @@ -35,7 +35,7 @@ func TestValidate(t *testing.T) { "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, }, - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 3.0, Currency: "USD"}}, }, @@ -53,7 +53,7 @@ func TestValidate(t *testing.T) { "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, }, - Video: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: ""}}, }, @@ -114,6 +114,19 @@ func TestValidate(t *testing.T) { }, expected: false, }, + { + name: "InstreamInvalid", + givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": { + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 150}}, + }, + }, + }, + }, + expected: false, + }, { name: "EmptyBidAdjustments", givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{}, diff --git a/exchange/bidder.go b/exchange/bidder.go index 552d0ef5476..0fd62a8889f 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -24,6 +24,7 @@ import ( "github.com/prebid/prebid-server/hooks/hookexecution" "github.com/prebid/prebid-server/version" + "github.com/prebid/openrtb/v19/adcom1" nativeRequests "github.com/prebid/openrtb/v19/native1/request" nativeResponse "github.com/prebid/openrtb/v19/native1/response" "github.com/prebid/openrtb/v19/openrtb2" @@ -340,7 +341,9 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if bidResponse.Bids[i].Bid != nil { originalBidCpm = bidResponse.Bids[i].Bid.Price bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate - bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustment.Apply(ruleToAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo) + + bidType := GetBidType(bidResponse.Bids[i].BidType, bidResponse.Bids[i].Bid.ImpID, bidderRequest.BidRequest.Imp) + bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustment.Apply(ruleToAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo, bidType) } if _, ok := seatBidMap[bidderName]; !ok { @@ -689,3 +692,18 @@ func compressToGZIP(requestBody []byte) []byte { w.Close() return b.Bytes() } + +func GetBidType(bidType openrtb_ext.BidType, impId string, imp []openrtb2.Imp) string { + if bidType == openrtb_ext.BidTypeVideo { + for _, imp := range imp { + if imp.ID == impId { + if imp.Video.Plcmt == adcom1.VideoPlcmtInstream { + return "video-instream" + } else if imp.Video.Plcmt == adcom1.VideoPlcmtAccompanyingContent { + return "video-outstream" + } + } + } + } + return string(bidType) +} diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 0c675ec9284..ec18dcf0943 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -17,6 +17,7 @@ import ( "time" "github.com/golang/glog" + "github.com/prebid/openrtb/v19/adcom1" nativeRequests "github.com/prebid/openrtb/v19/native1/request" nativeResponse "github.com/prebid/openrtb/v19/native1/response" "github.com/prebid/openrtb/v19/openrtb2" @@ -2988,3 +2989,56 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { }) assert.Equal(t, wantSeatBids, seatBids) } + +func TestGetBidType(t *testing.T) { + testCases := []struct { + name string + givenBidType openrtb_ext.BidType + givenImpId string + givenImp []openrtb2.Imp + expected string + }{ + { + name: "VideoInstream", + givenImp: []openrtb2.Imp{ + { + ID: "imp-id", + Video: &openrtb2.Video{ + Plcmt: adcom1.VideoPlcmtInstream, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeVideo, + givenImpId: "imp-id", + expected: "video-instream", + }, + { + name: "VideoOutstream", + givenImp: []openrtb2.Imp{ + { + ID: "imp-id", + Video: &openrtb2.Video{ + Plcmt: adcom1.VideoPlcmtAccompanyingContent, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeVideo, + givenImpId: "imp-id", + expected: "video-outstream", + }, + { + name: "NonVideoBidType", + givenImp: []openrtb2.Imp{}, + givenBidType: openrtb_ext.BidTypeBanner, + givenImpId: "imp-id", + expected: string(openrtb_ext.BidTypeBanner), + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actual := GetBidType(test.givenBidType, test.givenImpId, test.givenImp) + assert.Equal(t, test.expected, actual, "Bid type doesn't match") + }) + } +} diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 973b563b666..56e546f8775 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -158,11 +158,12 @@ type AdjustmentsByDealID map[string][]Adjustment // BidderName maps to a DealID that maps to the Adjustments type MediaType struct { - Banner map[BidderName]AdjustmentsByDealID `json:"banner,omitempty"` - Video map[BidderName]AdjustmentsByDealID `json:"video,omitempty"` - Audio map[BidderName]AdjustmentsByDealID `json:"audio,omitempty"` - Native map[BidderName]AdjustmentsByDealID `json:"native,omitempty"` - WildCard map[BidderName]AdjustmentsByDealID `json:"*,omitempty"` + Banner map[BidderName]AdjustmentsByDealID `json:"banner,omitempty"` + VideoInstream map[BidderName]AdjustmentsByDealID `json:"video-instream,omitempty"` + VideoOutstream map[BidderName]AdjustmentsByDealID `json:"video-outstream,omitempty"` + Audio map[BidderName]AdjustmentsByDealID `json:"audio,omitempty"` + Native map[BidderName]AdjustmentsByDealID `json:"native,omitempty"` + WildCard map[BidderName]AdjustmentsByDealID `json:"*,omitempty"` } type Adjustment struct { From 9708f828b3ece35577d4661199d2604784be78cf Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Tue, 2 May 2023 14:44:59 -0700 Subject: [PATCH 22/27] Address all feedback --- bidadjustment/apply.go | 7 +- bidadjustment/apply_test.go | 91 ++++++++++++++----- bidadjustment/build_rules.go | 26 ++++-- bidadjustment/build_rules_test.go | 43 ++++++--- bidadjustment/validate.go | 9 +- bidadjustment/validate_test.go | 20 ++-- endpoints/openrtb2/auction_test.go | 4 + .../cpm.json} | 0 .../multiplier.json} | 0 .../static.json} | 0 exchange/bidder.go | 16 ++-- exchange/bidder_test.go | 2 +- exchange/bidder_validate_bids.go | 2 +- exchange/bidder_validate_bids_test.go | 2 +- exchange/exchange.go | 7 +- exchange/exchange_test.go | 6 +- openrtb_ext/request.go | 6 +- 17 files changed, 161 insertions(+), 80 deletions(-) rename endpoints/openrtb2/sample-requests/{valid-whole/exemplary/bidadjustments-cpm.json => bidadjustments/cpm.json} (100%) rename endpoints/openrtb2/sample-requests/{valid-whole/exemplary/bidadjustments-simple.json => bidadjustments/multiplier.json} (100%) rename endpoints/openrtb2/sample-requests/{valid-whole/exemplary/bidadjustments-static.json => bidadjustments/static.json} (100%) diff --git a/bidadjustment/apply.go b/bidadjustment/apply.go index 3876b80c4ac..bbb34a6c1a1 100644 --- a/bidadjustment/apply.go +++ b/bidadjustment/apply.go @@ -8,7 +8,7 @@ import ( ) const ( - AdjustmentTypeCpm = "cpm" + AdjustmentTypeCPM = "cpm" AdjustmentTypeMultiplier = "multiplier" AdjustmentTypeStatic = "static" WildCard = "*" @@ -18,6 +18,7 @@ const ( const maxNumOfCombos = 8 const pricePrecision float64 = 10000 // Rounds to 4 Decimal Places +// Apply gets the highest priority adjustment slice given a map of rules, and applies those adjustments to a bid's price func Apply(rules map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo, bidType string) (float64, string) { adjustments := []openrtb_ext.Adjustment{} if len(rules) > 0 { @@ -39,8 +40,8 @@ func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency stri switch adjustment.Type { case AdjustmentTypeMultiplier: bidPrice = bidPrice * adjustment.Value - case AdjustmentTypeCpm: - convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, adjustment.Currency, currency) // Convert Adjustment to Bid Currency + case AdjustmentTypeCPM: + convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, adjustment.Currency, currency) if err != nil { return originalBidPrice, currency } diff --git a/bidadjustment/apply_test.go b/bidadjustment/apply_test.go index 1b1d08294c7..1bb5cbe07f9 100644 --- a/bidadjustment/apply_test.go +++ b/bidadjustment/apply_test.go @@ -38,7 +38,7 @@ func TestGetAndApply(t *testing.T) { givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "banner|bidderA|dealId": { { - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 1.0, Currency: adjCur, }, @@ -67,7 +67,7 @@ func TestGetAndApply(t *testing.T) { givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "*|bidderA|dealId": { { - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 1.0, Currency: adjCur, }, @@ -97,16 +97,15 @@ func TestGetAndApply(t *testing.T) { givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "*|*|dealId": { { - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 1.0, Currency: adjCur, }, }, "video-instream|*|*": { { - Type: AdjustmentTypeMultiplier, - Value: 2.0, - Currency: adjCur, + Type: AdjustmentTypeMultiplier, + Value: 2.0, }, }, }, @@ -115,6 +114,33 @@ func TestGetAndApply(t *testing.T) { expectedBidPrice: 20.0, expectedCurrency: bidCur, }, + { + name: "CpmAndMultiplierAdjustments", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 10.0, + DealID: "dealId", + }, + }, + givenBidType: VideoInstream, + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "video-instream|*|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + { + Type: AdjustmentTypeCPM, + Value: 1.0, + Currency: adjCur, + }, + }, + }, + givenBidderName: "bidderA", + setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, + expectedBidPrice: 17.5, + expectedCurrency: bidCur, + }, { name: "NilMap", givenBidInfo: &adapters.TypedBid{ @@ -141,8 +167,8 @@ func TestGetAndApply(t *testing.T) { reqInfo = adapters.NewExtraRequestInfo(mockConversions) } bidPrice, currencyAfterAdjustment := Apply(test.givenRuleToAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo, test.givenBidType) - assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") - assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") + assert.Equal(t, test.expectedBidPrice, bidPrice) + assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment) }) } } @@ -177,7 +203,7 @@ func TestApply(t *testing.T) { }{ { name: "CpmAdjustment", - givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.0, Currency: adjCur}}, + givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 1.0, Currency: adjCur}}, givenBidPrice: 10.58687, setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, expectedBidPrice: 8.0869, @@ -227,8 +253,8 @@ func TestApply(t *testing.T) { } bidPrice, currencyAfterAdjustment := apply(test.givenAdjustments, test.givenBidPrice, bidCur, &reqInfo) - assert.Equal(t, test.expectedBidPrice, bidPrice, "Incorrect bid prices") - assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment, "Incorrect currency") + assert.Equal(t, test.expectedBidPrice, bidPrice) + assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment) }) } } @@ -289,7 +315,7 @@ func TestGet(t *testing.T) { givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "banner|*|dealId": { { - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD", }, @@ -304,14 +330,14 @@ func TestGet(t *testing.T) { givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, }, { name: "Priority4", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "*|bidderA|dealId": { { - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD", }, @@ -326,14 +352,14 @@ func TestGet(t *testing.T) { givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, }, { name: "Priority5", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "banner|*|*": { { - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD", }, @@ -348,14 +374,14 @@ func TestGet(t *testing.T) { givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, }, { name: "Priority6", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "*|bidderA|*": { { - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD", }, @@ -370,14 +396,14 @@ func TestGet(t *testing.T) { givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, }, { name: "Priority7", givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ "*|*|dealId": { { - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD", }, @@ -392,7 +418,7 @@ func TestGet(t *testing.T) { givenBidType: openrtb_ext.BidTypeBanner, givenBidderName: "bidderA", givenDealId: "dealId", - expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}}, + expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}}, }, { name: "Priority8", @@ -436,12 +462,33 @@ func TestGet(t *testing.T) { givenDealId: "", expected: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, + { + name: "NoPriorityRulesMatch", + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "banner|bidderA|dealId": { + { + Type: AdjustmentTypeMultiplier, + Value: 1.1, + }, + }, + "banner|bidderA|*": { + { + Type: AdjustmentTypeMultiplier, + Value: 2.0, + }, + }, + }, + givenBidType: openrtb_ext.BidTypeVideo, + givenBidderName: "bidderB", + givenDealId: "diffDealId", + expected: nil, + }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { adjArray := get(test.givenRuleToAdjustments, string(test.givenBidType), string(test.givenBidderName), test.givenDealId) - assert.Equal(t, test.expected, adjArray, "Adjustment Array doesn't match") + assert.Equal(t, test.expected, adjArray) }) } } diff --git a/bidadjustment/build_rules.go b/bidadjustment/build_rules.go index cbbe792eaef..b8a7ee89b68 100644 --- a/bidadjustment/build_rules.go +++ b/bidadjustment/build_rules.go @@ -1,6 +1,7 @@ package bidadjustment import ( + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -11,16 +12,20 @@ const ( // BuildRules() will populate the rules map with a rule that's a combination of the mediaType, bidderName, and dealId for a particular adjustment // The result will be a map that'll map a given rule with its adjustment -func BuildRules(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments, rules map[string][]openrtb_ext.Adjustment) { +func BuildRules(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) map[string][]openrtb_ext.Adjustment { if bidAdjustments == nil { - return + return nil } + rules := make(map[string][]openrtb_ext.Adjustment) + buildRulesForMediaType(string(openrtb_ext.BidTypeBanner), bidAdjustments.MediaType.Banner, rules) buildRulesForMediaType(string(openrtb_ext.BidTypeAudio), bidAdjustments.MediaType.Audio, rules) buildRulesForMediaType(string(openrtb_ext.BidTypeNative), bidAdjustments.MediaType.Native, rules) buildRulesForMediaType(VideoInstream, bidAdjustments.MediaType.VideoInstream, rules) buildRulesForMediaType(VideoOutstream, bidAdjustments.MediaType.VideoOutstream, rules) buildRulesForMediaType(WildCard, bidAdjustments.MediaType.WildCard, rules) + + return rules } func buildRulesForMediaType(mediaType string, rulesByBidder map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID, rules map[string][]openrtb_ext.Adjustment) { @@ -32,6 +37,7 @@ func buildRulesForMediaType(mediaType string, rulesByBidder map[openrtb_ext.Bidd } } +// Merge takes bid adjustments defined on the request and on the account, and combines/validates them, with the adjustments on the request taking precedence. func Merge(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) { mergedBidAdj, err := merge(req, acctBidAdjs) if err != nil { @@ -39,6 +45,10 @@ func Merge(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestP } if !Validate(mergedBidAdj) { mergedBidAdj = nil + err = &errortypes.Warning{ + WarningCode: errortypes.BidAdjustmentWarningCode, + Message: "bid adjustment after merge was invalid", + } } return mergedBidAdj, err } @@ -70,7 +80,7 @@ func merge(req *openrtb_ext.RequestWrapper, acct *openrtb_ext.ExtRequestPrebidBi return extPrebid.BidAdjustments, nil } -func mergeForMediaType(reqAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID, acctAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID) map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID { +func mergeForMediaType(reqAdj, acctAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID) map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID { if reqAdj != nil && acctAdj == nil { return reqAdj } @@ -78,15 +88,15 @@ func mergeForMediaType(reqAdj map[openrtb_ext.BidderName]openrtb_ext.Adjustments return acctAdj } - for bidderName, dealIdToAdjustmentsMap := range acctAdj { + for bidderName, dealIDToAdjustments := range acctAdj { if _, ok := reqAdj[bidderName]; ok { - for dealID, acctAdjustmentsArray := range acctAdj[bidderName] { - if _, okay := reqAdj[bidderName][dealID]; !okay { - reqAdj[bidderName][dealID] = acctAdjustmentsArray + for dealID, acctAdjustments := range acctAdj[bidderName] { + if _, ok := reqAdj[bidderName][dealID]; !ok { + reqAdj[bidderName][dealID] = acctAdjustments } } } else { - reqAdj[bidderName] = dealIdToAdjustmentsMap + reqAdj[bidderName] = dealIDToAdjustments } } return reqAdj diff --git a/bidadjustment/build_rules_test.go b/bidadjustment/build_rules_test.go index 3d8e8d034e7..9e5ffb53cd6 100644 --- a/bidadjustment/build_rules_test.go +++ b/bidadjustment/build_rules_test.go @@ -13,7 +13,7 @@ func TestBuildRules(t *testing.T) { testCases := []struct { name string givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments - expectedMap map[string][]openrtb_ext.Adjustment + expectedRules map[string][]openrtb_ext.Adjustment }{ { name: "OneAdjustment", @@ -26,7 +26,7 @@ func TestBuildRules(t *testing.T) { }, }, }, - expectedMap: map[string][]openrtb_ext.Adjustment{ + expectedRules: map[string][]openrtb_ext.Adjustment{ "banner|bidderA|dealId": { { Type: AdjustmentTypeMultiplier, @@ -44,13 +44,13 @@ func TestBuildRules(t *testing.T) { "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, }, "*": { - "diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 1.1, Currency: "USD"}}, + "diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 1.1, Currency: "USD"}}, "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD"}}, }, }, VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "*": { - "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}, {Type: AdjustmentTypeCpm, Value: 0.18, Currency: "USD"}}, + "*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}, {Type: AdjustmentTypeCPM, Value: 0.18, Currency: "USD"}}, }, }, VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ @@ -60,7 +60,7 @@ func TestBuildRules(t *testing.T) { }, }, }, - expectedMap: map[string][]openrtb_ext.Adjustment{ + expectedRules: map[string][]openrtb_ext.Adjustment{ "banner|bidderA|dealId": { { Type: AdjustmentTypeMultiplier, @@ -69,7 +69,7 @@ func TestBuildRules(t *testing.T) { }, "banner|*|diffDealId": { { - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 1.1, Currency: "USD", }, @@ -87,7 +87,7 @@ func TestBuildRules(t *testing.T) { Value: 1.1, }, { - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 0.18, Currency: "USD", }, @@ -104,15 +104,14 @@ func TestBuildRules(t *testing.T) { { name: "NilAdjustments", givenBidAdjustments: nil, - expectedMap: map[string][]openrtb_ext.Adjustment{}, + expectedRules: nil, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - ruleToAdjustments := make(map[string][]openrtb_ext.Adjustment) - BuildRules(test.givenBidAdjustments, ruleToAdjustments) - assert.Equal(t, test.expectedMap, ruleToAdjustments) + rules := BuildRules(test.givenBidAdjustments) + assert.Equal(t, test.expectedRules, rules) }) } } @@ -122,6 +121,7 @@ func TestMergeAndValidate(t *testing.T) { name string givenRequestWrapper *openrtb_ext.RequestWrapper givenAccount *config.Account + expectError bool expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments }{ { @@ -140,6 +140,7 @@ func TestMergeAndValidate(t *testing.T) { }, }, }, + expectError: false, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ @@ -169,6 +170,16 @@ func TestMergeAndValidate(t *testing.T) { }, }, }, + expectError: true, + expectedBidAdjustments: nil, + }, + { + name: "InvalidJSON", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{}}`)}, + }, + givenAccount: &config.Account{}, + expectError: true, expectedBidAdjustments: nil, }, } @@ -176,7 +187,11 @@ func TestMergeAndValidate(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { mergedBidAdj, err := Merge(test.givenRequestWrapper, test.givenAccount.BidAdjustments) - assert.NoError(t, err, "Unexpected error received") + if !test.expectError { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) }) } @@ -291,7 +306,7 @@ func TestMerge(t *testing.T) { MediaType: openrtb_ext.MediaType{ Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 0.18, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 0.18, Currency: "USD"}}, }, "bidderB": { "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, @@ -417,7 +432,7 @@ func TestMerge(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { mergedBidAdj, err := merge(test.givenRequestWrapper, test.givenAccount.BidAdjustments) - assert.NoError(t, err, "Unexpected error received") + assert.NoError(t, err) assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) }) } diff --git a/bidadjustment/validate.go b/bidadjustment/validate.go index 06d10d1c760..c0ae3d4a27b 100644 --- a/bidadjustment/validate.go +++ b/bidadjustment/validate.go @@ -6,6 +6,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) +// Validate checks whether all provided bid adjustments are valid or not against the requirements defined in the issue func Validate(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) bool { if bidAdjustments == nil { return true @@ -36,11 +37,11 @@ func validateForMediaType(bidAdj map[openrtb_ext.BidderName]openrtb_ext.Adjustme if bidAdj[bidderName] == nil { return false } - for dealId := range bidAdj[bidderName] { - if bidAdj[bidderName][dealId] == nil { + for dealID := range bidAdj[bidderName] { + if bidAdj[bidderName][dealID] == nil { return false } - for _, adjustment := range bidAdj[bidderName][dealId] { + for _, adjustment := range bidAdj[bidderName][dealID] { if !validateAdjustment(adjustment) { return false } @@ -52,7 +53,7 @@ func validateForMediaType(bidAdj map[openrtb_ext.BidderName]openrtb_ext.Adjustme func validateAdjustment(adjustment openrtb_ext.Adjustment) bool { switch adjustment.Type { - case AdjustmentTypeCpm: + case AdjustmentTypeCPM: if adjustment.Currency != "" && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 { return true } diff --git a/bidadjustment/validate_test.go b/bidadjustment/validate_test.go index a7f3d7abde7..a0b4eb436eb 100644 --- a/bidadjustment/validate_test.go +++ b/bidadjustment/validate_test.go @@ -55,7 +55,7 @@ func TestValidate(t *testing.T) { }, VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: 3.0, Currency: ""}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: ""}}, }, }, }, @@ -68,7 +68,7 @@ func TestValidate(t *testing.T) { MediaType: openrtb_ext.MediaType{ WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: -1.1, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: -1.1, Currency: "USD"}}, }, }, }, @@ -94,7 +94,7 @@ func TestValidate(t *testing.T) { MediaType: openrtb_ext.MediaType{ Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCpm, Value: -1.1, Currency: "USD"}}, + "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: -1.1, Currency: "USD"}}, }, }, }, @@ -142,7 +142,7 @@ func TestValidate(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { actual := Validate(test.givenBidAdjustments) - assert.Equal(t, test.expected, actual, "Boolean didn't match") + assert.Equal(t, test.expected, actual) }) } } @@ -189,7 +189,7 @@ func TestValidateForMediaType(t *testing.T) { "bidderA": { "dealId": []openrtb_ext.Adjustment{ {Type: AdjustmentTypeMultiplier, Value: -1.1}, - {Type: AdjustmentTypeCpm, Value: -3.0, Currency: "USD"}, + {Type: AdjustmentTypeCPM, Value: -3.0, Currency: "USD"}, }, }, }, @@ -219,7 +219,7 @@ func TestValidateForMediaType(t *testing.T) { }, "bidderB": { "dealId": []openrtb_ext.Adjustment{ - {Type: AdjustmentTypeCpm, Value: 3.0, Currency: "USD"}, + {Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}, }, }, }, @@ -251,7 +251,7 @@ func TestValidateForMediaType(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { actual := validateForMediaType(test.givenBidAdj) - assert.Equal(t, test.expected, actual, "Boolean didn't match") + assert.Equal(t, test.expected, actual) }) } } @@ -265,7 +265,7 @@ func TestValidateAdjustment(t *testing.T) { { name: "ValidCpm", givenAdjustment: openrtb_ext.Adjustment{ - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 5.0, Currency: "USD", }, @@ -291,7 +291,7 @@ func TestValidateAdjustment(t *testing.T) { { name: "InvalidCpm", givenAdjustment: openrtb_ext.Adjustment{ - Type: AdjustmentTypeCpm, + Type: AdjustmentTypeCPM, Value: 5.0, Currency: "", }, @@ -332,7 +332,7 @@ func TestValidateAdjustment(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { actual := validateAdjustment(test.givenAdjustment) - assert.Equal(t, test.expected, actual, "Boolean didn't match") + assert.Equal(t, test.expected, actual) }) } } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 00c6d7f79cb..0c2ded0a044 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -114,6 +114,10 @@ func TestJsonSampleRequests(t *testing.T) { "Assert request with ad server targeting is processing correctly", "adservertargeting", }, + { + "Assert request with bid adjustments defined is processing correctly", + "bidadjustments", + }, } for _, tc := range testSuites { diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json b/endpoints/openrtb2/sample-requests/bidadjustments/cpm.json similarity index 100% rename from endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-cpm.json rename to endpoints/openrtb2/sample-requests/bidadjustments/cpm.json diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json b/endpoints/openrtb2/sample-requests/bidadjustments/multiplier.json similarity index 100% rename from endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-simple.json rename to endpoints/openrtb2/sample-requests/bidadjustments/multiplier.json diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json b/endpoints/openrtb2/sample-requests/bidadjustments/static.json similarity index 100% rename from endpoints/openrtb2/sample-requests/valid-whole/exemplary/bidadjustments-static.json rename to endpoints/openrtb2/sample-requests/bidadjustments/static.json diff --git a/exchange/bidder.go b/exchange/bidder.go index 0fd62a8889f..fea6cfeab1c 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -58,7 +58,7 @@ type AdaptedBidder interface { // // Any errors will be user-facing in the API. // Error messages should help publishers understand what might account for "bad" bids. - 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 map[string][]openrtb_ext.Adjustment) ([]*entities.PbsOrtbSeatBid, []error) + 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, []error) } // bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters @@ -117,7 +117,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 map[string][]openrtb_ext.Adjustment) ([]*entities.PbsOrtbSeatBid, []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, []error) { reject := hookExecutor.ExecuteBidderRequestStage(bidderRequest.BidRequest, string(bidderRequest.BidderName)) if reject != nil { return nil, []error{reject} @@ -342,7 +342,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde originalBidCpm = bidResponse.Bids[i].Bid.Price bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate - bidType := GetBidType(bidResponse.Bids[i].BidType, bidResponse.Bids[i].Bid.ImpID, bidderRequest.BidRequest.Imp) + bidType := getBidTypeForAdjustments(bidResponse.Bids[i].BidType, bidResponse.Bids[i].Bid.ImpID, bidderRequest.BidRequest.Imp) bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustment.Apply(ruleToAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo, bidType) } @@ -693,14 +693,14 @@ func compressToGZIP(requestBody []byte) []byte { return b.Bytes() } -func GetBidType(bidType openrtb_ext.BidType, impId string, imp []openrtb2.Imp) string { +func getBidTypeForAdjustments(bidType openrtb_ext.BidType, impID string, imp []openrtb2.Imp) string { if bidType == openrtb_ext.BidTypeVideo { for _, imp := range imp { - if imp.ID == impId { - if imp.Video.Plcmt == adcom1.VideoPlcmtInstream { - return "video-instream" - } else if imp.Video.Plcmt == adcom1.VideoPlcmtAccompanyingContent { + if imp.ID == impID { + if imp.Video.Plcmt == adcom1.VideoPlcmtAccompanyingContent { return "video-outstream" + } else { + return "video-instream" } } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index ec18dcf0943..42f6e6cc2ae 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -3037,7 +3037,7 @@ func TestGetBidType(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - actual := GetBidType(test.givenBidType, test.givenImpId, test.givenImp) + actual := getBidTypeForAdjustments(test.givenBidType, test.givenImpId, test.givenImp) assert.Equal(t, test.expected, actual, "Bid type doesn't match") }) } diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index 4d9682b934f..8283a674dac 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -31,7 +31,7 @@ type validatedBidder struct { bidder AdaptedBidder } -func (v *validatedBidder) 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 map[string][]openrtb_ext.Adjustment) ([]*entities.PbsOrtbSeatBid, []error) { +func (v *validatedBidder) 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, []error) { seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor, ruleToAdjustments) for _, seatBid := range seatBids { if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 { diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index 4d4203eb319..e7340e7d89b 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -357,6 +357,6 @@ type mockAdaptedBidder struct { errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) ([]*entities.PbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/exchange.go b/exchange/exchange.go index 0772028cca6..0eb9dfa1b87 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -302,8 +302,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog if err != nil { return nil, err } - bidAdjustmentRules := make(map[string][]openrtb_ext.Adjustment) - bidadjustment.BuildRules(mergedBidAdj, bidAdjustmentRules) + bidAdjustmentRules := bidadjustment.BuildRules(mergedBidAdj) bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExtPrebid) @@ -637,7 +636,7 @@ func (e *exchange) getAllBids( experiment *openrtb_ext.Experiment, hookExecutor hookexecution.StageExecutor, pbsRequestStartTime time.Time, - ruleToAdjustments map[string][]openrtb_ext.Adjustment) ( + bidAdjustmentRules map[string][]openrtb_ext.Adjustment) ( map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, *openrtb_ext.Fledge, @@ -678,7 +677,7 @@ func (e *exchange) getAllBids( addCallSignHeader: isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]), bidAdjustments: bidAdjustments, } - seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, ruleToAdjustments) + seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, bidAdjustmentRules) // Add in time reporting elapsed := time.Since(start) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index f19a53814b0..48941712f0f 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -5313,7 +5313,7 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) (seatBids []*entities.PbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (seatBids []*entities.PbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok { if expectedRequest != nil { if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) { @@ -5371,7 +5371,7 @@ type capturingRequestBidder struct { req *openrtb2.BidRequest } -func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) (seatBid []*entities.PbsOrtbSeatBid, errs []error) { +func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (seatBid []*entities.PbsOrtbSeatBid, errs []error) { b.req = bidderRequest.BidRequest return []*entities.PbsOrtbSeatBid{{}}, nil } @@ -5478,7 +5478,7 @@ func (e *emptyUsersync) HasAnyLiveSyncs() bool { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustmentMap map[string][]openrtb_ext.Adjustment) (posb []*entities.PbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (posb []*entities.PbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 56e546f8775..59e258fc095 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -150,13 +150,16 @@ type ExtRequestPrebidCacheVAST struct { ReturnCreative *bool `json:"returnCreative,omitempty"` } +// ExtRequestPrebidBidAdjustments defines the contract for bidrequest.ext.prebid.bidadjustments type ExtRequestPrebidBidAdjustments struct { MediaType MediaType `json:"mediatype,omitempty"` } +// AdjustmentsByDealID maps a dealID to a slice of bid adjustments type AdjustmentsByDealID map[string][]Adjustment -// BidderName maps to a DealID that maps to the Adjustments +// MediaType defines contract for bidrequest.ext.prebid.bidadjustments.mediatype +// BidderName will map to a DealID that will map to a slice of bid adjustments type MediaType struct { Banner map[BidderName]AdjustmentsByDealID `json:"banner,omitempty"` VideoInstream map[BidderName]AdjustmentsByDealID `json:"video-instream,omitempty"` @@ -166,6 +169,7 @@ type MediaType struct { WildCard map[BidderName]AdjustmentsByDealID `json:"*,omitempty"` } +// Adjustment defines the object that will be present in the slice of bid adjustments found from MediaType map type Adjustment struct { Type string `json:"adjtype,omitempty"` Value float64 `json:"value,omitempty"` From 39cfec93563bbcb545debafe3c110f9874ecb21b Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Wed, 3 May 2023 11:43:56 -0700 Subject: [PATCH 23/27] Fix error after merge --- config/accounts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/accounts.go b/config/accounts.go index 59413c1373a..a8d05813583 100644 --- a/config/accounts.go +++ b/config/accounts.go @@ -26,7 +26,7 @@ type Account struct { ID string `mapstructure:"id" json:"id"` Disabled bool `mapstructure:"disabled" json:"disabled"` CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"` - EventsEnabled bool `mapstructure:"events_enabled" json:"events_enabled"` + EventsEnabled *bool `mapstructure:"events_enabled" json:"events_enabled"` CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"` GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"` DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"` From 8c1e089eb97905b38e403893296ceb4f08340539 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Wed, 3 May 2023 12:18:24 -0700 Subject: [PATCH 24/27] Address error handling, edge cases --- bidadjustment/apply.go | 15 ++++++--- bidadjustment/apply_test.go | 54 ++++++++++++++++++++++++++----- bidadjustment/build_rules.go | 2 +- bidadjustment/build_rules_test.go | 19 +++++++++++ errortypes/severity.go | 6 ++-- exchange/bidder.go | 4 +-- exchange/exchange.go | 16 +++++---- 7 files changed, 91 insertions(+), 25 deletions(-) diff --git a/bidadjustment/apply.go b/bidadjustment/apply.go index bbb34a6c1a1..4fa3b737b16 100644 --- a/bidadjustment/apply.go +++ b/bidadjustment/apply.go @@ -17,6 +17,7 @@ const ( const maxNumOfCombos = 8 const pricePrecision float64 = 10000 // Rounds to 4 Decimal Places +const minBid = 0.1 // Apply gets the highest priority adjustment slice given a map of rules, and applies those adjustments to a bid's price func Apply(rules map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo, bidType string) (float64, string) { @@ -26,7 +27,15 @@ func Apply(rules map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid } else { return bidInfo.Bid.Price, currency } - return apply(adjustments, bidInfo.Bid.Price, currency, reqInfo) + adjustedPrice, adjustedCurrency := apply(adjustments, bidInfo.Bid.Price, currency, reqInfo) + + if bidInfo.Bid.DealID != "" && adjustedPrice < 0 { + return 0, currency + } + if bidInfo.Bid.DealID == "" && adjustedPrice <= 0 { + return minBid, currency + } + return adjustedPrice, adjustedCurrency } func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) { @@ -34,7 +43,6 @@ func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency stri return bidPrice, currency } originalBidPrice := bidPrice - originalCurrency := currency for _, adjustment := range adjustments { switch adjustment.Type { @@ -53,9 +61,6 @@ func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency stri } roundedBidPrice := math.Round(bidPrice*pricePrecision) / pricePrecision - if roundedBidPrice <= 0 { - return originalBidPrice, originalCurrency - } return roundedBidPrice, currency } diff --git a/bidadjustment/apply_test.go b/bidadjustment/apply_test.go index 1bb5cbe07f9..27daa9c73d6 100644 --- a/bidadjustment/apply_test.go +++ b/bidadjustment/apply_test.go @@ -141,6 +141,52 @@ func TestGetAndApply(t *testing.T) { expectedBidPrice: 17.5, expectedCurrency: bidCur, }, + { + name: "DealIdPresentAndNegativeAdjustedPrice", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 1.0, + DealID: "dealId", + }, + }, + givenBidType: VideoInstream, + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|dealId": { + { + Type: AdjustmentTypeCPM, + Value: 1.0, + Currency: adjCur, + }, + }, + }, + givenBidderName: "bidderA", + setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, + expectedBidPrice: 0.0, + expectedCurrency: bidCur, + }, + { + name: "NoDealIdNegativeAdjustedPrice", + givenBidInfo: &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + Price: 1.0, + DealID: "", + }, + }, + givenBidType: string(openrtb_ext.BidTypeAudio), + givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{ + "*|*|*": { + { + Type: AdjustmentTypeCPM, + Value: 1.0, + Currency: adjCur, + }, + }, + }, + givenBidderName: "bidderA", + setMock: func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) }, + expectedBidPrice: 0.1, + expectedCurrency: bidCur, + }, { name: "NilMap", givenBidInfo: &adapters.TypedBid{ @@ -225,14 +271,6 @@ func TestApply(t *testing.T) { expectedBidPrice: 30.0, expectedCurrency: bidCur, }, - { - name: "ReturnOriginalPrice", - givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: -1.0}}, - givenBidPrice: 10.0, - setMock: nil, - expectedBidPrice: 10.0, - expectedCurrency: bidCur, - }, { name: "NilAdjustment", givenAdjustments: nil, diff --git a/bidadjustment/build_rules.go b/bidadjustment/build_rules.go index b8a7ee89b68..bccb3bc86cf 100644 --- a/bidadjustment/build_rules.go +++ b/bidadjustment/build_rules.go @@ -47,7 +47,7 @@ func Merge(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestP mergedBidAdj = nil err = &errortypes.Warning{ WarningCode: errortypes.BidAdjustmentWarningCode, - Message: "bid adjustment after merge was invalid", + Message: "bid adjustment on account was invalid", } } return mergedBidAdj, err diff --git a/bidadjustment/build_rules_test.go b/bidadjustment/build_rules_test.go index 9e5ffb53cd6..263a782130e 100644 --- a/bidadjustment/build_rules_test.go +++ b/bidadjustment/build_rules_test.go @@ -173,6 +173,25 @@ func TestMergeAndValidate(t *testing.T) { expectError: true, expectedBidAdjustments: nil, }, + { + name: "InvalidAcctAdjustment", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + givenAccount: &config.Account{ + BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderB": { + "dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: -1.5}}, + }, + }, + }, + }, + }, + expectError: true, + expectedBidAdjustments: nil, + }, { name: "InvalidJSON", givenRequestWrapper: &openrtb_ext.RequestWrapper{ diff --git a/errortypes/severity.go b/errortypes/severity.go index 0838b09592e..efb12d4b155 100644 --- a/errortypes/severity.go +++ b/errortypes/severity.go @@ -15,7 +15,7 @@ const ( SeverityWarning ) -func isFatal(err error) bool { +func IsFatal(err error) bool { s, ok := err.(Coder) return !ok || s.Severity() == SeverityFatal } @@ -28,7 +28,7 @@ func isWarning(err error) bool { // ContainsFatalError checks if the error list contains a fatal error. func ContainsFatalError(errors []error) bool { for _, err := range errors { - if isFatal(err) { + if IsFatal(err) { return true } } @@ -41,7 +41,7 @@ func FatalOnly(errs []error) []error { errsFatal := make([]error, 0, len(errs)) for _, err := range errs { - if isFatal(err) { + if IsFatal(err) { errsFatal = append(errsFatal, err) } } diff --git a/exchange/bidder.go b/exchange/bidder.go index bb9023b12a1..e355c18ced1 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -701,11 +701,11 @@ func getBidTypeForAdjustments(bidType openrtb_ext.BidType, impID string, imp []o if imp.ID == impID { if imp.Video.Plcmt == adcom1.VideoPlcmtAccompanyingContent { return "video-outstream" - } else { - return "video-instream" } + break } } + return "video-instream" } return string(bidType) } diff --git a/exchange/exchange.go b/exchange/exchange.go index 0eb9dfa1b87..b22227a0c17 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -298,12 +298,6 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog r.FirstPartyData = resolvedFPD } - mergedBidAdj, err := bidadjustment.Merge(r.BidRequestWrapper, r.Account.BidAdjustments) - if err != nil { - return nil, err - } - bidAdjustmentRules := bidadjustment.BuildRules(mergedBidAdj) - bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExtPrebid) recordImpMetrics(r.BidRequestWrapper, e.me) @@ -324,6 +318,16 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, *r, requestExtLegacy, gdprDefaultValue) errs = append(errs, floorErrs...) + mergedBidAdj, err := bidadjustment.Merge(r.BidRequestWrapper, r.Account.BidAdjustments) + if err != nil { + if !errortypes.IsFatal(err) { + errs = append(errs, err) + } else { + return nil, err + } + } + bidAdjustmentRules := bidadjustment.BuildRules(mergedBidAdj) + e.me.RecordRequestPrivacy(privacyLabels) if len(r.StoredAuctionResponses) > 0 || len(r.StoredBidResponses) > 0 { From 1ce1b4314252391199b7e63f18507c952c155f94 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Wed, 3 May 2023 13:55:07 -0700 Subject: [PATCH 25/27] Add invalid end to end test --- endpoints/openrtb2/auction.go | 5 +- .../bidadjustments/invalid.json | 90 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 endpoints/openrtb2/sample-requests/bidadjustments/invalid.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 06b9c076f10..7f361720080 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -724,7 +724,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } if errs := validateRequestExt(req); len(errs) != 0 { - return append(errL, errs...) + if errortypes.ContainsFatalError(errs) { + return append(errL, errs...) + } + errL = append(errL, errs...) } if err := deps.validateSite(req); err != nil { diff --git a/endpoints/openrtb2/sample-requests/bidadjustments/invalid.json b/endpoints/openrtb2/sample-requests/bidadjustments/invalid.json new file mode 100644 index 00000000000..cbd5f758316 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/bidadjustments/invalid.json @@ -0,0 +1,90 @@ +{ + "description": "Bid Adjustment Test With Invalid Adjustment", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 2.00, + "dealid": "some-deal-id" + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "bidadjustments": { + "mediatype": { + "banner": { + "appnexus": { + "some-deal-id": [ + { + "adjtype": "multiplier", + "value": -2.0 + } + ] + } + } + } + }, + "debug": true + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 2.0, + "ext": { + "origbidcpm": 2, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file From 77b4dd0ffb81e4918c672d4d3879422edb6217c2 Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Wed, 3 May 2023 16:38:43 -0700 Subject: [PATCH 26/27] Fatal error tweak --- errortypes/severity.go | 6 +++--- exchange/exchange.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/errortypes/severity.go b/errortypes/severity.go index efb12d4b155..0838b09592e 100644 --- a/errortypes/severity.go +++ b/errortypes/severity.go @@ -15,7 +15,7 @@ const ( SeverityWarning ) -func IsFatal(err error) bool { +func isFatal(err error) bool { s, ok := err.(Coder) return !ok || s.Severity() == SeverityFatal } @@ -28,7 +28,7 @@ func isWarning(err error) bool { // ContainsFatalError checks if the error list contains a fatal error. func ContainsFatalError(errors []error) bool { for _, err := range errors { - if IsFatal(err) { + if isFatal(err) { return true } } @@ -41,7 +41,7 @@ func FatalOnly(errs []error) []error { errsFatal := make([]error, 0, len(errs)) for _, err := range errs { - if IsFatal(err) { + if isFatal(err) { errsFatal = append(errsFatal, err) } } diff --git a/exchange/exchange.go b/exchange/exchange.go index b22227a0c17..97dc95cadae 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -320,7 +320,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog mergedBidAdj, err := bidadjustment.Merge(r.BidRequestWrapper, r.Account.BidAdjustments) if err != nil { - if !errortypes.IsFatal(err) { + if !errortypes.ContainsFatalError([]error{err}) { errs = append(errs, err) } else { return nil, err From 4c55deab2acb997665e71e57eadbe23344fbc68c Mon Sep 17 00:00:00 2001 From: AlexBVolcy Date: Thu, 4 May 2023 10:51:37 -0700 Subject: [PATCH 27/27] Re-add comment, tweak error handling --- config/accounts.go | 2 +- exchange/exchange.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/config/accounts.go b/config/accounts.go index a8d05813583..f96eb53af08 100644 --- a/config/accounts.go +++ b/config/accounts.go @@ -26,7 +26,7 @@ type Account struct { ID string `mapstructure:"id" json:"id"` Disabled bool `mapstructure:"disabled" json:"disabled"` CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"` - EventsEnabled *bool `mapstructure:"events_enabled" json:"events_enabled"` + EventsEnabled *bool `mapstructure:"events_enabled" json:"events_enabled"` // Deprecated: Use events.enabled instead. CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"` GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"` DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"` diff --git a/exchange/exchange.go b/exchange/exchange.go index 97dc95cadae..7ce070eb052 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -320,11 +320,10 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog mergedBidAdj, err := bidadjustment.Merge(r.BidRequestWrapper, r.Account.BidAdjustments) if err != nil { - if !errortypes.ContainsFatalError([]error{err}) { - errs = append(errs, err) - } else { + if errortypes.ContainsFatalError([]error{err}) { return nil, err } + errs = append(errs, err) } bidAdjustmentRules := bidadjustment.BuildRules(mergedBidAdj)