From 04151f01e99924a13f0e57d89da0b4c6b28080b9 Mon Sep 17 00:00:00 2001 From: markiian Date: Mon, 28 Apr 2025 15:20:54 +0300 Subject: [PATCH 1/3] Add functional tests for bid adjustments matching strategy --- .../request/auction/BidAdjustmentRule.groovy | 8 +- .../functional/tests/BidAdjustmentSpec.groovy | 235 ++++++++++++++++++ 2 files changed, 240 insertions(+), 3 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy index 4fcfc1125e1..3292a000a1e 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy @@ -1,11 +1,8 @@ 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 -@JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) @ToString(includeNames = true, ignoreNulls = true) class BidAdjustmentRule { @@ -13,4 +10,9 @@ class BidAdjustmentRule { Map> wildcardBidder Map> generic Map> alias + Map> amx + @JsonProperty("ALIAS") + Map> aliasUpperCase + @JsonProperty("AlIaS") + Map> aliasCamelCase } 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 8effa51b231..f3343cd7069 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -79,6 +79,11 @@ class BidAdjustmentSpec extends BaseSpec { "adapters.amx.endpoint": "$networkServiceContainer.rootUri/auction".toString()] private static final PrebidServerService pbsService = pbsServiceFactory.getService(externalCurrencyConverterConfig + AMX_CONFIG) + @Override + def cleanupSpec() { + pbsServiceFactory.removeContainer(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" def bidRequest = BidRequest.getDefaultBidRequest(SITE).tap { @@ -1193,6 +1198,236 @@ class BidAdjustmentSpec extends BaseSpec { bidAdjustmentFactor << [0.9, 1.1] } + def "PBS should adjust bid price when bid adjustment bidder and bidder code different"() { + given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode" + def exactRulePrice = PBSUtils.randomPrice + def currency = USD + def adjustmentRule = new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency) + def bidAdjustmentRule = new BidAdjustmentRule(alias: [(WILDCARD): [adjustmentRule]]) + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [currency] + imp[0].ext.prebid.bidder.generic = null + imp[0].ext.prebid.bidder.amx = new Amx() + ext.prebid.tap { + bidAdjustments = new BidAdjustment(mediaType: [(BANNER): bidAdjustmentRule]) + alternateBidderCodes = new AlternateBidderCodes().tap { + enabled = true + bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])] + } + } + } + + and: "Default bid response with price and bidder code" + def originalPrice = PBSUtils.randomPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + seatbid.first.bid.first.ext = new BidExt(bidderCode: ALIAS) + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted according to exact rule" + assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, exactRulePrice, STATIC) + assert response.cur == bidResponse.cur + + and: "Original bid price and currency should be presented in bid.ext" + verifyAll(response.seatbid.first.bid.first.ext) { + origbidcpm == originalPrice + origbidcur == bidResponse.cur + } + + and: "Bidder request should contain currency from request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] + } + + def "PBS should adjust bid price when bid adjustment bidder with different case strategy and bidder code different"() { + given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode" + def exactRulePrice = PBSUtils.randomPrice + def currency = USD + def adjustmentRule = new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency) + def bidAdjustmentRule = new BidAdjustmentRule((bidAdjustmentRuleBidder): [(WILDCARD): [adjustmentRule]]) + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [currency] + imp[0].ext.prebid.bidder.generic = null + imp[0].ext.prebid.bidder.amx = new Amx() + ext.prebid.tap { + bidAdjustments = new BidAdjustment(mediaType: [(BANNER): bidAdjustmentRule]) + alternateBidderCodes = new AlternateBidderCodes().tap { + enabled = true + bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])] + } + } + } + + and: "Default bid response with price and bidder code" + def originalPrice = PBSUtils.randomPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + seatbid.first.bid.first.ext = new BidExt(bidderCode: ALIAS) + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted according to exact rule" + assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, exactRulePrice, STATIC) + assert response.cur == bidResponse.cur + + and: "Original bid price and currency should be presented in bid.ext" + verifyAll(response.seatbid.first.bid.first.ext) { + origbidcpm == originalPrice + origbidcur == bidResponse.cur + } + + and: "Bidder request should contain currency from request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] + + where: + bidAdjustmentRuleBidder << ["alias", "aliasUpperCase", "aliasCamelCase"] + } + + def "PBS should adjust bid price when bid adjustment bidder and bidder code same as requested"() { + given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode" + def exactRulePrice = PBSUtils.randomPrice + def currency = USD + def exactRule = new BidAdjustmentRule(amx: [(WILDCARD): [new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency)]]) + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [currency] + imp[0].ext.prebid.bidder.generic = null + imp[0].ext.prebid.bidder.amx = new Amx() + ext.prebid.tap { + bidAdjustments = new BidAdjustment(mediaType: [(BANNER): exactRule]) + alternateBidderCodes = new AlternateBidderCodes().tap { + enabled = true + bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])] + } + } + } + + and: "Default bid response with price and bidder code" + def originalPrice = PBSUtils.randomPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + seatbid.first.bid.first.ext = new BidExt(bidderCode: AMX) + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted according to exact rule" + assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, exactRulePrice, STATIC) + assert response.cur == bidResponse.cur + + and: "Original bid price and currency should be presented in bid.ext" + verifyAll(response.seatbid.first.bid.first.ext) { + origbidcpm == originalPrice + origbidcur == bidResponse.cur + } + + and: "Bidder request should contain currency from request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] + } + + def "PBS should adjust bid price when bid adjustment bidder is the same as bidder code"() { + given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode" + def exactRulePrice = PBSUtils.randomPrice + def currency = USD + def exactRule = new BidAdjustmentRule(alias: [(WILDCARD): [new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency)]]) + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [currency] + imp[0].ext.prebid.bidder.generic = null + imp[0].ext.prebid.bidder.amx = new Amx() + ext.prebid.tap { + bidAdjustments = new BidAdjustment(mediaType: [(BANNER): exactRule]) + alternateBidderCodes = new AlternateBidderCodes().tap { + enabled = true + bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])] + } + } + } + + and: "Default bid response with price and bidder code" + def originalPrice = PBSUtils.randomPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + seatbid.first.bid.first.ext = new BidExt(bidderCode: ALIAS) + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted according to exact rule" + assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, exactRulePrice, STATIC) + assert response.cur == bidResponse.cur + + and: "Original bid price and currency should be presented in bid.ext" + verifyAll(response.seatbid.first.bid.first.ext) { + origbidcpm == originalPrice + origbidcur == bidResponse.cur + } + + and: "Bidder request should contain currency from request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] + } + + def "PBS should adjust bid price when bid adjustment wildcard bidder and bidder code specified"() { + given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode" + def exactRulePrice = PBSUtils.randomPrice + def currency = USD + def exactRule = new BidAdjustmentRule(wildcardBidder: [(WILDCARD): [new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency)]]) + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [currency] + imp[0].ext.prebid.bidder.generic = null + imp[0].ext.prebid.bidder.amx = new Amx() + ext.prebid.tap { + bidAdjustments = new BidAdjustment(mediaType: [(BANNER): exactRule]) + alternateBidderCodes = new AlternateBidderCodes().tap { + enabled = true + bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])] + } + } + } + + and: "Default bid response with price and bidder code" + def originalPrice = PBSUtils.randomPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + seatbid.first.bid.first.ext = new BidExt(bidderCode: ALIAS) + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted according to exact rule" + assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, exactRulePrice, STATIC) + assert response.cur == bidResponse.cur + + and: "Original bid price and currency should be presented in bid.ext" + verifyAll(response.seatbid.first.bid.first.ext) { + origbidcpm == originalPrice + origbidcur == bidResponse.cur + } + + and: "Bidder request should contain currency from request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] + } + private static Map getExternalCurrencyConverterConfig() { ["auction.ad-server-currency" : DEFAULT_CURRENCY as String, "currency-converter.external-rates.enabled" : "true", From 3895d1dc428b98d7cb6824c83368f135a0d92cf9 Mon Sep 17 00:00:00 2001 From: markiian Date: Mon, 26 May 2025 18:19:29 +0300 Subject: [PATCH 2/3] Update after review --- .../functional/tests/BidAdjustmentSpec.groovy | 98 ++++++++++++++++++- 1 file changed, 94 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy index f3343cd7069..0d69e120a23 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -32,6 +32,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.ACUITYADS import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.AMX import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS @@ -1198,12 +1199,12 @@ class BidAdjustmentSpec extends BaseSpec { bidAdjustmentFactor << [0.9, 1.1] } - def "PBS should adjust bid price when bid adjustment bidder and bidder code different"() { + def "PBS shouldn't adjust bid price when bid adjustment rule doesn't match with bidder code"() { given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode" def exactRulePrice = PBSUtils.randomPrice def currency = USD def adjustmentRule = new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency) - def bidAdjustmentRule = new BidAdjustmentRule(alias: [(WILDCARD): [adjustmentRule]]) + def bidAdjustmentRule = new BidAdjustmentRule((bidAdjustmentRuleBidder): [(WILDCARD): [adjustmentRule]]) def bidRequest = BidRequest.defaultBidRequest.tap { cur = [currency] imp[0].ext.prebid.bidder.generic = null @@ -1222,7 +1223,66 @@ class BidAdjustmentSpec extends BaseSpec { def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { cur = currency seatbid.first.bid.first.price = originalPrice - seatbid.first.bid.first.ext = new BidExt(bidderCode: ALIAS) + seatbid.first.bid.first.ext = new BidExt(bidderCode: ACUITYADS) + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted according to exact rule" + assert response.seatbid.first.bid.first.price == originalPrice + assert response.cur == bidResponse.cur + + and: "Original bid price and currency should be presented in bid.ext" + verifyAll(response.seatbid.first.bid.first.ext) { + origbidcpm == originalPrice + origbidcur == bidResponse.cur + } + + and: "Bidder request should contain currency from request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] + + and: "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 == ACUITYADS + + where: + bidAdjustmentRuleBidder << ["alias", "aliasUpperCase", "aliasCamelCase"] + } + + def "PBS should adjust bid price when two bid adjustment rules are compatible"() { + given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode" + def exactRulePrice = PBSUtils.randomPrice + def currency = USD + def dealId = PBSUtils.randomString + def adjustmentRule = new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency) + def firstBidAdjustmentRule = new BidAdjustmentRule(amx: [(dealId): [adjustmentRule]]) + def secondBidAdjustmentRule = new BidAdjustmentRule(amx: [(WILDCARD): [adjustmentRule]]) + def bidRequest = BidRequest.defaultBidRequest.tap { + cur = [currency] + imp[0].ext.prebid.bidder.generic = null + imp[0].ext.prebid.bidder.amx = new Amx() + ext.prebid.tap { + bidAdjustments = new BidAdjustment(mediaType: [(BANNER): firstBidAdjustmentRule, + (ANY) : secondBidAdjustmentRule]) + alternateBidderCodes = new AlternateBidderCodes().tap { + enabled = true + bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])] + } + } + } + + and: "Default bid response with price and bidder code and dealId" + def originalPrice = PBSUtils.randomPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + seatbid.first.bid.first.ext = new BidExt(bidderCode: AMX) + seatbid.first.bid.first.dealid = dealId } bidder.setResponse(bidRequest.id, bidResponse) @@ -1242,9 +1302,15 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain currency from request" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.cur == [currency] + + and: "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 } - def "PBS should adjust bid price when bid adjustment bidder with different case strategy and bidder code different"() { + def "PBS should adjust bid price when bid adjustment bidder and bidder code different"() { given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode" def exactRulePrice = PBSUtils.randomPrice def currency = USD @@ -1289,6 +1355,12 @@ class BidAdjustmentSpec extends BaseSpec { def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.cur == [currency] + and: "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 == ALIAS + where: bidAdjustmentRuleBidder << ["alias", "aliasUpperCase", "aliasCamelCase"] } @@ -1336,6 +1408,12 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain currency from request" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.cur == [currency] + + and: "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 } def "PBS should adjust bid price when bid adjustment bidder is the same as bidder code"() { @@ -1381,6 +1459,12 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain currency from request" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.cur == [currency] + + and: "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 == ALIAS } def "PBS should adjust bid price when bid adjustment wildcard bidder and bidder code specified"() { @@ -1426,6 +1510,12 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain currency from request" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.cur == [currency] + + and: "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 == ALIAS } private static Map getExternalCurrencyConverterConfig() { From cad25ac033f5a30fb9f178649887b1073e19fc3e Mon Sep 17 00:00:00 2001 From: markiian Date: Wed, 28 May 2025 11:40:32 +0300 Subject: [PATCH 3/3] Update after review --- .../functional/model/request/auction/BidAdjustmentRule.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy index 3292a000a1e..5f57bc9a1ed 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidAdjustmentRule.groovy @@ -10,9 +10,9 @@ class BidAdjustmentRule { Map> wildcardBidder Map> generic Map> alias - Map> amx @JsonProperty("ALIAS") Map> aliasUpperCase @JsonProperty("AlIaS") Map> aliasCamelCase + Map> amx }