diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy index 54e0a451fc6..702918fa886 100644 --- a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy @@ -11,6 +11,7 @@ enum BidderName { BOGUS("bogus"), ALIAS("alias"), ALIAS_CAMEL_CASE("AlIaS"), + ALIAS_UPPER_CASE("ALIAS"), GENERIC_CAMEL_CASE("GeNerIc"), GENERIC("generic"), GENER_X("gener_x"), @@ -25,7 +26,10 @@ enum BidderName { ADKERNEL("adkernel"), IX("ix"), GRID("grid"), - MEDIANET("medianet") + MEDIANET("medianet"), + AMX("amx"), + AMX_CAMEL_CASE("AmX"), + AMX_UPPER_CASE("AMX"), @JsonValue final String value diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/GeneralBidderAdapter.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/GeneralBidderAdapter.groovy index 3184fb17fee..b583363974c 100644 --- a/src/test/groovy/org/prebid/server/functional/model/bidder/GeneralBidderAdapter.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/bidder/GeneralBidderAdapter.groovy @@ -1,8 +1,14 @@ package org.prebid.server.functional.model.bidder +import com.fasterxml.jackson.annotation.JsonProperty + class GeneralBidderAdapter extends Generic { String siteId List size String sid + @JsonProperty("ds") + String demandSource + @JsonProperty("bc") + BidderName bidderCode } diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy index 60563ac2f5e..9dded674fee 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy @@ -23,6 +23,9 @@ class AccountConfig { AccountSetting settings @JsonProperty("cookie_sync") AccountCookieSyncConfig cookieSyncSnakeCase + AlternateBidderCodes alternateBidderCodes + @JsonProperty("alternate_bidder_codes") + AlternateBidderCodes alternateBidderCodesSnakeCase static getDefaultAccountConfig() { new AccountConfig(status: AccountStatus.ACTIVE) diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AlternateBidderCodes.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AlternateBidderCodes.groovy new file mode 100644 index 00000000000..465acaad2cc --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/AlternateBidderCodes.groovy @@ -0,0 +1,16 @@ +package org.prebid.server.functional.model.config + +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 + +@EqualsAndHashCode +@ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) +class AlternateBidderCodes { + + Boolean enabled + Map bidders +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/BidderConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/BidderConfig.groovy new file mode 100644 index 00000000000..e139419e4e3 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/BidderConfig.groovy @@ -0,0 +1,21 @@ +package org.prebid.server.functional.model.config + +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.bidder.BidderName + +@EqualsAndHashCode +@ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) +class BidderConfig { + + Boolean enabled + List allowedBidderCodes + @JsonProperty("allowedbiddercodes") + List allowedBidderCodesLowerCase + @JsonProperty("allowed_bidder_codes") + List allowedBidderCodesSnakeCase +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Amx.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Amx.groovy new file mode 100644 index 00000000000..3ae0dcd17a3 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Amx.groovy @@ -0,0 +1,17 @@ +package org.prebid.server.functional.model.request.auction + +import com.fasterxml.jackson.annotation.JsonProperty +import org.prebid.server.functional.model.bidder.BidderAdapter +import org.prebid.server.functional.model.bidder.BidderName + +class Amx implements BidderAdapter { + + @JsonProperty("ct") + Integer creativeType + @JsonProperty("startdelay") + Integer startDelay + @JsonProperty("ds") + String demandSource + @JsonProperty("bc") + BidderName bidderCode +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Bidder.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Bidder.groovy index 125faa37c4b..9c4a17ec5cf 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Bidder.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Bidder.groovy @@ -12,6 +12,8 @@ import org.prebid.server.functional.model.bidder.Rubicon class Bidder { Generic alias + @JsonProperty("ALIAS") + Generic aliasUpperCase Generic generic @JsonProperty("gener_x") Generic generX @@ -26,6 +28,9 @@ class Bidder { Openx openxAlias Adrino adrino Generic nativo + Amx amx + @JsonProperty("AMX") + Amx amxUpperCase static Bidder getDefaultBidder() { new Bidder().tap { 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 30149d1a5ee..499dccea5a3 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 @@ -1,10 +1,12 @@ 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.ToString import org.prebid.server.functional.model.ChannelType import org.prebid.server.functional.model.bidder.BidderName +import org.prebid.server.functional.model.config.AlternateBidderCodes @JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) @ToString(includeNames = true, ignoreNulls = true) @@ -41,6 +43,8 @@ class Prebid { PrebidAnalytics analytics StoredAuctionResponse storedAuctionResponse PaaFormat paaFormat + @JsonProperty("alternatebiddercodes") + AlternateBidderCodes alternateBidderCodes static class Channel { diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExt.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExt.groovy index 61cdd7ad5f0..58d2e2178e3 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExt.groovy @@ -1,7 +1,9 @@ package org.prebid.server.functional.model.response.auction +import com.fasterxml.jackson.annotation.JsonProperty import groovy.transform.ToString import org.prebid.server.functional.model.Currency +import org.prebid.server.functional.model.bidder.BidderName @ToString(includeNames = true, ignoreNulls = true) class BidExt { @@ -10,4 +12,12 @@ class BidExt { BigDecimal origbidcpm Currency origbidcur DsaResponse dsa + @JsonProperty("ct") + Integer creativeType + @JsonProperty("startdelay") + Integer startDelay + @JsonProperty("ds") + String demandSource + @JsonProperty("bc") + BidderName bidderCode } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy index 267a23cc067..80f504a05ec 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy @@ -14,7 +14,9 @@ enum ErrorType { ALIAS("alias"), TARGETING("targeting"), IX("ix"), - OPENX("openx") + OPENX("openx"), + AMX("amx"), + AMX_UPPER_CASE("AMX"), @JsonValue final String value diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Meta.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Meta.groovy index d022daf9646..e8bb869ebae 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/Meta.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Meta.groovy @@ -1,12 +1,15 @@ package org.prebid.server.functional.model.response.auction +import com.fasterxml.jackson.annotation.JsonProperty import groovy.transform.ToString +import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.request.auction.RendererData @ToString(includeNames = true, ignoreNulls = true) class Meta { - String adapterCode + @JsonProperty("adaptercode") + BidderName adapterCode List advertiserDomains Integer advertiserId String advertiserName diff --git a/src/test/groovy/org/prebid/server/functional/tests/AliasSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AliasSpec.groovy index 4e544498f44..9013978f76f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/AliasSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/AliasSpec.groovy @@ -110,7 +110,7 @@ class AliasSpec extends BaseSpec { then: "Request should fail with error" def exception = thrown(PrebidServerException) assert exception.responseBody.contains("Invalid request format: request.ext.prebid.aliasgvlids. " + - "vendorId ${validId} refers to unknown bidder alias: ${bidderName}") + "vendorId ${validId} refers to unknown bidder alias: ${bidderName.toLowerCase()}") } def "PBS should return an error when GVL ID alias value is lower that one"() { @@ -126,7 +126,7 @@ class AliasSpec extends BaseSpec { then: "Request should fail with error" def exception = thrown(PrebidServerException) assert exception.responseBody.contains("Invalid request format: request.ext.prebid.aliasgvlids. " + - "Invalid vendorId ${invalidId} for alias: ${bidderName}. Choose a different vendorId, or remove this entry.") + "Invalid vendorId ${invalidId} for alias: ${bidderName.toLowerCase()}. Choose a different vendorId, or remove this entry.") where: invalidId << [PBSUtils.randomNegativeNumber, 0] @@ -145,7 +145,7 @@ class AliasSpec extends BaseSpec { then: "Request should fail with an error" def exception = thrown(PrebidServerException) assert exception.statusCode == BAD_REQUEST.code() - assert exception.responseBody == "Invalid request format: request.ext.prebid.aliases.$randomString " + + assert exception.responseBody == "Invalid request format: request.ext.prebid.aliases.${randomString.toLowerCase()} " + "refers to unknown bidder: $BOGUS.value" } diff --git a/src/test/groovy/org/prebid/server/functional/tests/AlternateBidderCodeSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AlternateBidderCodeSpec.groovy new file mode 100644 index 00000000000..048a4983050 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/AlternateBidderCodeSpec.groovy @@ -0,0 +1,1436 @@ +package org.prebid.server.functional.tests + +import org.prebid.server.functional.model.bidder.Generic +import org.prebid.server.functional.model.config.AccountConfig +import org.prebid.server.functional.model.config.AlternateBidderCodes +import org.prebid.server.functional.model.config.BidderConfig +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.request.auction.Amx +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.Targeting +import org.prebid.server.functional.model.response.auction.BidExt +import org.prebid.server.functional.model.response.auction.BidResponse +import org.prebid.server.functional.model.response.auction.ErrorType +import org.prebid.server.functional.service.PrebidServerService +import org.prebid.server.functional.util.PBSUtils +import spock.lang.Shared + +import static org.prebid.server.functional.model.AccountStatus.ACTIVE +import static org.prebid.server.functional.model.bidder.BidderName.ALIAS +import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_CAMEL_CASE +import static org.prebid.server.functional.model.bidder.BidderName.AMX +import static org.prebid.server.functional.model.bidder.BidderName.AMX_CAMEL_CASE +import static org.prebid.server.functional.model.bidder.BidderName.BOGUS +import static org.prebid.server.functional.model.bidder.BidderName.EMPTY +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.UNKNOWN +import static org.prebid.server.functional.model.bidder.BidderName.WILDCARD +import static org.prebid.server.functional.model.response.auction.BidRejectionReason.RESPONSE_REJECTED_GENERAL +import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer + +class AlternateBidderCodeSpec extends BaseSpec { + + private static final String ADAPTER_RESPONSE_VALIDATION_METRICS = "adapter.%s.response.validation.seat" + private static final String ERROR_BID_CODE_VALIDATION = "BidId `%s` validation messages: " + + "Error: invalid bidder code %s was set by the adapter %s for the account %s" + private static final String INVALID_BIDDER_CODE_LOGS = "invalid bidder code %s was set by the adapter %s for the account %s" + private static final Map AMX_CONFIG = ["adapters.amx.enabled" : "true", + "adapters.amx.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + @Shared + private static final PrebidServerService pbsServiceWithAmxBidder = pbsServiceFactory.getService(AMX_CONFIG) + + @Override + def cleanupSpec() { + pbsServiceFactory.removeContainer(AMX_CONFIG) + } + + def "PBS shouldn't discard bid amx alias when soft alias request with allowed bidder code"() { + given: "Default bid request with amx bidder" + def bidRequest = getBidRequestWithAmxBidder().tap { + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.amx = null + ext.prebid.aliases = [(ALIAS.value): AMX] + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: ALIAS) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response should contain seat" + assert response.seatbid.seat == [ALIAS] + + and: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${ALIAS}"] + assert targeting["hb_size_${ALIAS}"] + assert targeting["hb_bidder"] == ALIAS.value + assert targeting["hb_bidder_${ALIAS}"] == ALIAS.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings and error and seatNonBid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "PBS shouldn't emit validation metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(ALIAS)] + } + + def "PBS should populate meta demand source when bid response with demand source"() { + given: "Default bid request with amx bidder" + def bidRequest = getBidRequestWithAmxBidder() + + and: "Bid response with demand source" + def demandSource = PBSUtils.getRandomString() + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(demandSource: demandSource) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response should contain seat" + assert response.seatbid.seat == [AMX] + + and: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain demand source" + assert response.seatbid.bid.ext.prebid.meta.demandSource.flatten() == [demandSource] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${AMX}"] + assert targeting["hb_size_${AMX}"] + assert targeting["hb_bidder"] == AMX.value + assert targeting["hb_bidder_${AMX}"] == AMX.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings and error and seatNonBid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "PBS shouldn't emit validation metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + } + + def "PBS shouldn't populate meta demand source when bid response without demand source"() { + given: "Default bid request with amx bidder" + def bidRequest = getBidRequestWithAmxBidder() + + and: "Bid response without demand source" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(demandSource: null) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response should contain seat" + assert response.seatbid.seat == [AMX] + + and: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${AMX}"] + assert targeting["hb_size_${AMX}"] + assert targeting["hb_bidder"] == AMX.value + assert targeting["hb_bidder_${AMX}"] == AMX.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings and error and seatNonBid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "PBS shouldn't emit validation metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + } + + def "PBS shouldn't discard bid for amx bidder same seat in response as seat in bid.ext.bidderCode"() { + given: "Default bid request with amx bidder" + def bidRequest = getBidRequestWithAmxBidder() + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: bidderCode) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response should contain seat" + assert response.seatbid.seat == [bidderCode] + + and: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${bidderCode}"] + assert targeting["hb_size_${bidderCode}"] + assert targeting["hb_bidder"] == bidderCode.value + assert targeting["hb_bidder_${bidderCode}"] == bidderCode.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings and error and seatNonBid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "PBS shouldn't emit validation metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + where: + bidderCode << [AMX, AMX_CAMEL_CASE] + } + + def "PBS should discard bid for amx bidder when imp[].bidder isn't same as in bid.ext.bidderCode"() { + given: "Default bid request with amx bidder" + def bidRequest = getBidRequestWithAmxBidder() + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: bidderName) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't seat bid" + assert response.seatbid.isEmpty() + + and: "Response should seatNon bid with code 300" + assert response.ext.seatnonbid.size() == 1 + + def seatNonBid = response.ext.seatnonbid[0] + assert seatNonBid.seat == AMX.value + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL + + and: "Response should contain error" + def error = response.ext?.errors[ErrorType.AMX][0] + assert error.code == 5 + assert error.message == ERROR_BID_CODE_VALIDATION + .formatted(bidResponse.seatbid[0].bid[0].id, bidderName, AMX, bidRequest.accountId) + + and: "PBS should emit logs" + def logs = pbsServiceWithAmxBidder.getLogsByValue(bidRequest.accountId) + assert logs.contains(INVALID_BIDDER_CODE_LOGS.formatted(bidderName, AMX, bidRequest.accountId)) + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "PBS should emit metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + where: + bidderName << [BOGUS, UNKNOWN, WILDCARD] + } + + def "PBS should discard bid amx alias requested when imp[].bidder isn't same as in bid.ext.bidderCode"() { + given: "Default bid request with amx bidder" + def bidRequest = getBidRequestWithAmxBidder().tap { + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.amx = null + ext.prebid.aliases = [(ALIAS.value): AMX] + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: bidderName) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't seat bid" + assert response.seatbid.isEmpty() + + and: "Response should seatNon bid with code 300" + assert response.ext.seatnonbid.size() == 1 + + def seatNonBid = response.ext.seatnonbid[0] + assert seatNonBid.seat == ALIAS.value + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL + + and: "Response should contain error" + def error = response.ext?.errors[ErrorType.ALIAS][0] + assert error.code == 5 + assert error.message == ERROR_BID_CODE_VALIDATION + .formatted(bidResponse.seatbid[0].bid[0].id, bidderName, ALIAS, bidRequest.accountId) + + and: "PBS should emit logs" + def logs = pbsServiceWithAmxBidder.getLogsByValue(bidRequest.accountId) + assert logs.contains(INVALID_BIDDER_CODE_LOGS.formatted(bidderName, ALIAS, bidRequest.accountId)) + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "PBS should emit validation metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(ALIAS)] + + where: + bidderName << [BOGUS, UNKNOWN, WILDCARD] + } + + def "PBS shouldn't discard bid amx alias requested when imp[].bidder is same as in bid.ext.bidderCode and alternate bidder code allow"() { + given: "Default bid request with amx bidder" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap { + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.amx = null + ext.prebid.aliases = [(ALIAS.value): AMX] + ext.prebid.alternateBidderCodes = requestAlternateBidderCode + } + + and: "Save account config into DB with alternate bidder codes" + def account = getAccountWithAlternateBidderCode(bidRequest).tap { + config.alternateBidderCodes = accountAlternateBidderCodes + } + accountDao.save(account) + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, ALIAS).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response should contain exp data" + assert response.seatbid.seat == [GENERIC] + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${GENERIC}"] + assert targeting["hb_size_${GENERIC}"] + assert targeting["hb_bidder"] == GENERIC.value + assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings and error and seatNonBid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "PBS shouldn't emit validation metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(ALIAS)] + + where: + requestAlternateBidderCode | accountAlternateBidderCodes + new AlternateBidderCodes(enabled: true, bidders: [(ALIAS): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])]) | null + null | new AlternateBidderCodes(enabled: true, bidders: [(ALIAS): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])]) + } + + def "PBS shouldn't discard bid amx alias requested when imp[].bidder is same as in bid.ext.bidderCode"() { + given: "Default bid request with amx bidder" + def bidRequest = getBidRequestWithAmxBidder().tap { + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.amx = null + ext.prebid.aliases = [(ALIAS.value): AMX] + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, ALIAS).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: bidderCode) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response should contain exp data" + assert response.seatbid.seat == [bidderCode] + + and: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${bidderCode}"] + assert targeting["hb_size_${bidderCode}"] + assert targeting["hb_bidder"] == bidderCode.value + assert targeting["hb_bidder_${bidderCode}"] == bidderCode.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings and error and seatNonBid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "PBS shouldn't emit validation metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(bidderCode)] + + where: + bidderCode << [ALIAS, ALIAS_CAMEL_CASE] + } + + def "PBS shouldn't discard the bid or emit a response warning when account alternate bidder codes not fully configured"() { + given: "Default bid request with alternate bidder codes" + def bidRequest = getBidRequestWithAmxBidder() + + and: "Save account config into DB with alternate bidder codes" + def account = getAccountWithAlternateBidderCode(bidRequest).tap { + config.alternateBidderCodes = accountAlternateBidderCodes + } + accountDao.save(account) + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: AMX) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain seatbid.seat" + assert response.seatbid[0].seat == AMX + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${AMX}"] + assert targeting["hb_size_${AMX}"] + assert targeting["hb_bidder"] == AMX.value + assert targeting["hb_bidder_${AMX}"] == AMX.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings,errors and seatnonbid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Metric shouldn't be updated" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + where: + accountAlternateBidderCodes << [null, + new AlternateBidderCodes(), + new AlternateBidderCodes(enabled: true), + new AlternateBidderCodes(enabled: false), + new AlternateBidderCodes(bidders: [(AMX): new BidderConfig()]), + new AlternateBidderCodes(bidders: [(UNKNOWN): new BidderConfig()]), + new AlternateBidderCodes(enabled: true, bidders: [(AMX): new BidderConfig()]), + new AlternateBidderCodes(enabled: true, bidders: [(UNKNOWN): new BidderConfig()]), + new AlternateBidderCodes(enabled: false, bidders: [(UNKNOWN): new BidderConfig()]), + new AlternateBidderCodes(enabled: false, bidders: [(AMX): new BidderConfig()]), + new AlternateBidderCodes(bidders: [(AMX): new BidderConfig(enabled: false, allowedBidderCodes: [UNKNOWN])]), + new AlternateBidderCodes(bidders: [(UNKNOWN): new BidderConfig(enabled: false, allowedBidderCodes: [AMX])]), + new AlternateBidderCodes(enabled: false, bidders: [(AMX): new BidderConfig(enabled: false, allowedBidderCodes: [UNKNOWN])]), + new AlternateBidderCodes(enabled: false, bidders: [(UNKNOWN): new BidderConfig(enabled: false, allowedBidderCodes: [AMX])]), + new AlternateBidderCodes(enabled: true, bidders: [(AMX): new BidderConfig(enabled: false, allowedBidderCodes: [UNKNOWN])]), + new AlternateBidderCodes(enabled: true, bidders: [(UNKNOWN): new BidderConfig(enabled: false, allowedBidderCodes: [AMX])])] + } + + def "PBS shouldn't discard the bid or emit a response warning when request alternate bidder codes not fully configured"() { + given: "Default bid request with alternate bidder codes" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap { + ext.prebid.alternateBidderCodes = requestedAlternateBidderCodes + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: AMX) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain seatbid.seat" + assert response.seatbid[0].seat == AMX + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${AMX}"] + assert targeting["hb_size_${AMX}"] + assert targeting["hb_bidder"] == AMX.value + assert targeting["hb_bidder_${AMX}"] == AMX.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings,errors and seatnonbid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Metric shouldn't be updated" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + where: + requestedAlternateBidderCodes << [null, + new AlternateBidderCodes(), + new AlternateBidderCodes(enabled: true), + new AlternateBidderCodes(enabled: false), + new AlternateBidderCodes(bidders: [(AMX): new BidderConfig()]), + new AlternateBidderCodes(bidders: [(UNKNOWN): new BidderConfig()]), + new AlternateBidderCodes(enabled: true, bidders: [(AMX): new BidderConfig()]), + new AlternateBidderCodes(enabled: true, bidders: [(UNKNOWN): new BidderConfig()]), + new AlternateBidderCodes(enabled: false, bidders: [(UNKNOWN): new BidderConfig()]), + new AlternateBidderCodes(enabled: false, bidders: [(AMX): new BidderConfig()]), + new AlternateBidderCodes(bidders: [(AMX): new BidderConfig(enabled: false, allowedBidderCodes: [UNKNOWN])]), + new AlternateBidderCodes(bidders: [(UNKNOWN): new BidderConfig(enabled: false, allowedBidderCodes: [AMX])]), + new AlternateBidderCodes(enabled: false, bidders: [(AMX): new BidderConfig(enabled: false, allowedBidderCodes: [UNKNOWN])]), + new AlternateBidderCodes(enabled: false, bidders: [(UNKNOWN): new BidderConfig(enabled: false, allowedBidderCodes: [AMX])]), + new AlternateBidderCodes(enabled: true, bidders: [(AMX): new BidderConfig(enabled: false, allowedBidderCodes: [UNKNOWN])]), + new AlternateBidderCodes(enabled: true, bidders: [(UNKNOWN): new BidderConfig(enabled: false, allowedBidderCodes: [AMX])])] + } + + def "PBS shouldn't discard bid when alternate bidder code allows bidder codes fully configured and bidder requested in uppercase"() { + given: "Default bid request with AMX bidder" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap { + imp[0].ext.prebid.bidder.tap { + amxUpperCase = new Amx() + amx = null + } + ext.prebid.alternateBidderCodes.bidders[AMX].allowedBidderCodesLowerCase = [GENERIC] + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain seatbid.seat" + assert response.seatbid[0].seat == GENERIC + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${GENERIC}"] + assert targeting["hb_size_${GENERIC}"] + assert targeting["hb_bidder"] == GENERIC.value + assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Response shouldn't contain warnings,errors and seatnonbid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "Metric shouldn't be updated" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(GENERIC)] + } + + def "PBS shouldn't discard bid when alternate bidder code allows bidder codes fully configured with different case"() { + given: "Default bid request with amx bidder" + def bidRequest = getBidRequestWithAmxBidder() + + and: "Save account config into DB with alternate bidder codes" + def account = getAccountWithAlternateBidderCode(bidRequest).tap { + config = configAccountAlternateBidderCodes + } + accountDao.save(account) + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain seatbid.seat" + assert response.seatbid[0].seat == GENERIC + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${GENERIC}"] + assert targeting["hb_size_${GENERIC}"] + assert targeting["hb_bidder"] == GENERIC.value + assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings,errors and seatnonbid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Metric shouldn't be updated" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + where: + configAccountAlternateBidderCodes << [ + new AccountConfig(alternateBidderCodesSnakeCase: new AlternateBidderCodes(enabled: true, bidders: [(AMX): new BidderConfig(enabled: true, allowedBidderCodesSnakeCase: [GENERIC])])), + new AccountConfig(alternateBidderCodes: new AlternateBidderCodes(enabled: true, bidders: [(AMX): new BidderConfig(enabled: true, allowedBidderCodesSnakeCase: [GENERIC])])), + new AccountConfig(alternateBidderCodesSnakeCase: new AlternateBidderCodes(enabled: true, bidders: [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])]))] + } + + def "PBS should take precede of request and discard the bid and emit a response error when alternate bidder codes enabled and bidder came with different bidder code"() { + given: "Default bid request with alternate bidder codes" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode() + + and: "Save account config into DB with alternate bidder codes" + def account = getAccountWithAlternateBidderCode(bidRequest).tap { + config.alternateBidderCodes.bidders[AMX].allowedBidderCodes = [UNKNOWN] + } + accountDao.save(account) + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: UNKNOWN) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't seat bid" + assert response.seatbid.isEmpty() + + and: "Response should seatNon bid with code 300" + assert response.ext.seatnonbid.size() == 1 + + def seatNonBid = response.ext.seatnonbid[0] + assert seatNonBid.seat == AMX.value + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL + + and: "Response should contain error" + def error = response.ext?.errors[ErrorType.AMX][0] + assert error.code == 5 + assert error.message == ERROR_BID_CODE_VALIDATION + .formatted(bidResponse.seatbid[0].bid[0].id, UNKNOWN, AMX, bidRequest.accountId) + + and: "PBS should emit logs" + def logs = pbsServiceWithAmxBidder.getLogsByValue(bidRequest.accountId) + assert logs.contains(INVALID_BIDDER_CODE_LOGS.formatted(UNKNOWN, AMX, bidRequest.accountId)) + + and: "PBS should emit metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + } + + def "PBS should discard the bid and emit a response warning when alternate bidder codes disabled and bidder came with different bidderCode"() { + given: "Default bid request with alternate bidder codes" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap { + ext.prebid.alternateBidderCodes.enabled = requestedAlternateBidderCodes + } + + and: "Save account config into DB with alternate bidder codes" + def account = getAccountWithAlternateBidderCode(bidRequest).tap { + config.alternateBidderCodes.enabled = accountAlternateBidderCodes + } + accountDao.save(account) + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: UNKNOWN) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't seat bid" + assert response.seatbid.isEmpty() + + and: "Response should seatNon bid with code 300" + assert response.ext.seatnonbid.size() == 1 + + def seatNonBid = response.ext.seatnonbid[0] + assert seatNonBid.seat == AMX.value + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL + + and: "Response should contain error" + def error = response.ext?.errors[ErrorType.AMX][0] + assert error.code == 5 + assert error.message == ERROR_BID_CODE_VALIDATION + .formatted(bidResponse.seatbid[0].bid[0].id, UNKNOWN, AMX, bidRequest.accountId) + + and: "PBS should emit logs" + def logs = pbsServiceWithAmxBidder.getLogsByValue(bidRequest.accountId) + assert logs.contains(INVALID_BIDDER_CODE_LOGS.formatted(UNKNOWN, AMX, bidRequest.accountId)) + + and: "PBS should emit metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + where: + requestedAlternateBidderCodes | accountAlternateBidderCodes + false | true + false | false + false | null + null | false + } + + def "PBS shouldn't discard the bid or emit a response warning when account alternate bidder codes are enabled and allowed bidder codes are either a wildcard or empty"() { + given: "Default bid request with alternate bidder codes" + def bidRequest = getBidRequestWithAmxBidder() + + and: "Save account config into DB with alternate bidder codes" + def account = getAccountWithAlternateBidderCode(bidRequest).tap { + config.alternateBidderCodes.bidders[AMX].allowedBidderCodes = accountAllowedBidderCodes + } + accountDao.save(account) + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain seatbid.seat" + assert response.seatbid.seat.flatten() == [GENERIC] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${GENERIC}"] + assert targeting["hb_size_${GENERIC}"] + assert targeting["hb_bidder"] == GENERIC.value + assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Response shouldn't contain warnings and errors and seatNonBid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "PBs metric shouldn't be updated" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + where: + accountAllowedBidderCodes << [[WILDCARD], [WILDCARD, EMPTY], [EMPTY, WILDCARD], null] + } + + def "PBS shouldn't discard the bid or emit a response warning when request alternate bidder codes are enabled and allowed bidder codes are either a wildcard or empty"() { + given: "Default bid request with alternate bidder codes" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap { + ext.prebid.alternateBidderCodes.bidders[AMX].allowedBidderCodesLowerCase = requestedAllowedBidderCodes + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain seatbid.seat" + assert response.seatbid.seat.flatten() == [GENERIC] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${GENERIC}"] + assert targeting["hb_size_${GENERIC}"] + assert targeting["hb_bidder"] == GENERIC.value + assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings and errors and seatNonBid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "PBs metric shouldn't be updated" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + where: + requestedAllowedBidderCodes << [[WILDCARD], [WILDCARD, EMPTY], [EMPTY, WILDCARD], null] + } + + def "PBS shouldn't discard the bid or emit a response warning when request alternate bidder codes are enabled and the allowed bidder codes is same as bidder's request"() { + given: "Default bid request with alternate bidder codes" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap { + ext.prebid.alternateBidderCodes.bidders = requestAlternateBidders + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain seatbid.seat" + assert response.seatbid.seat.flatten() == [GENERIC] + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${GENERIC}"] + assert targeting["hb_size_${GENERIC}"] + assert targeting["hb_bidder"] == GENERIC.value + assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings and errors and seatNonBid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "PBs metric shouldn't be updated" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + where: + requestAlternateBidders << [[(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])], + [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC_CAMEL_CASE])], + [(AMX_CAMEL_CASE): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC_CAMEL_CASE])], + [(AMX_CAMEL_CASE): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])]] + } + + def "PBS shouldn't discard the bid or emit a response warning when account alternate bidder codes are enabled and the allowed bidder codes is same as bidder's request"() { + given: "Default bid request with alternate bidder codes" + def bidRequest = getBidRequestWithAmxBidder() + + and: "Save account config into DB with alternate bidder codes" + def account = getAccountWithAlternateBidderCode(bidRequest).tap { + config.alternateBidderCodes.bidders = accountAlternateBidders + } + accountDao.save(account) + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain seatbid.seat" + assert response.seatbid.seat.flatten() == [GENERIC] + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${GENERIC}"] + assert targeting["hb_size_${GENERIC}"] + assert targeting["hb_bidder"] == GENERIC.value + assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value + + and: "Response shouldn't contain warnings and errors and seatNonBid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "PBs metric shouldn't be updated" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + where: + accountAlternateBidders << [[(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])], + [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC_CAMEL_CASE])], + [(AMX_CAMEL_CASE): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC_CAMEL_CASE])], + [(AMX_CAMEL_CASE): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])]] + } + + def "PBS shouldn't discard the bid or emit a response warning when default account alternate bidder codes are enabled and the allowed bidder codes match the bidder's request"() { + given: "Pbs config with default-account-config of alternate bidder code" + def defaultAccountConfig = AccountConfig.defaultAccountConfig.tap { + alternateBidderCodes = new AlternateBidderCodes().tap { + it.enabled = true + it.bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])] + } + } + def config = AMX_CONFIG + ["settings.default-account-config": encode(defaultAccountConfig)] + def pbsService = pbsServiceFactory.getService(config) + + and: "Default bid request" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap { + ext.prebid.alternateBidderCodes = null + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: AMX) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsService) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Response should contain seatbid.seat" + assert response.seatbid.seat.flatten() == [AMX] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${AMX}"] + assert targeting["hb_size_${AMX}"] + assert targeting["hb_bidder"] == AMX.value + assert targeting["hb_bidder_${AMX}"] == AMX.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings and errors and seatnonbid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "Alert.general metric shouldn't be updated" + def metrics = pbsService.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(config) + } + + def "PBS should discard the bid and emit a response warning when request alternate bidder codes are enabled and the allowed bidder codes doesn't match the bidder's request"() { + given: "Default bid request with alternate bidder codes" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode() + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: requestedAllowedBidderCode) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't seat bid" + assert response.seatbid.isEmpty() + + and: "Response should seatNon bid with code 300" + assert response.ext.seatnonbid.size() == 1 + + def seatNonBid = response.ext.seatnonbid[0] + assert seatNonBid.seat == AMX.value + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL + + and: "Response should contain error" + def error = response.ext?.errors[ErrorType.AMX][0] + assert error.code == 5 + assert error.message == ERROR_BID_CODE_VALIDATION + .formatted(bidResponse.seatbid[0].bid[0].id, requestedAllowedBidderCode, AMX, bidRequest.accountId) + + and: "PBS should emit logs" + def logs = pbsServiceWithAmxBidder.getLogsByValue(bidRequest.accountId) + assert logs.contains(INVALID_BIDDER_CODE_LOGS.formatted(requestedAllowedBidderCode, AMX, bidRequest.accountId)) + + and: "PBS should emit metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + where: + requestedAllowedBidderCode << [UNKNOWN, BOGUS] + } + + def "PBS should discard the bid and emit a response warning when account alternate bidder codes are enabled and the allowed bidder codes doesn't match the bidder's request"() { + given: "Default bid request with alternate bidder codes" + def bidRequest = getBidRequestWithAmxBidder() + + and: "Save account config into DB with alternate bidder codes" + def account = getAccountWithAlternateBidderCode(bidRequest) + accountDao.save(account) + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: requestedAllowedBidderCode) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't seat bid" + assert response.seatbid.isEmpty() + + and: "Response should seatNon bid with code 300" + assert response.ext.seatnonbid.size() == 1 + + def seatNonBid = response.ext.seatnonbid[0] + assert seatNonBid.seat == AMX.value + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL + + and: "Response should contain error" + def error = response.ext?.errors[ErrorType.AMX][0] + assert error.code == 5 + assert error.message == ERROR_BID_CODE_VALIDATION + .formatted(bidResponse.seatbid[0].bid[0].id, requestedAllowedBidderCode, AMX, bidRequest.accountId) + + and: "PBS should emit logs" + def logs = pbsServiceWithAmxBidder.getLogsByValue(bidRequest.accountId) + assert logs.contains(INVALID_BIDDER_CODE_LOGS.formatted(requestedAllowedBidderCode, AMX, bidRequest.accountId)) + + and: "PBS should emit metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + where: + requestedAllowedBidderCode << [UNKNOWN, BOGUS] + } + + def "PBS should discard the bid and emit a response warning when default account alternate bidder codes are enabled and the allowed bidder codes doesn't match the bidder's request"() { + given: "Pbs config with default-account-config" + def defaultAccountConfig = AccountConfig.defaultAccountConfig.tap { + alternateBidderCodes = new AlternateBidderCodes().tap { + it.enabled = true + it.bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])] + } + } + def pbsConfig = AMX_CONFIG + ["settings.default-account-config": encode(defaultAccountConfig)] + def pbsService = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request" + def bidRequest = getBidRequestWithAmxBidder() + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: allowedBidderCodes) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsService) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't seat bid" + assert response.seatbid.isEmpty() + + and: "Response should seatNon bid with code 300" + assert response.ext.seatnonbid.size() == 1 + + def seatNonBid = response.ext.seatnonbid[0] + assert seatNonBid.seat == AMX.value + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL + + and: "Response should contain error" + def error = response.ext?.errors[ErrorType.AMX][0] + assert error.code == 5 + assert error.message == ERROR_BID_CODE_VALIDATION + .formatted(bidResponse.seatbid[0].bid[0].id, allowedBidderCodes, AMX, bidRequest.accountId) + + and: "PBS should emit logs" + def logs = pbsService.getLogsByValue(bidRequest.accountId) + assert logs.contains(INVALID_BIDDER_CODE_LOGS.formatted(allowedBidderCodes, AMX, bidRequest.accountId)) + + and: "Response shouldn't contain demand source" + assert !response?.seatbid?.bid?.ext?.prebid?.meta?.demandSource + + and: "PBS should emit metrics" + def metrics = pbsService.sendCollectedMetricsRequest() + assert metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + + where: + allowedBidderCodes << [BOGUS, UNKNOWN] + } + + def "PBS shouldn't discard bid when hard alias and alternate bidder allow bidder code"() { + given: "PBS config with bidder" + def pbsConfig = AMX_CONFIG + ["adapters.amx.aliases.alias.enabled" : "true", + "adapters.amx.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + def defaultPbsService = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with alias" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap { + imp[0].ext.prebid.bidder.tap { + amx = null + alias = new Generic() + } + ext.prebid.alternateBidderCodes.bidders = [(ALIAS): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])] + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, ALIAS).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [ALIAS] + + and: "Response should contain seat bid" + assert response.seatbid.seat == [GENERIC] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${GENERIC}"] + assert targeting["hb_size_${GENERIC}"] + assert targeting["hb_bidder"] == GENERIC.value + assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "PBS shouldn't emit validation metrics" + def metrics = defaultPbsService.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(GENERIC)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS shouldn't discard bid when alternate bidder code allow and soft alias with case"() { + given: "Default bid request with amx bidder" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap { + imp[0].ext.prebid.bidder.aliasUpperCase = new Generic() + imp[0].ext.prebid.bidder.amx = null + ext.prebid.aliases = [(ALIAS.value): AMX] + ext.prebid.alternateBidderCodes = requestAlternateBidderCode + } + + and: "Save account config into DB with alternate bidder codes" + def account = getAccountWithAlternateBidderCode(bidRequest).tap { + config.alternateBidderCodes = accountAlternateBidderCodes + } + accountDao.save(account) + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, ALIAS).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Flash metrics" + flushMetrics(pbsServiceWithAmxBidder) + + when: "PBS processes auction request" + def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest) + + then: "Bid response should contain exp data" + assert response.seatbid.seat == [GENERIC] + + and: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${GENERIC}"] + assert targeting["hb_size_${GENERIC}"] + assert targeting["hb_bidder"] == GENERIC.value + assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain warnings and error and seatNonBid" + assert !response.ext?.warnings + assert !response.ext?.errors + assert !response.ext?.seatnonbid + + and: "PBS shouldn't emit validation metrics" + def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(ALIAS)] + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(GENERIC)] + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)] + + where: + requestAlternateBidderCode | accountAlternateBidderCodes + new AlternateBidderCodes(enabled: true, bidders: [(ALIAS): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])]) | null + null | new AlternateBidderCodes(enabled: true, bidders: [(ALIAS): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])]) + } + + def "PBS should populate adapter code with requested bidder when conflict soft and hard alias and alternate bidder code"() { + given: "PBS config with bidder" + def pbsConfig = AMX_CONFIG + ["adapters.amx.aliases.alias.enabled" : "true", + "adapters.amx.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + def defaultPbsService = pbsServiceFactory.getService(pbsConfig) + + and: "Bid request with amx bidder and targeting" + def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap { + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.amx = null + imp[0].ext.prebid.bidder.generic = null + it.ext.prebid.aliases = [(ALIAS.value): GENERIC] + it.ext.prebid.alternateBidderCodes.bidders = [(ALIAS): new BidderConfig(enabled: true, allowedBidderCodesLowerCase: [GENERIC])] + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, ALIAS).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [ALIAS] + + and: "Response should contain seat bid" + assert response.seatbid.seat == [GENERIC] + + and: "Response shouldn't contain demand source" + assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${GENERIC}"] + assert targeting["hb_size_${GENERIC}"] + assert targeting["hb_bidder"] == GENERIC.value + assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + and: "PBS shouldn't emit validation metrics" + def metrics = defaultPbsService.sendCollectedMetricsRequest() + assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(GENERIC)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + private static Account getAccountWithAlternateBidderCode(BidRequest bidRequest) { + new Account().tap { + it.uuid = bidRequest.accountId + it.config = new AccountConfig(status: ACTIVE, alternateBidderCodes: new AlternateBidderCodes().tap { + it.enabled = true + it.bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])] + }) + } + } + + private static BidRequest getBidRequestWithAmxBidderAndAlternateBidderCode() { + getBidRequestWithAmxBidder().tap { + it.ext.prebid.alternateBidderCodes = new AlternateBidderCodes().tap { + enabled = true + bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodesLowerCase: [AMX])] + } + } + } + + private static BidRequest getBidRequestWithAmxBidder() { + BidRequest.defaultBidRequest.tap { + it.imp[0].ext.prebid.bidder.tap { + generic = null + amx = new Amx() + } + ext.prebid.tap { + returnAllBidStatus = true + targeting = new Targeting() + } + } + } +} 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 79eb960787f..d4fd06cad64 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -3,10 +3,13 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.Currency import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig +import org.prebid.server.functional.model.config.AlternateBidderCodes +import org.prebid.server.functional.model.config.BidderConfig import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse import org.prebid.server.functional.model.request.auction.AdjustmentRule import org.prebid.server.functional.model.request.auction.AdjustmentType +import org.prebid.server.functional.model.request.auction.Amx 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 @@ -14,6 +17,7 @@ import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Imp import org.prebid.server.functional.model.request.auction.VideoPlacementSubtypes import org.prebid.server.functional.model.request.auction.VideoPlcmtSubtype +import org.prebid.server.functional.model.response.auction.BidExt import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.service.PrebidServerService @@ -27,6 +31,7 @@ import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST import static org.prebid.server.functional.model.Currency.EUR import static org.prebid.server.functional.model.Currency.GBP import static org.prebid.server.functional.model.Currency.USD +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.RUBICON @@ -68,7 +73,9 @@ class BidAdjustmentSpec extends BaseSpec { private static final CurrencyConversion currencyConversion = new CurrencyConversion(networkServiceContainer).tap { setCurrencyConversionRatesResponse(CurrencyConversionRatesResponse.getDefaultCurrencyConversionRatesResponse(DEFAULT_CURRENCY_RATES)) } - private static final PrebidServerService pbsService = pbsServiceFactory.getService(externalCurrencyConverterConfig) + private static final Map AMX_CONFIG = ["adapters.amx.enabled" : "true", + "adapters.amx.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + private static final PrebidServerService pbsService = pbsServiceFactory.getService(externalCurrencyConverterConfig + AMX_CONFIG) def "PBS should adjust bid price for matching bidder when request has per-bidder bid adjustment factors"() { given: "Default bid request with bid adjustment" @@ -1074,6 +1081,71 @@ class BidAdjustmentSpec extends BaseSpec { adjustmentType << [CPM, STATIC] } + def "PBS should adjust bid price for matching bidder and alternate bidder code when request has per-bidder bid adjustment factors"() { + given: "Default bid request with bid adjustment and amx bidder" + def bidRequest = BidRequest.getDefaultBidRequest(SITE).tap { + imp[0].ext.prebid.bidder.generic = null + imp[0].ext.prebid.bidder.amx = new Amx() + it.ext.prebid.tap { + alternateBidderCodes = new AlternateBidderCodes().tap { + enabled = true + bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])] + } + bidAdjustmentFactors = new BidAdjustmentFactors(adjustments: [(GENERIC): bidAdjustmentFactor]) + } + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted" + assert response?.seatbid?.first?.bid?.first?.price == bidResponse.seatbid.first.bid.first.price * + bidAdjustmentFactor + + where: + bidAdjustmentFactor << [0.9, 1.1] + } + + def "PBS should prefer bid price adjustment based on media type and alternate bidder code when request has per-media-type bid adjustment factors"() { + given: "Default bid request with bid adjustment" + def bidRequest = BidRequest.getDefaultBidRequest(SITE).tap { + imp[0].ext.prebid.bidder.generic = null + imp[0].ext.prebid.bidder.amx = new Amx() + ext.prebid.tap { + bidAdjustmentFactors = new BidAdjustmentFactors().tap { + adjustments = [(GENERIC): randomDecimal] + mediaTypes = [(BANNER): [(GENERIC): bidAdjustmentFactor]] + } + alternateBidderCodes = new AlternateBidderCodes().tap { + enabled = true + bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])] + } + } + } + + and: "Bid response with bidder code" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC) + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted" + assert response?.seatbid?.first?.bid?.first?.price == bidResponse.seatbid.first.bid.first.price * + bidAdjustmentFactor + + where: + bidAdjustmentFactor << [0.9, 1.1] + } + private static Map getExternalCurrencyConverterConfig() { ["auction.ad-server-currency" : DEFAULT_CURRENCY as String, "currency-converter.external-rates.enabled" : "true", 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 e8151762026..5269d31b977 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy @@ -7,6 +7,7 @@ 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.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.BidRequest @@ -22,10 +23,12 @@ import org.prebid.server.functional.model.request.auction.Native 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.Site +import org.prebid.server.functional.model.request.auction.Targeting import org.prebid.server.functional.model.request.vtrack.VtrackRequest import org.prebid.server.functional.model.request.vtrack.xml.Vast import org.prebid.server.functional.model.response.auction.Adm import org.prebid.server.functional.model.response.auction.Bid +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 @@ -34,7 +37,12 @@ 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_CAMEL_CASE +import static org.prebid.server.functional.model.bidder.BidderName.OPENX import static org.prebid.server.functional.model.bidder.CompressionType.GZIP import static org.prebid.server.functional.model.bidder.CompressionType.NONE import static org.prebid.server.functional.model.request.auction.Asset.titleAsset @@ -1026,9 +1034,10 @@ class BidderParamsSpec extends BaseSpec { def "PBS should send request to bidder when adapters.bidder.aliases.bidder.meta-info.currency-accepted not specified"() { given: "PBS with adapter configuration" - def pbsConfig = ["adapters.generic.aliases.alias.enabled" : "true", - "adapters.generic.aliases.alias.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), - "adapters.generic.aliases.alias.meta-info.currency-accepted": ""] + def pbsConfig = [ + "adapters.generic.aliases.alias.enabled" : "true", + "adapters.generic.aliases.alias.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.generic.aliases.alias.meta-info.currency-accepted": ""] def pbsService = pbsServiceFactory.getService(pbsConfig) and: "Default bid request with alias bidder" @@ -1145,9 +1154,10 @@ class BidderParamsSpec extends BaseSpec { def "PBS should send request to bidder when adapters.bidder.aliases.bidder.meta-info.currency-accepted intersect with requested currency"() { given: "PBS with adapter configuration" - def pbsConfig = ["adapters.generic.aliases.alias.enabled" : "true", - "adapters.generic.aliases.alias.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), - "adapters.generic.aliases.alias.meta-info.currency-accepted": "${USD},${EUR}".toString()] + def pbsConfig = [ + "adapters.generic.aliases.alias.enabled" : "true", + "adapters.generic.aliases.alias.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.generic.aliases.alias.meta-info.currency-accepted": "${USD},${EUR}".toString()] def pbsService = pbsServiceFactory.getService(pbsConfig) and: "Default basic BidRequest with alias bidder" @@ -1189,9 +1199,10 @@ class BidderParamsSpec extends BaseSpec { def "PBS shouldn't send request to bidder and emit warning when adapters.bidder.aliases.bidder.meta-info.currency-accepted not intersect with requested currency"() { given: "PBS with adapter configuration" - def pbsConfig = ["adapters.generic.aliases.alias.enabled" : "true", - "adapters.generic.aliases.alias.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), - "adapters.generic.aliases.alias.meta-info.currency-accepted": "${JPY},${CHF}".toString()] + def pbsConfig = [ + "adapters.generic.aliases.alias.enabled" : "true", + "adapters.generic.aliases.alias.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.generic.aliases.alias.meta-info.currency-accepted": "${JPY},${CHF}".toString()] def pbsService = pbsServiceFactory.getService(pbsConfig) and: "Default basic BidRequest with alias bidder" @@ -1361,4 +1372,345 @@ class BidderParamsSpec extends BaseSpec { and: "targeting should be empty" assert response.seatbid.isEmpty() } + + def "PBS should send bidder code from imp[].ext.prebid.bidder to seatbid.bid.ext.prebid.meta.adapterCode"() { + given: "Default basic bid request" + def bidRequest = BidRequest.defaultBidRequest + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].seat = OPENX + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [BidderName.GENERIC] + + and: "Bidder request should be valid" + assert bidder.getBidderRequest(bidRequest.id) + } + + def "PBS should send bidder code from imp[].ext.prebid.bidder to seatbid.bid.ext.prebid.meta.adapterCode when requested soft alias"() { + given: "Default bid request with alias" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0].ext.prebid.bidder.tap { + generic = null + alias = new Generic() + } + ext.prebid.aliases = [(ALIAS.value): BidderName.GENERIC] + ext.prebid.targeting = new Targeting() + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [BidderName.GENERIC] + + and: "Response should contain seat bid" + assert response.seatbid.seat == [BidderName.ALIAS] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${BidderName.ALIAS}"] + assert targeting["hb_size_${BidderName.ALIAS}"] + assert targeting["hb_bidder"] == BidderName.ALIAS.value + assert targeting["hb_bidder_${BidderName.ALIAS}"] == BidderName.ALIAS.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + } + + def "PBS should populate same code for adapter code when make call for generic hard code alias"() { + given: "PBS config with bidder" + def pbsConfig = ["adapters.generic.aliases.alias.enabled" : "true", + "adapters.generic.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + def defaultPbsService = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with alias" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0].ext.prebid.bidder.tap { + generic = null + alias = new Generic() + } + ext.prebid.targeting = new Targeting() + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [BidderName.ALIAS] + + and: "Response should contain seat bid" + assert response.seatbid.seat == [BidderName.ALIAS] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${BidderName.ALIAS}"] + assert targeting["hb_size_${BidderName.ALIAS}"] + assert targeting["hb_bidder"] == BidderName.ALIAS.value + assert targeting["hb_bidder_${BidderName.ALIAS}"] == BidderName.ALIAS.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS should make call for alias when hard alias and demandSource specified"() { + given: "PBS config with bidder" + def pbsConfig = ["adapters.amx.enabled" : "true", + "adapters.amx.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.amx.aliases.alias.enabled" : "true", + "adapters.amx.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + def defaultPbsService = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid Request with generic and openx bidder within separate imps" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0].ext.prebid.bidder.tap { + generic = null + alias = new Generic() + } + ext.prebid.targeting = new Targeting() + } + + and: "Bid response with bidder code" + def demandSource = PBSUtils.randomString + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, BidderName.ALIAS).tap { + it.seatbid[0].bid[0].ext = new BidExt(demandSource: demandSource) + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain demand source" + assert response.seatbid.bid.ext.prebid.meta.demandSource.flatten() == [demandSource] + + and: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [BidderName.ALIAS] + + and: "Response should contain seat bid" + assert response.seatbid.seat == [BidderName.ALIAS] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${BidderName.ALIAS}"] + assert targeting["hb_size_${BidderName.ALIAS}"] + assert targeting["hb_bidder"] == BidderName.ALIAS.value + assert targeting["hb_bidder_${BidderName.ALIAS}"] == BidderName.ALIAS.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS should send bidder code from imp[].ext.prebid.bidder to seatbid.bid.ext.prebid.meta.adapterCode when requested soft alias with upper case"() { + given: "Default bid request with alias" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.aliases = [(ALIAS.value): BidderName.GENERIC] + ext.prebid.targeting = new Targeting() + imp[0].ext.prebid.bidder.tap { + generic = null + aliasUpperCase = new Generic() + } + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [BidderName.GENERIC] + + and: "Response should contain seat bid" + assert response.seatbid.seat == [ALIAS_UPPER_CASE] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${ALIAS_UPPER_CASE}"] + assert targeting["hb_size_${ALIAS_UPPER_CASE}"] + assert targeting["hb_bidder"] == ALIAS_UPPER_CASE.value + assert targeting["hb_bidder_${ALIAS_UPPER_CASE}"] == ALIAS_UPPER_CASE.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + } + + def "PBS should populate targeting with bidder in camel case when bidder with camel case was requested"() { + given: "Default bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0].ext.prebid.bidder.generic = null + imp[0].ext.prebid.bidder.genericCamelCase = new Generic() + it.ext.prebid.targeting = new Targeting() + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${GENERIC_CAMEL_CASE}"] + assert targeting["hb_size_${GENERIC_CAMEL_CASE}"] + assert targeting["hb_bidder"] == GENERIC_CAMEL_CASE.value + assert targeting["hb_bidder_${GENERIC_CAMEL_CASE}"] == GENERIC_CAMEL_CASE.value + + and: "Bid response should contain seat" + assert response.seatbid.seat == [GENERIC_CAMEL_CASE] + + and: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [BidderName.GENERIC] + } + + def "PBS should make call for alias in upper case when soft alias specified with same name in upper case strategy"() { + given: "Default bid request with soft alias and targeting" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.aliases = [(ALIAS.value): BidderName.GENERIC] + imp[0].ext.prebid.bidder.aliasUpperCase = new Generic() + imp[0].ext.prebid.bidder.generic = null + it.ext.prebid.targeting = new Targeting() + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [BidderName.GENERIC] + + and: "Response should contain seat bid" + assert response.seatbid.seat == [ALIAS_UPPER_CASE] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${ALIAS_UPPER_CASE}"] + assert targeting["hb_size_${ALIAS_UPPER_CASE}"] + assert targeting["hb_bidder"] == ALIAS_UPPER_CASE.value + assert targeting["hb_bidder_${ALIAS_UPPER_CASE}"] == ALIAS_UPPER_CASE.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + } + + def "PBS should populate adapter code with requested bidder when conflict with soft and hard alias"() { + given: "PBS config with bidder" + def pbsConfig = ["adapters.amx.enabled" : "true", + "adapters.amx.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.amx.aliases.alias.enabled" : "true", + "adapters.amx.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + def defaultPbsService = pbsServiceFactory.getService(pbsConfig) + + and: "Bid request with amx bidder and targeting" + def bidRequest = BidRequest.getDefaultBidRequest().tap { + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.amx = null + imp[0].ext.prebid.bidder.generic = null + it.ext.prebid.aliases = [(ALIAS.value): BidderName.GENERIC] + it.ext.prebid.targeting = new Targeting() + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [BidderName.ALIAS] + + and: "Response should contain seat bid" + assert response.seatbid.seat == [BidderName.ALIAS] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${BidderName.ALIAS}"] + assert targeting["hb_size_${BidderName.ALIAS}"] + assert targeting["hb_bidder"] == BidderName.ALIAS.value + assert targeting["hb_bidder_${BidderName.ALIAS}"] == BidderName.ALIAS.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS should populate adapter code with requested bidder when conflict with soft and generic hard alias"() { + given: "PBS config with bidders" + def pbsConfig = ["adapters.amx.enabled" : "true", + "adapters.amx.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.generic.aliases.alias.enabled" : "true", + "adapters.generic.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + def defaultPbsService = pbsServiceFactory.getService(pbsConfig) + + and: "Bid request with amx bidder and targeting" + def bidRequest = BidRequest.getDefaultBidRequest().tap { + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.amx = null + imp[0].ext.prebid.bidder.generic = null + it.ext.prebid.aliases = [(ALIAS.value): AMX] + it.ext.prebid.targeting = new Targeting() + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [BidderName.ALIAS] + + and: "Response should contain seat bid" + assert response.seatbid.seat == [BidderName.ALIAS] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${BidderName.ALIAS}"] + assert targeting["hb_size_${BidderName.ALIAS}"] + assert targeting["hb_bidder"] == BidderName.ALIAS.value + assert targeting["hb_bidder_${BidderName.ALIAS}"] == BidderName.ALIAS.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS should properly populate bidder code when soft alias ignore standalone adapter"() { + given: "PBS config with amx bidder" + def pbsConfig = ["adapters.amx.enabled" : "true", + "adapters.amx.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + def defaultPbsService = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with soft alias and targeting" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0].ext.prebid.bidder.amx = new Amx() + imp[0].ext.prebid.bidder.generic = null + ext.prebid.targeting = new Targeting() + ext.prebid.aliases = [(AMX.value): BidderName.GENERIC] + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain adapter code" + assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX] + + and: "Response should contain seat" + assert response.seatbid.seat == [AMX] + + and: "Response should contain bidder targeting" + def targeting = response.seatbid[0].bid[0].ext.prebid.targeting + assert targeting["hb_pb_${AMX}"] + assert targeting["hb_size_${AMX}"] + assert targeting["hb_bidder"] == AMX.value + assert targeting["hb_bidder_${AMX}"] == AMX.value + + and: "Bidder request should be valid" + assert bidder.getBidderRequests(bidRequest.id) + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy index 1694a17dbaf..b4cbdd3fb88 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy @@ -233,7 +233,7 @@ class ResponseCorrectionSpec extends ModuleBaseSpec { assert response.seatbid.bid.ext.prebid.type.flatten() == [mediaType] and: "Response shouldn't contain media type for prebid meta" - assert !response?.seatbid?.bid?.ext?.prebid?.meta?.mediaType?.flatten()?.size() + assert !response?.seatbid?.first?.bid?.first?.ext?.prebid?.meta?.mediaType and: "Response shouldn't contain errors" assert !response.ext.errors @@ -516,7 +516,7 @@ class ResponseCorrectionSpec extends ModuleBaseSpec { assert response.seatbid.bid.ext.prebid.type.flatten() == [VIDEO] and: "Response shouldn't contain media type for prebid meta" - assert !response?.seatbid?.bid?.ext?.prebid?.meta?.mediaType?.flatten()?.size() + assert !response?.seatbid?.first?.bid?.first?.ext?.prebid?.meta?.mediaType and: "Response shouldn't contain errors" assert !response.ext.errors