From 7e4ce74961f46f0c5b77ef6754a3bc07080bb903 Mon Sep 17 00:00:00 2001 From: markiian Date: Fri, 5 Dec 2025 14:46:58 +0200 Subject: [PATCH 01/13] Update: Remove unnecessary metics checks --- pom.xml | 2 +- .../functional/tests/BidderParamsSpec.groovy | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1c198a3df57..bc8e7707460 100644 --- a/pom.xml +++ b/pom.xml @@ -372,7 +372,7 @@ ${maven-surefire-plugin.version} - false + true ${skipUnitTests} diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy index 220585f008f..de1892c2ac3 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy @@ -3,6 +3,7 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.bidder.AppNexus import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.bidder.Generic +import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredImp import org.prebid.server.functional.model.db.StoredRequest @@ -11,6 +12,8 @@ import org.prebid.server.functional.model.request.auction.Adrino import org.prebid.server.functional.model.request.auction.Amx import org.prebid.server.functional.model.request.auction.AuctionEnvironment import org.prebid.server.functional.model.request.auction.Banner +import org.prebid.server.functional.model.request.auction.BidAdjustmentFactors +import org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Device import org.prebid.server.functional.model.request.auction.AnyUnsupportedBidder @@ -34,14 +37,20 @@ import org.prebid.server.functional.model.response.auction.BidExt import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.CcpaConsent +import spock.lang.IgnoreRest import static org.prebid.server.functional.model.Currency.CHF import static org.prebid.server.functional.model.Currency.EUR import static org.prebid.server.functional.model.Currency.JPY import static org.prebid.server.functional.model.Currency.USD +import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_UPPER_CASE import static org.prebid.server.functional.model.bidder.BidderName.AMX import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS +import static org.prebid.server.functional.model.bidder.BidderName.GENERIC +import static org.prebid.server.functional.model.bidder.BidderName.GENERIC +import static org.prebid.server.functional.model.bidder.BidderName.GENERIC +import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.bidder.BidderName.GENERIC_CAMEL_CASE import static org.prebid.server.functional.model.bidder.BidderName.OPENX import static org.prebid.server.functional.model.bidder.CompressionType.GZIP @@ -51,6 +60,7 @@ import static org.prebid.server.functional.model.request.auction.AuctionEnvironm import static org.prebid.server.functional.model.request.auction.AuctionEnvironment.NOT_SUPPORTED import static org.prebid.server.functional.model.request.auction.AuctionEnvironment.SERVER_ORCHESTRATED import static org.prebid.server.functional.model.request.auction.AuctionEnvironment.UNKNOWN +import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.BANNER import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP import static org.prebid.server.functional.model.request.auction.DistributionChannel.DOOH import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE @@ -1790,4 +1800,80 @@ class BidderParamsSpec extends BaseSpec { cleanup: "Stop and remove pbs container" pbsServiceFactory.removeContainer(pbsConfig) } + + + //todo:ext.prebid.returnallbidstatus (boolean) + // Never needed inside the adapter code + // Exposes nothing because it’s a boolean + + def "PBS shouldn't send returnallbidstatus to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.returnAllBidStatus = true + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain returnAll" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.returnAllBidStatus + } + + //todo: ext.prebid.aliasgvlids (map) + // Never needed inside the adapter code + + @IgnoreRest + def "PBS shouldn't send aliasgvlids to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.generic = null + ext.prebid.aliasgvlids = ["alias": 123] + ext.prebid.aliases = [(ALIAS.value): BidderName.GENERIC] + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain aliasgvlids" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.aliasgvlids + } + + //todo: ext.prebid.bidadjustmentfactors and ext.prebid.bidadjustments (map) + // Never needed inside the adapter code (for now) but according to the requirements adapters they might be needed to reverse price floors according to the https://magnite.atlassian.net/browse/HB-20538 + // bidadjustments are missed in the table + // The value is taken from request/account + // The rules of other bidders if any are exposed. + + @IgnoreRest + def "PBS shouldn't send bid adjustment media type to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.bidAdjustmentFactors = new BidAdjustmentFactors().tap { + it.adjustments = [(BidderName.GENERIC): BigDecimal.ONE] + it.mediaTypes = [(BidAdjustmentMediaType.BANNER): [(BidderName.GENERIC): BigDecimal.ONE]] + } + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain bid adjustment factors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.bidAdjustmentFactors + } } From 1233b6ca60015ce9f02773a6a4f94c7f1979ae86 Mon Sep 17 00:00:00 2001 From: markiian Date: Fri, 12 Dec 2025 11:51:51 +0200 Subject: [PATCH 02/13] Update: Add coverage for bidder removal fields --- .../functional/model/request/Channel.groovy | 11 + .../auction/ConsentedProvidersSettings.groovy | 2 + .../model/request/auction/DeviceExt.groovy | 3 + .../model/request/auction/DevicePrebid.groovy | 11 + .../auction/ImpExtContextDataAdServer.groovy | 2 + .../model/request/auction/Interstitial.groovy | 15 + .../model/request/auction/Prebid.groovy | 10 +- .../request/auction/PrebidCurrency.groovy | 2 + .../model/request/auction/UserTime.groovy | 2 + .../server/functional/tests/AmpFpdSpec.groovy | 3 - .../functional/tests/AuctionSpec.groovy | 51 +- .../BidderFieldDisplayBehaviorSpec.groovy | 617 ++++++++++++++++++ .../functional/tests/BidderParamsSpec.groovy | 87 +-- .../server/functional/tests/DebugSpec.groovy | 2 +- .../functional/tests/ProfileSpec.groovy | 9 +- .../pricefloors/PriceFloorsRulesSpec.groovy | 2 +- .../tests/privacy/CcpaAuctionSpec.groovy | 2 +- .../tests/privacy/GdprAuctionSpec.groovy | 2 +- 18 files changed, 693 insertions(+), 140 deletions(-) create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/Channel.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/auction/DevicePrebid.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/auction/Interstitial.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy diff --git a/src/test/groovy/org/prebid/server/functional/model/request/Channel.groovy b/src/test/groovy/org/prebid/server/functional/model/request/Channel.groovy new file mode 100644 index 00000000000..9782c3896ba --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/Channel.groovy @@ -0,0 +1,11 @@ +package org.prebid.server.functional.model.request + +import groovy.transform.ToString +import org.prebid.server.functional.model.ChannelType + +@ToString(includeNames = true, ignoreNulls = true) +class Channel { + + ChannelType name + String version +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/ConsentedProvidersSettings.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/ConsentedProvidersSettings.groovy index aa7bd511cb2..720ddb40541 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/ConsentedProvidersSettings.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/ConsentedProvidersSettings.groovy @@ -2,10 +2,12 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) +@EqualsAndHashCode class ConsentedProvidersSettings { String consentedProviders diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy index 72337563b4b..b5b3d62f87d 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy @@ -1,13 +1,16 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.annotation.JsonValue +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString @ToString +@EqualsAndHashCode class DeviceExt { Atts atts String cdep + DevicePrebid prebid enum Atts { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/DevicePrebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/DevicePrebid.groovy new file mode 100644 index 00000000000..f6cc123133f --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/DevicePrebid.groovy @@ -0,0 +1,11 @@ +package org.prebid.server.functional.model.request.auction + +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString + +@EqualsAndHashCode +@ToString(includeNames = true, ignoreNulls = true) +class DevicePrebid { + + Interstitial interstitial +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExtContextDataAdServer.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExtContextDataAdServer.groovy index 775fa3d8c76..528bc6e8a7c 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExtContextDataAdServer.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExtContextDataAdServer.groovy @@ -2,8 +2,10 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString +@EqualsAndHashCode @JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) @ToString(includeNames = true, ignoreNulls = true) class ImpExtContextDataAdServer { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Interstitial.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Interstitial.groovy new file mode 100644 index 00000000000..7a56ca6899d --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Interstitial.groovy @@ -0,0 +1,15 @@ +package org.prebid.server.functional.model.request.auction + +import com.fasterxml.jackson.annotation.JsonProperty +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString + +@EqualsAndHashCode +@ToString(includeNames = true, ignoreNulls = true) +class Interstitial { + + @JsonProperty("minwidthperc") + Integer minWidthPercentage + @JsonProperty("minheightperc") + Integer minHeightPercentage +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy index 23b4e7f87a5..6cfccb0533a 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy @@ -7,6 +7,7 @@ import groovy.transform.ToString import org.prebid.server.functional.model.ChannelType import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.config.AlternateBidderCodes +import org.prebid.server.functional.model.request.Channel @JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) @ToString(includeNames = true, ignoreNulls = true) @@ -26,6 +27,8 @@ class Prebid { ExtRequestPrebidData data List bidderConfig List schains + List noSale + Long auctionTimestamp Amp amp Channel channel List multibid @@ -49,10 +52,5 @@ class Prebid { List profileNames @JsonProperty("kvps") Map keyValuePairs - - static class Channel { - - ChannelType name - String version - } + Boolean supportDeals } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidCurrency.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidCurrency.groovy index 98488883cb7..f54f92634dc 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidCurrency.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidCurrency.groovy @@ -2,11 +2,13 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.model.Currency @JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) @ToString(includeNames = true, ignoreNulls = true) +@EqualsAndHashCode class PrebidCurrency { Map> rates diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/UserTime.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/UserTime.groovy index bc81ab3fc6f..f509d46aa68 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/UserTime.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/UserTime.groovy @@ -1,8 +1,10 @@ package org.prebid.server.functional.model.request.auction +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) +@EqualsAndHashCode class UserTime { Integer userdow diff --git a/src/test/groovy/org/prebid/server/functional/tests/AmpFpdSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AmpFpdSpec.groovy index e376f611e84..412f4776e69 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/AmpFpdSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/AmpFpdSpec.groovy @@ -233,9 +233,6 @@ class AmpFpdSpec extends BaseSpec { def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) assert ampStoredRequest.ext.prebid.bidderConfig[0].config.ortb2.site.domain == bidderRequest.site.domain assert ampStoredRequest.ext.prebid.bidderConfig[0].config.ortb2.user.keywords == bidderRequest.user.keywords - - and: "Bidder request shouldn't contain bidder config" - assert !bidderRequest.ext.prebid.bidderConfig } def "PBS should populate FPD from root when bidder was defined in prebid data"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy index 1506e2e0a4d..aa658800c22 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy @@ -26,6 +26,7 @@ import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.util.HttpUtil import org.prebid.server.functional.util.PBSUtils +import spock.lang.IgnoreRest import spock.lang.Shared import static org.prebid.server.functional.model.AccountStatus.ACTIVE @@ -125,37 +126,6 @@ class AuctionSpec extends BaseSpec { "invalid-stored-impr" | { bidReq, storedReq -> bidReq.imp[0].ext.prebid.storedRequest = storedReq } } - def "PBS should copy imp level passThrough to bidresponse.seatbid[].bid[].ext.prebid.passThrough when the passThrough is present"() { - given: "Default bid request with passThrough" - def randomString = PBSUtils.randomString - def passThrough = [(randomString): randomString] - def bidRequest = BidRequest.defaultBidRequest.tap { - imp[0].ext.prebid.passThrough = passThrough - } - - when: "Requesting PBS auction" - def response = defaultPbsService.sendAuctionRequest(bidRequest) - - then: "BidResponse should contain the same passThrough as on request" - assert response.seatbid.first().bid.first().ext.prebid.passThrough == passThrough - } - - def "PBS should copy global level passThrough object to bidresponse.ext.prebid.passThrough when passThrough is present"() { - given: "Default bid request with passThrough" - def randomString = PBSUtils.randomString - def passThrough = [(randomString): randomString] - def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.passThrough = passThrough - } - - when: "Requesting PBS auction" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "BidResponse should contain the same passThrough as on request" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.ext.prebid.passThrough == passThrough - } - def "PBS should populate bidder request buyeruid from buyeruids when buyeruids with appropriate bidder present in request"() { given: "Bid request with buyeruids" def buyeruid = PBSUtils.randomString @@ -336,25 +306,6 @@ class AuctionSpec extends BaseSpec { assert !bidderRequest.ext.prebid.aliases } - def "PBS auction should pass ext.prebid.sdk requested to bidder request when sdk specified"() { - given: "Default bid request with aliases" - def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.sdk = new Sdk(renderers: [new Renderer( - name: PBSUtils.randomString, - version: PBSUtils.randomString, - data: new RendererData(any: PBSUtils.randomString))]) - } - - when: "Requesting PBS auction" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request should contain sdk value same in request" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.ext.prebid.sdk.renderers.name == bidRequest.ext.prebid.sdk.renderers.name - assert bidderRequest.ext.prebid.sdk.renderers.version == bidRequest.ext.prebid.sdk.renderers.version - assert bidderRequest.ext.prebid.sdk.renderers.data.any == bidRequest.ext.prebid.sdk.renderers.data.any - } - def "PBS auction should pass meta object to bid response when meta specified "() { given: "Default bid request with aliases" def bidRequest = BidRequest.defaultBidRequest diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy new file mode 100644 index 00000000000..f121183a29a --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy @@ -0,0 +1,617 @@ +package org.prebid.server.functional.tests + +import org.prebid.server.functional.model.bidder.BidderName +import org.prebid.server.functional.model.bidder.Generic +import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse +import org.prebid.server.functional.model.request.Channel +import org.prebid.server.functional.model.request.amp.AmpRequest +import org.prebid.server.functional.model.request.auction.AdServerTargeting +import org.prebid.server.functional.model.request.auction.Amp +import org.prebid.server.functional.model.request.auction.BidAdjustmentFactors +import org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.BidderConfig +import org.prebid.server.functional.model.request.auction.BidderConfigOrtb +import org.prebid.server.functional.model.request.auction.ConsentedProvidersSettings +import org.prebid.server.functional.model.request.auction.DebugCondition +import org.prebid.server.functional.model.request.auction.Device +import org.prebid.server.functional.model.request.auction.DeviceExt +import org.prebid.server.functional.model.request.auction.EidPermission +import org.prebid.server.functional.model.request.auction.Events +import org.prebid.server.functional.model.request.auction.ExtPrebidBidderConfig +import org.prebid.server.functional.model.request.auction.ExtRequestPrebidData +import org.prebid.server.functional.model.request.auction.Interstitial +import org.prebid.server.functional.model.request.auction.PrebidAnalytics +import org.prebid.server.functional.model.request.auction.PrebidCache +import org.prebid.server.functional.model.request.auction.DevicePrebid +import org.prebid.server.functional.model.request.auction.PrebidCurrency +import org.prebid.server.functional.model.request.auction.PrebidStoredRequest +import org.prebid.server.functional.model.request.auction.Renderer +import org.prebid.server.functional.model.request.auction.RendererData +import org.prebid.server.functional.model.request.auction.Sdk +import org.prebid.server.functional.model.request.auction.Site +import org.prebid.server.functional.model.request.auction.SiteExt +import org.prebid.server.functional.model.request.auction.SiteExtData +import org.prebid.server.functional.model.request.auction.TraceLevel +import org.prebid.server.functional.model.request.auction.User +import org.prebid.server.functional.model.request.auction.UserExt +import org.prebid.server.functional.model.request.auction.UserExtPrebid +import org.prebid.server.functional.model.request.auction.UserTime +import org.prebid.server.functional.model.response.auction.BidResponse +import org.prebid.server.functional.util.PBSUtils +import spock.lang.IgnoreRest + +import static org.prebid.server.functional.model.ChannelType.WEB +import static org.prebid.server.functional.model.bidder.BidderName.ALIAS +import static org.prebid.server.functional.model.bidder.BidderName.GENERIC +import static org.prebid.server.functional.model.bidder.BidderName.UNKNOWN +import static org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse.defaultConversionRates +import static org.prebid.server.functional.model.request.auction.DebugCondition.ENABLED +import static org.prebid.server.functional.model.request.auction.DeviceExt.Atts.* +import static org.prebid.server.functional.model.request.auction.PaaFormat.ORIGINAL +import static org.prebid.server.functional.model.request.auction.TraceLevel.BASIC +import static org.prebid.server.functional.model.response.auction.ErrorType.ALIAS + +class BidderFieldDisplayBehaviorSpec extends BaseSpec { + + //todo:ext.prebid.returnallbidstatus (boolean) + // Never needed inside the adapter code + // Exposes nothing because it’s a boolean + + def "PBS shouldn't send returnallbidstatus to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.returnAllBidStatus = true + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain returnAll" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.returnAllBidStatus + } + + //todo: ext.prebid.aliasgvlids (map) + // Never needed inside the adapter code + + @IgnoreRest + def "PBS shouldn't pass aliasgvlids and aliases to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.generic = null + ext.prebid.aliasgvlids = ["alias": 123] + ext.prebid.aliases = [(ALIAS.value): BidderName.GENERIC] + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain aliasgvlids and aliases" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.aliasgvlids + assert !bidderRequest.ext.prebid.aliases + } + + //todo: ext.prebid.bidadjustmentfactors and ext.prebid.bidadjustments (map) + // Never needed inside the adapter code (for now) but according to the requirements adapters they might be needed to reverse price floors according to the https://magnite.atlassian.net/browse/HB-20538 + // bidadjustments are missed in the table + // The value is taken from request/account + // The rules of other bidders if any are exposed. + + def "PBS shouldn't send bid adjustment media type to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.tap { + bidAdjustmentFactors = new BidAdjustmentFactors().tap { + it.adjustments = [(BidderName.GENERIC): BigDecimal.ONE] + it.mediaTypes = [(BidAdjustmentMediaType.BANNER): [(BidderName.GENERIC): BigDecimal.ONE]] + } + } + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain bid adjustment factors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.bidAdjustmentFactors + } + + //todo: ext.prebid.adservertargeting (object) + // Never needed inside the adapter code + + def "PBS shouldn't send bid adServerTargeting to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.tap { + adServerTargeting = [ + new AdServerTargeting().tap { + key = "custom_bid_request" + source = "bidrequest" + value = "imp.id" + }] + } + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain adServerTargeting" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.adServerTargeting + } + + //todo: ext.prebid.supportdeals (boolean) + // Never needed inside the adapter code + // Exposes nothing because it’s a boolean + + + def "PBS shouldn't send supportDeals to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.supportDeals = PBSUtils.randomBoolean + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain supportDeals" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.supportDeals + } + + //todo: ext.prebid.cache (object) + // Never needed inside the adapter code + // Not sure it exposes something because it’s only about ttlseconds integers and some booleans + + @IgnoreRest + def "PBS shouldn't pass ext.prebid.cache to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.cache = new PrebidCache(winningOnly: PBSUtils.randomBoolean) + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain cache" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.cache + } + + //todo: ext.prebid.channel (object) + // Never needed inside the adapter code + // Not sure it exposes something because it’s only about ttlseconds integers and some booleans + + def "PBS shouldn't pass ext.prebid.channel to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.channel = new Channel(name: WEB) + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain ext.prebid.channel" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.channel == bidRequest.ext.prebid.channel + } + + + //todo: ext.prebid.currency.rates (object) + + @IgnoreRest + def "PBS should pass ext.prebid.currency.rates to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.currency = new PrebidCurrency( + usePbsRates: PBSUtils.getRandomBoolean(), + rates: getDefaultConversionRates()) + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain ext.prebid.currency.rates" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.currency == bidRequest.ext.prebid.currency + } + + //todo: ext.prebid.data.bidder (object) + + @IgnoreRest + def "PBS shouldn't pass ext.prebid.data.{bidders,eidpermissions} to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.data = new ExtRequestPrebidData(bidders: [GENERIC.value], + eidpermissions: [new EidPermission(source: PBSUtils.randomString, bidders: [GENERIC])]) + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain ext.prebid.data.{bidders,eidpermissions}" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest?.ext?.prebid?.data?.bidders + assert !bidderRequest?.ext?.prebid?.data?.eidpermissions + } + + @IgnoreRest + def "PBS shouldn't pass ext.prebid.data.{trace,debug} to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.trace = BASIC + ext.prebid.debug = ENABLED + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain ext.prebid.data.{debug,trace}" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.trace + assert bidderRequest.ext.prebid.debug + } + + //todo: ext.prebid.events (object) + // Never needed inside the adapter code + + def "PBS shouldn't send bid events to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.events = new Events() + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain events" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.events + } + + //todo: ext.prebid.nosale (object) + // Never needed inside the adapter code + + def "PBS shouldn't send noSale to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.noSale = [PBSUtils.randomString] + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain noSale" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.noSale + } + + //todo: ext.prebid.multibid (array of objects) + // Used in RubiconBidder only + + + //todo: ext.prebid.amp (object) + // Mistakenly used in the InvibesBidder + // Never used in other bidder + // Not mentioned in the table + // ADD AMP REQUEST!!! + + def "PBS shouldn't send amp to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.amp = new Amp(data: AmpRequest.getDefaultAmpRequest()) + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain noSale" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.amp + } + + //todo: ext.prebid.analytics (object) + // Never needed inside the adapter code + + def "PBS shouldn't send analytics to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.analytics = new PrebidAnalytics() + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain analytics" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.analytics + } + + //todo: need ask + // ext.prebid.bidderparams and ext.prebid.bidders (object) !!! PAY ATTENTION on ext.prebid.bidderparams + // According to the table ext.prebid.bidderparams should not be exposed as-is, instead it should replaced with the ext.prebid.bidderparams.bidder from ext.prebid.bidders, the ext.prebid.bidders presumably shouldn’t be exposed at all - BUT in my test the ext.prebid.bidderparams and ext.prebid.bidders are kept as-is + // ext.prebid.bidders is used in RubiconBidder + // ext.prebid.bidderparams is used in MediaGoBidder and PubmaticBidder + // ext.prebid.floors (object) + // + // Needed only in RubiconBidder + // The value is taken from the floors provider + + def "PBS should copy imp level passThrough to bidresponse.seatbid[].bid[].ext.prebid.passThrough when the passThrough is present"() { + given: "Default bid request with passThrough" + def randomString = PBSUtils.randomString + def passThrough = [(randomString): randomString] + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0].ext.prebid.passThrough = passThrough + } + + when: "Requesting PBS auction" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain the same passThrough as on request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.passThrough + + and: "Response should contain the same passThrough as on request" + assert response.seatbid.first().bid.first().ext.prebid.passThrough == passThrough + + and: "Response shouldn't contain in ext.prebid.passThrough" + assert !response.ext.prebid.passThrough + } + + def "PBS should copy global level passThrough object to bidresponse.ext.prebid.passThrough when passThrough is present"() { + given: "Default bid request with passThrough" + def randomString = PBSUtils.randomString + def passThrough = [(randomString): randomString] + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.passThrough = passThrough + } + + when: "Requesting PBS auction" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response shouldn't contain the same passThrough as on request" + assert !response.seatbid.first().bid.first().ext.prebid.passThrough + + and: "Response should contain in ext.prebid.passThrough" + assert response.ext.prebid.passThrough == passThrough + + and: "Bidder request shouldn't contain the same passThrough as on request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.passThrough + } + + //todo : ext.prebid.sdk (object) + // Never used at all - maybe it’s deprecated + // Not mentioned in the table + + def "PBS auction should pass ext.prebid.sdk requested to bidder request when sdk specified"() { + given: "Default bid request with aliases" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.sdk = new Sdk(renderers: [new Renderer( + name: PBSUtils.randomString, + version: PBSUtils.randomString, + data: new RendererData(any: PBSUtils.randomString))]) + } + + when: "Requesting PBS auction" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain sdk value same in request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.sdk.renderers.name == bidRequest.ext.prebid.sdk.renderers.name + assert bidderRequest.ext.prebid.sdk.renderers.version == bidRequest.ext.prebid.sdk.renderers.version + assert bidderRequest.ext.prebid.sdk.renderers.data.any == bidRequest.ext.prebid.sdk.renderers.data.any + } + + def "PBS auction shouldn't pass ext.prebid.paaformat to bidder request when paaformat specified"() { + given: "Default bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.paaFormat = ORIGINAL + } + + when: "Requesting PBS auction" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain paaForamt value same in request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.paaFormat + } + + def "PBS should pass srid to the bidder request when srid present"() { + given: "Default basic BidRequest with generic bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.storedRequest = new PrebidStoredRequest(id: PBSUtils.randomNumber) + } + + and: "Default stored request" + def storedRequestModel = BidRequest.defaultStoredRequest + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(bidRequest.ext.prebid.storedRequest.id, storedRequestModel) + storedRequestDao.save(storedRequest) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request sho ext.prebid" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.storedRequest.id + } + + //todo: user.ext + // The whole object is exposed except for the prebid field + // consent, eids, data fields are needed for some bidders + + def "PBS should pass user.ext"() { + given: "Default basic BidRequest with generic bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + user = new User(ext: new UserExt().tap { + fcapids = [PBSUtils.randomString] + time = new UserTime(userdow: PBSUtils.randomNumber, userhour: PBSUtils.randomNumber) + prebid = new UserExtPrebid(buyeruids: [(GENERIC): PBSUtils.randomString]) + consentedProvidersSettings = new ConsentedProvidersSettings(consentedProviders: PBSUtils.randomString) + }) + } + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain user.ext.prebid" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.user.ext.prebid + + and: "Bidder request should contain fcapid,time,consentedProvidedSettings" + assert bidderRequest.user.ext.fcapids == bidRequest.user.ext.fcapids + assert bidderRequest.user.ext.time == bidRequest.user.ext.time + assert bidderRequest.user.ext.consentedProvidersSettings == bidRequest.user.ext.consentedProvidersSettings + } + + //todo :site.ext + // The whole object is exposed + // data and amp fields are needed for some bidders + + def "PBS should pass site.ext"() { + given: "Default basic BidRequest with generic bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + site.ext = new SiteExt(amp: 0, data: new SiteExtData(id: PBSUtils.randomString)) + } + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain user.ext.{amp,data}" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.site.ext.data == bidRequest.site.ext.data + assert bidderRequest.site.ext.amp == bidRequest.site.ext.amp + } + + //todo :device.ext.prebid + // The whole object is exposed + + @IgnoreRest + def "PBS should pass device.ext.prebid"() { + given: "Default basic bid request with generic bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + device = new Device( + ext: new DeviceExt( + atts: UNKNOWN, + cdep: PBSUtils.randomString, + prebid: new DevicePrebid(interstitial: new Interstitial( + minHeightPercentage: PBSUtils.getRandomNumber(0, 100), + minWidthPercentage: PBSUtils.getRandomNumber(0, 100))))) + } + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain requested device.ext" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.device.ext == bidRequest.device.ext + } + + //todo: ext.prebid.adservertargeting (object) + // Never needed inside the adapter code + + @IgnoreRest + def "PBS should pass ext.prebid.auctiontimestamp to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.auctionTimestamp = PBSUtils.randomNumber + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain auctionTimestamp" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.auctionTimestamp + } + + //todo: ext.prebid.adservertargeting (object) + // Never needed inside the adapter code + + @IgnoreRest + def "PBS shouldn't pass ext.prebid.bidderConfig to bidder request"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.bidderConfig = [new ExtPrebidBidderConfig(bidders: [GENERIC], config: + new BidderConfig(ortb2: new BidderConfigOrtb(site: Site.configFPDSite, user: User.configFPDUser)))] + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain bidderConfig" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.bidderConfig + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy index de1892c2ac3..72ea7c5f32c 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy @@ -8,7 +8,9 @@ import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredImp import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.amp.AmpRequest +import org.prebid.server.functional.model.request.auction.AdServerTargeting import org.prebid.server.functional.model.request.auction.Adrino +import org.prebid.server.functional.model.request.auction.Amp import org.prebid.server.functional.model.request.auction.Amx import org.prebid.server.functional.model.request.auction.AuctionEnvironment import org.prebid.server.functional.model.request.auction.Banner @@ -17,6 +19,7 @@ import org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Device import org.prebid.server.functional.model.request.auction.AnyUnsupportedBidder +import org.prebid.server.functional.model.request.auction.Events import org.prebid.server.functional.model.request.auction.Geo import org.prebid.server.functional.model.request.auction.Imp import org.prebid.server.functional.model.request.auction.ImpExt @@ -24,8 +27,14 @@ import org.prebid.server.functional.model.request.auction.ImpExtContext import org.prebid.server.functional.model.request.auction.ImpExtContextData import org.prebid.server.functional.model.request.auction.InterestGroupAuctionSupport import org.prebid.server.functional.model.request.auction.Native +import org.prebid.server.functional.model.request.auction.Pmp +import org.prebid.server.functional.model.request.auction.PrebidAnalytics +import org.prebid.server.functional.model.request.auction.PrebidCache import org.prebid.server.functional.model.request.auction.PrebidOptions import org.prebid.server.functional.model.request.auction.PrebidStoredRequest +import org.prebid.server.functional.model.request.auction.Renderer +import org.prebid.server.functional.model.request.auction.RendererData +import org.prebid.server.functional.model.request.auction.Sdk import org.prebid.server.functional.model.request.auction.Site import org.prebid.server.functional.model.request.auction.Source import org.prebid.server.functional.model.request.auction.Targeting @@ -51,6 +60,7 @@ import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.bidder.BidderName.GENERIC +import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.bidder.BidderName.GENERIC_CAMEL_CASE import static org.prebid.server.functional.model.bidder.BidderName.OPENX import static org.prebid.server.functional.model.bidder.CompressionType.GZIP @@ -64,6 +74,7 @@ import static org.prebid.server.functional.model.request.auction.BidAdjustmentMe import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP import static org.prebid.server.functional.model.request.auction.DistributionChannel.DOOH import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE +import static org.prebid.server.functional.model.request.auction.PaaFormat.ORIGINAL import static org.prebid.server.functional.model.request.auction.SecurityLevel.NON_SECURE import static org.prebid.server.functional.model.request.auction.SecurityLevel.SECURE import static org.prebid.server.functional.model.response.auction.BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY @@ -1800,80 +1811,4 @@ class BidderParamsSpec extends BaseSpec { cleanup: "Stop and remove pbs container" pbsServiceFactory.removeContainer(pbsConfig) } - - - //todo:ext.prebid.returnallbidstatus (boolean) - // Never needed inside the adapter code - // Exposes nothing because it’s a boolean - - def "PBS shouldn't send returnallbidstatus to bidder request"() { - given: "Default basic bid request" - def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.returnAllBidStatus = true - } - - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request shouldn't contain returnAll" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.ext.prebid.returnAllBidStatus - } - - //todo: ext.prebid.aliasgvlids (map) - // Never needed inside the adapter code - - @IgnoreRest - def "PBS shouldn't send aliasgvlids to bidder request"() { - given: "Default basic bid request" - def bidRequest = BidRequest.defaultBidRequest.tap { - imp[0].ext.prebid.bidder.alias = new Generic() - imp[0].ext.prebid.bidder.generic = null - ext.prebid.aliasgvlids = ["alias": 123] - ext.prebid.aliases = [(ALIAS.value): BidderName.GENERIC] - } - - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request shouldn't contain aliasgvlids" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.ext.prebid.aliasgvlids - } - - //todo: ext.prebid.bidadjustmentfactors and ext.prebid.bidadjustments (map) - // Never needed inside the adapter code (for now) but according to the requirements adapters they might be needed to reverse price floors according to the https://magnite.atlassian.net/browse/HB-20538 - // bidadjustments are missed in the table - // The value is taken from request/account - // The rules of other bidders if any are exposed. - - @IgnoreRest - def "PBS shouldn't send bid adjustment media type to bidder request"() { - given: "Default basic bid request" - def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.bidAdjustmentFactors = new BidAdjustmentFactors().tap { - it.adjustments = [(BidderName.GENERIC): BigDecimal.ONE] - it.mediaTypes = [(BidAdjustmentMediaType.BANNER): [(BidderName.GENERIC): BigDecimal.ONE]] - } - } - - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request shouldn't contain bid adjustment factors" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.ext.prebid.bidAdjustmentFactors - } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/DebugSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/DebugSpec.groovy index ea169ad00ee..cb21fc92675 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/DebugSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/DebugSpec.groovy @@ -362,7 +362,7 @@ class DebugSpec extends BaseSpec { assert !response.ext?.warnings } - def "PBS should return STORED_BID_RESPONSE call type when call from stored bid response "() { + def "PBS should return STORED_BID_RESPONSE call type when call from stored bid response"() { given: "Default basic BidRequest with stored response" def bidRequest = BidRequest.defaultBidRequest def storedResponseId = PBSUtils.randomNumber diff --git a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy index c6d600d518c..21b5464e08f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.tests +import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountProfilesConfigs @@ -35,6 +36,7 @@ import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.testcontainers.container.PrebidServerContainer import org.prebid.server.functional.util.PBSUtils import org.testcontainers.images.builder.Transferable +import spock.lang.IgnoreRest import spock.lang.PendingFeature import static org.prebid.server.functional.model.AccountStatus.ACTIVE @@ -1267,6 +1269,7 @@ class ProfileSpec extends BaseSpec { } } + @IgnoreRest def "PBS should emit error and metrics when imp profile called from request level"() { given: "Default bidRequest with request profile" def accountId = PBSUtils.randomNumber as String @@ -1299,7 +1302,8 @@ class ProfileSpec extends BaseSpec { assert metrics[MISSING_ACCOUNT_PROFILE_METRIC.formatted(accountId)] == 1 and: "Bidder request should contain data from profile" - verifyAll(bidder.getBidderRequest(bidRequest.id)) { + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + verifyAll(bidderRequest) { it.site.id == bidRequest.site.id it.site.name == bidRequest.site.name it.site.domain == bidRequest.site.domain @@ -1319,6 +1323,9 @@ class ProfileSpec extends BaseSpec { it.device.macmd5 == bidRequest.device.macmd5 it.device.dpidmd5 == bidRequest.device.dpidmd5 } + + and: "Bidder request shouldn't contain ext.prebid.profile" + assert !bidderRequest.ext.prebid.profileNames } def "PBS should emit error and metrics when imp profile missing"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsRulesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsRulesSpec.groovy index b586688398b..6c9638194c8 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsRulesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsRulesSpec.groovy @@ -13,6 +13,7 @@ import org.prebid.server.functional.model.pricefloors.MediaType import org.prebid.server.functional.model.pricefloors.PriceFloorData import org.prebid.server.functional.model.pricefloors.PriceFloorSchema import org.prebid.server.functional.model.pricefloors.Rule +import org.prebid.server.functional.model.request.Channel import org.prebid.server.functional.model.request.auction.Amx import org.prebid.server.functional.model.request.auction.Banner import org.prebid.server.functional.model.request.auction.BidRequest @@ -61,7 +62,6 @@ import static org.prebid.server.functional.model.request.auction.DistributionCha import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE import static org.prebid.server.functional.model.request.auction.FetchStatus.ERROR import static org.prebid.server.functional.model.request.auction.Location.NO_DATA -import static org.prebid.server.functional.model.request.auction.Prebid.Channel import static org.prebid.server.functional.model.response.auction.BidRejectionReason.RESPONSE_REJECTED_DUE_TO_PRICE_FLOOR import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaAuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaAuctionSpec.groovy index d544c7a2788..c1b275e7ed3 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaAuctionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaAuctionSpec.groovy @@ -2,6 +2,7 @@ package org.prebid.server.functional.tests.privacy import org.prebid.server.functional.model.ChannelType import org.prebid.server.functional.model.config.AccountCcpaConfig +import org.prebid.server.functional.model.request.Channel import org.prebid.server.functional.model.request.auction.DistributionChannel import org.prebid.server.functional.util.privacy.BogusConsent import org.prebid.server.functional.util.privacy.CcpaConsent @@ -16,7 +17,6 @@ import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_REQUEST import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_EIDS import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_PRECISE_GEO import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_UFPD -import static org.prebid.server.functional.model.request.auction.Prebid.Channel import static org.prebid.server.functional.model.request.auction.TraceLevel.BASIC import static org.prebid.server.functional.model.request.auction.TraceLevel.VERBOSE import static org.prebid.server.functional.util.privacy.CcpaConsent.Signal.ENFORCED diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy index 299d911a398..7097a30d6c7 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy @@ -8,6 +8,7 @@ import org.prebid.server.functional.model.config.AccountMetricsVerbosityLevel import org.prebid.server.functional.model.config.PurposeConfig import org.prebid.server.functional.model.config.PurposeEnforcement import org.prebid.server.functional.model.pricefloors.Country +import org.prebid.server.functional.model.request.Channel import org.prebid.server.functional.model.request.auction.DistributionChannel import org.prebid.server.functional.model.request.auction.Regs import org.prebid.server.functional.model.request.auction.RegsExt @@ -40,7 +41,6 @@ import static org.prebid.server.functional.model.request.auction.ActivityType.FE import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_EIDS import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_PRECISE_GEO import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_UFPD -import static org.prebid.server.functional.model.request.auction.Prebid.Channel import static org.prebid.server.functional.model.request.auction.PublicCountryIp.BGR_IP import static org.prebid.server.functional.model.request.auction.TraceLevel.BASIC import static org.prebid.server.functional.model.request.auction.TraceLevel.VERBOSE From 5ca717cef89b43096aab4123fb6fd8ca5a82d0b3 Mon Sep 17 00:00:00 2001 From: Danylo Date: Thu, 11 Dec 2025 23:55:04 +0100 Subject: [PATCH 03/13] Refactor `MediaTypeProcessor` --- .../server/auction/ExchangeService.java | 99 ++++------- .../BidderRequestCleaner.java | 40 +++++ .../BidderRequestCurrencyBlocker.java | 54 ++++++ .../BidderRequestMediaFilter.java | 135 ++++++++++++++ .../BidderRequestPostProcessor.java | 14 ++ .../BidderRequestPreferredMediaProcessor.java | 159 +++++++++++++++++ .../BidderRequestRejectedException.java | 20 +++ .../CompositeBidderRequestPostProcessor.java | 44 +++++ .../BidderMediaTypeProcessor.java | 134 -------------- .../CompositeMediaTypeProcessor.java | 45 ----- .../MediaTypeProcessingResult.java | 25 --- .../MediaTypeProcessor.java | 10 -- .../MultiFormatMediaTypeProcessor.java | 143 --------------- .../org/prebid/server/bidder/BidderInfo.java | 6 +- .../spring/config/ServiceConfiguration.java | 44 +++-- .../server/auction/ExchangeServiceTest.java | 105 +++++------ .../BidderRequestCleanerTest.java | 70 ++++++++ .../BidderRequestCurrencyBlockerTest.java | 166 ++++++++++++++++++ .../BidderRequestMediaFilterTest.java} | 90 ++++++---- ...erRequestPreferredMediaProcessorTest.java} | 130 +++++++------- ...mpositeBidderRequestPostProcessorTest.java | 89 ++++++++++ .../CompositeMediaTypeProcessorTest.java | 96 ---------- 22 files changed, 1028 insertions(+), 690 deletions(-) create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlocker.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilter.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessor.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessor.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestRejectedException.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessor.java delete mode 100644 src/main/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessor.java delete mode 100644 src/main/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessor.java delete mode 100644 src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessingResult.java delete mode 100644 src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessor.java delete mode 100644 src/main/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessor.java create mode 100644 src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java create mode 100644 src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlockerTest.java rename src/test/java/org/prebid/server/auction/{mediatypeprocessor/BidderMediaTypeProcessorTest.java => bidderrequestpostprocessor/BidderRequestMediaFilterTest.java} (61%) rename src/test/java/org/prebid/server/auction/{mediatypeprocessor/MultiFormatMediaTypeProcessorTest.java => bidderrequestpostprocessor/BidderRequestPreferredMediaProcessorTest.java} (65%) create mode 100644 src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessorTest.java delete mode 100644 src/test/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessorTest.java diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java index 8bea0bc315e..c4ae087d205 100644 --- a/src/main/java/org/prebid/server/auction/ExchangeService.java +++ b/src/main/java/org/prebid/server/auction/ExchangeService.java @@ -19,7 +19,6 @@ import io.vertx.core.CompositeFuture; import io.vertx.core.Future; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.ListUtils; import org.apache.commons.collections4.map.CaseInsensitiveMap; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -32,9 +31,9 @@ import org.prebid.server.activity.infrastructure.payload.impl.BidRequestActivityInvocationPayload; import org.prebid.server.auction.aliases.AlternateBidderCodesConfig; import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestPostProcessor; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestRejectedException; import org.prebid.server.auction.externalortb.StoredResponseProcessor; -import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessingResult; -import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessor; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.AuctionParticipation; import org.prebid.server.auction.model.BidRejectionReason; @@ -51,7 +50,6 @@ import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.BidderCatalog; -import org.prebid.server.bidder.BidderInfo; import org.prebid.server.bidder.HttpBidderRequester; import org.prebid.server.bidder.Usersyncer; import org.prebid.server.bidder.model.BidderBid; @@ -144,7 +142,7 @@ public class ExchangeService { private final ImpAdjuster impAdjuster; private final SupplyChainResolver supplyChainResolver; private final DebugResolver debugResolver; - private final MediaTypeProcessor mediaTypeProcessor; + private final BidderRequestPostProcessor bidderRequestPostProcessor; private final UidUpdater uidUpdater; private final TimeoutResolver timeoutResolver; private final TimeoutFactory timeoutFactory; @@ -171,7 +169,7 @@ public ExchangeService(double logSamplingRate, ImpAdjuster impAdjuster, SupplyChainResolver supplyChainResolver, DebugResolver debugResolver, - MediaTypeProcessor mediaTypeProcessor, + BidderRequestPostProcessor bidderRequestPostProcessor, UidUpdater uidUpdater, TimeoutResolver timeoutResolver, TimeoutFactory timeoutFactory, @@ -198,7 +196,7 @@ public ExchangeService(double logSamplingRate, this.impAdjuster = Objects.requireNonNull(impAdjuster); this.supplyChainResolver = Objects.requireNonNull(supplyChainResolver); this.debugResolver = Objects.requireNonNull(debugResolver); - this.mediaTypeProcessor = Objects.requireNonNull(mediaTypeProcessor); + this.bidderRequestPostProcessor = Objects.requireNonNull(bidderRequestPostProcessor); this.uidUpdater = Objects.requireNonNull(uidUpdater); this.timeoutResolver = Objects.requireNonNull(timeoutResolver); this.timeoutFactory = Objects.requireNonNull(timeoutFactory); @@ -435,7 +433,7 @@ private void removeInvalidBidRejectionTrackers(Map BidderAliases aliases) { final Set bidderNames = new HashSet<>(bidRejectionTrackers.keySet()); - for (String bidder: bidderNames) { + for (String bidder : bidderNames) { if (!isValidBidder(bidder, aliases)) { bidRejectionTrackers.remove(bidder); logger.warn("Pre-rejected impressions of the bidder {} have been removed. " @@ -492,13 +490,12 @@ private Future> extractAuctionParticipations( .filter(bidder -> isBidderCallActivityAllowed(bidder, context)) .distinct() .toList(); - final Map> impBidderToStoredBidResponse = - storedResponseResult.getImpBidderToStoredBidResponse(); + return makeAuctionParticipation( bidders, context, aliases, - impBidderToStoredBidResponse, + storedResponseResult.getImpBidderToStoredBidResponse(), imps, bidderToMultiBid); } @@ -539,7 +536,7 @@ private Future> makeAuctionParticipation( final BidRequest bidRequest = context.getBidRequest(); final ExtRequest requestExt = bidRequest.getExt(); - final ExtRequestPrebid prebid = requestExt == null ? null : requestExt.getPrebid(); + final ExtRequestPrebid prebid = requestExt != null ? requestExt.getPrebid() : null; final Map biddersToConfigs = getBiddersToConfigs(prebid); final Map> eidPermissions = getEidPermissions(prebid); final Map> bidderToUserAndDevice = @@ -1157,69 +1154,39 @@ private Future processAndRequestBids(AuctionContext auctionConte Timeout timeout, BidderAliases aliases) { - final String bidderName = bidderRequest.getBidder(); - final MediaTypeProcessingResult mediaTypeProcessingResult = mediaTypeProcessor.process( - bidderRequest.getBidRequest(), bidderName, aliases, auctionContext.getAccount()); - final List mediaTypeProcessingErrors = mediaTypeProcessingResult.getErrors(); - if (mediaTypeProcessingResult.isRejected()) { - return processReject( - auctionContext, - BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE, - mediaTypeProcessingErrors, - bidderName); - } - if (isUnacceptableCurrency(auctionContext, aliases.resolveBidder(bidderName))) { - return processReject( - auctionContext, - BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY, - List.of(BidderError.generic("No match between the configured currencies and bidRequest.cur")), - bidderName); - } - - return Future.succeededFuture(mediaTypeProcessingResult.getBidRequest()) - .map(bidderRequest::with) - .compose(modifiedBidderRequest -> invokeHooksAndRequestBids( - auctionContext, modifiedBidderRequest, timeout, aliases)) - .map(bidderResponse -> bidderResponse.with( - addWarnings(bidderResponse.getSeatBid(), mediaTypeProcessingErrors))); - } - - private boolean isUnacceptableCurrency(AuctionContext auctionContext, String originalBidderName) { - final List requestCurrencies = auctionContext.getBidRequest().getCur(); - final List bidAcceptableCurrencies = - Optional.ofNullable(bidderCatalog.bidderInfoByName(originalBidderName)) - .map(BidderInfo::getCurrencyAccepted) - .orElse(null); - - if (CollectionUtils.isEmpty(requestCurrencies) || CollectionUtils.isEmpty(bidAcceptableCurrencies)) { - return false; - } - - return !CollectionUtils.containsAny(requestCurrencies, bidAcceptableCurrencies); - } - - private static Future processReject(AuctionContext auctionContext, - BidRejectionReason bidRejectionReason, - List warnings, - String bidderName) { - - auctionContext.getBidRejectionTrackers() - .get(bidderName) - .rejectAll(bidRejectionReason); - final BidderSeatBid bidderSeatBid = BidderSeatBid.builder() - .warnings(warnings) - .build(); - return Future.succeededFuture(BidderResponse.of(bidderName, bidderSeatBid, 0)); + return bidderRequestPostProcessor.process(bidderRequest, aliases, auctionContext) + .compose(result -> invokeHooksAndRequestBids(auctionContext, result.getValue(), timeout, aliases) + .map(response -> response.with(addWarnings(response.getSeatBid(), result.getErrors())))) + .recover(throwable -> recoverBidderRequestRejection( + auctionContext, bidderRequest.getBidder(), throwable)); } private static BidderSeatBid addWarnings(BidderSeatBid seatBid, List warnings) { return CollectionUtils.isNotEmpty(warnings) ? seatBid.toBuilder() - .warnings(ListUtils.union(warnings, seatBid.getWarnings())) + .warnings(ListUtil.union(warnings, seatBid.getWarnings())) .build() : seatBid; } + private static Future recoverBidderRequestRejection(AuctionContext auctionContext, + String bidderName, + Throwable throwable) { + + if (throwable instanceof BidderRequestRejectedException rejection) { + auctionContext.getBidRejectionTrackers() + .get(bidderName) + .rejectAll(rejection.getRejectionReason()); + final BidderSeatBid bidderSeatBid = BidderSeatBid.builder() + .warnings(rejection.getErrors()) + .build(); + + return Future.succeededFuture(BidderResponse.of(bidderName, bidderSeatBid, 0)); + } + + return Future.failedFuture(throwable); + } + private Future invokeHooksAndRequestBids(AuctionContext auctionContext, BidderRequest bidderRequest, Timeout timeout, diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java new file mode 100644 index 00000000000..9028d337014 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java @@ -0,0 +1,40 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.BidRequest; +import io.vertx.core.Future; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; + +import java.util.Collections; + +public class BidderRequestCleaner implements BidderRequestPostProcessor { + + @Override + public Future> process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext) { + + final BidRequest bidRequest = bidderRequest.getBidRequest(); + final ExtRequest ext = bidRequest.getExt(); + final ExtRequestPrebid extPrebid = ext != null ? ext.getPrebid() : null; + final ObjectNode bidderControls = extPrebid != null ? extPrebid.getBiddercontrols() : null; + + if (bidderControls == null) { + return resultOf(bidderRequest); + } + + final ExtRequest cleanedExt = ExtRequest.of(extPrebid.toBuilder().biddercontrols(null).build()); + cleanedExt.addProperties(ext.getProperties()); + + return resultOf(bidderRequest.with(bidRequest.toBuilder().ext(cleanedExt).build())); + } + + private static Future> resultOf(BidderRequest bidderRequest) { + return Future.succeededFuture(Result.of(bidderRequest, Collections.emptyList())); + } +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlocker.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlocker.java new file mode 100644 index 00000000000..5cdeef5e8d3 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlocker.java @@ -0,0 +1,54 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.iab.openrtb.request.BidRequest; +import io.vertx.core.Future; +import org.apache.commons.collections4.CollectionUtils; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.BidderCatalog; +import org.prebid.server.bidder.BidderInfo; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.Result; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +public class BidderRequestCurrencyBlocker implements BidderRequestPostProcessor { + + private final BidderCatalog bidderCatalog; + + public BidderRequestCurrencyBlocker(BidderCatalog bidderCatalog) { + this.bidderCatalog = Objects.requireNonNull(bidderCatalog); + } + + @Override + public Future> process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext) { + + if (isAcceptableCurrency(bidderRequest.getBidRequest(), aliases.resolveBidder(bidderRequest.getBidder()))) { + return Future.succeededFuture(Result.of(bidderRequest, Collections.emptyList())); + } + + return Future.failedFuture(new BidderRequestRejectedException( + BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY, + List.of(BidderError.generic("No match between the configured currencies and bidRequest.cur")))); + } + + private boolean isAcceptableCurrency(BidRequest bidRequest, String originalBidderName) { + final List requestCurrencies = bidRequest.getCur(); + final Set bidAcceptableCurrencies = + Optional.ofNullable(bidderCatalog.bidderInfoByName(originalBidderName)) + .map(BidderInfo::getCurrencyAccepted) + .orElse(null); + + return CollectionUtils.isEmpty(requestCurrencies) + || CollectionUtils.isEmpty(bidAcceptableCurrencies) + || requestCurrencies.stream().anyMatch(bidAcceptableCurrencies::contains); + } +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilter.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilter.java new file mode 100644 index 00000000000..27166febb37 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilter.java @@ -0,0 +1,135 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.iab.openrtb.request.Audio; +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Native; +import com.iab.openrtb.request.Video; +import io.vertx.core.Future; +import org.apache.commons.lang3.ObjectUtils; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.BidderCatalog; +import org.prebid.server.bidder.BidderInfo; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.spring.config.bidder.model.MediaType; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class BidderRequestMediaFilter implements BidderRequestPostProcessor { + + private static final EnumSet NONE_OF_MEDIA_TYPES = EnumSet.noneOf(MediaType.class); + + private final BidderCatalog bidderCatalog; + + public BidderRequestMediaFilter(BidderCatalog bidderCatalog) { + this.bidderCatalog = Objects.requireNonNull(bidderCatalog); + } + + @Override + public Future> process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext) { + + final String resolvedBidderName = aliases.resolveBidder(bidderRequest.getBidder()); + final BidRequest bidRequest = bidderRequest.getBidRequest(); + final Set supportedMediaTypes = extractSupportedMediaTypes(bidRequest, resolvedBidderName); + if (supportedMediaTypes.isEmpty()) { + return rejected(List.of(BidderError.badInput("Bidder does not support any media types."))); + } + + final List errors = new ArrayList<>(); + final BidRequest modifiedBidRequest = processBidRequest(bidRequest, supportedMediaTypes, errors); + + return modifiedBidRequest != null + ? Future.succeededFuture(Result.of(bidderRequest.with(modifiedBidRequest), errors)) + : rejected(errors); + } + + private static Future> rejected(List errors) { + return Future.failedFuture( + new BidderRequestRejectedException(BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE, errors)); + } + + private Set extractSupportedMediaTypes(BidRequest bidRequest, String bidderName) { + final BidderInfo.CapabilitiesInfo capabilitiesInfo = bidderCatalog + .bidderInfoByName(bidderName) + .getCapabilities(); + + final BidderInfo.PlatformInfo site = bidRequest.getSite() != null ? capabilitiesInfo.getSite() : null; + final BidderInfo.PlatformInfo app = bidRequest.getApp() != null ? capabilitiesInfo.getApp() : null; + final BidderInfo.PlatformInfo dooh = bidRequest.getDooh() != null ? capabilitiesInfo.getDooh() : null; + + return Stream.of(site, app, dooh) + .filter(Objects::nonNull) + .findFirst() + .map(BidderInfo.PlatformInfo::getMediaTypes) + .filter(mediaTypes -> !mediaTypes.isEmpty()) + .map(EnumSet::copyOf) + .orElse(NONE_OF_MEDIA_TYPES); + } + + private static BidRequest processBidRequest(BidRequest bidRequest, + Set supportedMediaTypes, + List errors) { + + final List modifiedImps = bidRequest.getImp().stream() + .map(imp -> processImp(imp, supportedMediaTypes, errors)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (modifiedImps.isEmpty()) { + errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering.")); + return null; + } + + return bidRequest.toBuilder().imp(modifiedImps).build(); + } + + private static Imp processImp(Imp imp, Set supportedMediaTypes, List errors) { + final Set impMediaTypes = getMediaTypes(imp); + if (supportedMediaTypes.containsAll(impMediaTypes)) { + return imp; + } + + final Banner banner = supportedMediaTypes.contains(MediaType.BANNER) ? imp.getBanner() : null; + final Video video = supportedMediaTypes.contains(MediaType.VIDEO) ? imp.getVideo() : null; + final Audio audio = supportedMediaTypes.contains(MediaType.AUDIO) ? imp.getAudio() : null; + final Native xNative = supportedMediaTypes.contains(MediaType.NATIVE) ? imp.getXNative() : null; + + if (ObjectUtils.allNull(banner, video, audio, xNative)) { + errors.add(BidderError.badInput(""" + Imp %s does not have a supported media type \ + and has been removed from the request for this bidder.""".formatted(imp.getId()))); + + return null; + } + + return imp.toBuilder() + .banner(banner) + .video(video) + .audio(audio) + .xNative(xNative) + .build(); + } + + private static Set getMediaTypes(Imp imp) { + return Stream.of( + imp.getBanner() != null ? MediaType.BANNER : null, + imp.getVideo() != null ? MediaType.VIDEO : null, + imp.getAudio() != null ? MediaType.AUDIO : null, + imp.getXNative() != null ? MediaType.NATIVE : null) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(MediaType.class))); + } +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessor.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessor.java new file mode 100644 index 00000000000..f3acff4f5ff --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessor.java @@ -0,0 +1,14 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import io.vertx.core.Future; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.model.Result; + +public interface BidderRequestPostProcessor { + + Future> process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext); +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessor.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessor.java new file mode 100644 index 00000000000..3ad073e78fb --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessor.java @@ -0,0 +1,159 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.fasterxml.jackson.databind.JsonNode; +import com.iab.openrtb.request.Audio; +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Native; +import com.iab.openrtb.request.Video; +import io.vertx.core.Future; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.BidderCatalog; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.settings.model.Account; +import org.prebid.server.settings.model.AccountAuctionConfig; +import org.prebid.server.spring.config.bidder.model.MediaType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +public class BidderRequestPreferredMediaProcessor implements BidderRequestPostProcessor { + + private static final String PREF_MTYPE_FIELD = "prefmtype"; + + private final BidderCatalog bidderCatalog; + + public BidderRequestPreferredMediaProcessor(BidderCatalog bidderCatalog) { + this.bidderCatalog = Objects.requireNonNull(bidderCatalog); + } + + @Override + public Future> process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext) { + + final String bidderName = bidderRequest.getBidder(); + final BidRequest bidRequest = bidderRequest.getBidRequest(); + + final String resolvedBidderName = aliases.resolveBidder(bidderName); + if (isMultiFormatSupported(resolvedBidderName)) { + return noAction(bidderRequest); + } + + final Optional preferredMediaType = preferredMediaType(bidRequest, bidderName) + .or(() -> preferredMediaType(auctionContext.getAccount(), resolvedBidderName)); + if (preferredMediaType.isEmpty()) { + return noAction(bidderRequest); + } + + final List errors = new ArrayList<>(); + final BidRequest modifiedBidRequest = processBidRequest(bidRequest, preferredMediaType.get(), errors); + + return modifiedBidRequest != null + ? Future.succeededFuture(Result.of(bidderRequest.with(modifiedBidRequest), errors)) + : Future.failedFuture(new BidderRequestRejectedException( + BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE, errors)); + } + + private static Future> noAction(BidderRequest bidderRequest) { + return Future.succeededFuture(Result.of(bidderRequest, Collections.emptyList())); + } + + private boolean isMultiFormatSupported(String bidder) { + return bidderCatalog.bidderInfoByName(bidder).getOrtb().isMultiFormatSupported(); + } + + private static Optional preferredMediaType(BidRequest bidRequest, String bidderName) { + return Optional.ofNullable(bidRequest.getExt()) + .map(ExtRequest::getPrebid) + .map(ExtRequestPrebid::getBiddercontrols) + .map(bidders -> getBidder(bidderName, bidders)) + .map(bidder -> bidder.get(PREF_MTYPE_FIELD)) + .filter(JsonNode::isTextual) + .map(JsonNode::textValue) + .map(MediaType::of); + } + + private static Optional preferredMediaType(Account account, String bidderName) { + return Optional.ofNullable(account.getAuction()) + .map(AccountAuctionConfig::getPreferredMediaTypes) + .map(preferredMediaTypes -> preferredMediaTypes.get(bidderName)); + } + + private static JsonNode getBidder(String bidderName, JsonNode biddersNode) { + final Iterator fieldNames = biddersNode.fieldNames(); + while (fieldNames.hasNext()) { + final String fieldName = fieldNames.next(); + if (StringUtils.equalsIgnoreCase(bidderName, fieldName)) { + return biddersNode.get(fieldName); + } + } + + return null; + } + + private static BidRequest processBidRequest(BidRequest bidRequest, + MediaType preferredMediaType, + List errors) { + + final List modifiedImps = bidRequest.getImp().stream() + .map(imp -> processImp(imp, preferredMediaType, errors)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (modifiedImps.isEmpty()) { + errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering.")); + return null; + } + + return bidRequest.toBuilder().imp(modifiedImps).build(); + } + + private static Imp processImp(Imp imp, MediaType preferredMediaType, List errors) { + if (!isMultiFormat(imp)) { + return imp; + } + + final Banner banner = preferredMediaType == MediaType.BANNER ? imp.getBanner() : null; + final Video video = preferredMediaType == MediaType.VIDEO ? imp.getVideo() : null; + final Audio audio = preferredMediaType == MediaType.AUDIO ? imp.getAudio() : null; + final Native xNative = preferredMediaType == MediaType.NATIVE ? imp.getXNative() : null; + + if (ObjectUtils.allNull(banner, video, audio, xNative)) { + errors.add(BidderError.badInput(""" + Imp %s does not have a media type after filtering \ + and has been removed from the request for this bidder.""".formatted(imp.getId()))); + + return null; + } + + return imp.toBuilder() + .banner(banner) + .video(video) + .audio(audio) + .xNative(xNative) + .build(); + } + + private static boolean isMultiFormat(Imp imp) { + int count = 0; + return (imp.getBanner() != null && ++count > 1) + || (imp.getVideo() != null && ++count > 1) + || (imp.getAudio() != null && ++count > 1) + || (imp.getXNative() != null && ++count > 1); + } +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestRejectedException.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestRejectedException.java new file mode 100644 index 00000000000..6beeaa5069f --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestRejectedException.java @@ -0,0 +1,20 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import lombok.Getter; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.bidder.model.BidderError; + +import java.util.List; +import java.util.Objects; + +@Getter +public class BidderRequestRejectedException extends RuntimeException { + + private final BidRejectionReason rejectionReason; + private final List errors; + + public BidderRequestRejectedException(BidRejectionReason bidRejectionReason, List errors) { + this.rejectionReason = Objects.requireNonNull(bidRejectionReason); + this.errors = Objects.requireNonNull(errors); + } +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessor.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessor.java new file mode 100644 index 00000000000..033b4223927 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessor.java @@ -0,0 +1,44 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import io.vertx.core.Future; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.util.ListUtil; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class CompositeBidderRequestPostProcessor implements BidderRequestPostProcessor { + + private final List bidderRequestPostProcessors; + + public CompositeBidderRequestPostProcessor(List bidderRequestPostProcessors) { + this.bidderRequestPostProcessors = Objects.requireNonNull(bidderRequestPostProcessors); + } + + @Override + public Future> process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext) { + + Future> result = initialResult(bidderRequest); + for (BidderRequestPostProcessor bidderRequestPostProcessor : bidderRequestPostProcessors) { + result = result.compose(previous -> + bidderRequestPostProcessor.process(previous.getValue(), aliases, auctionContext) + .map(current -> mergeErrors(previous, current))); + } + + return result; + } + + private static Future> initialResult(BidderRequest bidderRequest) { + return Future.succeededFuture(Result.of(bidderRequest, Collections.emptyList())); + } + + private static Result mergeErrors(Result previous, Result current) { + return Result.of(current.getValue(), ListUtil.union(previous.getErrors(), current.getErrors())); + } +} diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessor.java deleted file mode 100644 index 17828059175..00000000000 --- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessor.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Imp; -import org.apache.commons.collections4.SetUtils; -import org.prebid.server.auction.aliases.BidderAliases; -import org.prebid.server.bidder.BidderCatalog; -import org.prebid.server.bidder.BidderInfo; -import org.prebid.server.bidder.model.BidderError; -import org.prebid.server.settings.model.Account; -import org.prebid.server.spring.config.bidder.model.MediaType; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * {@link BidderMediaTypeProcessor} is an implementation of {@link MediaTypeProcessor} that - * can be used to remove media types from {@link Imp} unsupported by specific bidder. - */ -public class BidderMediaTypeProcessor implements MediaTypeProcessor { - - private static final EnumSet NONE_OF_MEDIA_TYPES = EnumSet.noneOf(MediaType.class); - - private final BidderCatalog bidderCatalog; - - public BidderMediaTypeProcessor(BidderCatalog bidderCatalog) { - this.bidderCatalog = Objects.requireNonNull(bidderCatalog); - } - - @Override - public MediaTypeProcessingResult process(BidRequest bidRequest, - String bidderName, - BidderAliases aliases, - Account account) { - final String resolvedBidderName = aliases.resolveBidder(bidderName); - final Set supportedMediaTypes = extractSupportedMediaTypes(bidRequest, resolvedBidderName); - if (supportedMediaTypes.isEmpty()) { - return MediaTypeProcessingResult.rejected(Collections.singletonList( - BidderError.badInput("Bidder does not support any media types."))); - } - - final List errors = new ArrayList<>(); - final BidRequest modifiedBidRequest = processBidRequest(bidRequest, supportedMediaTypes, errors); - - return modifiedBidRequest != null - ? MediaTypeProcessingResult.succeeded(modifiedBidRequest, errors) - : MediaTypeProcessingResult.rejected(errors); - } - - private Set extractSupportedMediaTypes(BidRequest bidRequest, String bidderName) { - final BidderInfo.CapabilitiesInfo capabilitiesInfo = bidderCatalog.bidderInfoByName(bidderName) - .getCapabilities(); - - final Supplier fetchSupportedMediaTypes; - if (bidRequest.getSite() != null) { - fetchSupportedMediaTypes = capabilitiesInfo::getSite; - } else if (bidRequest.getApp() != null) { - fetchSupportedMediaTypes = capabilitiesInfo::getApp; - } else { - fetchSupportedMediaTypes = capabilitiesInfo::getDooh; - } - - return Optional.ofNullable(fetchSupportedMediaTypes.get()) - .map(BidderInfo.PlatformInfo::getMediaTypes) - .filter(mediaTypes -> !mediaTypes.isEmpty()) - .map(EnumSet::copyOf) - .orElse(NONE_OF_MEDIA_TYPES); - } - - private BidRequest processBidRequest(BidRequest bidRequest, - Set supportedMediaTypes, - List errors) { - - final List modifiedImps = bidRequest.getImp().stream() - .map(imp -> processImp(imp, supportedMediaTypes, errors)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - if (modifiedImps.isEmpty()) { - errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering.")); - return null; - } - - return bidRequest.toBuilder().imp(modifiedImps).build(); - } - - private static Imp processImp(Imp imp, Set supportedMediaTypes, List errors) { - final Set impMediaTypes = getMediaTypes(imp); - final Set unsupportedMediaTypes = SetUtils.difference(impMediaTypes, supportedMediaTypes); - - if (unsupportedMediaTypes.isEmpty()) { - return imp; - } - - if (impMediaTypes.equals(unsupportedMediaTypes)) { - errors.add(BidderError.badInput("Imp " + imp.getId() + " does not have a supported media type " - + "and has been removed from the request for this bidder.")); - - return null; - } - - final Imp.ImpBuilder impBuilder = imp.toBuilder(); - unsupportedMediaTypes.forEach(unsupportedMediaType -> removeMediaType(impBuilder, unsupportedMediaType)); - - return impBuilder.build(); - } - - private static Set getMediaTypes(Imp imp) { - return Stream.of( - imp.getBanner() != null ? MediaType.BANNER : null, - imp.getVideo() != null ? MediaType.VIDEO : null, - imp.getAudio() != null ? MediaType.AUDIO : null, - imp.getXNative() != null ? MediaType.NATIVE : null) - .filter(Objects::nonNull) - .collect(Collectors.toCollection(() -> EnumSet.noneOf(MediaType.class))); - } - - private static void removeMediaType(Imp.ImpBuilder impBuilder, MediaType mediaType) { - switch (mediaType) { - case BANNER -> impBuilder.banner(null); - case VIDEO -> impBuilder.video(null); - case AUDIO -> impBuilder.audio(null); - case NATIVE -> impBuilder.xNative(null); - } - } -} diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessor.java deleted file mode 100644 index 5dbd2bb3ae6..00000000000 --- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessor.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.iab.openrtb.request.BidRequest; -import org.prebid.server.auction.aliases.BidderAliases; -import org.prebid.server.bidder.model.BidderError; -import org.prebid.server.settings.model.Account; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class CompositeMediaTypeProcessor implements MediaTypeProcessor { - - private final List mediaTypeProcessors; - - public CompositeMediaTypeProcessor(List mediaTypeProcessors) { - this.mediaTypeProcessors = Objects.requireNonNull(mediaTypeProcessors); - } - - @Override - public MediaTypeProcessingResult process(BidRequest originalBidRequest, - String bidderName, - BidderAliases aliases, - Account account) { - BidRequest bidRequest = originalBidRequest; - final List errors = new ArrayList<>(); - - for (MediaTypeProcessor mediaTypeProcessor : mediaTypeProcessors) { - final MediaTypeProcessingResult result = mediaTypeProcessor.process( - bidRequest, - bidderName, - aliases, - account); - - bidRequest = result.getBidRequest(); - errors.addAll(result.getErrors()); - - if (result.isRejected()) { - return MediaTypeProcessingResult.rejected(errors); - } - } - - return MediaTypeProcessingResult.succeeded(bidRequest, errors); - } -} diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessingResult.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessingResult.java deleted file mode 100644 index b9a6780d2e8..00000000000 --- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessingResult.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.iab.openrtb.request.BidRequest; -import lombok.Value; -import org.prebid.server.bidder.model.BidderError; - -import java.util.List; - -@Value(staticConstructor = "of") -public class MediaTypeProcessingResult { - - BidRequest bidRequest; - - List errors; - - boolean rejected; - - public static MediaTypeProcessingResult succeeded(BidRequest bidRequest, List errors) { - return MediaTypeProcessingResult.of(bidRequest, errors, false); - } - - public static MediaTypeProcessingResult rejected(List errors) { - return MediaTypeProcessingResult.of(null, errors, true); - } -} diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessor.java deleted file mode 100644 index 0cf5f19bd6a..00000000000 --- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessor.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.iab.openrtb.request.BidRequest; -import org.prebid.server.auction.aliases.BidderAliases; -import org.prebid.server.settings.model.Account; - -public interface MediaTypeProcessor { - - MediaTypeProcessingResult process(BidRequest bidRequest, String bidderName, BidderAliases aliases, Account account); -} diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessor.java deleted file mode 100644 index 0fda09f919e..00000000000 --- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessor.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.fasterxml.jackson.databind.JsonNode; -import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Imp; -import org.apache.commons.lang3.StringUtils; -import org.prebid.server.auction.aliases.BidderAliases; -import org.prebid.server.bidder.BidderCatalog; -import org.prebid.server.bidder.model.BidderError; -import org.prebid.server.proto.openrtb.ext.request.ExtRequest; -import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; -import org.prebid.server.settings.model.Account; -import org.prebid.server.settings.model.AccountAuctionConfig; -import org.prebid.server.spring.config.bidder.model.MediaType; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -public class MultiFormatMediaTypeProcessor implements MediaTypeProcessor { - - private static final String PREF_MTYPE_FIELD = "prefmtype"; - - private final BidderCatalog bidderCatalog; - - public MultiFormatMediaTypeProcessor(BidderCatalog bidderCatalog) { - this.bidderCatalog = Objects.requireNonNull(bidderCatalog); - } - - @Override - public MediaTypeProcessingResult process(BidRequest bidRequest, - String bidderName, - BidderAliases aliases, - Account account) { - final String resolvedBidderName = aliases.resolveBidder(bidderName); - //todo: ext.prebid.biddercontrols clean-up should NOT be here - // Suggestion: keep biddercontrols in the Auction Context - // and clean it up on the extraction auction participants step - final BidRequest.BidRequestBuilder bidRequestBuilder = Optional.ofNullable(bidRequest.getExt()) - .map(ExtRequest::getPrebid) - .map(prebid -> prebid.toBuilder().biddercontrols(null).build()) - .map(prebid -> { - final ExtRequest extRequest = ExtRequest.of(prebid); - extRequest.addProperties(bidRequest.getExt().getProperties()); - return extRequest; - }) - .map(extRequest -> bidRequest.toBuilder().ext(extRequest)) - .orElse(bidRequest.toBuilder()); - if (isMultiFormatSupported(resolvedBidderName)) { - return MediaTypeProcessingResult.succeeded(bidRequestBuilder.build(), Collections.emptyList()); - } - - final MediaType preferredMediaType = preferredMediaType(bidRequest, account, bidderName, resolvedBidderName); - if (preferredMediaType == null) { - return MediaTypeProcessingResult.succeeded(bidRequestBuilder.build(), Collections.emptyList()); - } - - final List errors = new ArrayList<>(); - final List updatedImps = bidRequest.getImp().stream() - .map(imp -> processImp(imp, preferredMediaType, errors)) - .filter(Objects::nonNull) - .toList(); - - if (updatedImps.isEmpty()) { - errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering.")); - return MediaTypeProcessingResult.rejected(errors); - } - - return MediaTypeProcessingResult.succeeded(bidRequestBuilder.imp(updatedImps).build(), errors); - } - - private boolean isMultiFormatSupported(String bidder) { - return bidderCatalog.bidderInfoByName(bidder).getOrtb().isMultiFormatSupported(); - } - - private MediaType preferredMediaType(BidRequest bidRequest, - Account account, - String originalBidderName, - String resolvedBidderName) { - - return Optional.ofNullable(bidRequest.getExt()) - .map(ExtRequest::getPrebid) - .map(ExtRequestPrebid::getBiddercontrols) - .map(bidders -> getBidder(originalBidderName, bidders)) - .map(bidder -> bidder.get(PREF_MTYPE_FIELD)) - .filter(JsonNode::isTextual) - .map(JsonNode::textValue) - .map(MediaType::of) - .or(() -> Optional.ofNullable(account.getAuction()) - .map(AccountAuctionConfig::getPreferredMediaTypes) - .map(preferredMediaTypes -> preferredMediaTypes.get(resolvedBidderName))) - .orElse(null); - } - - private static JsonNode getBidder(String bidderName, JsonNode biddersNode) { - final Iterator fieldNames = biddersNode.fieldNames(); - while (fieldNames.hasNext()) { - final String fieldName = fieldNames.next(); - if (StringUtils.equalsIgnoreCase(bidderName, fieldName)) { - return biddersNode.get(fieldName); - } - } - return null; - } - - private static Imp processImp(Imp imp, MediaType preferredMediaType, List errors) { - if (!isMultiFormat(imp)) { - return imp; - } - - final Imp updatedImp = switch (preferredMediaType) { - case BANNER -> imp.getBanner() != null - ? imp.toBuilder().video(null).audio(null).xNative(null).build() - : null; - case VIDEO -> imp.getVideo() != null - ? imp.toBuilder().banner(null).audio(null).xNative(null).build() - : null; - case AUDIO -> imp.getAudio() != null - ? imp.toBuilder().banner(null).video(null).xNative(null).build() - : null; - case NATIVE -> imp.getXNative() != null - ? imp.toBuilder().banner(null).video(null).audio(null).build() - : null; - }; - - if (updatedImp == null) { - errors.add(BidderError.badInput("Imp " + imp.getId() + " does not have a media type after filtering " - + "and has been removed from the request for this bidder.")); - } - return updatedImp; - } - - private static boolean isMultiFormat(Imp imp) { - int count = 0; - return (imp.getBanner() != null && ++count > 1) - || (imp.getVideo() != null && ++count > 1) - || (imp.getAudio() != null && ++count > 1) - || (imp.getXNative() != null && ++count > 1); - } -} diff --git a/src/main/java/org/prebid/server/bidder/BidderInfo.java b/src/main/java/org/prebid/server/bidder/BidderInfo.java index 1ff8f323701..d26cc2c4b2c 100644 --- a/src/main/java/org/prebid/server/bidder/BidderInfo.java +++ b/src/main/java/org/prebid/server/bidder/BidderInfo.java @@ -7,7 +7,9 @@ import org.prebid.server.spring.config.bidder.model.CompressionType; import org.prebid.server.spring.config.bidder.model.MediaType; +import java.util.HashSet; import java.util.List; +import java.util.Set; @Value(staticConstructor = "of") public class BidderInfo { @@ -28,7 +30,7 @@ public class BidderInfo { List vendors; - List currencyAccepted; + Set currencyAccepted; GdprInfo gdpr; @@ -72,7 +74,7 @@ public static BidderInfo create(boolean enabled, platformInfo(siteMediaTypes), platformInfo(doohMediaTypes)), supportedVendors, - currencyAccepted, + currencyAccepted != null ? new HashSet<>(currencyAccepted) : null, new GdprInfo(vendorId), ccpaEnforced, modifyingVastXmlAllowed, diff --git a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java index 228d4702435..2bb6e853bcd 100644 --- a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java @@ -33,6 +33,12 @@ import org.prebid.server.auction.VideoResponseFactory; import org.prebid.server.auction.VideoStoredRequestProcessor; import org.prebid.server.auction.WinningBidComparatorFactory; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestCleaner; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestCurrencyBlocker; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestMediaFilter; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestPostProcessor; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestPreferredMediaProcessor; +import org.prebid.server.auction.bidderrequestpostprocessor.CompositeBidderRequestPostProcessor; import org.prebid.server.auction.categorymapping.BasicCategoryMappingService; import org.prebid.server.auction.categorymapping.CategoryMappingService; import org.prebid.server.auction.categorymapping.NoOpCategoryMappingService; @@ -47,10 +53,6 @@ import org.prebid.server.auction.gpp.processor.GppContextProcessor; import org.prebid.server.auction.gpp.processor.tcfeuv2.TcfEuV2ContextProcessor; import org.prebid.server.auction.gpp.processor.uspv1.UspV1ContextProcessor; -import org.prebid.server.auction.mediatypeprocessor.BidderMediaTypeProcessor; -import org.prebid.server.auction.mediatypeprocessor.CompositeMediaTypeProcessor; -import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessor; -import org.prebid.server.auction.mediatypeprocessor.MultiFormatMediaTypeProcessor; import org.prebid.server.auction.privacy.contextfactory.AmpPrivacyContextFactory; import org.prebid.server.auction.privacy.contextfactory.AuctionPrivacyContextFactory; import org.prebid.server.auction.privacy.contextfactory.CookieSyncPrivacyContextFactory; @@ -136,6 +138,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import jakarta.validation.constraints.Min; import java.io.IOException; @@ -792,19 +796,35 @@ BidderCatalog bidderCatalog(List bidderDeps) { } @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + BidderRequestPostProcessor bidderRequestCurrencyBlocker(BidderCatalog bidderCatalog) { + return new BidderRequestCurrencyBlocker(bidderCatalog); + } + + @Bean + @Order(0) @ConditionalOnProperty(prefix = "auction.filter-imp-media-type", name = "enabled", havingValue = "true") - MediaTypeProcessor bidderMediaTypeProcessor(BidderCatalog bidderCatalog) { - return new BidderMediaTypeProcessor(bidderCatalog); + BidderRequestPostProcessor bidderRequestMediaFilter(BidderCatalog bidderCatalog) { + return new BidderRequestMediaFilter(bidderCatalog); } @Bean - MediaTypeProcessor multiFormatMediaTypeProcessor(BidderCatalog bidderCatalog) { - return new MultiFormatMediaTypeProcessor(bidderCatalog); + @Order(0) + BidderRequestPostProcessor bidderRequestPreferredMediaProcessor(BidderCatalog bidderCatalog) { + return new BidderRequestPreferredMediaProcessor(bidderCatalog); } @Bean - CompositeMediaTypeProcessor compositeMediaTypeProcessor(List mediaTypeProcessors) { - return new CompositeMediaTypeProcessor(mediaTypeProcessors); + @Order(Ordered.LOWEST_PRECEDENCE) + BidderRequestPostProcessor bidderRequestCleaner() { + return new BidderRequestCleaner(); + } + + @Bean + CompositeBidderRequestPostProcessor compositeBidderRequestPostProcessor( + List bidderRequestPostProcessors) { + + return new CompositeBidderRequestPostProcessor(bidderRequestPostProcessors); } @Bean @@ -916,7 +936,7 @@ ExchangeService exchangeService( ImpAdjuster impAdjuster, SupplyChainResolver supplyChainResolver, DebugResolver debugResolver, - CompositeMediaTypeProcessor mediaTypeProcessor, + CompositeBidderRequestPostProcessor bidderRequestPostProcessor, UidUpdater uidUpdater, TimeoutResolver timeoutResolver, TimeoutFactory timeoutFactory, @@ -944,7 +964,7 @@ ExchangeService exchangeService( impAdjuster, supplyChainResolver, debugResolver, - mediaTypeProcessor, + bidderRequestPostProcessor, uidUpdater, timeoutResolver, timeoutFactory, diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index 20561c5b685..582b5341f24 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -43,18 +43,20 @@ import org.prebid.server.activity.Activity; import org.prebid.server.activity.ComponentType; import org.prebid.server.activity.infrastructure.ActivityInfrastructure; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestPostProcessor; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestRejectedException; import org.prebid.server.auction.externalortb.StoredResponseProcessor; -import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessingResult; -import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessor; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.AuctionParticipation; +import org.prebid.server.auction.model.BidRejectionReason; import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidRequestCacheInfo; import org.prebid.server.auction.model.BidderPrivacyResult; import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.auction.model.BidderResponse; -import org.prebid.server.auction.model.MultiBidConfig; import org.prebid.server.auction.model.ImpRejection; +import org.prebid.server.auction.model.MultiBidConfig; +import org.prebid.server.auction.model.Rejection; import org.prebid.server.auction.model.StoredResponseResult; import org.prebid.server.auction.model.TimeoutContext; import org.prebid.server.auction.model.debug.DebugContext; @@ -69,6 +71,7 @@ import org.prebid.server.bidder.model.BidderError; import org.prebid.server.bidder.model.BidderSeatBid; import org.prebid.server.bidder.model.Price; +import org.prebid.server.bidder.model.Result; import org.prebid.server.cookie.UidsCookie; import org.prebid.server.exception.InvalidRequestException; import org.prebid.server.exception.PreBidException; @@ -208,7 +211,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.prebid.server.auction.model.BidRejectionReason.NO_BID; -import static org.prebid.server.auction.model.BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY; +import static org.prebid.server.auction.model.BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.video; @@ -237,7 +240,7 @@ public class ExchangeServiceTest extends VertxTest { private DebugResolver debugResolver; @Mock(strictness = LENIENT) - private MediaTypeProcessor mediaTypeProcessor; + private BidderRequestPostProcessor bidderRequestPostProcessor; @Mock(strictness = LENIENT) private UidUpdater uidUpdater; @@ -363,8 +366,8 @@ public void setUp() { given(bidsAdjuster.validateAndAdjustBids(any(), any(), any())) .willAnswer(invocation -> invocation.getArgument(0)); - given(mediaTypeProcessor.process(any(), anyString(), any(), any())) - .willAnswer(invocation -> MediaTypeProcessingResult.succeeded(invocation.getArgument(0), emptyList())); + given(bidderRequestPostProcessor.process(any(), any(), any())) + .willAnswer(invocation -> Future.succeededFuture(Result.of(invocation.getArgument(0), emptyList()))); given(uidUpdater.updateUid(any(), any(), any())) .willAnswer(inv -> Optional.ofNullable((AuctionContext) inv.getArgument(1)) @@ -3923,7 +3926,7 @@ public void shouldReduceBidsHavingDealIdWithSameImpIdByBidderWithToleratingNotOb Deal.builder().id("dealId2").build())) .build())); final BidRequest bidRequest = givenBidRequest(singletonList(imp), identity()); - final AuctionContext auctionContext = givenRequestContext(bidRequest).toBuilder().build(); + final AuctionContext auctionContext = givenRequestContext(bidRequest); final BidderBid bidderBid = givenBidderBid( Bid.builder().id("bidId2").impid("impId1").dealid("dealId2").price(BigDecimal.ONE).build()); @@ -3958,7 +3961,7 @@ public void shouldReduceBidsHavingDealIdWithSameImpIdByBidderWithToleratingNotOb Deal.builder().id("dealId2").build())) .build())); final BidRequest bidRequest = givenBidRequest(singletonList(imp), identity()); - final AuctionContext auctionContext = givenRequestContext(bidRequest).toBuilder().build(); + final AuctionContext auctionContext = givenRequestContext(bidRequest); givenBidder(givenSeatBid(emptyList())); @@ -3976,75 +3979,52 @@ public void shouldReduceBidsHavingDealIdWithSameImpIdByBidderWithToleratingNotOb } @Test - public void shouldResponseWithEmptySeatBidIfBidderNotSupportProvidedMediaTypes() { + public void shouldResponseWithAddedWarningsFromBidderRequestPostProcessor() { // given final Imp imp = givenImp(singletonMap("bidder1", 1), builder -> builder.id("impId1")); final BidRequest bidRequest = givenBidRequest(singletonList(imp), identity()); - final AuctionContext auctionContext = givenRequestContext(bidRequest).toBuilder().build(); + final AuctionContext auctionContext = givenRequestContext(bidRequest); - given(mediaTypeProcessor.process(any(), anyString(), any(), any())) - .willReturn(MediaTypeProcessingResult.rejected(Collections.singletonList( - BidderError.badInput("MediaTypeProcessor error.")))); - given(bidResponseCreator.create( - argThat(argument -> argument.getAuctionParticipations().getFirst() - .getBidderResponse() - .equals(BidderResponse.of( - "bidder1", - BidderSeatBid.builder() - .warnings(Collections.singletonList( - BidderError.badInput("MediaTypeProcessor error."))) - .build(), - 0))), - any(), - any())) - .willReturn(Future.succeededFuture(BidResponse.builder().id("uniqId").build())); + given(bidderRequestPostProcessor.process(any(), any(), any())) + .willAnswer(invocation -> Future.succeededFuture(Result.of( + invocation.getArgument(0), + singletonList(BidderError.badInput("BidderRequestPostProcessor error."))))); + givenBidder(givenSeatBid(emptyList())); // when - final Future result = target.holdAuction(auctionContext); + target.holdAuction(auctionContext); // then - assertThat(result.result()) - .extracting(AuctionContext::getBidResponse) - .isEqualTo(BidResponse.builder().id("uniqId").build()); + verify(httpBidderRequester).requestBids(any(), any(), any(), any(), any(), any(), anyBoolean()); + + final ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(storedResponseProcessor).updateStoredBidResponse(captor.capture()); + assertThat(captor.getValue()) + .extracting(AuctionParticipation::getBidderResponse) + .extracting(BidderResponse::getSeatBid) + .flatExtracting(BidderSeatBid::getWarnings) + .containsExactly(BidderError.badInput("BidderRequestPostProcessor error.")); } @Test - public void shouldResponseWithEmptySeatBidIfBidderNotSupportRequestCurrency() { + public void shouldResponseWithEmptySeatBidIfRejectedByBidderRequestPostProcessor() { // given final Imp imp = givenImp(singletonMap("bidder1", 1), builder -> builder.id("impId1")); - final BidRequest bidRequest = givenBidRequest(singletonList(imp), - bidRequestBuilder -> bidRequestBuilder.cur(singletonList("USD"))); + final BidRequest bidRequest = givenBidRequest(singletonList(imp), identity()); final AuctionContext auctionContext = givenRequestContext(bidRequest); - given(bidderCatalog.bidderInfoByName(anyString())).willReturn(BidderInfo.create( - true, - null, - false, - null, - null, - null, - null, - null, - null, - null, - 0, - singletonList("CAD"), - false, - false, - CompressionType.NONE, - Ortb.of(false), - 0L)); - + given(bidderRequestPostProcessor.process(any(), any(), any())) + .willReturn(Future.failedFuture(new BidderRequestRejectedException( + BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE, + singletonList(BidderError.badInput("BidderRequestPostProcessor error."))))); given(bidResponseCreator.create( argThat(argument -> argument.getAuctionParticipations().getFirst() .getBidderResponse() .equals(BidderResponse.of( "bidder1", BidderSeatBid.builder() - .warnings(Collections.singletonList( - BidderError.generic( - "No match between the configured currencies and bidRequest.cur" - ))) + .warnings(singletonList( + BidderError.badInput("BidderRequestPostProcessor error."))) .build(), 0))), any(), @@ -4055,14 +4035,17 @@ public void shouldResponseWithEmptySeatBidIfBidderNotSupportRequestCurrency() { final Future result = target.holdAuction(auctionContext); // then + verifyNoInteractions(httpBidderRequester); assertThat(result.result()) .extracting(AuctionContext::getBidResponse) .isEqualTo(BidResponse.builder().id("uniqId").build()); assertThat(result.result()) .extracting(AuctionContext::getBidRejectionTrackers) - .extracting(rejectionTrackers -> rejectionTrackers.get("bidder1")) - .extracting(BidRejectionTracker::getRejected) - .isEqualTo(Set.of(ImpRejection.of("bidder1", "impId1", REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY))); + .extracting(trackers -> trackers.get("bidder1")) + .extracting(BidRejectionTracker::getAllRejected) + .extracting(rejections -> rejections.get("impId1")) + .asInstanceOf(InstanceOfAssertFactories.list(Rejection.class)) + .containsExactly(ImpRejection.of("bidder1", "impId1", REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE)); } @Test @@ -4249,7 +4232,7 @@ private void givenTarget(boolean enabledStrictAppSiteDoohValidation) { fpdResolver, impAdjuster, supplyChainResolver, debugResolver, - mediaTypeProcessor, + bidderRequestPostProcessor, uidUpdater, timeoutResolver, timeoutFactory, diff --git a/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java new file mode 100644 index 00000000000..dec73e28f79 --- /dev/null +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java @@ -0,0 +1,70 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.BidRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.VertxTest; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +public class BidderRequestCleanerTest extends VertxTest { + + private static final String BIDDER = "bidder"; + + private BidderRequestCleaner target; + + @BeforeEach + public void setUp() { + target = new BidderRequestCleaner(); + } + + @Test + public void processShouldReturnSameRequest() { + // given + final BidderRequest bidderRequest = givenBidderRequest(null); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnCleanedRequest() { + // given + final BidderRequest bidderRequest = givenBidderRequest(mapper.createObjectNode()); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getBiddercontrols) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + private static BidderRequest givenBidderRequest(ObjectNode bidderControls) { + return BidderRequest.builder() + .bidRequest(BidRequest.builder() + .ext(ExtRequest.of(ExtRequestPrebid.builder() + .biddercontrols(bidderControls) + .build())) + .build()) + .bidder(BIDDER) + .build(); + } +} diff --git a/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlockerTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlockerTest.java new file mode 100644 index 00000000000..b85616a116a --- /dev/null +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlockerTest.java @@ -0,0 +1,166 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.iab.openrtb.request.BidRequest; +import io.vertx.core.Future; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.auction.versionconverter.OrtbVersion; +import org.prebid.server.bidder.BidderCatalog; +import org.prebid.server.bidder.BidderInfo; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.spring.config.bidder.model.CompressionType; +import org.prebid.server.spring.config.bidder.model.Ortb; + +import java.util.List; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class BidderRequestCurrencyBlockerTest { + + private static final String BIDDER = "bidder"; + + @Mock + private BidderCatalog bidderCatalog; + @Mock + private BidderAliases bidderAliases; + + private BidderRequestCurrencyBlocker target; + + @BeforeEach + public void setUp() { + target = new BidderRequestCurrencyBlocker(bidderCatalog); + when(bidderAliases.resolveBidder(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); + } + + @Test + public void processShouldReturnSameRequestIfAcceptedCurrenciesAreNotConfigured() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(null)); + final BidderRequest bidderRequest = givenBidderRequest(List.of("USD")); + + // when + final Result result = target.process(bidderRequest, bidderAliases, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnSameRequestIfAcceptedCurrenciesAreEmpty() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(emptyList())); + final BidderRequest bidderRequest = givenBidderRequest(List.of("USD")); + + // when + final Result result = target.process(bidderRequest, bidderAliases, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnSameRequestIfRequestCurrencyIsNull() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(List.of("USD"))); + final BidderRequest bidderRequest = givenBidderRequest(null); + + // when + final Result result = target.process(bidderRequest, bidderAliases, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnSameRequestIfRequestCurrencyIsEmpty() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(List.of("USD"))); + final BidderRequest bidderRequest = givenBidderRequest(emptyList()); + + // when + final Result result = target.process(bidderRequest, bidderAliases, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnSameRequestIfRequestCurrencyContainsAcceptedCurrency() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(List.of("EUR", "USD"))); + final BidderRequest bidderRequest = givenBidderRequest(List.of("UAH", "USD")); + + // when + final Result result = target.process(bidderRequest, bidderAliases, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnFailureIfRequestDoesNotContainsAcceptedCurrency() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(List.of("EUR", "USD"))); + final BidderRequest bidderRequest = givenBidderRequest(List.of("UAH")); + + // when + final Future> result = target.process(bidderRequest, bidderAliases, null); + + // then + assertThat(result.failed()).isTrue(); + assertThat(result.cause()) + .asInstanceOf(InstanceOfAssertFactories.type(BidderRequestRejectedException.class)) + .satisfies(e -> { + assertThat(e.getRejectionReason()) + .isEqualTo(BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY); + assertThat(e.getErrors()).containsExactly( + BidderError.generic("No match between the configured currencies and bidRequest.cur")); + }); + } + + private static BidderInfo givenBidderInfo(List currencies) { + return BidderInfo.create( + true, + OrtbVersion.ORTB_2_6, + false, + "endpoint", + null, + "maintainerEmail", + null, + null, + null, + emptyList(), + 0, + currencies, + false, + false, + CompressionType.NONE, + Ortb.of(false), + 0L); + } + + private static BidderRequest givenBidderRequest(List currencies) { + return BidderRequest.builder() + .bidRequest(BidRequest.builder().cur(currencies).build()) + .bidder(BIDDER) + .build(); + } +} diff --git a/src/test/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessorTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilterTest.java similarity index 61% rename from src/test/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessorTest.java rename to src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilterTest.java index d6e002489f8..00133131e29 100644 --- a/src/test/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessorTest.java +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilterTest.java @@ -1,4 +1,4 @@ -package org.prebid.server.auction.mediatypeprocessor; +package org.prebid.server.auction.bidderrequestpostprocessor; import com.iab.openrtb.request.App; import com.iab.openrtb.request.Audio; @@ -8,6 +8,8 @@ import com.iab.openrtb.request.Native; import com.iab.openrtb.request.Site; import com.iab.openrtb.request.Video; +import io.vertx.core.Future; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -15,10 +17,13 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.VertxTest; import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.BidderCatalog; import org.prebid.server.bidder.BidderInfo; import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.Result; import org.prebid.server.spring.config.bidder.model.CompressionType; import org.prebid.server.spring.config.bidder.model.MediaType; import org.prebid.server.spring.config.bidder.model.Ortb; @@ -41,7 +46,7 @@ import static org.prebid.server.spring.config.bidder.model.MediaType.VIDEO; @ExtendWith(MockitoExtension.class) -public class BidderMediaTypeProcessorTest extends VertxTest { +public class BidderRequestMediaFilterTest extends VertxTest { private static final String BIDDER = "bidder"; @@ -50,42 +55,50 @@ public class BidderMediaTypeProcessorTest extends VertxTest { @Mock private BidderAliases bidderAliases; - private BidderMediaTypeProcessor target; + private BidderRequestMediaFilter target; @BeforeEach public void setUp() { - target = new BidderMediaTypeProcessor(bidderCatalog); + target = new BidderRequestMediaFilter(bidderCatalog); when(bidderAliases.resolveBidder(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); } @Test - public void processShouldReturnRejectedResultAndErrorIfBidderNotSupportAnyMediaType() { + public void processShouldReturnRejectedResultAndErrorIfBidderDoesNotSupportAnyMediaType() { // given given(bidderCatalog.bidderInfoByName(BIDDER)) .willReturn(givenBidderInfo(emptyList(), emptyList(), emptyList())); - final BidRequest bidRequest = givenBidRequest(identity(), givenImp(BANNER)); + final BidderRequest bidderRequest = givenBidderRequest(identity(), givenImp(BANNER)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final Future> result = target.process(bidderRequest, bidderAliases, null); // then - assertThat(result.isRejected()).isTrue(); - assertThat(result.getErrors()).containsExactly( - BidderError.badInput("Bidder does not support any media types.")); + assertThat(result.failed()).isTrue(); + assertThat(result.cause()) + .asInstanceOf(InstanceOfAssertFactories.type(BidderRequestRejectedException.class)) + .satisfies(e -> { + assertThat(e.getRejectionReason()) + .isEqualTo(BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE); + assertThat(e.getErrors()).containsExactly( + BidderError.badInput("Bidder does not support any media types.")); + }); } @Test public void processShouldUseAppMediaTypesIfAppPresent() { // given - final BidRequest bidRequest = givenBidRequest(request -> request.app(App.builder().build()), givenImp(BANNER)); given(bidderCatalog.bidderInfoByName(BIDDER)) .willReturn(givenBidderInfo(singletonList(BANNER), singletonList(AUDIO), singletonList(NATIVE))); + final BidderRequest bidderRequest = givenBidderRequest( + request -> request.app(App.builder().build()), + givenImp(BANNER)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final Result result = target.process(bidderRequest, bidderAliases, null).result(); // then - assertThat(result.getBidRequest()).isEqualTo(bidRequest); + assertThat(result.getValue()).isEqualTo(bidderRequest); assertThat(result.getErrors()).isEmpty(); } @@ -94,18 +107,19 @@ public void processShouldRemoveUnsupportedMediaTypes() { // given given(bidderCatalog.bidderInfoByName(BIDDER)) .willReturn(givenBidderInfo(emptyList(), List.of(BANNER, AUDIO), emptyList())); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( request -> request.site(Site.builder().build()), givenImp(AUDIO, NATIVE), givenImp(BANNER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final Result result = target.process(bidderRequest, bidderAliases, null).result(); // then - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .containsExactly(givenImp(AUDIO), givenImp(BANNER)); assertThat(result.getErrors()).isEmpty(); } @@ -115,18 +129,19 @@ public void processShouldRemoveImpWithOnlyUnsupportedMediaTypes() { // given given(bidderCatalog.bidderInfoByName(BIDDER)) .willReturn(givenBidderInfo(emptyList(), asList(BANNER, AUDIO), emptyList())); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( request -> request.site(Site.builder().build()), givenImp(VIDEO, NATIVE), givenImp(BANNER, AUDIO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final Result result = target.process(bidderRequest, bidderAliases, null).result(); // then - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .containsExactly(givenImp(BANNER, AUDIO)); assertThat(result.getErrors()).containsExactly( BidderError.badInput("Imp " + null + " does not have a supported media type " @@ -138,22 +153,28 @@ public void processShouldReturnRejectedResultIfRequestDoesNotContainsAnyImpWithS // given given(bidderCatalog.bidderInfoByName(BIDDER)) .willReturn(givenBidderInfo(emptyList(), asList(BANNER, AUDIO), emptyList())); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( request -> request.site(Site.builder().build()), givenImp(VIDEO), givenImp(NATIVE)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final Future> result = target.process(bidderRequest, bidderAliases, null); // then - assertThat(result.isRejected()).isTrue(); - assertThat(result.getErrors()).containsExactly( - BidderError.badInput("Imp " + null + " does not have a supported media type " - + "and has been removed from the request for this bidder."), - BidderError.badInput("Imp " + null + " does not have a supported media type " - + "and has been removed from the request for this bidder."), - BidderError.badInput("Bid request contains 0 impressions after filtering.")); + assertThat(result.failed()).isTrue(); + assertThat(result.cause()) + .asInstanceOf(InstanceOfAssertFactories.type(BidderRequestRejectedException.class)) + .satisfies(e -> { + assertThat(e.getRejectionReason()) + .isEqualTo(BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE); + assertThat(e.getErrors()).containsExactly( + BidderError.badInput("Imp null does not have a supported media type" + + " and has been removed from the request for this bidder."), + BidderError.badInput("Imp null does not have a supported media type" + + " and has been removed from the request for this bidder."), + BidderError.badInput("Bid request contains 0 impressions after filtering.")); + }); } private static BidderInfo givenBidderInfo(List appMediaTypes, @@ -179,10 +200,13 @@ private static BidderInfo givenBidderInfo(List appMediaTypes, 0L); } - private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer, - Imp... imps) { + private static BidderRequest givenBidderRequest(UnaryOperator bidRequestCustomizer, + Imp... imps) { - return bidRequestCustomizer.apply(BidRequest.builder()).imp(asList(imps)).build(); + return BidderRequest.builder() + .bidRequest(bidRequestCustomizer.apply(BidRequest.builder()).imp(asList(imps)).build()) + .bidder(BIDDER) + .build(); } private static Imp givenImp(MediaType... mediaTypes) { diff --git a/src/test/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessorTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessorTest.java similarity index 65% rename from src/test/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessorTest.java rename to src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessorTest.java index a8da73fe320..99050665bcc 100644 --- a/src/test/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessorTest.java +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessorTest.java @@ -1,4 +1,4 @@ -package org.prebid.server.auction.mediatypeprocessor; +package org.prebid.server.auction.bidderrequestpostprocessor; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.Audio; @@ -7,6 +7,7 @@ import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Native; import com.iab.openrtb.request.Video; +import io.vertx.core.Future; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,10 +16,14 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.VertxTest; import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.BidderCatalog; import org.prebid.server.bidder.BidderInfo; import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.Result; import org.prebid.server.proto.openrtb.ext.request.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; import org.prebid.server.settings.model.Account; @@ -44,7 +49,7 @@ import static org.prebid.server.spring.config.bidder.model.MediaType.VIDEO; @ExtendWith(MockitoExtension.class) -public class MultiFormatMediaTypeProcessorTest extends VertxTest { +public class BidderRequestPreferredMediaProcessorTest extends VertxTest { private static final String BIDDER = "bidder"; @@ -53,11 +58,11 @@ public class MultiFormatMediaTypeProcessorTest extends VertxTest { @Mock private BidderAliases bidderAliases; - private MultiFormatMediaTypeProcessor target; + private BidderRequestPreferredMediaProcessor target; @BeforeEach public void setUp() { - target = new MultiFormatMediaTypeProcessor(bidderCatalog); + target = new BidderRequestPreferredMediaProcessor(bidderCatalog); when(bidderAliases.resolveBidder(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); } @@ -65,14 +70,13 @@ public void setUp() { public void processShouldReturnSameBidRequestIfMultiFormatSupported() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(true)); - final BidRequest bidRequest = givenBidRequest(identity(), givenImp(BANNER, VIDEO)); + final BidderRequest bidderRequest = givenBidderRequest(identity(), givenImp(BANNER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final Result result = target.process(bidderRequest, bidderAliases, null).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()).isEqualTo(bidRequest); + assertThat(result.getValue()).isEqualTo(bidderRequest); assertThat(result.getErrors()).isEmpty(); } @@ -80,15 +84,14 @@ public void processShouldReturnSameBidRequestIfMultiFormatSupported() { public void processShouldReturnSameBidRequestIfPreferredMediaTypeNotSpecified() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(false)); - final BidRequest bidRequest = givenBidRequest(identity(), givenImp(BANNER, VIDEO)); - final Account account = givenAccount(null); + final BidderRequest bidderRequest = givenBidderRequest(identity(), givenImp(BANNER, VIDEO)); + final AuctionContext auctionContext = givenAuctionContext(null); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final Result result = target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()).isEqualTo(bidRequest); + assertThat(result.getValue()).isEqualTo(bidderRequest); assertThat(result.getErrors()).isEmpty(); } @@ -96,15 +99,15 @@ public void processShouldReturnSameBidRequestIfPreferredMediaTypeNotSpecified() public void processShouldReturnImpWithPreferredMediaType() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(false)); - final BidRequest bidRequest = givenBidRequest(identity(), givenImp(BANNER, VIDEO, AUDIO, NATIVE)); - final Account account = givenAccount(Map.of(BIDDER, VIDEO)); + final BidderRequest bidderRequest = givenBidderRequest(identity(), givenImp(BANNER, VIDEO, AUDIO, NATIVE)); + final AuctionContext auctionContext = givenAuctionContext(Map.of(BIDDER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final Result result = target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) .asInstanceOf(InstanceOfAssertFactories.list(Imp.class)) .containsExactly(givenImp(VIDEO)); @@ -120,28 +123,23 @@ public void processShouldUseRequestLevelPreferredMediaTypeFirst() { final ObjectNode bidderControls = mapper.createObjectNode(); bidderControls.putObject(BIDDER).put("prefmtype", "video"); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( request -> request.ext(ExtRequest.of(ExtRequestPrebid.builder() .biddercontrols(bidderControls) .build())), givenImp(BANNER, VIDEO, AUDIO, NATIVE)); - final Account account = givenAccount(Map.of("resolvedBidderName", AUDIO)); + final AuctionContext auctionContext = givenAuctionContext(Map.of("resolvedBidderName", AUDIO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final Result result = target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) .asInstanceOf(InstanceOfAssertFactories.list(Imp.class)) .containsExactly(givenImp(VIDEO)); - assertThat(result.getBidRequest()) - .extracting(BidRequest::getExt) - .extracting(ExtRequest::getPrebid) - .extracting(ExtRequestPrebid::getBiddercontrols) - .isNull(); assertThat(result.getErrors()).isEmpty(); } @@ -154,28 +152,23 @@ public void processShouldUseRequestLevelPreferredMediaTypeFirstCaseInsensitive() final ObjectNode bidderControls = mapper.createObjectNode(); bidderControls.putObject(BIDDER.toUpperCase()).put("prefmtype", "video"); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( request -> request.ext(ExtRequest.of(ExtRequestPrebid.builder() .biddercontrols(bidderControls) .build())), givenImp(BANNER, VIDEO, AUDIO, NATIVE)); - final Account account = givenAccount(Map.of("resolvedBidderName", AUDIO)); + final AuctionContext auctionContext = givenAuctionContext(Map.of("resolvedBidderName", AUDIO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final Result result = target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) .asInstanceOf(InstanceOfAssertFactories.list(Imp.class)) .containsExactly(givenImp(VIDEO)); - assertThat(result.getBidRequest()) - .extracting(BidRequest::getExt) - .extracting(ExtRequest::getPrebid) - .extracting(ExtRequestPrebid::getBiddercontrols) - .isNull(); assertThat(result.getErrors()).isEmpty(); } @@ -183,21 +176,21 @@ public void processShouldUseRequestLevelPreferredMediaTypeFirstCaseInsensitive() public void processShouldSkipImpsWithSingleMediaType() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(false)); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( identity(), givenImp(BANNER), givenImp(VIDEO), givenImp(BANNER, VIDEO, AUDIO, NATIVE), givenImp(AUDIO), givenImp(NATIVE)); - final Account account = givenAccount(Map.of(BIDDER, VIDEO)); + final AuctionContext auctionContext = givenAuctionContext(Map.of(BIDDER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final Result result = target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) .asInstanceOf(InstanceOfAssertFactories.list(Imp.class)) .containsExactly( @@ -213,7 +206,7 @@ public void processShouldSkipImpsWithSingleMediaType() { public void processShouldFilterMultiFormatImpsWithoutPreferredMediaType() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(false)); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( identity(), givenImp(BANNER, AUDIO), givenImp(BANNER, VIDEO), @@ -221,14 +214,14 @@ public void processShouldFilterMultiFormatImpsWithoutPreferredMediaType() { givenImp(VIDEO, AUDIO), givenImp(VIDEO, NATIVE), givenImp(AUDIO, NATIVE)); - final Account account = givenAccount(Map.of(BIDDER, VIDEO)); + final AuctionContext auctionContext = givenAuctionContext(Map.of(BIDDER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final Result result = target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) .asInstanceOf(InstanceOfAssertFactories.list(Imp.class)) .containsExactly( @@ -248,18 +241,24 @@ public void processShouldFilterMultiFormatImpsWithoutPreferredMediaType() { public void processShouldRejectEmptyRequestAfterFiltering() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(false)); - final BidRequest bidRequest = givenBidRequest(identity(), givenImp(BANNER, AUDIO, NATIVE)); - final Account account = givenAccount(Map.of(BIDDER, VIDEO)); + final BidderRequest bidderRequest = givenBidderRequest(identity(), givenImp(BANNER, AUDIO, NATIVE)); + final AuctionContext auctionContext = givenAuctionContext(Map.of(BIDDER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final Future> result = target.process(bidderRequest, bidderAliases, auctionContext); // then - assertThat(result.isRejected()).isTrue(); - assertThat(result.getErrors()).containsExactly( - BidderError.badInput("Imp null does not have a media type after filtering" - + " and has been removed from the request for this bidder."), - BidderError.badInput("Bid request contains 0 impressions after filtering.")); + assertThat(result.failed()).isTrue(); + assertThat(result.cause()) + .asInstanceOf(InstanceOfAssertFactories.type(BidderRequestRejectedException.class)) + .satisfies(e -> { + assertThat(e.getRejectionReason()) + .isEqualTo(BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE); + assertThat(e.getErrors()).containsExactly( + BidderError.badInput("Imp null does not have a media type after filtering" + + " and has been removed from the request for this bidder."), + BidderError.badInput("Bid request contains 0 impressions after filtering.")); + }); } private static BidderInfo givenBidderInfo(boolean multiFormatSupported) { @@ -283,10 +282,13 @@ private static BidderInfo givenBidderInfo(boolean multiFormatSupported) { 0L); } - private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer, - Imp... imps) { + private static BidderRequest givenBidderRequest(UnaryOperator bidRequestCustomizer, + Imp... imps) { - return bidRequestCustomizer.apply(BidRequest.builder()).imp(asList(imps)).build(); + return BidderRequest.builder() + .bidder(BIDDER) + .bidRequest(bidRequestCustomizer.apply(BidRequest.builder()).imp(asList(imps)).build()) + .build(); } private static Imp givenImp(MediaType... mediaTypes) { @@ -309,10 +311,12 @@ private static Imp givenImp(MediaType... mediaTypes) { return impBuilder.build(); } - private static Account givenAccount(Map bidderToPreferredMediaType) { - return Account.builder() - .auction(AccountAuctionConfig.builder() - .preferredMediaTypes(bidderToPreferredMediaType) + private static AuctionContext givenAuctionContext(Map bidderToPreferredMediaType) { + return AuctionContext.builder() + .account(Account.builder() + .auction(AccountAuctionConfig.builder() + .preferredMediaTypes(bidderToPreferredMediaType) + .build()) .build()) .build(); } diff --git a/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessorTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessorTest.java new file mode 100644 index 00000000000..885025245db --- /dev/null +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessorTest.java @@ -0,0 +1,89 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import io.vertx.core.Future; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.Result; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CompositeBidderRequestPostProcessorTest { + + @Mock + private BidderRequestPostProcessor bidderRequestPostProcessor1; + + @Mock + private BidderRequestPostProcessor bidderRequestPostProcessor2; + + @Mock(strictness = LENIENT) + private BidderAliases bidderAliases; + + private CompositeBidderRequestPostProcessor target; + + @BeforeEach + public void setUp() { + target = new CompositeBidderRequestPostProcessor(asList( + bidderRequestPostProcessor1, bidderRequestPostProcessor2)); + when(bidderAliases.resolveBidder(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); + } + + @Test + public void processShouldReturnExpectedResult() { + // given + given(bidderRequestPostProcessor1.process(any(), any(), any())) + .willReturn(Future.succeededFuture(Result.of( + BidderRequest.builder().bidder("processed by bidderRequestPostProcessor1").build(), + singletonList(BidderError.badInput("Error from bidderRequestPostProcessor1"))))); + + given(bidderRequestPostProcessor2.process( + argThat(request -> "processed by bidderRequestPostProcessor1".equals(request.getBidder())), + any(), + any())) + .willReturn(Future.succeededFuture(Result.of( + BidderRequest.builder().bidder("processed by bidderRequestPostProcessor2").build(), + singletonList(BidderError.badInput("Error from bidderRequestPostProcessor2"))))); + + // when + final Future> result = target.process(null, bidderAliases, null); + + // then + assertThat(result.succeeded()).isTrue(); + assertThat(result.result().getValue()) + .isEqualTo(BidderRequest.builder().bidder("processed by bidderRequestPostProcessor2").build()); + assertThat(result.result().getErrors()) + .containsExactly( + BidderError.badInput("Error from bidderRequestPostProcessor1"), + BidderError.badInput("Error from bidderRequestPostProcessor2")); + } + + @Test + public void processShouldReturnExpectedResultIfSomeOfProcessorsFails() { + // given + given(bidderRequestPostProcessor1.process(any(), any(), any())) + .willReturn(Future.failedFuture("Error from bidderRequestPostProcessor1")); + + // when + final Future> result = target.process(null, bidderAliases, null); + + // then + assertThat(result.failed()).isTrue(); + assertThat(result.cause().getMessage()).isEqualTo("Error from bidderRequestPostProcessor1"); + verifyNoInteractions(bidderRequestPostProcessor2); + } +} diff --git a/src/test/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessorTest.java b/src/test/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessorTest.java deleted file mode 100644 index 47c40634788..00000000000 --- a/src/test/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessorTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.iab.openrtb.request.BidRequest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.prebid.server.auction.aliases.BidderAliases; -import org.prebid.server.bidder.model.BidderError; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mock.Strictness.LENIENT; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class CompositeMediaTypeProcessorTest { - - @Mock - private MediaTypeProcessor mediaTypeProcessor1; - - @Mock - private MediaTypeProcessor mediaTypeProcessor2; - - @Mock(strictness = LENIENT) - private BidderAliases bidderAliases; - - private CompositeMediaTypeProcessor target; - - @BeforeEach - public void setUp() { - target = new CompositeMediaTypeProcessor(asList(mediaTypeProcessor1, mediaTypeProcessor2)); - when(bidderAliases.resolveBidder(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); - } - - @Test - public void processShouldReturnExpectedResult() { - // given - given(mediaTypeProcessor1.process(any(), anyString(), any(), any())) - .willReturn(MediaTypeProcessingResult.succeeded( - BidRequest.builder().id("processed by mediaTypeProcessor1").build(), - singletonList(BidderError.badInput("Error from mediaTypeProcessor1")))); - - given(mediaTypeProcessor2.process( - argThat(request -> "processed by mediaTypeProcessor1".equals(request.getId())), - anyString(), - any(), - any())) - .willReturn(MediaTypeProcessingResult.succeeded( - BidRequest.builder().id("processed by mediaTypeProcessor2").build(), - singletonList(BidderError.badInput("Error from mediaTypeProcessor2")))); - - // when - final MediaTypeProcessingResult result = target.process( - BidRequest.builder().build(), - "bidder", - bidderAliases, - null); - - // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) - .isEqualTo(BidRequest.builder().id("processed by mediaTypeProcessor2").build()); - assertThat(result.getErrors()) - .containsExactly( - BidderError.badInput("Error from mediaTypeProcessor1"), - BidderError.badInput("Error from mediaTypeProcessor2")); - } - - @Test - public void processShouldReturnExpectedResultIfRejectedBySomeOfProcessors() { - // given - given(mediaTypeProcessor1.process(any(), anyString(), any(), any())) - .willReturn(MediaTypeProcessingResult.rejected( - singletonList(BidderError.badInput("Error from mediaTypeProcessor1")))); - - // when - final MediaTypeProcessingResult result = target.process( - BidRequest.builder().build(), - "bidder", - bidderAliases, - null); - - // then - assertThat(result.isRejected()).isTrue(); - assertThat(result.getErrors()).containsExactly(BidderError.badInput("Error from mediaTypeProcessor1")); - verifyNoInteractions(mediaTypeProcessor2); - } -} From c32bcc2e71008435a3a8b36f81d0a18f5d494e0b Mon Sep 17 00:00:00 2001 From: markiian Date: Mon, 22 Dec 2025 12:12:22 +0200 Subject: [PATCH 04/13] Update: Add coverage for bidder removal fields --- .../model/request/auction/AppPrebid.groovy | 2 + .../model/request/auction/MultiBid.groovy | 2 + .../model/request/auction/Prebid.groovy | 1 + .../functional/tests/AuctionSpec.groovy | 3 + .../functional/tests/BidAdjustmentSpec.groovy | 4 + .../BidderFieldDisplayBehaviorSpec.groovy | 144 +++++++++--------- .../tests/BidderInsensitiveCaseSpec.groovy | 19 --- .../functional/tests/BidderParamsSpec.groovy | 5 +- .../tests/FilterMultiFormatSpec.groovy | 12 ++ .../functional/tests/MultibidSpec.groovy | 7 + .../server/functional/tests/NativeSpec.groovy | 1 + .../server/functional/tests/SchainSpec.groovy | 3 + .../functional/tests/TargetingSpec.groovy | 9 ++ .../GppTransmitTidActivitiesSpec.groovy | 4 +- 14 files changed, 120 insertions(+), 96 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/AppPrebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/AppPrebid.groovy index edb365d4d6f..f201aad5da4 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/AppPrebid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/AppPrebid.groovy @@ -1,8 +1,10 @@ package org.prebid.server.functional.model.request.auction +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) +@EqualsAndHashCode class AppPrebid { String source diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/MultiBid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/MultiBid.groovy index 5349fd495ad..83cafd74d45 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/MultiBid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/MultiBid.groovy @@ -2,11 +2,13 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.model.bidder.BidderName @ToString(includeNames = true, ignoreNulls = false) @JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) +@EqualsAndHashCode class MultiBid { BidderName bidder diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy index 6cfccb0533a..a815cc36d16 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy @@ -53,4 +53,5 @@ class Prebid { @JsonProperty("kvps") Map keyValuePairs Boolean supportDeals + String integration } diff --git a/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy index aa658800c22..a15ed3ab644 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy @@ -139,6 +139,9 @@ class AuctionSpec extends BaseSpec { then: "Bidder request should contain buyeruid from the user.ext.prebid.buyeruids" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest?.user?.buyeruid == buyeruid + + and: "Bidder request shouldn't contain user.ext.prebid.buyeruids" + assert !bidderRequest.user.ext.prebid.buyeruids } def "PBS shouldn't populate bidder request buyeruid from buyeruids when buyeruids without appropriate bidder present in request"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy index 7da1c89fffb..0090b10020a 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -96,6 +96,10 @@ class BidAdjustmentSpec extends BaseSpec { assert response?.seatbid?.first?.bid?.first?.price == bidResponse.seatbid.first.bid.first.price * bidAdjustmentFactor + and: "Bidder request shouldn't contain bid adjustment factors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.bidAdjustmentFactors + where: bidAdjustmentFactor << [0.9, 1.1] } diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy index f121183a29a..2932d4558ce 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy @@ -3,18 +3,18 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.bidder.Generic import org.prebid.server.functional.model.db.StoredRequest -import org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse import org.prebid.server.functional.model.request.Channel import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.AdServerTargeting import org.prebid.server.functional.model.request.auction.Amp +import org.prebid.server.functional.model.request.auction.AppExt +import org.prebid.server.functional.model.request.auction.AppPrebid import org.prebid.server.functional.model.request.auction.BidAdjustmentFactors import org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.BidderConfig import org.prebid.server.functional.model.request.auction.BidderConfigOrtb import org.prebid.server.functional.model.request.auction.ConsentedProvidersSettings -import org.prebid.server.functional.model.request.auction.DebugCondition import org.prebid.server.functional.model.request.auction.Device import org.prebid.server.functional.model.request.auction.DeviceExt import org.prebid.server.functional.model.request.auction.EidPermission @@ -33,7 +33,6 @@ import org.prebid.server.functional.model.request.auction.Sdk import org.prebid.server.functional.model.request.auction.Site import org.prebid.server.functional.model.request.auction.SiteExt import org.prebid.server.functional.model.request.auction.SiteExtData -import org.prebid.server.functional.model.request.auction.TraceLevel import org.prebid.server.functional.model.request.auction.User import org.prebid.server.functional.model.request.auction.UserExt import org.prebid.server.functional.model.request.auction.UserExtPrebid @@ -43,15 +42,15 @@ import org.prebid.server.functional.util.PBSUtils import spock.lang.IgnoreRest import static org.prebid.server.functional.model.ChannelType.WEB -import static org.prebid.server.functional.model.bidder.BidderName.ALIAS -import static org.prebid.server.functional.model.bidder.BidderName.GENERIC -import static org.prebid.server.functional.model.bidder.BidderName.UNKNOWN + +import static org.prebid.server.functional.model.bidder.BidderName.RUBICON import static org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse.defaultConversionRates import static org.prebid.server.functional.model.request.auction.DebugCondition.ENABLED -import static org.prebid.server.functional.model.request.auction.DeviceExt.Atts.* +import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP import static org.prebid.server.functional.model.request.auction.PaaFormat.ORIGINAL import static org.prebid.server.functional.model.request.auction.TraceLevel.BASIC import static org.prebid.server.functional.model.response.auction.ErrorType.ALIAS +import static org.prebid.server.functional.model.response.auction.ErrorType.GENERIC class BidderFieldDisplayBehaviorSpec extends BaseSpec { @@ -59,7 +58,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { // Never needed inside the adapter code // Exposes nothing because it’s a boolean - def "PBS shouldn't send returnallbidstatus to bidder request"() { + def "PBS shouldn't pass ext.prebid.returnAllBidStatus to bidder request"() { given: "Default basic bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.returnAllBidStatus = true @@ -80,8 +79,8 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { //todo: ext.prebid.aliasgvlids (map) // Never needed inside the adapter code - @IgnoreRest - def "PBS shouldn't pass aliasgvlids and aliases to bidder request"() { + + def "PBS shouldn't pass aliasGvlIds to bidder request when aliasGvlIds specified"() { given: "Default basic bid request" def bidRequest = BidRequest.defaultBidRequest.tap { imp[0].ext.prebid.bidder.alias = new Generic() @@ -109,29 +108,6 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { // The value is taken from request/account // The rules of other bidders if any are exposed. - def "PBS shouldn't send bid adjustment media type to bidder request"() { - given: "Default basic bid request" - def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.tap { - bidAdjustmentFactors = new BidAdjustmentFactors().tap { - it.adjustments = [(BidderName.GENERIC): BigDecimal.ONE] - it.mediaTypes = [(BidAdjustmentMediaType.BANNER): [(BidderName.GENERIC): BigDecimal.ONE]] - } - } - } - - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request shouldn't contain bid adjustment factors" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.ext.prebid.bidAdjustmentFactors - } - //todo: ext.prebid.adservertargeting (object) // Never needed inside the adapter code @@ -187,7 +163,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { // Never needed inside the adapter code // Not sure it exposes something because it’s only about ttlseconds integers and some booleans - @IgnoreRest + def "PBS shouldn't pass ext.prebid.cache to bidder request"() { given: "Default basic bid request" def bidRequest = BidRequest.defaultBidRequest.tap { @@ -231,7 +207,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { //todo: ext.prebid.currency.rates (object) - @IgnoreRest + def "PBS should pass ext.prebid.currency.rates to bidder request"() { given: "Default basic bid request" def bidRequest = BidRequest.defaultBidRequest.tap { @@ -252,14 +228,13 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert bidderRequest.ext.prebid.currency == bidRequest.ext.prebid.currency } - //todo: ext.prebid.data.bidder (object) + //todo: ext.prebid.data.{bidder,eidpermissions} (object) - @IgnoreRest def "PBS shouldn't pass ext.prebid.data.{bidders,eidpermissions} to bidder request"() { given: "Default basic bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.data = new ExtRequestPrebidData(bidders: [GENERIC.value], - eidpermissions: [new EidPermission(source: PBSUtils.randomString, bidders: [GENERIC])]) + eidpermissions: [new EidPermission(source: PBSUtils.randomString, bidders: [BidderName.GENERIC])]) } and: "Default bid response" @@ -275,12 +250,13 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest?.ext?.prebid?.data?.eidpermissions } - @IgnoreRest - def "PBS shouldn't pass ext.prebid.data.{trace,debug} to bidder request"() { + + def "PBS should pass ext.prebid.{trace,debug,integration} to bidder request"() { given: "Default basic bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.trace = BASIC ext.prebid.debug = ENABLED + ext.prebid.integration = PBSUtils.randomString } and: "Default bid response" @@ -294,6 +270,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.ext.prebid.trace assert bidderRequest.ext.prebid.debug + assert bidderRequest.ext.prebid.integration } //todo: ext.prebid.events (object) @@ -478,27 +455,6 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest.ext.prebid.paaFormat } - def "PBS should pass srid to the bidder request when srid present"() { - given: "Default basic BidRequest with generic bidder" - def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.storedRequest = new PrebidStoredRequest(id: PBSUtils.randomNumber) - } - - and: "Default stored request" - def storedRequestModel = BidRequest.defaultStoredRequest - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(bidRequest.ext.prebid.storedRequest.id, storedRequestModel) - storedRequestDao.save(storedRequest) - - when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request sho ext.prebid" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.ext.prebid.storedRequest.id - } - //todo: user.ext // The whole object is exposed except for the prebid field // consent, eids, data fields are needed for some bidders @@ -509,7 +465,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { user = new User(ext: new UserExt().tap { fcapids = [PBSUtils.randomString] time = new UserTime(userdow: PBSUtils.randomNumber, userhour: PBSUtils.randomNumber) - prebid = new UserExtPrebid(buyeruids: [(GENERIC): PBSUtils.randomString]) + prebid = new UserExtPrebid(buyeruids: [(BidderName.GENERIC): PBSUtils.randomString]) consentedProvidersSettings = new ConsentedProvidersSettings(consentedProviders: PBSUtils.randomString) }) } @@ -517,9 +473,9 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) - then: "Bidder request shouldn't contain user.ext.prebid" + then: "Bidder request shouldn't contain user.ext.prebid.buyeruids" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.user.ext.prebid + assert !bidderRequest.user.ext.prebid.buyeruids and: "Bidder request should contain fcapid,time,consentedProvidedSettings" assert bidderRequest.user.ext.fcapids == bidRequest.user.ext.fcapids @@ -549,13 +505,13 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { //todo :device.ext.prebid // The whole object is exposed - @IgnoreRest - def "PBS should pass device.ext.prebid"() { + + def "PBS should pass device.ext.prebid.interstitial to bidder request when interstitial specified"() { given: "Default basic bid request with generic bidder" def bidRequest = BidRequest.defaultBidRequest.tap { device = new Device( ext: new DeviceExt( - atts: UNKNOWN, + atts: DeviceExt.Atts.UNKNOWN, cdep: PBSUtils.randomString, prebid: new DevicePrebid(interstitial: new Interstitial( minHeightPercentage: PBSUtils.getRandomNumber(0, 100), @@ -573,17 +529,13 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { //todo: ext.prebid.adservertargeting (object) // Never needed inside the adapter code - @IgnoreRest + def "PBS should pass ext.prebid.auctiontimestamp to bidder request"() { given: "Default basic bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.auctionTimestamp = PBSUtils.randomNumber } - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) @@ -595,11 +547,11 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { //todo: ext.prebid.adservertargeting (object) // Never needed inside the adapter code - @IgnoreRest + def "PBS shouldn't pass ext.prebid.bidderConfig to bidder request"() { given: "Default basic bid request" def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.bidderConfig = [new ExtPrebidBidderConfig(bidders: [GENERIC], config: + ext.prebid.bidderConfig = [new ExtPrebidBidderConfig(bidders: [BidderName.GENERIC], config: new BidderConfig(ortb2: new BidderConfigOrtb(site: Site.configFPDSite, user: User.configFPDUser)))] } @@ -614,4 +566,48 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert !bidderRequest.ext.prebid.bidderConfig } + + def "PBS shouldn't pass bidder param to the bidder when bidder param bidder not requested"() { + given: "Default bid request with populated ext.prebid.bidderParams" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.bidderParams = [(RUBICON.value): PBSUtils.randomString] + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response shouldn't contain error" + assert !response.ext?.errors + + and: "Response shouldn't contain warning" + assert !response.ext?.warnings + + and: "Bidder request shouldn't contain bidder param" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.ext.prebid.bidderParams + } + + + def "PBS should pass bidder app.ext.prebid.source to the bidder whe bidder param bidder not requested"() { + def bidRequest = BidRequest.getDefaultBidRequest(APP).tap { + app.ext = new AppExt(prebid: new AppPrebid(source: PBSUtils.getRandomString(), version: PBSUtils.randomString)) + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response shouldn't contain error" + assert !response.ext?.errors + + and: "Response shouldn't contain warning" + assert !response.ext?.warnings + + and: "Bidder request should contain bidder param" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.app.ext.prebid.source == bidRequest.app.ext.prebid.source + } + //todo : WE DON'T have such field ext.prebid.experimentadscert.enabled + // Enabled Ads.Cert 2.0 Authenticated Connections on supported outgoing bidder requests. + // boolean no + } diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderInsensitiveCaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderInsensitiveCaseSpec.groovy index f2fb2803f1f..ef74c3a9a06 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderInsensitiveCaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderInsensitiveCaseSpec.groovy @@ -201,25 +201,6 @@ class BidderInsensitiveCaseSpec extends BaseSpec { assert response.seatbid?.first()?.bid?.last()?.ext?.prebid?.targeting } - def "PBS should populate bidder request buyeruid from buyeruids when buyeruids with appropriate bidder present in request"() { - given: "Bid request with buyeruids" - def buyeruid = PBSUtils.randomString - def bidRequest = BidRequest.defaultBidRequest.tap { - imp[0].ext.prebid.bidder.tap { - genericCamelCase = new Generic() - generic = null - } - user = new User(ext: new UserExt(prebid: new UserExtPrebid(buyeruids: [(GENERIC_CAMEL_CASE): buyeruid]))) - } - - when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request should contain buyeruid from the user.ext.prebid.buyeruids" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest?.user?.buyeruid == buyeruid - } - def "PBS should be able to match requested bidder with original bidder name in ext.prebid.aliase"() { given: "Default bid request with alias" def bidRequest = BidRequest.defaultBidRequest.tap { diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy index 72ea7c5f32c..55485b1fe76 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy @@ -1445,8 +1445,9 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain repose millis with corresponding bidder" assert response.ext.responsetimemillis.containsKey(ALIAS.value) - and: "Bidder request should be valid" - assert bidder.getBidderRequests(bidRequest.id) + and: "Bidder request should be valid and not contain aliases" + def bidderRequests = bidder.getBidderRequests(bidRequest.id) + assert !bidderRequests.ext.prebid.aliases } def "PBS should populate same code for adapter code when make call for generic hard code alias"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/FilterMultiFormatSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/FilterMultiFormatSpec.groovy index 1d36b8beadc..1ab45de4bec 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/FilterMultiFormatSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/FilterMultiFormatSpec.groovy @@ -65,6 +65,9 @@ class FilterMultiFormatSpec extends BaseSpec { assert bidderRequest.imp[0].banner assert bidderRequest.imp[0].audio + and: "Bidder request shouldn't contain biddercontrol" + assert !bidderRequest.ext.prebid.bidderControls + where: bidderControls << [ new BidderControls(generic: new GenericPreferredBidder(preferredMediaType: BANNER)), @@ -117,6 +120,9 @@ class FilterMultiFormatSpec extends BaseSpec { assert bidderRequest.imp[0].banner assert !bidderRequest.imp[0].audio + and: "Bidder request shouldn't contain biddercontrol" + assert !bidderRequest.ext.prebid.bidderControls + where: bidderControls << [ new BidderControls(generic: new GenericPreferredBidder(preferredMediaType: BANNER)), @@ -144,6 +150,9 @@ class FilterMultiFormatSpec extends BaseSpec { assert bidderRequest.imp.banner assert bidderRequest.imp.audio + and: "Bidder request shouldn't contain biddercontrol" + assert !bidderRequest.ext.prebid.bidderControls + where: bidderControls << [ new BidderControls(generic: new GenericPreferredBidder(preferredMediaType: BANNER)), @@ -221,6 +230,9 @@ class FilterMultiFormatSpec extends BaseSpec { assert bidderRequest.imp[0].banner assert !bidderRequest.imp[0].audio + and: "Bidder request shouldn't contain biddercontrol" + assert !bidderRequest.ext.prebid.bidderControls + where: bidderControls << [ new BidderControls(generic: new GenericPreferredBidder(preferredMediaType: BANNER)), diff --git a/src/test/groovy/org/prebid/server/functional/tests/MultibidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/MultibidSpec.groovy index 233c863cbf6..405d473a8c6 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/MultibidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/MultibidSpec.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.tests +import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.db.Account @@ -9,6 +10,7 @@ import org.prebid.server.functional.model.request.auction.Targeting import org.prebid.server.functional.model.response.auction.Bid import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils +import spock.lang.IgnoreRest import static org.prebid.server.functional.model.bidder.BidderName.GENERIC @@ -41,6 +43,7 @@ class MultibidSpec extends BaseSpec { assert !response.seatbid?.first()?.bid?.last()?.ext?.prebid?.targeting } + @IgnoreRest def "PBS should return seatbid[].bid[].ext.prebid.targeting for non-winning bid in multi-bid response when includeBidderKeys = true"() { given: "Default basic BidRequest with generic bidder with includeBidderKeys = true" def bidRequest = BidRequest.defaultBidRequest @@ -66,6 +69,10 @@ class MultibidSpec extends BaseSpec { then: "PBS should return targeting for non-winning bid" assert response.seatbid?.first()?.bid?.last()?.ext?.prebid?.targeting + + and: "Bidder request should contain multibid" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.multibid == [multiBid] } def "PBS should prefer bidRequest over account level config"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/NativeSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/NativeSpec.groovy index 7456930cc53..b81afc30ec7 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/NativeSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/NativeSpec.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.tests +import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.db.StoredResponse import org.prebid.server.functional.model.request.auction.Asset import org.prebid.server.functional.model.request.auction.AssetImage diff --git a/src/test/groovy/org/prebid/server/functional/tests/SchainSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SchainSpec.groovy index db67f6ce397..f283c5f0e9b 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SchainSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SchainSpec.groovy @@ -41,6 +41,9 @@ class SchainSpec extends BaseSpec { then: "Configured schain node should be appended to the end of the node array" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.source?.schain?.nodes == supplyChain.nodes + GLOBAL_SUPPLY_SCHAIN_NODE + + and: "Bidder request should contain schain as requested" + assert !bidderRequest.ext.prebid.schains } def "PBS should copy ext.schain to source.ext.schain when source.ext.schain doesn't exist"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index d9d337b3c27..b464b58c86f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -2,6 +2,7 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.bidder.Generic import org.prebid.server.functional.model.bidder.Openx +import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountRankingConfig @@ -382,6 +383,10 @@ class TargetingSpec extends BaseSpec { then: "Amp response shouldn't contain custom targeting" assert !response.targeting[customKey] + and: "Bidder request shouldn't contain ext.prebid.adservertargeting" + def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + assert !bidderRequest.ext.prebid.targeting + where: customSource | customValue "bidrequest" | "imp" @@ -450,6 +455,10 @@ class TargetingSpec extends BaseSpec { .every(map -> map.keySet() .every(key -> key.length() <= targetingLength))) + and: "Bidder request shouldn't contain ext.prebid.adservertargeting" + def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + assert !bidderRequest.ext.prebid.targeting + cleanup: "Stop and remove pbs container" pbsServiceFactory.removeContainer(pbsConfig) } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitTidActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitTidActivitiesSpec.groovy index df0d0ab531f..c3f359d2113 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitTidActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitTidActivitiesSpec.groovy @@ -87,12 +87,14 @@ class GppTransmitTidActivitiesSpec extends PrivacyBaseSpec { then: "Bidder request should generate (source/imp.ext).tid" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - verifyAll { bidderRequest.imp[0].ext.tid bidderRequest.source.tid } + and: "Bidder request should contain ext.prebid.creativeTids" + assert bidderRequest.ext.prebid.createTids + where: "Activities fields name in different case" activities << [AllowActivities.getDefaultAllowActivities(TRANSMIT_TID, Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, false)])), new AllowActivities().tap { transmitTidKebabCase = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, false)]) }, From e73a6b078638a5a9b0e9177b47e2ea720585478e Mon Sep 17 00:00:00 2001 From: Danylo Date: Tue, 23 Dec 2025 20:36:13 +0100 Subject: [PATCH 05/13] Remove not expected fields from bidder request --- .../BidderRequestCleaner.java | 174 +++++++++++++++++- 1 file changed, 166 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java index 9028d337014..7a705975176 100644 --- a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java @@ -1,16 +1,28 @@ package org.prebid.server.auction.bidderrequestpostprocessor; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import io.vertx.core.Future; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import org.prebid.server.auction.aliases.BidderAliases; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.bidder.model.Result; +import org.prebid.server.model.UpdateResult; import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestBidAdjustmentFactors; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidAlternateBidderCodes; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidAlternateBidderCodesBidder; +import org.prebid.server.proto.openrtb.ext.request.ImpMediaType; +import java.math.BigDecimal; import java.util.Collections; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.Map; public class BidderRequestCleaner implements BidderRequestPostProcessor { @@ -20,21 +32,167 @@ public Future> process(BidderRequest bidderRequest, AuctionContext auctionContext) { final BidRequest bidRequest = bidderRequest.getBidRequest(); - final ExtRequest ext = bidRequest.getExt(); + final UpdateResult cleanedExt = cleanExt(bidRequest.getExt(), bidderRequest.getBidder()); + + final BidderRequest cleanedBidderRequest = cleanedExt.isUpdated() + ? bidderRequest.with(bidRequest.toBuilder().ext(cleanedExt.getValue()).build()) + : bidderRequest; + + return Future.succeededFuture(Result.of(cleanedBidderRequest, Collections.emptyList())); + } + + private UpdateResult cleanExt(ExtRequest ext, String bidder) { final ExtRequestPrebid extPrebid = ext != null ? ext.getPrebid() : null; - final ObjectNode bidderControls = extPrebid != null ? extPrebid.getBiddercontrols() : null; + if (extPrebid == null) { + return UpdateResult.unaltered(ext); + } + + final UpdateResult cleanedBidAdjustmentFactors = + cleanBidAdjustmentFactors(extPrebid.getBidadjustmentfactors(), bidder); + final UpdateResult cleanedBidAdjustments = + cleanBidAdjustments(extPrebid.getBidadjustments(), bidder); + final UpdateResult cleanedAlternateCodes = + cleanAlternateCodes(extPrebid.getAlternateBidderCodes(), bidder); - if (bidderControls == null) { - return resultOf(bidderRequest); + if (!cleanedBidAdjustmentFactors.isUpdated() + && !cleanedBidAdjustments.isUpdated() + && !cleanedAlternateCodes.isUpdated() + && ObjectUtils.allNull( + extPrebid.getReturnallbidstatus(), + extPrebid.getAliasgvlids(), + extPrebid.getAdservertargeting(), + extPrebid.getCache(), + extPrebid.getEvents(), + extPrebid.getNosale(), + extPrebid.getBiddercontrols(), + extPrebid.getAnalytics(), + extPrebid.getPassthrough(), + extPrebid.getKvps())) { + + return UpdateResult.unaltered(ext); } - final ExtRequest cleanedExt = ExtRequest.of(extPrebid.toBuilder().biddercontrols(null).build()); + final ExtRequest cleanedExt = ExtRequest.of(extPrebid.toBuilder() + .returnallbidstatus(null) + .aliasgvlids(null) + .bidadjustmentfactors(cleanedBidAdjustmentFactors.getValue()) + .bidadjustments(cleanedBidAdjustments.getValue()) + .adservertargeting(null) + .cache(null) + .events(null) + .nosale(null) + .biddercontrols(null) + .analytics(null) + .passthrough(null) + .kvps(null) + .alternateBidderCodes(cleanedAlternateCodes.getValue()) + .build()); cleanedExt.addProperties(ext.getProperties()); - return resultOf(bidderRequest.with(bidRequest.toBuilder().ext(cleanedExt).build())); + return UpdateResult.updated(cleanedExt); } - private static Future> resultOf(BidderRequest bidderRequest) { - return Future.succeededFuture(Result.of(bidderRequest, Collections.emptyList())); + private static UpdateResult cleanBidAdjustmentFactors( + ExtRequestBidAdjustmentFactors bidAdjustmentFactors, + String bidder) { + + if (bidAdjustmentFactors == null) { + return UpdateResult.unaltered(null); + } + + final Map cleanedAdjustments = + cleanBidderMap(bidAdjustmentFactors.getAdjustments(), bidder); + final EnumMap> cleanedMediaTypes = + cleanMediaTypes(bidAdjustmentFactors.getMediatypes(), bidder); + + if (cleanedAdjustments == null && cleanedMediaTypes == null) { + return UpdateResult.updated(null); + } + + final ExtRequestBidAdjustmentFactors cleanedBidAdjustmentFactors = ExtRequestBidAdjustmentFactors.builder() + .mediatypes(cleanedMediaTypes) + .build(); + if (cleanedAdjustments != null) { + cleanedAdjustments.forEach(cleanedBidAdjustmentFactors::addFactor); + } + + return UpdateResult.updated(cleanedBidAdjustmentFactors); + } + + private static Map cleanBidderMap(Map map, String bidder) { + if (map == null) { + return null; + } + + for (Map.Entry entry : map.entrySet()) { + if (StringUtils.equalsIgnoreCase(entry.getKey(), bidder)) { + return Collections.singletonMap(entry.getKey(), entry.getValue()); + } + } + + return null; + } + + private static EnumMap> cleanMediaTypes( + EnumMap> mediaTypes, + String bidder) { + + if (mediaTypes == null) { + return null; + } + + final EnumMap> cleanedMediaTypes = new EnumMap<>(ImpMediaType.class); + for (Map.Entry> entry : mediaTypes.entrySet()) { + final Map cleanedMap = cleanBidderMap(entry.getValue(), bidder); + if (cleanedMap != null) { + cleanedMediaTypes.put(entry.getKey(), cleanedMap); + } + } + + return !cleanedMediaTypes.isEmpty() ? cleanedMediaTypes : null; + } + + private static UpdateResult cleanBidAdjustments(ObjectNode bidAdjustments, String bidder) { + if (bidAdjustments == null) { + return UpdateResult.unaltered(null); + } + + final ObjectNode cleanedBidAdjustments = bidAdjustments.deepCopy(); + for (Iterator maps = cleanedBidAdjustments.elements(); maps.hasNext(); ) { + final JsonNode bidderMap = maps.next(); + if (!bidderMap.isObject()) { + continue; + } + + for (Iterator bidders = bidderMap.fieldNames(); bidders.hasNext(); ) { + if (!StringUtils.equalsIgnoreCase(bidders.next(), bidder)) { + bidders.remove(); + } + } + + if (bidderMap.isEmpty()) { + maps.remove(); + } + } + + return !cleanedBidAdjustments.isEmpty() + ? UpdateResult.updated(cleanedBidAdjustments) + : UpdateResult.updated(null); + } + + private static UpdateResult cleanAlternateCodes( + ExtRequestPrebidAlternateBidderCodes alternateBidderCodes, + String bidder) { + + final Map bidders = alternateBidderCodes != null + ? alternateBidderCodes.getBidders() + : null; + if (bidders == null) { + return UpdateResult.unaltered(alternateBidderCodes); + } + + return UpdateResult.updated(ExtRequestPrebidAlternateBidderCodes.of( + alternateBidderCodes.getEnabled(), + cleanBidderMap(bidders, bidder))); } } From 34d44336094504f76e55247b2e9bacb0c5826b35 Mon Sep 17 00:00:00 2001 From: markiian Date: Wed, 31 Dec 2025 13:04:43 +0200 Subject: [PATCH 06/13] Add more coverage for bidder removal fields --- .../request/auction/AdjustmentRule.groovy | 2 + .../model/request/auction/AppExt.groovy | 2 + .../model/request/auction/AppExtData.groovy | 2 + .../request/auction/BidAdjustment.groovy | 2 + .../auction/BidAdjustmentFactors.groovy | 2 + .../request/auction/BidAdjustmentRule.groovy | 2 + .../model/request/auction/Prebid.groovy | 2 +- .../functional/tests/BidAdjustmentSpec.groovy | 11 +- .../BidderFieldDisplayBehaviorSpec.groovy | 297 +++++++----------- .../functional/tests/BidderParamsSpec.groovy | 28 +- .../functional/tests/MultibidSpec.groovy | 7 +- .../functional/tests/ProfileSpec.groovy | 3 - .../server/functional/tests/SchainSpec.groovy | 2 +- .../functional/tests/TargetingSpec.groovy | 4 +- 14 files changed, 148 insertions(+), 218 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/AdjustmentRule.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/AdjustmentRule.groovy index 953f66fd988..f3d02d15e0f 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/AdjustmentRule.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/AdjustmentRule.groovy @@ -3,11 +3,13 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.model.Currency @JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) @ToString(includeNames = true, ignoreNulls = true) +@EqualsAndHashCode class AdjustmentRule { @JsonProperty('adjtype') diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExt.groovy index ee3c1c9a8f0..4a1d0d237a3 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExt.groovy @@ -1,8 +1,10 @@ package org.prebid.server.functional.model.request.auction +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) +@EqualsAndHashCode class AppExt { AppExtData data diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExtData.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExtData.groovy index 3d12506410c..fbe97c6ba4b 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExtData.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExtData.groovy @@ -1,8 +1,10 @@ package org.prebid.server.functional.model.request.auction +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) +@EqualsAndHashCode class AppExtData { String language diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustment.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustment.groovy index 7f7250a6a75..f10d985e801 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustment.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustment.groovy @@ -2,11 +2,13 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.util.PBSUtils @JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) @ToString(includeNames = true, ignoreNulls = true) +@EqualsAndHashCode class BidAdjustment { Map mediaType diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentFactors.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentFactors.groovy index 9cb90edb27b..3c6360faa81 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentFactors.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentFactors.groovy @@ -4,11 +4,13 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter import com.fasterxml.jackson.annotation.JsonAnySetter import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.model.bidder.BidderName @JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) @ToString(includeNames = true, ignoreNulls = true) +@EqualsAndHashCode class BidAdjustmentFactors { @JsonAnySetter diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy index 92af741601f..67f23edcf99 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy @@ -1,9 +1,11 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.annotation.JsonProperty +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) +@EqualsAndHashCode class BidAdjustmentRule { @JsonProperty('*') diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy index a815cc36d16..7a991c399da 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy @@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString -import org.prebid.server.functional.model.ChannelType import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.config.AlternateBidderCodes import org.prebid.server.functional.model.request.Channel @@ -54,4 +53,5 @@ class Prebid { Map keyValuePairs Boolean supportDeals String integration + Map bidders } diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy index 0090b10020a..c373798d48d 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -1,6 +1,5 @@ package org.prebid.server.functional.tests - import org.prebid.server.functional.model.bidder.Generic import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig @@ -53,6 +52,7 @@ import static org.prebid.server.functional.model.request.auction.VideoPlcmtSubty import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer import static org.prebid.server.functional.util.PBSUtils.getRandomDecimal +import static org.prebid.server.functional.util.PBSUtils.roundDecimal class BidAdjustmentSpec extends BaseSpec { @@ -106,7 +106,7 @@ class BidAdjustmentSpec extends BaseSpec { def "PBS should prefer bid price adjustment based on media type when request has per-media-type bid adjustment factors"() { given: "Default bid request with bid adjustment" - def bidAdjustment = randomDecimal + def bidAdjustment = roundDecimal(getRandomDecimal(), 0) def mediaTypeBidAdjustment = bidAdjustmentFactor def bidRequest = BidRequest.getDefaultBidRequest(SITE).tap { ext.prebid.bidAdjustmentFactors = new BidAdjustmentFactors().tap { @@ -126,6 +126,10 @@ class BidAdjustmentSpec extends BaseSpec { assert response?.seatbid?.first?.bid?.first?.price == bidResponse.seatbid.first.bid.first.price * mediaTypeBidAdjustment + and: "Bidder request should contain bid bid adjustment factors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.bidAdjustmentFactors == bidRequest.ext.prebid.bidAdjustmentFactors + where: bidAdjustmentFactor << [0.9, 1.1] } @@ -239,6 +243,9 @@ class BidAdjustmentSpec extends BaseSpec { assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] + and: "Bidder request should contain bid adjustments" + assert bidderRequest.ext.prebid.bidAdjustments == bidRequest.ext.prebid.bidAdjustments + where: adjustmentType | ruleValue | mediaType | bidRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy index 2932d4558ce..36d2a55d118 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy @@ -2,31 +2,32 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.bidder.Generic -import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.config.AlternateBidderCodes import org.prebid.server.functional.model.request.Channel import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.AdServerTargeting import org.prebid.server.functional.model.request.auction.Amp import org.prebid.server.functional.model.request.auction.AppExt +import org.prebid.server.functional.model.request.auction.AppExtData import org.prebid.server.functional.model.request.auction.AppPrebid -import org.prebid.server.functional.model.request.auction.BidAdjustmentFactors -import org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.BidderConfig import org.prebid.server.functional.model.request.auction.BidderConfigOrtb import org.prebid.server.functional.model.request.auction.ConsentedProvidersSettings import org.prebid.server.functional.model.request.auction.Device import org.prebid.server.functional.model.request.auction.DeviceExt +import org.prebid.server.functional.model.request.auction.Dooh +import org.prebid.server.functional.model.request.auction.DoohExt import org.prebid.server.functional.model.request.auction.EidPermission import org.prebid.server.functional.model.request.auction.Events import org.prebid.server.functional.model.request.auction.ExtPrebidBidderConfig import org.prebid.server.functional.model.request.auction.ExtRequestPrebidData import org.prebid.server.functional.model.request.auction.Interstitial +import org.prebid.server.functional.model.request.auction.PaaFormat import org.prebid.server.functional.model.request.auction.PrebidAnalytics import org.prebid.server.functional.model.request.auction.PrebidCache import org.prebid.server.functional.model.request.auction.DevicePrebid import org.prebid.server.functional.model.request.auction.PrebidCurrency -import org.prebid.server.functional.model.request.auction.PrebidStoredRequest import org.prebid.server.functional.model.request.auction.Renderer import org.prebid.server.functional.model.request.auction.RendererData import org.prebid.server.functional.model.request.auction.Sdk @@ -39,25 +40,19 @@ import org.prebid.server.functional.model.request.auction.UserExtPrebid import org.prebid.server.functional.model.request.auction.UserTime import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils -import spock.lang.IgnoreRest import static org.prebid.server.functional.model.ChannelType.WEB - import static org.prebid.server.functional.model.bidder.BidderName.RUBICON import static org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse.defaultConversionRates import static org.prebid.server.functional.model.request.auction.DebugCondition.ENABLED import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP -import static org.prebid.server.functional.model.request.auction.PaaFormat.ORIGINAL +import static org.prebid.server.functional.model.request.auction.DistributionChannel.DOOH import static org.prebid.server.functional.model.request.auction.TraceLevel.BASIC import static org.prebid.server.functional.model.response.auction.ErrorType.ALIAS import static org.prebid.server.functional.model.response.auction.ErrorType.GENERIC class BidderFieldDisplayBehaviorSpec extends BaseSpec { - //todo:ext.prebid.returnallbidstatus (boolean) - // Never needed inside the adapter code - // Exposes nothing because it’s a boolean - def "PBS shouldn't pass ext.prebid.returnAllBidStatus to bidder request"() { given: "Default basic bid request" def bidRequest = BidRequest.defaultBidRequest.tap { @@ -71,21 +66,17 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) - then: "Bidder request shouldn't contain returnAll" + then: "Bidder request shouldn't contain returnAllBidStatus" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert !bidderRequest.ext.prebid.returnAllBidStatus } - //todo: ext.prebid.aliasgvlids (map) - // Never needed inside the adapter code - - - def "PBS shouldn't pass aliasGvlIds to bidder request when aliasGvlIds specified"() { + def "PBS shouldn't pass aliasGvlIds to bidder request when aliasGvlIds or aliases specified"() { given: "Default basic bid request" def bidRequest = BidRequest.defaultBidRequest.tap { imp[0].ext.prebid.bidder.alias = new Generic() imp[0].ext.prebid.bidder.generic = null - ext.prebid.aliasgvlids = ["alias": 123] + ext.prebid.aliasgvlids = [(ALIAS.value): PBSUtils.randomNumber] ext.prebid.aliases = [(ALIAS.value): BidderName.GENERIC] } @@ -102,17 +93,8 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest.ext.prebid.aliases } - //todo: ext.prebid.bidadjustmentfactors and ext.prebid.bidadjustments (map) - // Never needed inside the adapter code (for now) but according to the requirements adapters they might be needed to reverse price floors according to the https://magnite.atlassian.net/browse/HB-20538 - // bidadjustments are missed in the table - // The value is taken from request/account - // The rules of other bidders if any are exposed. - - //todo: ext.prebid.adservertargeting (object) - // Never needed inside the adapter code - - def "PBS shouldn't send bid adServerTargeting to bidder request"() { - given: "Default basic bid request" + def "PBS shouldn't pass adServerTargeting to bidder request when adServerTargeting specified"() { + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.tap { adServerTargeting = [ @@ -136,13 +118,8 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest.ext.prebid.adServerTargeting } - //todo: ext.prebid.supportdeals (boolean) - // Never needed inside the adapter code - // Exposes nothing because it’s a boolean - - - def "PBS shouldn't send supportDeals to bidder request"() { - given: "Default basic bid request" + def "PBS should pass supportDeals to bidder request when supportDeals specified"() { + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.supportDeals = PBSUtils.randomBoolean } @@ -154,18 +131,13 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) - then: "Bidder request shouldn't contain supportDeals" + then: "Bidder request should contain requested supportDeals" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.ext.prebid.supportDeals + assert bidderRequest.ext.prebid.supportDeals == bidRequest.ext.prebid.supportDeals } - //todo: ext.prebid.cache (object) - // Never needed inside the adapter code - // Not sure it exposes something because it’s only about ttlseconds integers and some booleans - - - def "PBS shouldn't pass ext.prebid.cache to bidder request"() { - given: "Default basic bid request" + def "PBS shouldn't pass ext.prebid.cache to bidder request when cache specified"() { + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.cache = new PrebidCache(winningOnly: PBSUtils.randomBoolean) } @@ -182,12 +154,8 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest.ext.prebid.cache } - //todo: ext.prebid.channel (object) - // Never needed inside the adapter code - // Not sure it exposes something because it’s only about ttlseconds integers and some booleans - - def "PBS shouldn't pass ext.prebid.channel to bidder request"() { - given: "Default basic bid request" + def "PBS should pass ext.prebid.channel to bidder request when channel specified"() { + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.channel = new Channel(name: WEB) } @@ -204,43 +172,29 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert bidderRequest.ext.prebid.channel == bidRequest.ext.prebid.channel } - - //todo: ext.prebid.currency.rates (object) - - - def "PBS should pass ext.prebid.currency.rates to bidder request"() { - given: "Default basic bid request" + def "PBS should pass ext.prebid.currency.{usePbsRates/rates} to bidder request when usePbsRates or rates specified"() { + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.currency = new PrebidCurrency( usePbsRates: PBSUtils.getRandomBoolean(), rates: getDefaultConversionRates()) } - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) - then: "Bidder request should contain ext.prebid.currency.rates" + then: "Bidder request should contain ext.prebid.currency" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.ext.prebid.currency == bidRequest.ext.prebid.currency } - //todo: ext.prebid.data.{bidder,eidpermissions} (object) - def "PBS shouldn't pass ext.prebid.data.{bidders,eidpermissions} to bidder request"() { - given: "Default basic bid request" + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.data = new ExtRequestPrebidData(bidders: [GENERIC.value], eidpermissions: [new EidPermission(source: PBSUtils.randomString, bidders: [BidderName.GENERIC])]) } - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) @@ -252,40 +206,29 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { def "PBS should pass ext.prebid.{trace,debug,integration} to bidder request"() { - given: "Default basic bid request" + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.trace = BASIC ext.prebid.debug = ENABLED ext.prebid.integration = PBSUtils.randomString } - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) - then: "Bidder request shouldn't contain ext.prebid.data.{debug,trace}" + then: "Bidder request should contain ext.prebid.data.{debug,trace}" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.ext.prebid.trace assert bidderRequest.ext.prebid.debug assert bidderRequest.ext.prebid.integration } - //todo: ext.prebid.events (object) - // Never needed inside the adapter code - - def "PBS shouldn't send bid events to bidder request"() { - given: "Default basic bid request" + def "PBS shouldn't pass ext.prebid.events to bidder request when events specified"() { + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.events = new Events() } - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) @@ -294,19 +237,26 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest.ext.prebid.events } - //todo: ext.prebid.nosale (object) - // Never needed inside the adapter code + def "PBS should pass ext.prebid.bidders to bidder request ext.prebid.bidderparams.bidder when ext.prebid.bidders specified"() { + given: "Default bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.bidders = [(BidderName.GENERIC.value): (BidderName.GENERIC.value)] + } + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) - def "PBS shouldn't send noSale to bidder request"() { - given: "Default basic bid request" + then: "Bidder request should contain bidderParams" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.bidderParams + } + + def "PBS shouldn't pass ext.prebid.noSale to bidder request when noSale specified"() { + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.noSale = [PBSUtils.randomString] } - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) @@ -315,47 +265,26 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest.ext.prebid.noSale } - //todo: ext.prebid.multibid (array of objects) - // Used in RubiconBidder only - - - //todo: ext.prebid.amp (object) - // Mistakenly used in the InvibesBidder - // Never used in other bidder - // Not mentioned in the table - // ADD AMP REQUEST!!! - - def "PBS shouldn't send amp to bidder request"() { - given: "Default basic bid request" + def "PBS should pass ext.prebid.amp to bidder request when amp specified"() { + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.amp = new Amp(data: AmpRequest.getDefaultAmpRequest()) } - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) - then: "Bidder request shouldn't contain noSale" + then: "Bidder request should contain requested amp" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.ext.prebid.amp + assert bidderRequest.ext.prebid.amp } - //todo: ext.prebid.analytics (object) - // Never needed inside the adapter code - - def "PBS shouldn't send analytics to bidder request"() { - given: "Default basic bid request" + def "PBS shouldn't pass ext.prebid.analytics to bidder request when analytics specified"() { + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.analytics = new PrebidAnalytics() } - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) - bidder.setResponse(bidRequest.id, bidResponse) - when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) @@ -364,16 +293,6 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest.ext.prebid.analytics } - //todo: need ask - // ext.prebid.bidderparams and ext.prebid.bidders (object) !!! PAY ATTENTION on ext.prebid.bidderparams - // According to the table ext.prebid.bidderparams should not be exposed as-is, instead it should replaced with the ext.prebid.bidderparams.bidder from ext.prebid.bidders, the ext.prebid.bidders presumably shouldn’t be exposed at all - BUT in my test the ext.prebid.bidderparams and ext.prebid.bidders are kept as-is - // ext.prebid.bidders is used in RubiconBidder - // ext.prebid.bidderparams is used in MediaGoBidder and PubmaticBidder - // ext.prebid.floors (object) - // - // Needed only in RubiconBidder - // The value is taken from the floors provider - def "PBS should copy imp level passThrough to bidresponse.seatbid[].bid[].ext.prebid.passThrough when the passThrough is present"() { given: "Default bid request with passThrough" def randomString = PBSUtils.randomString @@ -382,7 +301,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { imp[0].ext.prebid.passThrough = passThrough } - when: "Requesting PBS auction" + when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) then: "Bidder request shouldn't contain the same passThrough as on request" @@ -404,7 +323,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { ext.prebid.passThrough = passThrough } - when: "Requesting PBS auction" + when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) then: "Response shouldn't contain the same passThrough as on request" @@ -418,12 +337,8 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest.ext.prebid.passThrough } - //todo : ext.prebid.sdk (object) - // Never used at all - maybe it’s deprecated - // Not mentioned in the table - def "PBS auction should pass ext.prebid.sdk requested to bidder request when sdk specified"() { - given: "Default bid request with aliases" + given: "Default bid request with ext.prebid.sdk" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.sdk = new Sdk(renderers: [new Renderer( name: PBSUtils.randomString, @@ -431,7 +346,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { data: new RendererData(any: PBSUtils.randomString))]) } - when: "Requesting PBS auction" + when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) then: "Bidder request should contain sdk value same in request" @@ -441,25 +356,53 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert bidderRequest.ext.prebid.sdk.renderers.data.any == bidRequest.ext.prebid.sdk.renderers.data.any } - def "PBS auction shouldn't pass ext.prebid.paaformat to bidder request when paaformat specified"() { + def "PBS auction should pass ext.prebid.paaformat to bidder request when paaformat specified"() { given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.paaFormat = ORIGINAL + ext.prebid.paaFormat = PaaFormat.ORIGINAL + } + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain paaFormat value same in request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.paaFormat == bidRequest.ext.prebid.paaFormat + } + + def "PBS auction shouldn't pass ext.prebid.kvps to bidder request when kvps specified"() { + given: "Default bid request with kvps" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.keyValuePairs = [(PBSUtils.randomString): PBSUtils.randomString] } - when: "Requesting PBS auction" + when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) - then: "Bidder request shouldn't contain paaForamt value same in request" + then: "Bidder request shouldn't contain keyValuePairs" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.ext.prebid.paaFormat + assert !bidderRequest.ext.prebid.keyValuePairs } - //todo: user.ext - // The whole object is exposed except for the prebid field - // consent, eids, data fields are needed for some bidders + def "PBS auction should pass ext.prebid.alternatebiddercodes to bidder request when alternate bidder codes specified"() { + given: "Default bid request with alternateBidderCodes" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.alternateBidderCodes = new AlternateBidderCodes().tap { + it.enabled = true + it.bidders = [(BidderName.GENERIC): new org.prebid.server.functional.model.config.BidderConfig(enabled: true, allowedBidderCodes: [BidderName.GENERIC])] + } + } + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain ext.prebid.alternateBidderCodes" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.alternateBidderCodes.enabled == bidRequest.ext.prebid.alternateBidderCodes.enabled + assert bidderRequest.ext.prebid.alternateBidderCodes.bidders == [(BidderName.GENERIC): new org.prebid.server.functional.model.config.BidderConfig(enabled: true)] + } - def "PBS should pass user.ext"() { + def "PBS should pass user.ext to bidder request when user.ext specified"() { given: "Default basic BidRequest with generic bidder" def bidRequest = BidRequest.defaultBidRequest.tap { user = new User(ext: new UserExt().tap { @@ -483,11 +426,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert bidderRequest.user.ext.consentedProvidersSettings == bidRequest.user.ext.consentedProvidersSettings } - //todo :site.ext - // The whole object is exposed - // data and amp fields are needed for some bidders - - def "PBS should pass site.ext"() { + def "PBS should pass site.ext to bidder request when site.ext specified"() { given: "Default basic BidRequest with generic bidder" def bidRequest = BidRequest.defaultBidRequest.tap { site.ext = new SiteExt(amp: 0, data: new SiteExtData(id: PBSUtils.randomString)) @@ -496,17 +435,13 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) - then: "Bidder request should contain user.ext.{amp,data}" + then: "Bidder request should contain site.ext.{amp,data}" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.site.ext.data == bidRequest.site.ext.data assert bidderRequest.site.ext.amp == bidRequest.site.ext.amp } - //todo :device.ext.prebid - // The whole object is exposed - - - def "PBS should pass device.ext.prebid.interstitial to bidder request when interstitial specified"() { + def "PBS should pass device.ext to bidder request when device.ext specified"() { given: "Default basic bid request with generic bidder" def bidRequest = BidRequest.defaultBidRequest.tap { device = new Device( @@ -526,12 +461,8 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert bidderRequest.device.ext == bidRequest.device.ext } - //todo: ext.prebid.adservertargeting (object) - // Never needed inside the adapter code - - - def "PBS should pass ext.prebid.auctiontimestamp to bidder request"() { - given: "Default basic bid request" + def "PBS should pass ext.prebid.auctionTimestamp to bidder request when auctionTimestamp specified"() { + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.auctionTimestamp = PBSUtils.randomNumber } @@ -544,12 +475,8 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert bidderRequest.ext.prebid.auctionTimestamp } - //todo: ext.prebid.adservertargeting (object) - // Never needed inside the adapter code - - - def "PBS shouldn't pass ext.prebid.bidderConfig to bidder request"() { - given: "Default basic bid request" + def "PBS shouldn't pass ext.prebid.bidderConfig to bidder request when bidderConfig specified"() { + given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.bidderConfig = [new ExtPrebidBidderConfig(bidders: [BidderName.GENERIC], config: new BidderConfig(ortb2: new BidderConfigOrtb(site: Site.configFPDSite, user: User.configFPDUser)))] @@ -587,27 +514,31 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest.ext.prebid.bidderParams } - - def "PBS should pass bidder app.ext.prebid.source to the bidder whe bidder param bidder not requested"() { + def "PBS should pass bidder app.ext to the bidder request when app ext specified"() { def bidRequest = BidRequest.getDefaultBidRequest(APP).tap { - app.ext = new AppExt(prebid: new AppPrebid(source: PBSUtils.getRandomString(), version: PBSUtils.randomString)) + app.ext = new AppExt(data: new AppExtData(language: PBSUtils.randomString), + prebid: new AppPrebid(source: PBSUtils.getRandomString(), version: PBSUtils.randomString)) } when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + defaultPbsService.sendAuctionRequest(bidRequest) - then: "Response shouldn't contain error" - assert !response.ext?.errors + then: "Bidder request should contain app.ext" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.app.ext == bidRequest.app.ext + } - and: "Response shouldn't contain warning" - assert !response.ext?.warnings + def "PBS should pass dooh.ext to bidder request when dooh.ext specified"() { + given: "Default bid request with bidRequest.dooh" + def bidRequest = BidRequest.getDefaultBidRequest(DOOH).tap { + dooh = new Dooh(id: PBSUtils.randomString, ext: DoohExt.defaultDoohExt) + } - and: "Bidder request should contain bidder param" + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain dooh.ext" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.app.ext.prebid.source == bidRequest.app.ext.prebid.source + assert bidderRequest.dooh.ext == bidRequest.dooh.ext } - //todo : WE DON'T have such field ext.prebid.experimentadscert.enabled - // Enabled Ads.Cert 2.0 Authenticated Connections on supported outgoing bidder requests. - // boolean no - } diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy index 55485b1fe76..eeea498b126 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy @@ -3,23 +3,17 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.bidder.AppNexus import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.bidder.Generic -import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredImp import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.amp.AmpRequest -import org.prebid.server.functional.model.request.auction.AdServerTargeting import org.prebid.server.functional.model.request.auction.Adrino -import org.prebid.server.functional.model.request.auction.Amp import org.prebid.server.functional.model.request.auction.Amx import org.prebid.server.functional.model.request.auction.AuctionEnvironment import org.prebid.server.functional.model.request.auction.Banner -import org.prebid.server.functional.model.request.auction.BidAdjustmentFactors -import org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Device import org.prebid.server.functional.model.request.auction.AnyUnsupportedBidder -import org.prebid.server.functional.model.request.auction.Events import org.prebid.server.functional.model.request.auction.Geo import org.prebid.server.functional.model.request.auction.Imp import org.prebid.server.functional.model.request.auction.ImpExt @@ -27,14 +21,8 @@ import org.prebid.server.functional.model.request.auction.ImpExtContext import org.prebid.server.functional.model.request.auction.ImpExtContextData import org.prebid.server.functional.model.request.auction.InterestGroupAuctionSupport import org.prebid.server.functional.model.request.auction.Native -import org.prebid.server.functional.model.request.auction.Pmp -import org.prebid.server.functional.model.request.auction.PrebidAnalytics -import org.prebid.server.functional.model.request.auction.PrebidCache import org.prebid.server.functional.model.request.auction.PrebidOptions import org.prebid.server.functional.model.request.auction.PrebidStoredRequest -import org.prebid.server.functional.model.request.auction.Renderer -import org.prebid.server.functional.model.request.auction.RendererData -import org.prebid.server.functional.model.request.auction.Sdk import org.prebid.server.functional.model.request.auction.Site import org.prebid.server.functional.model.request.auction.Source import org.prebid.server.functional.model.request.auction.Targeting @@ -46,21 +34,14 @@ import org.prebid.server.functional.model.response.auction.BidExt import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.CcpaConsent -import spock.lang.IgnoreRest import static org.prebid.server.functional.model.Currency.CHF import static org.prebid.server.functional.model.Currency.EUR import static org.prebid.server.functional.model.Currency.JPY import static org.prebid.server.functional.model.Currency.USD -import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_UPPER_CASE import static org.prebid.server.functional.model.bidder.BidderName.AMX import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS -import static org.prebid.server.functional.model.bidder.BidderName.GENERIC -import static org.prebid.server.functional.model.bidder.BidderName.GENERIC -import static org.prebid.server.functional.model.bidder.BidderName.GENERIC -import static org.prebid.server.functional.model.bidder.BidderName.GENERIC -import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.bidder.BidderName.GENERIC_CAMEL_CASE import static org.prebid.server.functional.model.bidder.BidderName.OPENX import static org.prebid.server.functional.model.bidder.CompressionType.GZIP @@ -70,11 +51,9 @@ import static org.prebid.server.functional.model.request.auction.AuctionEnvironm import static org.prebid.server.functional.model.request.auction.AuctionEnvironment.NOT_SUPPORTED import static org.prebid.server.functional.model.request.auction.AuctionEnvironment.SERVER_ORCHESTRATED import static org.prebid.server.functional.model.request.auction.AuctionEnvironment.UNKNOWN -import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.BANNER import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP import static org.prebid.server.functional.model.request.auction.DistributionChannel.DOOH import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE -import static org.prebid.server.functional.model.request.auction.PaaFormat.ORIGINAL import static org.prebid.server.functional.model.request.auction.SecurityLevel.NON_SECURE import static org.prebid.server.functional.model.request.auction.SecurityLevel.SECURE import static org.prebid.server.functional.model.response.auction.BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY @@ -283,9 +262,12 @@ class BidderParamsSpec extends BaseSpec { when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) - then: "Response should contain zoneId value from imp[*].ext.prebid.bidder.BIDDER" + then: "Bidder request should contain zoneId value from imp[*].ext.prebid.bidder.BIDDER" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp[0]?.ext?.bidder?.firstParam == firstParam + + and: "Bidder request should contain requested bidder param for related itself bidder" + assert bidderRequest.ext.prebid.bidderParams[GENERIC] == bidRequest.ext.prebid.bidderParams[GENERIC] } def "PBS should send bidder params from imp[*].ext.prebid.bidder.BIDDER when ext.prebid.bidderparams.BIDDER isn't specified"() { @@ -803,7 +785,7 @@ class BidderParamsSpec extends BaseSpec { when: "Requesting PBS auction" defaultPbsService.sendAuctionRequest(bidRequest) - then: "Response should contain imp[0].secure same value as in request" + then: "Bidder request should contain imp[0].secure same value as in request" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp[0].secure == secureBidderRequest diff --git a/src/test/groovy/org/prebid/server/functional/tests/MultibidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/MultibidSpec.groovy index 405d473a8c6..e200f3562ed 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/MultibidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/MultibidSpec.groovy @@ -1,6 +1,5 @@ package org.prebid.server.functional.tests -import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.db.Account @@ -10,7 +9,6 @@ import org.prebid.server.functional.model.request.auction.Targeting import org.prebid.server.functional.model.response.auction.Bid import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils -import spock.lang.IgnoreRest import static org.prebid.server.functional.model.bidder.BidderName.GENERIC @@ -41,9 +39,12 @@ class MultibidSpec extends BaseSpec { then: "PBS should not return targeting for non-winning bid" assert !response.seatbid?.first()?.bid?.last()?.ext?.prebid?.targeting + + and: "Bid request should contain requested ext.prebid.multiBid" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.multibid == [multiBid] } - @IgnoreRest def "PBS should return seatbid[].bid[].ext.prebid.targeting for non-winning bid in multi-bid response when includeBidderKeys = true"() { given: "Default basic BidRequest with generic bidder with includeBidderKeys = true" def bidRequest = BidRequest.defaultBidRequest diff --git a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy index 21b5464e08f..abfec1295fe 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy @@ -1,6 +1,5 @@ package org.prebid.server.functional.tests -import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountProfilesConfigs @@ -36,7 +35,6 @@ import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.testcontainers.container.PrebidServerContainer import org.prebid.server.functional.util.PBSUtils import org.testcontainers.images.builder.Transferable -import spock.lang.IgnoreRest import spock.lang.PendingFeature import static org.prebid.server.functional.model.AccountStatus.ACTIVE @@ -1269,7 +1267,6 @@ class ProfileSpec extends BaseSpec { } } - @IgnoreRest def "PBS should emit error and metrics when imp profile called from request level"() { given: "Default bidRequest with request profile" def accountId = PBSUtils.randomNumber as String diff --git a/src/test/groovy/org/prebid/server/functional/tests/SchainSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SchainSpec.groovy index f283c5f0e9b..9b0dce39aec 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SchainSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SchainSpec.groovy @@ -42,7 +42,7 @@ class SchainSpec extends BaseSpec { def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.source?.schain?.nodes == supplyChain.nodes + GLOBAL_SUPPLY_SCHAIN_NODE - and: "Bidder request should contain schain as requested" + and: "Bidder request shouldn't contain schains as requested" assert !bidderRequest.ext.prebid.schains } diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index b464b58c86f..d21b343f900 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -2,7 +2,6 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.bidder.Generic import org.prebid.server.functional.model.bidder.Openx -import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountRankingConfig @@ -383,9 +382,10 @@ class TargetingSpec extends BaseSpec { then: "Amp response shouldn't contain custom targeting" assert !response.targeting[customKey] - and: "Bidder request shouldn't contain ext.prebid.adservertargeting" + and: "Bidder request shouldn't contain ext.prebid.{targeting,adservertargeting}" def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) assert !bidderRequest.ext.prebid.targeting + assert !bidderRequest.ext.prebid.adServerTargeting where: customSource | customValue From 3b9796cfc8ea36f29a7c4df55c9812231397980c Mon Sep 17 00:00:00 2001 From: markiian Date: Wed, 31 Dec 2025 13:12:43 +0200 Subject: [PATCH 07/13] Replace to false `false` --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bc8e7707460..1c198a3df57 100644 --- a/pom.xml +++ b/pom.xml @@ -372,7 +372,7 @@ ${maven-surefire-plugin.version} - true + false ${skipUnitTests} From 0f62f16089372eaacc1e4bb96451cbb238c6bd9f Mon Sep 17 00:00:00 2001 From: Danylo Date: Mon, 5 Jan 2026 15:32:01 +0100 Subject: [PATCH 08/13] Add units --- .../BidderRequestCleanerTest.java | 319 +++++++++++++++++- .../org/prebid/server/it/ApplicationTest.java | 8 +- .../it/amp/test-generic-bid-request.json | 10 - .../it/amp/test-genericAlias-bid-request.json | 10 - .../test-video-appnexus-bid-request-1.json | 3 - .../test-video-appnexus-bid-request-2.json | 3 - .../test-generic-bid-request.json | 3 - .../multi_bid/test-generic-bid-request-1.json | 9 - .../test-genericAlias-bid-request-1.json | 8 - 9 files changed, 314 insertions(+), 59 deletions(-) diff --git a/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java index dec73e28f79..9eae017aefb 100644 --- a/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java @@ -10,8 +10,22 @@ import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.bidder.model.Result; import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestBidAdjustmentFactors; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidAlternateBidderCodes; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidAlternateBidderCodesBidder; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidCache; +import org.prebid.server.proto.openrtb.ext.request.ImpMediaType; +import java.math.BigDecimal; +import java.util.EnumMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singleton; +import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(MockitoExtension.class) @@ -29,7 +43,7 @@ public void setUp() { @Test public void processShouldReturnSameRequest() { // given - final BidderRequest bidderRequest = givenBidderRequest(null); + final BidderRequest bidderRequest = givenBidderRequest(identity()); // when final Result result = target.process(bidderRequest, null, null).result(); @@ -40,9 +54,243 @@ public void processShouldReturnSameRequest() { } @Test - public void processShouldReturnCleanedRequest() { + public void processShouldCleanBidAdjustmentFactors() { + // given + final EnumMap> mediaTypes = new EnumMap<>(ImpMediaType.class); + mediaTypes.put(ImpMediaType.banner, Map.of("other", BigDecimal.ONE)); + mediaTypes.put(ImpMediaType.video, Map.of( + "other", BigDecimal.ONE, + "biddEr", BigDecimal.ONE)); + final ExtRequestBidAdjustmentFactors factors = ExtRequestBidAdjustmentFactors.builder() + .mediatypes(mediaTypes) + .build(); + factors.addFactor("bIdder", BigDecimal.ONE); + + final BidderRequest bidderRequest = givenBidderRequest(extPrebid -> extPrebid.bidadjustmentfactors(factors)); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getBidadjustmentfactors) + .satisfies(factorsResult -> { + assertThat(factorsResult.getAdjustments()).containsExactly(Map.entry("bIdder", BigDecimal.ONE)); + assertThat(factorsResult.getMediatypes()).containsExactly( + Map.entry(ImpMediaType.video, Map.of("biddEr", BigDecimal.ONE))); + }); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldRemoveBidAdjustmentFactors() { + // given + final EnumMap> mediaTypes = new EnumMap<>(ImpMediaType.class); + mediaTypes.put(ImpMediaType.banner, Map.of("other", BigDecimal.ONE)); + final ExtRequestBidAdjustmentFactors factors = ExtRequestBidAdjustmentFactors.builder() + .mediatypes(mediaTypes) + .build(); + factors.addFactor("other", BigDecimal.ONE); + + final BidderRequest bidderRequest = givenBidderRequest(extPrebid -> extPrebid.bidadjustmentfactors(factors)); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getBidadjustmentfactors) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldCleanBidAdjustments() { + // given + final ObjectNode bidAdjustments = mapper.valueToTree(Map.of( + "banner", Map.of("other", 1), + "video", Map.of("other", 1, "biddEr", 1))); + + final BidderRequest bidderRequest = givenBidderRequest(extPrebid -> extPrebid.bidadjustments(bidAdjustments)); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getBidadjustments) + .isEqualTo(mapper.valueToTree(Map.of("video", Map.of("biddEr", 1)))); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldRemoveBidAdjustments() { + // given + final ObjectNode bidAdjustments = mapper.valueToTree(Map.of("banner", Map.of("other", 1))); + + final BidderRequest bidderRequest = givenBidderRequest(extPrebid -> extPrebid.bidadjustments(bidAdjustments)); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getBidadjustments) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldCleanAlternateBidderCodes() { + // given + final ExtRequestPrebidAlternateBidderCodes codes = ExtRequestPrebidAlternateBidderCodes.of( + true, Map.of( + "other", ExtRequestPrebidAlternateBidderCodesBidder.of(true, singleton("otherV")), + "biddEr", ExtRequestPrebidAlternateBidderCodesBidder.of(true, singleton("bidderV")))); + + final BidderRequest bidderRequest = givenBidderRequest(extPrebid -> extPrebid.alternateBidderCodes(codes)); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getAlternateBidderCodes) + .isEqualTo(ExtRequestPrebidAlternateBidderCodes.of(true, Map.of( + "biddEr", ExtRequestPrebidAlternateBidderCodesBidder.of(true, singleton("bidderV"))))); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldRemoveReturnAllBidStatus() { + // given + final BidderRequest bidderRequest = givenBidderRequest(extPrebid -> extPrebid.returnallbidstatus(true)); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getReturnallbidstatus) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldRemoveAliasGvlIds() { + // given + final BidderRequest bidderRequest = givenBidderRequest(extPrebid -> extPrebid.aliasgvlids(emptyMap())); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getAliasgvlids) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldRemoveAdServerTargeting() { + // given + final BidderRequest bidderRequest = givenBidderRequest(extPrebid -> extPrebid.adservertargeting(emptyList())); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getAdservertargeting) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldRemoveCache() { + // given + final BidderRequest bidderRequest = givenBidderRequest( + extPrebid -> extPrebid.cache(ExtRequestPrebidCache.EMPTY)); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getCache) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldRemoveEvents() { + // given + final BidderRequest bidderRequest = givenBidderRequest( + extPrebid -> extPrebid.events(mapper.createObjectNode())); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getEvents) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldRemoveNoSale() { + // given + final BidderRequest bidderRequest = givenBidderRequest(extPrebid -> extPrebid.nosale(emptyList())); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getNosale) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldRemoveBidderControls() { // given - final BidderRequest bidderRequest = givenBidderRequest(mapper.createObjectNode()); + final BidderRequest bidderRequest = givenBidderRequest( + extPrebid -> extPrebid.biddercontrols(mapper.createObjectNode())); // when final Result result = target.process(bidderRequest, null, null).result(); @@ -57,12 +305,69 @@ public void processShouldReturnCleanedRequest() { assertThat(result.getErrors()).isEmpty(); } - private static BidderRequest givenBidderRequest(ObjectNode bidderControls) { + @Test + public void processShouldRemoveAnalytics() { + // given + final BidderRequest bidderRequest = givenBidderRequest( + extPrebid -> extPrebid.analytics(mapper.createObjectNode())); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getAnalytics) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldRemovePassthrough() { + // given + final BidderRequest bidderRequest = givenBidderRequest( + extPrebid -> extPrebid.passthrough(mapper.createObjectNode())); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getPassthrough) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldRemoveKvps() { + // given + final BidderRequest bidderRequest = givenBidderRequest( + extPrebid -> extPrebid.kvps(mapper.createObjectNode())); + + // when + final Result result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getKvps) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + private static BidderRequest givenBidderRequest( + UnaryOperator extPrebidCustomizer) { + return BidderRequest.builder() .bidRequest(BidRequest.builder() - .ext(ExtRequest.of(ExtRequestPrebid.builder() - .biddercontrols(bidderControls) - .build())) + .ext(ExtRequest.of(extPrebidCustomizer.apply(ExtRequestPrebid.builder()).build())) .build()) .bidder(BIDDER) .build(); diff --git a/src/test/java/org/prebid/server/it/ApplicationTest.java b/src/test/java/org/prebid/server/it/ApplicationTest.java index 3a3710c6e6e..f28c7d28939 100644 --- a/src/test/java/org/prebid/server/it/ApplicationTest.java +++ b/src/test/java/org/prebid/server/it/ApplicationTest.java @@ -130,18 +130,14 @@ public void openrtb2MultiBidAuctionShouldRespondWithMoreThanOneBid() throws IOEx WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/genericAlias-exchange")) .withRequestBody(equalToJson( - jsonFrom( - "openrtb2/multi_bid/test-genericAlias-bid-request-1.json" - ))) + jsonFrom("openrtb2/multi_bid/test-genericAlias-bid-request-1.json"))) .willReturn(aResponse().withBody(jsonFrom( "openrtb2/multi_bid/test-genericAlias-bid-response-1.json")))); // pre-bid cache WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/cache")) .withRequestBody(equalToBidCacheRequest( - jsonFrom( - "openrtb2/multi_bid/test-cache-generic-genericAlias-request.json" - ))) + jsonFrom("openrtb2/multi_bid/test-cache-generic-genericAlias-request.json"))) .willReturn(aResponse() .withTransformers("cache-response-transformer") .withTransformerParameter("matcherName", diff --git a/src/test/resources/org/prebid/server/it/amp/test-generic-bid-request.json b/src/test/resources/org/prebid/server/it/amp/test-generic-bid-request.json index 4d45a82bbc0..fdba46fb7d2 100644 --- a/src/test/resources/org/prebid/server/it/amp/test-generic-bid-request.json +++ b/src/test/resources/org/prebid/server/it/amp/test-generic-bid-request.json @@ -104,9 +104,6 @@ "storedrequest": { "id": "test-amp-stored-request" }, - "cache": { - "bids": {} - }, "auctiontimestamp": 0, "amp": { "data": { @@ -124,13 +121,6 @@ "account": "accountId" } }, - "adservertargeting": [ - { - "key": "static_keyword1", - "source": "static", - "value": "static_value1" - } - ], "channel": { "name": "amp" }, diff --git a/src/test/resources/org/prebid/server/it/amp/test-genericAlias-bid-request.json b/src/test/resources/org/prebid/server/it/amp/test-genericAlias-bid-request.json index 65febdb9a16..afccf4ce628 100644 --- a/src/test/resources/org/prebid/server/it/amp/test-genericAlias-bid-request.json +++ b/src/test/resources/org/prebid/server/it/amp/test-genericAlias-bid-request.json @@ -102,9 +102,6 @@ "storedrequest": { "id": "test-amp-stored-request" }, - "cache": { - "bids": {} - }, "auctiontimestamp": 0, "amp": { "data": { @@ -122,13 +119,6 @@ "timeout": "10000000" } }, - "adservertargeting": [ - { - "key": "static_keyword1", - "source": "static", - "value": "static_value1" - } - ], "channel": { "name": "amp" }, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-appnexus-bid-request-1.json b/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-appnexus-bid-request-1.json index 5e19435fdc2..1d07d919797 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-appnexus-bid-request-1.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-appnexus-bid-request-1.json @@ -303,9 +303,6 @@ "translatecategories": false } }, - "cache": { - "vastxml": {} - }, "channel": { "name": "web" } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-appnexus-bid-request-2.json b/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-appnexus-bid-request-2.json index 1b09479f3ea..43d91e2e81c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-appnexus-bid-request-2.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-appnexus-bid-request-2.json @@ -96,9 +96,6 @@ "translatecategories": false } }, - "cache": { - "vastxml": {} - }, "channel": { "name": "web" } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-generic-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-generic-bid-request.json index 754ed9ddfff..da6529ee46c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-generic-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-generic-bid-request.json @@ -94,9 +94,6 @@ "includewinners" : true, "includebidderkeys" : true }, - "cache" : { - "bids" : { } - }, "auctiontimestamp" : 0, "server" : { "externalurl" : "http://localhost:8080", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-generic-bid-request-1.json b/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-generic-bid-request-1.json index 0c72e7ce2a4..19637b681f7 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-generic-bid-request-1.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-generic-bid-request-1.json @@ -22,13 +22,6 @@ "ext": { "prebid": { "auctiontimestamp": 1000, - "cache": { - "bids": { - }, - "vastxml": { - "ttlseconds": 120 - } - }, "channel": { "name": "web" }, @@ -44,8 +37,6 @@ "usepbsrates": false }, "debug": 0, - "events": { - }, "multibid": [ { "bidder": "generic", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-genericAlias-bid-request-1.json b/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-genericAlias-bid-request-1.json index f44b91769f3..c4deb7877af 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-genericAlias-bid-request-1.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-genericAlias-bid-request-1.json @@ -104,14 +104,6 @@ "includewinners": true, "includebidderkeys": true }, - "cache": { - "bids": { - }, - "vastxml": { - "ttlseconds": 120 - } - }, - "events": {}, "auctiontimestamp": 1000, "channel": { "name": "web" From 1bb910d8d98f9038fb2046906589c26da11c69d8 Mon Sep 17 00:00:00 2001 From: Danylo Date: Thu, 8 Jan 2026 16:09:49 +0100 Subject: [PATCH 09/13] Fix bug --- .../BidderRequestCleaner.java | 5 +++-- .../BidderRequestCleanerTest.java | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java index 7a705975176..fff39f2090c 100644 --- a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java @@ -158,7 +158,8 @@ private static UpdateResult cleanBidAdjustments(ObjectNode bidAdjust } final ObjectNode cleanedBidAdjustments = bidAdjustments.deepCopy(); - for (Iterator maps = cleanedBidAdjustments.elements(); maps.hasNext(); ) { + final JsonNode mediaTypeToBidAdjustments = cleanedBidAdjustments.path("mediatype"); + for (Iterator maps = mediaTypeToBidAdjustments.elements(); maps.hasNext(); ) { final JsonNode bidderMap = maps.next(); if (!bidderMap.isObject()) { continue; @@ -175,7 +176,7 @@ private static UpdateResult cleanBidAdjustments(ObjectNode bidAdjust } } - return !cleanedBidAdjustments.isEmpty() + return !mediaTypeToBidAdjustments.isEmpty() ? UpdateResult.updated(cleanedBidAdjustments) : UpdateResult.updated(null); } diff --git a/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java index 9eae017aefb..64cfe8159f7 100644 --- a/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java @@ -114,8 +114,9 @@ public void processShouldRemoveBidAdjustmentFactors() { public void processShouldCleanBidAdjustments() { // given final ObjectNode bidAdjustments = mapper.valueToTree(Map.of( - "banner", Map.of("other", 1), - "video", Map.of("other", 1, "biddEr", 1))); + "mediatype", Map.of( + "banner", Map.of("other", 1), + "video", Map.of("other", 1, "biddEr", 1)))); final BidderRequest bidderRequest = givenBidderRequest(extPrebid -> extPrebid.bidadjustments(bidAdjustments)); @@ -128,14 +129,15 @@ public void processShouldCleanBidAdjustments() { .extracting(BidRequest::getExt) .extracting(ExtRequest::getPrebid) .extracting(ExtRequestPrebid::getBidadjustments) - .isEqualTo(mapper.valueToTree(Map.of("video", Map.of("biddEr", 1)))); + .isEqualTo(mapper.valueToTree(Map.of("mediatype", Map.of("video", Map.of("biddEr", 1))))); assertThat(result.getErrors()).isEmpty(); } @Test public void processShouldRemoveBidAdjustments() { // given - final ObjectNode bidAdjustments = mapper.valueToTree(Map.of("banner", Map.of("other", 1))); + final ObjectNode bidAdjustments = mapper.valueToTree(Map.of( + "mediatype", Map.of("banner", Map.of("other", 1)))); final BidderRequest bidderRequest = givenBidderRequest(extPrebid -> extPrebid.bidadjustments(bidAdjustments)); From ea0c3c01c7f3a8f6d74252227557562162a1ca2c Mon Sep 17 00:00:00 2001 From: markiian Date: Thu, 8 Jan 2026 17:14:19 +0200 Subject: [PATCH 10/13] Update after review --- .../functional/model/request/Channel.groovy | 2 + .../model/request/amp/AmpRequest.groovy | 2 + .../model/request/auction/Amp.groovy | 2 + .../functional/tests/AuctionSpec.groovy | 3 +- .../functional/tests/BidAdjustmentSpec.groovy | 12 +-- .../BidderFieldDisplayBehaviorSpec.groovy | 88 +++++++------------ .../functional/tests/BidderParamsSpec.groovy | 3 +- .../server/functional/tests/NativeSpec.groovy | 1 - .../functional/tests/ProfileSpec.groovy | 4 +- .../functional/tests/TargetingSpec.groovy | 12 +-- 10 files changed, 56 insertions(+), 73 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/request/Channel.groovy b/src/test/groovy/org/prebid/server/functional/model/request/Channel.groovy index 9782c3896ba..0a21c56899f 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/Channel.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/Channel.groovy @@ -1,9 +1,11 @@ package org.prebid.server.functional.model.request +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.model.ChannelType @ToString(includeNames = true, ignoreNulls = true) +@EqualsAndHashCode class Channel { ChannelType name diff --git a/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy index fffb2c86e05..d7a2d3ae3c6 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy @@ -2,11 +2,13 @@ package org.prebid.server.functional.model.request.amp import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.util.PBSUtils @ToString(includeNames = true, ignoreNulls = true) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) +@EqualsAndHashCode class AmpRequest { String tagId diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Amp.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Amp.groovy index cbc381ce564..88638e60a34 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Amp.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Amp.groovy @@ -1,9 +1,11 @@ package org.prebid.server.functional.model.request.auction +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.model.request.amp.AmpRequest @ToString(includeNames = true, ignoreNulls = true) +@EqualsAndHashCode class Amp { AmpRequest data diff --git a/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy index a15ed3ab644..a4c7d07f92e 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy @@ -26,7 +26,6 @@ import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.util.HttpUtil import org.prebid.server.functional.util.PBSUtils -import spock.lang.IgnoreRest import spock.lang.Shared import static org.prebid.server.functional.model.AccountStatus.ACTIVE @@ -141,7 +140,7 @@ class AuctionSpec extends BaseSpec { assert bidderRequest?.user?.buyeruid == buyeruid and: "Bidder request shouldn't contain user.ext.prebid.buyeruids" - assert !bidderRequest.user.ext.prebid.buyeruids + assert !bidderRequest?.user?.ext?.prebid?.buyeruids } def "PBS shouldn't populate bidder request buyeruid from buyeruids when buyeruids without appropriate bidder present in request"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy index c373798d48d..979b3128754 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -98,7 +98,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request shouldn't contain bid adjustment factors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.ext.prebid.bidAdjustmentFactors + assert bidderRequest.ext.prebid.bidAdjustmentFactors == bidRequest.ext.prebid.bidAdjustmentFactors where: bidAdjustmentFactor << [0.9, 1.1] @@ -211,10 +211,12 @@ class BidAdjustmentSpec extends BaseSpec { def currency = USD def impPrice = PBSUtils.randomPrice def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) - bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) - bidRequest.cur = [currency] - bidRequest.imp.first.bidFloor = impPrice - bidRequest.imp.first.bidFloorCur = currency + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) + cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency + } and: "Default bid response" def originalPrice = PBSUtils.randomPrice diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy index 36d2a55d118..0709facb714 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy @@ -19,12 +19,10 @@ import org.prebid.server.functional.model.request.auction.DeviceExt import org.prebid.server.functional.model.request.auction.Dooh import org.prebid.server.functional.model.request.auction.DoohExt import org.prebid.server.functional.model.request.auction.EidPermission -import org.prebid.server.functional.model.request.auction.Events import org.prebid.server.functional.model.request.auction.ExtPrebidBidderConfig import org.prebid.server.functional.model.request.auction.ExtRequestPrebidData import org.prebid.server.functional.model.request.auction.Interstitial import org.prebid.server.functional.model.request.auction.PaaFormat -import org.prebid.server.functional.model.request.auction.PrebidAnalytics import org.prebid.server.functional.model.request.auction.PrebidCache import org.prebid.server.functional.model.request.auction.DevicePrebid import org.prebid.server.functional.model.request.auction.PrebidCurrency @@ -48,7 +46,6 @@ import static org.prebid.server.functional.model.request.auction.DebugCondition. import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP import static org.prebid.server.functional.model.request.auction.DistributionChannel.DOOH import static org.prebid.server.functional.model.request.auction.TraceLevel.BASIC -import static org.prebid.server.functional.model.response.auction.ErrorType.ALIAS import static org.prebid.server.functional.model.response.auction.ErrorType.GENERIC class BidderFieldDisplayBehaviorSpec extends BaseSpec { @@ -76,8 +73,8 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { def bidRequest = BidRequest.defaultBidRequest.tap { imp[0].ext.prebid.bidder.alias = new Generic() imp[0].ext.prebid.bidder.generic = null - ext.prebid.aliasgvlids = [(ALIAS.value): PBSUtils.randomNumber] - ext.prebid.aliases = [(ALIAS.value): BidderName.GENERIC] + ext.prebid.aliasgvlids = [(BidderName.ALIAS.value): PBSUtils.randomNumber] + ext.prebid.aliases = [(BidderName.ALIAS.value): BidderName.GENERIC] } and: "Default bid response" @@ -95,13 +92,16 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { def "PBS shouldn't pass adServerTargeting to bidder request when adServerTargeting specified"() { given: "Default bid request" + def customBidRequest = "custom_bid_request" + def sourceOfBidRequest = "bidrequest" + def impId = "imp.id" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.tap { adServerTargeting = [ new AdServerTargeting().tap { - key = "custom_bid_request" - source = "bidrequest" - value = "imp.id" + key = customBidRequest + source = sourceOfBidRequest + value = impId }] } } @@ -176,7 +176,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.currency = new PrebidCurrency( - usePbsRates: PBSUtils.getRandomBoolean(), + usePbsRates: PBSUtils.randomBoolean, rates: getDefaultConversionRates()) } @@ -204,7 +204,6 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest?.ext?.prebid?.data?.eidpermissions } - def "PBS should pass ext.prebid.{trace,debug,integration} to bidder request"() { given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { @@ -216,17 +215,19 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) - then: "Bidder request should contain ext.prebid.data.{debug,trace}" + then: "Bidder request should contain ext.prebid.data.{debug,trace,integration}" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.ext.prebid.trace - assert bidderRequest.ext.prebid.debug - assert bidderRequest.ext.prebid.integration + verifyAll(bidderRequest) { + bidderRequest.ext.prebid.trace == bidRequest.ext.prebid.trace + bidderRequest.ext.prebid.debug == bidRequest.ext.prebid.debug + bidderRequest.ext.prebid.integration == bidRequest.ext.prebid.integration + } } def "PBS shouldn't pass ext.prebid.events to bidder request when events specified"() { given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.events = new Events() + enableEvents() } when: "PBS processes auction request" @@ -237,20 +238,6 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert !bidderRequest.ext.prebid.events } - def "PBS should pass ext.prebid.bidders to bidder request ext.prebid.bidderparams.bidder when ext.prebid.bidders specified"() { - given: "Default bid request" - def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.bidders = [(BidderName.GENERIC.value): (BidderName.GENERIC.value)] - } - - when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request should contain bidderParams" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.ext.prebid.bidderParams - } - def "PBS shouldn't pass ext.prebid.noSale to bidder request when noSale specified"() { given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { @@ -276,21 +263,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { then: "Bidder request should contain requested amp" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.ext.prebid.amp - } - - def "PBS shouldn't pass ext.prebid.analytics to bidder request when analytics specified"() { - given: "Default bid request" - def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.analytics = new PrebidAnalytics() - } - - when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request shouldn't contain analytics" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.ext.prebid.analytics + assert bidderRequest.ext.prebid.amp == bidRequest.ext.prebid.amp } def "PBS should copy imp level passThrough to bidresponse.seatbid[].bid[].ext.prebid.passThrough when the passThrough is present"() { @@ -351,9 +324,11 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { then: "Bidder request should contain sdk value same in request" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.ext.prebid.sdk.renderers.name == bidRequest.ext.prebid.sdk.renderers.name - assert bidderRequest.ext.prebid.sdk.renderers.version == bidRequest.ext.prebid.sdk.renderers.version - assert bidderRequest.ext.prebid.sdk.renderers.data.any == bidRequest.ext.prebid.sdk.renderers.data.any + verifyAll { + bidderRequest.ext.prebid.sdk.renderers.name == bidRequest.ext.prebid.sdk.renderers.name + bidderRequest.ext.prebid.sdk.renderers.version == bidRequest.ext.prebid.sdk.renderers.version + bidderRequest.ext.prebid.sdk.renderers.data.any == bidRequest.ext.prebid.sdk.renderers.data.any + } } def "PBS auction should pass ext.prebid.paaformat to bidder request when paaformat specified"() { @@ -418,12 +393,14 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { then: "Bidder request shouldn't contain user.ext.prebid.buyeruids" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert !bidderRequest.user.ext.prebid.buyeruids + assert !bidderRequest?.user?.ext?.prebid?.buyeruids - and: "Bidder request should contain fcapid,time,consentedProvidedSettings" - assert bidderRequest.user.ext.fcapids == bidRequest.user.ext.fcapids - assert bidderRequest.user.ext.time == bidRequest.user.ext.time - assert bidderRequest.user.ext.consentedProvidersSettings == bidRequest.user.ext.consentedProvidersSettings + and: "Bidder request should contain user.ext.{fcapid,time,consentedProvidersSettings}" + verifyAll(bidderRequest) { + bidderRequest.user.ext.fcapids == bidRequest.user.ext.fcapids + bidderRequest.user.ext.time == bidRequest.user.ext.time + bidderRequest.user.ext.consentedProvidersSettings == bidRequest.user.ext.consentedProvidersSettings + } } def "PBS should pass site.ext to bidder request when site.ext specified"() { @@ -506,10 +483,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { then: "Response shouldn't contain error" assert !response.ext?.errors - and: "Response shouldn't contain warning" - assert !response.ext?.warnings - - and: "Bidder request shouldn't contain bidder param" + and: "Generic bidder request shouldn't contain bidder param" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert !bidderRequest.ext.prebid.bidderParams } @@ -517,7 +491,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { def "PBS should pass bidder app.ext to the bidder request when app ext specified"() { def bidRequest = BidRequest.getDefaultBidRequest(APP).tap { app.ext = new AppExt(data: new AppExtData(language: PBSUtils.randomString), - prebid: new AppPrebid(source: PBSUtils.getRandomString(), version: PBSUtils.randomString)) + prebid: new AppPrebid(source: PBSUtils.randomString, version: PBSUtils.randomString)) } when: "PBS processes auction request" diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy index eeea498b126..9dd11977525 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy @@ -34,6 +34,7 @@ import org.prebid.server.functional.model.response.auction.BidExt import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.CcpaConsent +import spock.lang.IgnoreRest import static org.prebid.server.functional.model.Currency.CHF import static org.prebid.server.functional.model.Currency.EUR @@ -1428,7 +1429,7 @@ class BidderParamsSpec extends BaseSpec { assert response.ext.responsetimemillis.containsKey(ALIAS.value) and: "Bidder request should be valid and not contain aliases" - def bidderRequests = bidder.getBidderRequests(bidRequest.id) + def bidderRequests = bidder.getBidderRequests(bidRequest.id).first assert !bidderRequests.ext.prebid.aliases } diff --git a/src/test/groovy/org/prebid/server/functional/tests/NativeSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/NativeSpec.groovy index b81afc30ec7..7456930cc53 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/NativeSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/NativeSpec.groovy @@ -1,6 +1,5 @@ package org.prebid.server.functional.tests -import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.db.StoredResponse import org.prebid.server.functional.model.request.auction.Asset import org.prebid.server.functional.model.request.auction.AssetImage diff --git a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy index abfec1295fe..dd3b49efcc1 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy @@ -1321,8 +1321,8 @@ class ProfileSpec extends BaseSpec { it.device.dpidmd5 == bidRequest.device.dpidmd5 } - and: "Bidder request shouldn't contain ext.prebid.profile" - assert !bidderRequest.ext.prebid.profileNames + and: "Bidder request should contain ext.prebid.profile" + assert bidderRequest.ext.prebid.profileNames } def "PBS should emit error and metrics when imp profile missing"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index d21b343f900..008f4c6a721 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -382,9 +382,11 @@ class TargetingSpec extends BaseSpec { then: "Amp response shouldn't contain custom targeting" assert !response.targeting[customKey] - and: "Bidder request shouldn't contain ext.prebid.{targeting,adservertargeting}" + and: "Bidder request should contain ext.prebid.targeting" def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) - assert !bidderRequest.ext.prebid.targeting + assert bidderRequest.ext.prebid.targeting + + and: "Bidder request shouldn't contain ext.prebid.adservertargeting" assert !bidderRequest.ext.prebid.adServerTargeting where: @@ -455,9 +457,9 @@ class TargetingSpec extends BaseSpec { .every(map -> map.keySet() .every(key -> key.length() <= targetingLength))) - and: "Bidder request shouldn't contain ext.prebid.adservertargeting" - def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) - assert !bidderRequest.ext.prebid.targeting + and: "Bidder request should contain ext.prebid.adservertargeting" + def bidderRequest = bidder.getBidderRequests(bidRequest.id) + assert bidderRequest.ext.prebid.targeting cleanup: "Stop and remove pbs container" pbsServiceFactory.removeContainer(pbsConfig) From 15cdd97a949c45953b9304903089180e65a0ad93 Mon Sep 17 00:00:00 2001 From: markiian Date: Thu, 8 Jan 2026 18:36:13 +0200 Subject: [PATCH 11/13] Update targeting --- .../server/functional/tests/BidAdjustmentSpec.groovy | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy index 979b3128754..cf945b2bc6c 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -211,12 +211,10 @@ class BidAdjustmentSpec extends BaseSpec { def currency = USD def impPrice = PBSUtils.randomPrice def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) - def bidRequest = BidRequest.defaultBidRequest.tap { - ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) - cur = [currency] - imp.first.bidFloor = impPrice - imp.first.bidFloorCur = currency - } + bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) + bidRequest.cur = [currency] + bidRequest.imp.first.bidFloor = impPrice + bidRequest.imp.first.bidFloorCur = currency and: "Default bid response" def originalPrice = PBSUtils.randomPrice From f55bd7bf2fd8fec33ebcbfdb4b665c999acc17c2 Mon Sep 17 00:00:00 2001 From: markiian Date: Thu, 8 Jan 2026 22:54:00 +0200 Subject: [PATCH 12/13] Update after review --- .../BidderFieldDisplayBehaviorSpec.groovy | 119 +++++++++++++++++- .../GppTransmitTidActivitiesSpec.groovy | 3 - 2 files changed, 118 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy index 0709facb714..c12d4f6d432 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy @@ -6,10 +6,14 @@ import org.prebid.server.functional.model.config.AlternateBidderCodes import org.prebid.server.functional.model.request.Channel import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.AdServerTargeting +import org.prebid.server.functional.model.request.auction.AdjustmentRule import org.prebid.server.functional.model.request.auction.Amp import org.prebid.server.functional.model.request.auction.AppExt import org.prebid.server.functional.model.request.auction.AppExtData import org.prebid.server.functional.model.request.auction.AppPrebid +import org.prebid.server.functional.model.request.auction.BidAdjustment +import org.prebid.server.functional.model.request.auction.BidAdjustmentFactors +import org.prebid.server.functional.model.request.auction.BidAdjustmentRule import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.BidderConfig import org.prebid.server.functional.model.request.auction.BidderConfigOrtb @@ -22,6 +26,7 @@ import org.prebid.server.functional.model.request.auction.EidPermission import org.prebid.server.functional.model.request.auction.ExtPrebidBidderConfig import org.prebid.server.functional.model.request.auction.ExtRequestPrebidData import org.prebid.server.functional.model.request.auction.Interstitial +import org.prebid.server.functional.model.request.auction.MultiBid import org.prebid.server.functional.model.request.auction.PaaFormat import org.prebid.server.functional.model.request.auction.PrebidCache import org.prebid.server.functional.model.request.auction.DevicePrebid @@ -40,16 +45,41 @@ import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils import static org.prebid.server.functional.model.ChannelType.WEB +import static org.prebid.server.functional.model.Currency.USD +import static org.prebid.server.functional.model.bidder.BidderName.OPENX import static org.prebid.server.functional.model.bidder.BidderName.RUBICON import static org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse.defaultConversionRates +import static org.prebid.server.functional.model.request.auction.AdjustmentType.MULTIPLIER +import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.BANNER import static org.prebid.server.functional.model.request.auction.DebugCondition.ENABLED import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP import static org.prebid.server.functional.model.request.auction.DistributionChannel.DOOH +import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE import static org.prebid.server.functional.model.request.auction.TraceLevel.BASIC import static org.prebid.server.functional.model.response.auction.ErrorType.GENERIC +import static org.prebid.server.functional.util.PBSUtils.getRandomDecimal +import static org.prebid.server.functional.util.PBSUtils.roundDecimal class BidderFieldDisplayBehaviorSpec extends BaseSpec { + def "PBS should pass ext.prebid.createTids to bidder request"() { + given: "Default bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.createTids = PBSUtils.randomBoolean + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain createTids" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.createTids == bidRequest.ext.prebid.createTids + } + def "PBS shouldn't pass ext.prebid.returnAllBidStatus to bidder request"() { given: "Default basic bid request" def bidRequest = BidRequest.defaultBidRequest.tap { @@ -364,7 +394,8 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { def bidRequest = BidRequest.defaultBidRequest.tap { ext.prebid.alternateBidderCodes = new AlternateBidderCodes().tap { it.enabled = true - it.bidders = [(BidderName.GENERIC): new org.prebid.server.functional.model.config.BidderConfig(enabled: true, allowedBidderCodes: [BidderName.GENERIC])] + it.bidders = [(BidderName.GENERIC): new org.prebid.server.functional.model.config.BidderConfig(enabled: true, allowedBidderCodes: [BidderName.GENERIC]), + (RUBICON) : new org.prebid.server.functional.model.config.BidderConfig(enabled: true, allowedBidderCodes: [RUBICON])] } } @@ -515,4 +546,90 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.dooh.ext == bidRequest.dooh.ext } + + def "PBS should pass ext.prebid.multiBid only bidder related entity for each bidder"() { + given: "Default basic BidRequest with generic bidder with includeBidderKeys = false" + def bidRequest = BidRequest.defaultBidRequest + + and: "Set maxbids = 2 for generic and rubicon bidder" + def maxBids = 2 + def genericMultiBid = new MultiBid(bidder: BidderName.GENERIC, maxBids: maxBids, targetBidderCodePrefix: PBSUtils.randomString) + def rubiconMultiBid = new MultiBid(bidder: RUBICON, maxBids: maxBids, targetBidderCodePrefix: PBSUtils.randomString) + bidRequest.ext.prebid.multibid = [genericMultiBid, rubiconMultiBid] + + and: "Default basic bid" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bid request should contain requested ext.prebid.multiBid" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.multibid == [genericMultiBid] + } + + def "PBS should pass ext.prebid.bidAdjustment only bidder related entry for each bidder"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def rule = new BidAdjustmentRule().tap { + generic = ["*": [new AdjustmentRule(adjustmentType: MULTIPLIER, value: PBSUtils.randomPrice, currency: currency)]] + openx = ["*": [new AdjustmentRule(adjustmentType: MULTIPLIER, value: PBSUtils.randomPrice, currency: currency)]] + } + def bidRequest = BidRequest.getDefaultBidRequest().tap { + ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, rule) + cur = [currency] + imp.first.bidFloor = PBSUtils.randomPrice + imp.first.bidFloorCur = currency + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain generic bid adjustments" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.bidAdjustments.version == bidRequest.ext.prebid.bidAdjustments.version + assert bidderRequest.ext.prebid.bidAdjustments.mediaType[BANNER].generic == bidRequest.ext.prebid.bidAdjustments.mediaType[BANNER].generic + + and: "Bidder request shouldn't contain openx bid adjustments" + assert !bidderRequest.ext.prebid.bidAdjustments.mediaType[BANNER].openx + } + + def "PBS should pass ext.prebid.bidAdjustmentFactors only bidder related entry for each bidder"() { + given: "Default bid request with bid adjustment" + def bidAdjustment = roundDecimal(getRandomDecimal(), 0) + def mediaTypeBidAdjustment = bidAdjustmentFactor + def bidRequest = BidRequest.getDefaultBidRequest(SITE).tap { + ext.prebid.bidAdjustmentFactors = new BidAdjustmentFactors().tap { + adjustments = [(BidderName.GENERIC): bidAdjustment, (OPENX): bidAdjustment] + mediaTypes = [(BANNER): [(BidderName.GENERIC): mediaTypeBidAdjustment], + (BANNER): [(OPENX): mediaTypeBidAdjustment]] + } + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain generic bid adjustment factors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.ext.prebid.bidAdjustmentFactors.adjustments[BidderName.GENERIC] == bidRequest.ext.prebid.bidAdjustmentFactors.adjustments[BidderName.GENERIC] + assert bidderRequest.ext.prebid.bidAdjustmentFactors.mediaTypes[BANNER][GENERIC] == bidRequest.ext.prebid.bidAdjustmentFactors.mediaTypes[BANNER][GENERIC] + + and: "Bidder request shouldn't contain opneX bid adjustment factors for generic call" + assert bidderRequest.ext.prebid.bidAdjustmentFactors.adjustments[OPENX] + assert bidderRequest.ext.prebid.bidAdjustmentFactors.mediaTypes[BANNER][OPENX] + + where: + bidAdjustmentFactor << [0.9, 1.1] + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitTidActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitTidActivitiesSpec.groovy index c3f359d2113..da13a3c1ff8 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitTidActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitTidActivitiesSpec.groovy @@ -92,9 +92,6 @@ class GppTransmitTidActivitiesSpec extends PrivacyBaseSpec { bidderRequest.source.tid } - and: "Bidder request should contain ext.prebid.creativeTids" - assert bidderRequest.ext.prebid.createTids - where: "Activities fields name in different case" activities << [AllowActivities.getDefaultAllowActivities(TRANSMIT_TID, Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, false)])), new AllowActivities().tap { transmitTidKebabCase = Activity.getDefaultActivity([ActivityRule.getDefaultActivityRule(Condition.baseCondition, false)]) }, From dc3d8e69cbf27954790ac6473f82e91a464ffd52 Mon Sep 17 00:00:00 2001 From: markiian Date: Fri, 9 Jan 2026 13:14:53 +0200 Subject: [PATCH 13/13] Add coverage for `ext?.prebid?.bidAdjustmentFactors?.mediaTypes` --- .../tests/BidderFieldDisplayBehaviorSpec.groovy | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy index c12d4f6d432..955a677bd65 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderFieldDisplayBehaviorSpec.groovy @@ -51,6 +51,7 @@ import static org.prebid.server.functional.model.bidder.BidderName.RUBICON import static org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse.defaultConversionRates import static org.prebid.server.functional.model.request.auction.AdjustmentType.MULTIPLIER import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.BANNER +import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.VIDEO import static org.prebid.server.functional.model.request.auction.DebugCondition.ENABLED import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP import static org.prebid.server.functional.model.request.auction.DistributionChannel.DOOH @@ -62,6 +63,8 @@ import static org.prebid.server.functional.util.PBSUtils.roundDecimal class BidderFieldDisplayBehaviorSpec extends BaseSpec { + private static final WILDCARD = "*" + def "PBS should pass ext.prebid.createTids to bidder request"() { given: "Default bid request" def bidRequest = BidRequest.defaultBidRequest.tap { @@ -575,8 +578,8 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { given: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD def rule = new BidAdjustmentRule().tap { - generic = ["*": [new AdjustmentRule(adjustmentType: MULTIPLIER, value: PBSUtils.randomPrice, currency: currency)]] - openx = ["*": [new AdjustmentRule(adjustmentType: MULTIPLIER, value: PBSUtils.randomPrice, currency: currency)]] + generic = [(WILDCARD): [new AdjustmentRule(adjustmentType: MULTIPLIER, value: PBSUtils.randomPrice, currency: currency)]] + openx = [(WILDCARD): [new AdjustmentRule(adjustmentType: MULTIPLIER, value: PBSUtils.randomPrice, currency: currency)]] } def bidRequest = BidRequest.getDefaultBidRequest().tap { ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, rule) @@ -609,7 +612,7 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { ext.prebid.bidAdjustmentFactors = new BidAdjustmentFactors().tap { adjustments = [(BidderName.GENERIC): bidAdjustment, (OPENX): bidAdjustment] mediaTypes = [(BANNER): [(BidderName.GENERIC): mediaTypeBidAdjustment], - (BANNER): [(OPENX): mediaTypeBidAdjustment]] + (VIDEO): [(OPENX): mediaTypeBidAdjustment]] } } @@ -626,8 +629,9 @@ class BidderFieldDisplayBehaviorSpec extends BaseSpec { assert bidderRequest.ext.prebid.bidAdjustmentFactors.mediaTypes[BANNER][GENERIC] == bidRequest.ext.prebid.bidAdjustmentFactors.mediaTypes[BANNER][GENERIC] and: "Bidder request shouldn't contain opneX bid adjustment factors for generic call" - assert bidderRequest.ext.prebid.bidAdjustmentFactors.adjustments[OPENX] - assert bidderRequest.ext.prebid.bidAdjustmentFactors.mediaTypes[BANNER][OPENX] + assert !bidderRequest?.ext?.prebid?.bidAdjustmentFactors?.adjustments[OPENX] + assert !bidderRequest?.ext?.prebid?.bidAdjustmentFactors?.mediaTypes[BANNER][OPENX] + assert !bidderRequest?.ext?.prebid?.bidAdjustmentFactors?.mediaTypes[VIDEO] where: bidAdjustmentFactor << [0.9, 1.1]