diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidRejectionReason.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidRejectionReason.groovy index 3f14bac3db1..79cf8ad9317 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidRejectionReason.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidRejectionReason.groovy @@ -14,6 +14,7 @@ enum BidRejectionReason { REQUEST_BLOCKED_UNSUPPORTED_CHANNEL(201), REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE(202), REQUEST_BLOCKED_PRIVACY(204), + REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY(205), RESPONSE_REJECTED_GENERAL(300), RESPONSE_REJECTED_DUE_TO_PRICE_FLOOR(301), 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 8b087120db0..cab50bd816b 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.tests +import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.bidder.Generic import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredImp @@ -16,19 +17,20 @@ import org.prebid.server.functional.model.request.auction.ImpExtContext import org.prebid.server.functional.model.request.auction.ImpExtContextData import org.prebid.server.functional.model.request.auction.Native import org.prebid.server.functional.model.request.auction.PrebidStoredRequest -import org.prebid.server.functional.model.request.auction.RegsExt import org.prebid.server.functional.model.request.auction.Site 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.BidResponse -import org.prebid.server.functional.model.response.auction.ErrorType import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.CcpaConsent +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.APPNEXUS -import static org.prebid.server.functional.model.bidder.BidderName.GENERIC 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 @@ -37,11 +39,15 @@ import static org.prebid.server.functional.model.request.auction.DistributionCha import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE import static org.prebid.server.functional.model.request.auction.SecurityLevel.NON_SECURE import static org.prebid.server.functional.model.request.auction.SecurityLevel.SECURE +import static org.prebid.server.functional.model.response.auction.BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY +import static org.prebid.server.functional.model.response.auction.ErrorType.ALIAS +import static org.prebid.server.functional.model.response.auction.ErrorType.GENERIC import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID import static org.prebid.server.functional.model.response.auction.MediaType.AUDIO import static org.prebid.server.functional.model.response.auction.MediaType.BANNER import static org.prebid.server.functional.model.response.auction.MediaType.NATIVE import static org.prebid.server.functional.model.response.auction.MediaType.VIDEO +import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer import static org.prebid.server.functional.util.HttpUtil.CONTENT_ENCODING_HEADER import static org.prebid.server.functional.util.privacy.CcpaConsent.Signal.ENFORCED @@ -58,7 +64,7 @@ class BidderParamsSpec extends BaseSpec { def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain httpcalls" - assert response.ext?.debug?.httpcalls[GENERIC.value] + assert response.ext?.debug?.httpcalls[BidderName.GENERIC.value] and: "Response should not contain error" assert !response.ext?.errors @@ -84,7 +90,7 @@ class BidderParamsSpec extends BaseSpec { def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain error" - assert response.ext?.errors[ErrorType.GENERIC]*.code == [2] + assert response.ext?.errors[GENERIC]*.code == [2] where: adapterDefault | generic | adapterConfig @@ -212,7 +218,7 @@ class BidderParamsSpec extends BaseSpec { bidRequest.imp.first().ext.prebid.bidder.generic = new Generic(firstParam: firstParam) and: "Set bidderParam to bidRequest" - bidRequest.ext.prebid.bidderParams = [(GENERIC): [firstParam: PBSUtils.randomNumber]] + bidRequest.ext.prebid.bidderParams = [(BidderName.GENERIC): [firstParam: PBSUtils.randomNumber]] when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) @@ -247,7 +253,7 @@ class BidderParamsSpec extends BaseSpec { and: "Set bidderParam to bidRequest" def secondParam = PBSUtils.randomNumber - bidRequest.ext.prebid.bidderParams = [(GENERIC): [secondParam: secondParam]] + bidRequest.ext.prebid.bidderParams = [(BidderName.GENERIC): [secondParam: secondParam]] when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) @@ -289,8 +295,8 @@ class BidderParamsSpec extends BaseSpec { def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain error" - assert response.ext?.errors[ErrorType.GENERIC]*.code == [999] - assert response.ext?.errors[ErrorType.GENERIC]*.message == ["host name must not be empty"] + assert response.ext?.errors[GENERIC]*.code == [999] + assert response.ext?.errors[GENERIC]*.message == ["host name must not be empty"] } def "PBS should reject bidder when bidder params from request doesn't satisfy json-schema for auction request"() { @@ -395,8 +401,8 @@ class BidderParamsSpec extends BaseSpec { assert response.seatbid.isEmpty() and: "Response should contain error" - assert response.ext?.warnings[ErrorType.GENERIC]*.code == [2] - assert response.ext?.warnings[ErrorType.GENERIC]*.message == ["Bidder does not support any media types."] + assert response.ext?.warnings[GENERIC]*.code == [2] + assert response.ext?.warnings[GENERIC]*.message == ["Bidder does not support any media types."] where: configMediaType | bidRequest @@ -512,8 +518,8 @@ class BidderParamsSpec extends BaseSpec { assert bidderRequest.imp[0].nativeObj and: "Response should contain error" - assert response.ext?.warnings[ErrorType.GENERIC]*.code == [2] - assert response.ext?.warnings[ErrorType.GENERIC]*.message == + assert response.ext?.warnings[GENERIC]*.code == [2] + assert response.ext?.warnings[GENERIC]*.message == ["Imp ${bidRequest.imp[0].id} does not have a supported media type and has been removed from the " + "request for this bidder." as String] @@ -531,7 +537,7 @@ class BidderParamsSpec extends BaseSpec { def bidResponse = pbsService.sendAuctionRequest(bidRequest) then: "Bid response should contain proper warning" - assert bidResponse.ext?.warnings[ErrorType.GENERIC]?.message.contains("Bid request contains 0 impressions after filtering.") + assert bidResponse.ext?.warnings[GENERIC]?.message.contains("Bid request contains 0 impressions after filtering.") and: "Bid response shouldn't contain any seatbid" assert !bidResponse.seatbid @@ -565,7 +571,7 @@ class BidderParamsSpec extends BaseSpec { def response = pbsService.sendAuctionRequest(bidRequest) then: "Bid response should contain proper warning" - assert response.ext?.warnings[ErrorType.GENERIC]?.message == + assert response.ext?.warnings[GENERIC]?.message == ["Imp ${bidRequest.imp[1].id} does not have a supported media type and has been removed from the request for this bidder."] and: "Bid response should contain seatbid" @@ -600,8 +606,8 @@ class BidderParamsSpec extends BaseSpec { assert bidder.getRequestCount(bidRequest.id) == 0 and: "Response should contain errors" - assert response.ext?.warnings[ErrorType.GENERIC]*.code == [2, 2] - assert response.ext?.warnings[ErrorType.GENERIC]*.message == + assert response.ext?.warnings[GENERIC]*.code == [2, 2] + assert response.ext?.warnings[GENERIC]*.message == ["Imp ${bidRequest.imp[0].id} does not have a supported media type and has been removed from " + "the request for this bidder.", "Bid request contains 0 impressions after filtering."] @@ -646,7 +652,7 @@ class BidderParamsSpec extends BaseSpec { def response = pbsService.sendAuctionRequest(bidRequest) then: "Bidder request should contain header Content-Encoding = gzip" - assert response.ext?.debug?.httpcalls?.get(GENERIC.value)?.requestHeaders?.first() + assert response.ext?.debug?.httpcalls?.get(BidderName.GENERIC.value)?.requestHeaders?.first() ?.get(CONTENT_ENCODING_HEADER)?.first() == compressionType } @@ -662,7 +668,7 @@ class BidderParamsSpec extends BaseSpec { def response = pbsService.sendAuctionRequest(bidRequest) then: "Bidder request should not contain header Content-Encoding" - assert !response.ext?.debug?.httpcalls?.get(GENERIC.value)?.requestHeaders?.first() + assert !response.ext?.debug?.httpcalls?.get(BidderName.GENERIC.value)?.requestHeaders?.first() ?.get(CONTENT_ENCODING_HEADER) } @@ -805,4 +811,233 @@ class BidderParamsSpec extends BaseSpec { tid == impExt.tid } } + + def "PBS should send request to bidder when adapters.bidder.meta-info.currency-accepted not specified"() { + given: "PBS with adapter configuration" + def pbsService = pbsServiceFactory.getService("adapters.generic.meta-info.currency-accepted": "") + + and: "Default bid request with generic bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [USD] + ext.prebid.returnAllBidStatus = true + } + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain http calls" + assert response.ext?.debug?.httpcalls[BidderName.GENERIC.value] + + and: "Response should contain seatBid" + assert response.seatbid.bid.flatten().size() == 1 + + and: "Bidder request should be valid" + assert bidder.getBidderRequest(bidRequest.id) + + and: "Response shouldn't contain error" + assert !response.ext?.errors + + and: "Response shouldn't contain warning" + assert !response.ext?.warnings + + and: "PBS response shouldn't contain seatNonBid" + assert !response.ext.seatnonbid + } + + def "PBS should send request to bidder when adapters.bidder.aliases.bidder.meta-info.currency-accepted not specified"() { + given: "PBS with adapter configuration" + def pbsService = pbsServiceFactory.getService( + "adapters.generic.aliases.alias.enabled" : "true", + "adapters.generic.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString(), + "adapters.generic.aliases.alias.meta-info.currency-accepted": "") + + and: "Default bid request with alias bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [USD] + ext.prebid.returnAllBidStatus = true + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.generic = null + } + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain http calls" + assert response.ext?.debug?.httpcalls[BidderName.ALIAS.value] + + and: "Response should contain seatBid" + assert response.seatbid.bid.flatten().size() == 1 + + and: "Bidder request should be valid" + assert bidder.getBidderRequest(bidRequest.id) + + and: "Response shouldn't contain error" + assert !response.ext?.errors + + and: "Response shouldn't contain warning" + assert !response.ext?.warnings + + and: "PBS response shouldn't contain seatNonBid" + assert !response.ext.seatnonbid + } + + def "PBS should send request to bidder when adapters.bidder.meta-info.currency-accepted intersect with requested currency"() { + given: "PBS with adapter configuration" + def pbsService = pbsServiceFactory.getService("adapters.generic.meta-info.currency-accepted": "${USD},${EUR}".toString()) + + and: "Default basic generic BidRequest" + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [USD] + ext.prebid.returnAllBidStatus = true + } + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain http calls" + assert response.ext?.debug?.httpcalls[BidderName.GENERIC.value] + + and: "Response should contain seatBid" + assert response.seatbid.bid.flatten().size() == 1 + + and: "Bidder request should be valid" + assert bidder.getBidderRequest(bidRequest.id) + + and: "Response shouldn't contain error" + assert !response.ext?.errors + + and: "Response shouldn't contain warning" + assert !response.ext?.warnings + + and: "PBS response shouldn't contain seatNonBid and contain errors" + assert !response.ext.seatnonbid + } + + def "PBS shouldn't send request to bidder and emit warning when adapters.bidder.meta-info.currency-accepted not intersect with requested currency"() { + given: "PBS with adapter configuration" + def pbsService = pbsServiceFactory.getService("adapters.generic.meta-info.currency-accepted": "${JPY},${CHF}".toString()) + + and: "Default basic generic BidRequest" + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [USD] + ext.prebid.returnAllBidStatus = true + } + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Response shouldn't contain http calls" + assert !response.ext?.debug?.httpcalls + + and: "Response shouldn't contain seatBid" + assert !response.seatbid + + and: "Pbs shouldn't make bidder request" + assert !bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain error" + assert !response.ext?.errors + + and: "Response should seatNon bid with code 205" + assert response.ext.seatnonbid.size() == 1 + + and: "PBS should emit an warnings" + assert response.ext?.warnings[GENERIC]*.code == [999] + assert response.ext?.warnings[GENERIC]*.message == + ["No match between the configured currencies and bidRequest.cur"] + + def seatNonBid = response.ext.seatnonbid[0] + assert seatNonBid.seat == BidderName.GENERIC.value + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY + } + + 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 pbsService = pbsServiceFactory.getService( + "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()) + + and: "Default basic BidRequest with alias bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [USD] + ext.prebid.returnAllBidStatus = true + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.generic = null + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain http calls" + assert response.ext?.debug?.httpcalls[ALIAS.value] + + and: "Response should contain seatBid" + assert response.seatbid.bid.flatten().size() == 1 + + and: "Bidder request should be valid" + assert bidder.getBidderRequest(bidRequest.id) + + and: "Response shouldn't contain error" + assert !response.ext?.errors + + and: "Response shouldn't contain warning" + assert !response.ext?.warnings + + and: "PBS response shouldn't contain seatNonBid and contain errors" + assert !response.ext.seatnonbid + } + + 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 pbsService = pbsServiceFactory.getService( + "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()) + + and: "Default basic BidRequest with alias bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [USD] + ext.prebid.returnAllBidStatus = true + imp[0].ext.prebid.bidder.alias = new Generic() + imp[0].ext.prebid.bidder.generic = null + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Response shouldn't contain http calls" + assert !response.ext?.debug?.httpcalls + + and: "Response shouldn't contain seatBid" + assert !response.seatbid + + and: "Pbs shouldn't make bidder request" + assert !bidder.getBidderRequests(bidRequest.id) + + and: "Response shouldn't contain error" + assert !response.ext?.errors + + and: "PBS should emit an warnings" + assert response.ext?.warnings[ALIAS]*.code == [999] + assert response.ext?.warnings[ALIAS]*.message == + ["No match between the configured currencies and bidRequest.cur"] + + and: "Response should seatNon bid with code 205" + assert response.ext.seatnonbid.size() == 1 + + def seatNonBid = response.ext.seatnonbid[0] + assert seatNonBid.seat == BidderName.ALIAS.value + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY + } }