From 082aadd2f6efe879fec38f240bf880773ab346f7 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Thu, 27 Mar 2025 11:54:39 +0200 Subject: [PATCH 1/6] Tests: adjust floors for bidadjustments --- .../functional/tests/BidAdjustmentSpec.groovy | 250 +++++++++++++++--- 1 file changed, 210 insertions(+), 40 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 79eb960787f..d7faee741e6 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -192,9 +192,12 @@ class BidAdjustmentSpec extends BaseSpec { def "PBS should adjust bid price for matching bidder when request has bidAdjustments config"() { given: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD + def impPrice = PBSUtils.randomPrice def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) bidRequest.cur = [currency] + bidRequest.imp.first.bidFloor = impPrice + bidRequest.imp.first.bidFloorCur = currency and: "Default bid response" def originalPrice = PBSUtils.randomPrice @@ -217,9 +220,90 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain default currency" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, ruleValue as BigDecimal, adjustmentType)] + + where: + adjustmentType | ruleValue | mediaType | bidRequest + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest + + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest + + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest + } + + def "PBS should adjust bid price for matching bidder and left original bidderRequest with null floors when request has bidAdjustments config"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) + bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) + bidRequest.cur = [currency] + bidRequest.imp.first.bidFloor = null + bidRequest.imp.first.bidFloorCur = currency + + and: "Default bid response" + def originalPrice = PBSUtils.randomPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + 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 == getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType) + 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 floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [null] where: adjustmentType | ruleValue | mediaType | bidRequest @@ -270,10 +354,18 @@ class BidAdjustmentSpec extends BaseSpec { given: "Default BidRequest with ext.prebid.bidAdjustments" def dealId = PBSUtils.randomString def currency = USD + def firstImpPrice = PBSUtils.randomPrice + def secondImpPrice = PBSUtils.randomPrice def rule = new BidAdjustmentRule(generic: [(dealId): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) - bidRequest.imp.add(Imp.defaultImpression) bidRequest.cur = [currency] + bidRequest.imp.first.bidFloor = firstImpPrice + bidRequest.imp.first.bidFloorCur = currency + def secondImp = Imp.defaultImpression.tap { + bidFloor = secondImpPrice + bidFloorCur = currency + } + bidRequest.imp.add(secondImp) and: "Default bid response" def originalPrice = PBSUtils.randomPrice @@ -281,14 +373,13 @@ class BidAdjustmentSpec extends BaseSpec { cur = currency seatbid.first.bid.first.price = originalPrice seatbid.first.bid.first.dealid = dealId - } + } as BidResponse bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted for big with dealId" - response.seatbid.first.bid.find { it.dealid == dealId } assert response.seatbid.first.bid.findAll() { it.dealid == dealId }.price == [getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType)] and: "Price shouldn't be updated for bid with different dealId" @@ -302,9 +393,10 @@ class BidAdjustmentSpec extends BaseSpec { assert response.seatbid.first.bid.ext.first.origbidcur == bidResponse.cur assert response.seatbid.first.bid.ext.last.origbidcur == bidResponse.cur - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency, currency] + assert bidderRequest.imp.bidFloor.sort() == [getReverseAdjustedPrice(firstImpPrice, ruleValue as BigDecimal, adjustmentType), secondImpPrice].sort() where: adjustmentType | ruleValue | mediaType | bidRequest @@ -352,13 +444,18 @@ class BidAdjustmentSpec extends BaseSpec { } def "PBS should adjust bid price for matching bidder when account config has bidAdjustments"() { - given: "Default bid response" - def originalPrice = PBSUtils.randomPrice + given: "BidRequest with floors" + def impPrice = PBSUtils.randomPrice def currency = USD + bidRequest.imp.first.bidFloor = impPrice + bidRequest.imp.first.bidFloorCur = currency + + and: "Default bid response" + def originalPrice = PBSUtils.randomPrice def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = currency seatbid.first.bid.first.price = originalPrice - } + } as BidResponse bidder.setResponse(bidRequest.id, bidResponse) and: "Account in the DB with bidAdjustments" @@ -380,9 +477,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, ruleValue as BigDecimal, adjustmentType)] where: adjustmentType | ruleValue | mediaType | bidRequest @@ -430,8 +528,13 @@ class BidAdjustmentSpec extends BaseSpec { } def "PBS should prioritize BidAdjustmentRule from request when account and request config bidAdjustments conflict"() { - given: "Default BidRequest with ext.prebid.bidAdjustments" + given: "BidRequest with floors" + def impPrice = PBSUtils.randomPrice def currency = USD + bidRequest.imp.first.bidFloor = impPrice + bidRequest.imp.first.bidFloorCur = currency + + and: "Default BidRequest with ext.prebid.bidAdjustments" def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) bidRequest.cur = [currency] @@ -447,7 +550,7 @@ class BidAdjustmentSpec extends BaseSpec { def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = currency seatbid.first.bid.first.price = originalPrice - } + } as BidResponse bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" @@ -463,9 +566,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, ruleValue as BigDecimal, adjustmentType)] where: adjustmentType | ruleValue | mediaType | bidRequest @@ -515,11 +619,14 @@ class BidAdjustmentSpec extends BaseSpec { def "PBS should prioritize exact bid price adjustment for matching bidder when request has exact and general bidAdjustment"() { given: "Default BidRequest with ext.prebid.bidAdjustments" def exactRulePrice = PBSUtils.randomPrice + def impPrice = PBSUtils.randomPrice def currency = USD def exactRule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency)]]) def generalRule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: STATIC, value: PBSUtils.randomPrice, currency: currency)]]) def bidRequest = BidRequest.defaultBidRequest.tap { cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency ext.prebid.bidAdjustments = new BidAdjustment(mediaType: [(BANNER): exactRule, (ANY): generalRule]) } @@ -544,19 +651,23 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, exactRulePrice, STATIC)] } def "PBS should adjust bid price for matching bidder in provided order when bidAdjustments have multiple matching rules"() { given: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD + def impPrice = PBSUtils.randomPrice def firstRule = new AdjustmentRule(adjustmentType: firstRuleType, value: PBSUtils.randomPrice, currency: currency) def secondRule = new AdjustmentRule(adjustmentType: secondRuleType, value: PBSUtils.randomPrice, currency: currency) def bidAdjustmentMultyRule = new BidAdjustmentRule(generic: [(WILDCARD): [firstRule, secondRule]]) def bidRequest = BidRequest.defaultBidRequest.tap { cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, bidAdjustmentMultyRule) } @@ -583,9 +694,12 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency] + def rawReverseAdjustedBidPrice = getReverseAdjustedPrice(impPrice, firstRule.value as BigDecimal, firstRule.adjustmentType) + def ReversedAdjustBidPrice = getAdjustedPrice(rawReverseAdjustedBidPrice, secondRule.value as BigDecimal, secondRule.adjustmentType) + assert bidderRequest.imp.bidFloor == [ReversedAdjustBidPrice] where: firstRuleType | secondRuleType @@ -604,8 +718,12 @@ class BidAdjustmentSpec extends BaseSpec { given: "Default BidRequest with ext.prebid.bidAdjustments" def adjustmentRule = new AdjustmentRule(adjustmentType: CPM, value: PBSUtils.randomPrice, currency: GBP) def bidAdjustmentMultyRule = new BidAdjustmentRule(generic: [(WILDCARD): [adjustmentRule]]) + def currency = EUR + def impPrice = PBSUtils.randomPrice def bidRequest = BidRequest.defaultBidRequest.tap { cur = [EUR] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, bidAdjustmentMultyRule) } @@ -623,7 +741,7 @@ class BidAdjustmentSpec extends BaseSpec { then: "Final bid price should be adjusted" def convertedAdjustment = convertCurrency(adjustmentRule.value, adjustmentRule.currency, bidResponse.cur) def adjustedBidPrice = getAdjustedPrice(originalPrice, convertedAdjustment, adjustmentRule.adjustmentType) - assert response.seatbid.first.bid.first.price == convertCurrency(adjustedBidPrice, bidResponse.cur, bidRequest.cur.first) + assert response.seatbid.first.bid.first.price == convertCurrency(adjustedBidPrice, bidResponse.cur, currency) and: "Original bid price and currency should be presented in bid.ext" verifyAll(response.seatbid.first.bid.first.ext) { @@ -631,17 +749,22 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == bidRequest.cur + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, convertedAdjustment, adjustmentRule.adjustmentType)] } def "PBS should change original currency when static bidAdjustments and original response have different currencies"() { given: "Default BidRequest with ext.prebid.bidAdjustments" def adjustmentRule = new AdjustmentRule(adjustmentType: STATIC, value: PBSUtils.randomPrice, currency: GBP) def bidAdjustmentMultyRule = new BidAdjustmentRule(generic: [(WILDCARD): [adjustmentRule]]) + def currency = EUR + def impPrice = PBSUtils.randomPrice def bidRequest = BidRequest.defaultBidRequest.tap { - cur = [EUR] + cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, bidAdjustmentMultyRule) } @@ -657,7 +780,7 @@ class BidAdjustmentSpec extends BaseSpec { def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted and converted to original request cur" - assert response.seatbid.first.bid.first.price == convertCurrency(adjustmentRule.value, adjustmentRule.currency, bidRequest.cur.first) + assert response.seatbid.first.bid.first.price == convertCurrency(adjustmentRule.value, adjustmentRule.currency, currency) assert response.cur == bidRequest.cur.first and: "Original bid price and currency should be presented in bid.ext" @@ -666,19 +789,23 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == bidRequest.cur + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [impPrice] } def "PBS should apply bidAdjustments after bidAdjustmentFactors when both are present"() { given: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD + def impPrice = PBSUtils.randomPrice def bidAdjustmentFactorsPrice = PBSUtils.randomPrice def adjustmentRule = new AdjustmentRule(adjustmentType: adjustmentType, value: PBSUtils.randomPrice, currency: currency) def bidAdjustmentMultyRule = new BidAdjustmentRule(generic: [(WILDCARD): [adjustmentRule]]) def bidRequest = BidRequest.defaultBidRequest.tap { cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, bidAdjustmentMultyRule) ext.prebid.bidAdjustmentFactors = new BidAdjustmentFactors(adjustments: [(GENERIC): bidAdjustmentFactorsPrice]) } @@ -705,9 +832,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, adjustmentPrice, adjustmentType)] where: adjustmentType << [MULTIPLIER, CPM, STATIC] @@ -719,16 +847,19 @@ class BidAdjustmentSpec extends BaseSpec { and: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD + def impPrice = PBSUtils.randomPrice def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) bidRequest.cur = [currency] + bidRequest.imp.first.bidFloor = impPrice + bidRequest.imp.first.bidFloorCur = currency and: "Default bid response" def originalPrice = PBSUtils.randomPrice def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = currency seatbid.first.bid.first.price = originalPrice - } + } as BidResponse bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" @@ -754,9 +885,10 @@ class BidAdjustmentSpec extends BaseSpec { def logs = pbsService.getLogsByTime(startTime) assert getLogsByText(logs, errorMessage) - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [impPrice] where: adjustmentType | ruleValue | mediaType | bidRequest @@ -845,9 +977,12 @@ class BidAdjustmentSpec extends BaseSpec { def "PBS shouldn't adjust bid price for matching bidder when request has different bidder name in bidAdjustments config"() { given: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD + def impPrice = PBSUtils.randomPrice def rule = new BidAdjustmentRule(alias: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: PBSUtils.randomPrice, currency: currency)]]) def bidRequest = BidRequest.defaultBidRequest.tap { cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, rule) } @@ -875,9 +1010,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [impPrice] where: adjustmentType << [MULTIPLIER, CPM, STATIC] @@ -889,10 +1025,13 @@ class BidAdjustmentSpec extends BaseSpec { and: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD + def impPrice = PBSUtils.randomPrice def adjustmentPrice = PBSUtils.randomPrice.toDouble() def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: adjustmentPrice, currency: null)]]) def bidRequest = BidRequest.defaultBidRequest.tap { cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, rule) } @@ -927,9 +1066,10 @@ class BidAdjustmentSpec extends BaseSpec { def logs = pbsService.getLogsByTime(startTime) assert getLogsByText(logs, errorMessage) - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [impPrice] where: adjustmentType << [CPM, STATIC] @@ -939,9 +1079,12 @@ class BidAdjustmentSpec extends BaseSpec { given: "Default BidRequest with ext.prebid.bidAdjustments" def adjustmentPrice = PBSUtils.randomPrice def currency = USD + def impPrice = PBSUtils.randomPrice def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: adjustmentPrice, currency: null)]]) def bidRequest = BidRequest.defaultBidRequest.tap { cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(UNKNOWN, rule) } @@ -969,9 +1112,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [impPrice] where: adjustmentType << [MULTIPLIER, CPM, STATIC] @@ -983,10 +1127,13 @@ class BidAdjustmentSpec extends BaseSpec { and: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD + def impPrice = PBSUtils.randomPrice def adjustmentPrice = PBSUtils.randomPrice.toDouble() def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: AdjustmentType.UNKNOWN, value: adjustmentPrice, currency: currency)]]) def bidRequest = BidRequest.defaultBidRequest.tap { cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, rule) } @@ -1030,9 +1177,14 @@ class BidAdjustmentSpec extends BaseSpec { given: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD def adjustmentPrice = PBSUtils.randomPrice + def impPrice = PBSUtils.randomPrice def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: MULTIPLIER, value: adjustmentPrice, currency: null)]]) def bidRequest = BidRequest.defaultBidRequest.tap { cur = [currency] + imp.first.tap { + bidFloor = impPrice + bidFloorCur = currency + } ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, rule) } @@ -1066,13 +1218,16 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain currency from request" + and: "Bidder request should contain floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) - assert bidderRequest.cur == [currency] + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, adjustmentPrice, MULTIPLIER)] where: adjustmentType << [CPM, STATIC] } + + private static Map getExternalCurrencyConverterConfig() { ["auction.ad-server-currency" : DEFAULT_CURRENCY as String, @@ -1128,6 +1283,21 @@ class BidAdjustmentSpec extends BaseSpec { } } + private static BigDecimal getReverseAdjustedPrice(BigDecimal originalPrice, + BigDecimal adjustedValue, + AdjustmentType adjustmentType) { + switch (adjustmentType) { + case MULTIPLIER: + return PBSUtils.roundDecimal(originalPrice / adjustedValue, BID_ADJUST_PRECISION) + case CPM: + return PBSUtils.roundDecimal(originalPrice + adjustedValue, BID_ADJUST_PRECISION) + case STATIC: + return originalPrice + default: + return adjustedValue + } + } + private static BidRequest getDefaultVideoRequestWithPlacement(VideoPlacementSubtypes videoPlacementSubtypes) { BidRequest.defaultVideoRequest.tap { imp.first.video.tap { From 39eec19aa6e98e05a546437b95fede1441a339e3 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Fri, 16 May 2025 11:03:11 +0300 Subject: [PATCH 2/6] update functional tests for adjust floors --- .../model/request/auction/BidRequest.groovy | 5 + .../model/response/auction/Bid.groovy | 2 +- .../functional/tests/BidAdjustmentSpec.groovy | 67 +- .../PriceFloorsAdjustmentSpec.groovy | 1229 +++++++++++++++++ .../pricefloors/PriceFloorsBaseSpec.groovy | 23 +- .../PriceFloorsCurrencySpec.groovy | 18 +- 6 files changed, 1281 insertions(+), 63 deletions(-) create mode 100644 src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy index aa9da45a4b6..26e9ddc2057 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.model.Currency +import org.prebid.server.functional.model.response.auction.MediaType import static org.prebid.server.functional.model.request.auction.DebugCondition.ENABLED import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP @@ -57,6 +58,10 @@ class BidRequest { getDefaultRequest(channel, Imp.getDefaultImpression(AUDIO)) } + static BidRequest getDefaultBidRequest(MediaType mediaType, DistributionChannel channel = SITE) { + getDefaultRequest(channel, Imp.getDefaultImpression(mediaType)) + } + static BidRequest getDefaultStoredRequest() { getDefaultBidRequest().tap { site = null diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy index 22e29b76908..127ed32bfd9 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy @@ -60,7 +60,7 @@ class Bid implements ObjectMapperWrapper { new Bid().tap { id = UUID.randomUUID() impid = imp.id - price = PBSUtils.getRandomPrice() + price = imp.bidFloor != null ? imp.bidFloor : PBSUtils.getRandomPrice() crid = 1 height = imp.banner && imp.banner.format ? imp.banner.format.first().height : null weight = imp.banner && imp.banner.format ? imp.banner.format.first().weight : null 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 46e4de50fa5..d5490c929a2 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -229,10 +229,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] - assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, ruleValue as BigDecimal, adjustmentType)] + assert bidderRequest.imp.bidFloor == [impPrice] where: adjustmentType | ruleValue | mediaType | bidRequest @@ -309,7 +309,7 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [null] @@ -402,10 +402,10 @@ class BidAdjustmentSpec extends BaseSpec { assert response.seatbid.first.bid.ext.first.origbidcur == bidResponse.cur assert response.seatbid.first.bid.ext.last.origbidcur == bidResponse.cur - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency, currency] - assert bidderRequest.imp.bidFloor.sort() == [getReverseAdjustedPrice(firstImpPrice, ruleValue as BigDecimal, adjustmentType), secondImpPrice].sort() + assert bidderRequest.imp.bidFloor.sort() == [firstImpPrice, secondImpPrice].sort() where: adjustmentType | ruleValue | mediaType | bidRequest @@ -486,10 +486,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] - assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, ruleValue as BigDecimal, adjustmentType)] + assert bidderRequest.imp.bidFloor == [impPrice] where: adjustmentType | ruleValue | mediaType | bidRequest @@ -575,10 +575,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] - assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, ruleValue as BigDecimal, adjustmentType)] + assert bidderRequest.imp.bidFloor == [impPrice] where: adjustmentType | ruleValue | mediaType | bidRequest @@ -660,10 +660,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] - assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, exactRulePrice, STATIC)] + assert bidderRequest.imp.bidFloor == [impPrice] } def "PBS should adjust bid price for matching bidder in provided order when bidAdjustments have multiple matching rules"() { @@ -703,12 +703,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] - def rawReverseAdjustedBidPrice = getReverseAdjustedPrice(impPrice, firstRule.value as BigDecimal, firstRule.adjustmentType) - def ReversedAdjustBidPrice = getAdjustedPrice(rawReverseAdjustedBidPrice, secondRule.value as BigDecimal, secondRule.adjustmentType) - assert bidderRequest.imp.bidFloor == [ReversedAdjustBidPrice] + assert bidderRequest.imp.bidFloor == [impPrice] where: firstRuleType | secondRuleType @@ -758,10 +756,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] - assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, convertedAdjustment, adjustmentRule.adjustmentType)] + assert bidderRequest.imp.bidFloor == [impPrice] } def "PBS should change original currency when static bidAdjustments and original response have different currencies"() { @@ -798,7 +796,7 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -841,10 +839,10 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] - assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, adjustmentPrice, adjustmentType)] + assert bidderRequest.imp.bidFloor == [impPrice] where: adjustmentType << [MULTIPLIER, CPM, STATIC] @@ -894,7 +892,7 @@ class BidAdjustmentSpec extends BaseSpec { def logs = pbsService.getLogsByTime(startTime) assert getLogsByText(logs, errorMessage) - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -1019,7 +1017,7 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -1075,7 +1073,7 @@ class BidAdjustmentSpec extends BaseSpec { def logs = pbsService.getLogsByTime(startTime) assert getLogsByText(logs, errorMessage) - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -1121,7 +1119,7 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -1227,17 +1225,15 @@ class BidAdjustmentSpec extends BaseSpec { origbidcur == bidResponse.cur } - and: "Bidder request should contain floors" + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp.bidFloorCur == [currency] - assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, adjustmentPrice, MULTIPLIER)] + assert bidderRequest.imp.bidFloor == [impPrice] where: 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 { @@ -1402,21 +1398,6 @@ class BidAdjustmentSpec extends BaseSpec { } } - private static BigDecimal getReverseAdjustedPrice(BigDecimal originalPrice, - BigDecimal adjustedValue, - AdjustmentType adjustmentType) { - switch (adjustmentType) { - case MULTIPLIER: - return PBSUtils.roundDecimal(originalPrice / adjustedValue, BID_ADJUST_PRECISION) - case CPM: - return PBSUtils.roundDecimal(originalPrice + adjustedValue, BID_ADJUST_PRECISION) - case STATIC: - return originalPrice - default: - return adjustedValue - } - } - private static BidRequest getDefaultVideoRequestWithPlacement(VideoPlacementSubtypes videoPlacementSubtypes) { BidRequest.defaultVideoRequest.tap { imp.first.video.tap { diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy new file mode 100644 index 00000000000..7b760462008 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy @@ -0,0 +1,1229 @@ +package org.prebid.server.functional.tests.pricefloors + + +import org.prebid.server.functional.model.config.AccountAuctionConfig +import org.prebid.server.functional.model.config.AccountConfig +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.pricefloors.PriceFloorField +import org.prebid.server.functional.model.pricefloors.Rule +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.Audio +import org.prebid.server.functional.model.request.auction.BidAdjustment +import org.prebid.server.functional.model.request.auction.BidAdjustmentFactors +import org.prebid.server.functional.model.request.auction.BidAdjustmentRule +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.DistributionChannel +import org.prebid.server.functional.model.request.auction.ExtPrebidFloors +import org.prebid.server.functional.model.request.auction.Imp +import org.prebid.server.functional.model.request.auction.Native +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.BidResponse +import org.prebid.server.functional.model.response.auction.MediaType +import org.prebid.server.functional.service.PrebidServerService +import org.prebid.server.functional.util.PBSUtils + +import java.time.Instant + +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.GENERIC +import static org.prebid.server.functional.model.request.auction.AdjustmentType.CPM +import static org.prebid.server.functional.model.request.auction.AdjustmentType.MULTIPLIER +import static org.prebid.server.functional.model.request.auction.AdjustmentType.STATIC +import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.ANY +import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.AUDIO +import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.NATIVE +import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.BANNER +import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.UNKNOWN +import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.VIDEO_IN_STREAM +import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.VIDEO_OUT_STREAM +import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE +import static org.prebid.server.functional.model.request.auction.VideoPlacementSubtypes.IN_STREAM as IN_PLACEMENT_STREAM +import static org.prebid.server.functional.model.request.auction.VideoPlcmtSubtype.IN_STREAM as IN_PLCMT_STREAM +import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID + +class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { + + private static final Integer MIN_ADJUST_VALUE = 0 + private static final Integer MAX_MULTIPLIER_ADJUST_VALUE = 99 + private static final VideoPlacementSubtypes RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM = PBSUtils.getRandomEnum(VideoPlacementSubtypes, [IN_PLACEMENT_STREAM]) + private static final VideoPlcmtSubtype RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM = PBSUtils.getRandomEnum(VideoPlcmtSubtype, [IN_PLCMT_STREAM]) + private static final Integer MAX_CPM_ADJUST_VALUE = 5 + private static final Integer MAX_STATIC_ADJUST_VALUE = Integer.MAX_VALUE + private static final String WILDCARD = '*' + + private static final Map config = CURRENCY_CONVERTER_CONFIG + + FLOORS_CONFIG + + GENERIC_ALIAS_CONFIG + + ["adapter-defaults.ortb.multiformat-supported": "true"] + private static final PrebidServerService pbsService = pbsServiceFactory.getService(config) + + def cleanupSpec() { + pbsServiceFactory.removeContainer(config) + } + + def "PBS should reverse imp.floors for matching bidder when request has bidAdjustments config"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) + bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) + bidRequest.cur = [currency] + bidRequest.imp.first.bidFloor = impPrice + bidRequest.imp.first.bidFloorCur = currency + + and: "Default bid response" + def originalPrice = PBSUtils.getRandomDecimal(impPrice) + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted" + assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType) + 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 reversed floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, ruleValue as BigDecimal, adjustmentType)] + + where: + adjustmentType | ruleValue | mediaType | bidRequest + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + } + + def "PBS should left original bidderRequest with null floors when request has bidAdjustments config"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) + bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) + bidRequest.cur = [currency] + bidRequest.imp.first.bidFloor = null + bidRequest.imp.first.bidFloorCur = currency + + and: "Default bid response" + def originalPrice = PBSUtils.randomPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted" + assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType) + 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 original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [null] + + where: + adjustmentType | ruleValue | mediaType | bidRequest + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + } + + def "PBS should reverse imp.floors for matching bidder when request with multiple imps has specific bidAdjustments config"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def firstImpPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def secondImpPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) + bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) + bidRequest.cur = [currency] + bidRequest.imp.first.bidFloor = firstImpPrice + bidRequest.imp.first.bidFloorCur = currency + def secondImp = Imp.defaultImpression.tap { + bidFloor = secondImpPrice + bidFloorCur = currency + } + bidRequest.imp.add(secondImp) + + and: "Default bid response" + def originalPrice = PBSUtils.randomPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + seatbid.first.bid.first.dealid = PBSUtils.randomString + } as BidResponse + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted for big with dealId" + assert response.seatbid.first.bid.findAll() { it.impid == bidRequest.imp.first.id }.price == [getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType)] + + and: "Price shouldn't be updated for bid with different dealId" + assert response.seatbid.first.bid.findAll() { it.impid == bidRequest.imp.last.id }.price == [bidResponse.seatbid.first.bid.last.price] + + and: "Response currency should stay the same" + assert response.cur == bidResponse.cur + + and: "Original bid price and currency should be presented in bid.ext" + assert response.seatbid.first.bid.ext.origbidcpm.sort() == bidResponse.seatbid.first.bid.price.sort() + assert response.seatbid.first.bid.ext.first.origbidcur == bidResponse.cur + assert response.seatbid.first.bid.ext.last.origbidcur == bidResponse.cur + + and: "Bidder request should contain reversed imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency, currency] + assert bidderRequest.imp.bidFloor.sort() == [getReverseAdjustedPrice(firstImpPrice, ruleValue as BigDecimal, adjustmentType), secondImpPrice].sort() + + where: + adjustmentType | ruleValue | mediaType | bidRequest + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + } + + def "PBS shouldn't reverse imp.floors for matching bidder with specific dealId when request with multiple imps has bidAdjustments config"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def dealId = PBSUtils.randomString + def currency = USD + def firstImpPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def secondImpPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def rule = new BidAdjustmentRule(generic: [(dealId): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) + bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) + bidRequest.cur = [currency] + bidRequest.imp.first.bidFloor = firstImpPrice + bidRequest.imp.first.bidFloorCur = currency + def secondImp = Imp.defaultImpression.tap { + bidFloor = secondImpPrice + bidFloorCur = currency + } + bidRequest.imp.add(secondImp) + + and: "Default bid response" + def originalPrice = PBSUtils.randomPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + seatbid.first.bid.first.dealid = dealId + } as BidResponse + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted for big with dealId" + assert response.seatbid.first.bid.findAll() { it.dealid == dealId }.price == [getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType)] + + and: "Price shouldn't be updated for bid with different dealId" + assert response.seatbid.first.bid.findAll() { it.dealid != dealId }.price == bidResponse.seatbid.first.bid.findAll() { it.dealid != dealId }.price + + and: "Response currency should stay the same" + assert response.cur == bidResponse.cur + + and: "Original bid price and currency should be presented in bid.ext" + assert response.seatbid.first.bid.ext.origbidcpm.sort() == bidResponse.seatbid.first.bid.price.sort() + assert response.seatbid.first.bid.ext.first.origbidcur == bidResponse.cur + assert response.seatbid.first.bid.ext.last.origbidcur == bidResponse.cur + + and: "Bidder request should contain original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency, currency] + assert bidderRequest.imp.bidFloor.sort() == [firstImpPrice, secondImpPrice].sort() + + where: + adjustmentType | ruleValue | mediaType | bidRequest + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + } + + def "PBS should reverse imp.floors for matching bidder when account config has bidAdjustments"() { + given: "BidRequest with floors" + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def currency = USD + bidRequest.imp.first.bidFloor = impPrice + bidRequest.imp.first.bidFloorCur = currency + + and: "Default bid response" + def originalPrice = PBSUtils.randomDecimal + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } as BidResponse + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Account in the DB with bidAdjustments" + def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) + def accountConfig = new AccountAuctionConfig(bidAdjustments: BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule)) + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: accountConfig)) + accountDao.save(account) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted" + assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType) + 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 reversed imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, ruleValue as BigDecimal, adjustmentType)] + + where: + adjustmentType | ruleValue | mediaType | bidRequest + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + } + + def "PBS should prioritize BidAdjustmentRule from request when account and request config bidAdjustments conflict"() { + given: "BidRequest with floors" + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def currency = USD + bidRequest.imp.first.bidFloor = impPrice + bidRequest.imp.first.bidFloorCur = currency + + and: "Default BidRequest with ext.prebid.bidAdjustments" + def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) + bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) + bidRequest.cur = [currency] + + and: "Account in the DB with bidAdjustments" + def accountRule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) + def accountConfig = new AccountAuctionConfig(bidAdjustments: BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, accountRule)) + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: accountConfig)) + accountDao.save(account) + + and: "Default bid response" + def originalPrice = PBSUtils.randomDecimal + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } as BidResponse + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted according to request config" + assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType) + 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 original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, ruleValue as BigDecimal, adjustmentType)] + + where: + adjustmentType | ruleValue | mediaType | bidRequest + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | BANNER | getBidRequestWithFloors(MediaType.BANNER) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | ANY | getBidRequestWithFloors(MediaType.BANNER) + } + + def "PBS should prioritize exact imp.floors reverser for matching bidder when request has exact and general bidAdjustment"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def exactRulePrice = PBSUtils.randomDecimal + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def currency = USD + def exactRule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency)]]) + def generalRule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: STATIC, value: PBSUtils.randomPrice, currency: currency)]]) + def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency + ext.prebid.bidAdjustments = new BidAdjustment(mediaType: [(BANNER): exactRule, (ANY): generalRule]) + } + + and: "Default bid response" + def originalPrice = PBSUtils.randomPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.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 original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, exactRulePrice, STATIC)] + } + + def "PBS should adjust bid price for matching bidder in provided order when bidAdjustments have multiple matching rules"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def firstRule = new AdjustmentRule(adjustmentType: firstRuleType, value: firstRuleValue, currency: currency) + def secondRule = new AdjustmentRule(adjustmentType: secondRuleType, value: secondRuleValue, currency: currency) + def bidAdjustmentMultyRule = new BidAdjustmentRule(generic: [(WILDCARD): [firstRule, secondRule]]) + def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency + ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, bidAdjustmentMultyRule) + } + + and: "Default bid response" + def originalPrice = PBSUtils.randomDecimal + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted" + def rawAdjustedBidPrice = getAdjustedPrice(originalPrice, firstRule.value as BigDecimal, firstRule.adjustmentType) + def adjustedBidPrice = getAdjustedPrice(rawAdjustedBidPrice, secondRule.value as BigDecimal, secondRule.adjustmentType) + assert response.seatbid.first.bid.first.price == adjustedBidPrice + 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 original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [applyReverseAdjustments(impPrice, [firstRule, secondRule])] + + where: + firstRuleType | secondRuleType | firstRuleValue | secondRuleValue + MULTIPLIER | CPM | PBSUtils.randomPrice | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) + MULTIPLIER | STATIC | PBSUtils.randomPrice | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) + MULTIPLIER | MULTIPLIER | PBSUtils.randomPrice | PBSUtils.randomPrice + CPM | CPM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, 1) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, 1) + CPM | STATIC | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) + CPM | MULTIPLIER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.randomPrice + STATIC | CPM | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | PBSUtils.randomPrice + STATIC | STATIC | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) + STATIC | MULTIPLIER | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | PBSUtils.randomPrice + } + + def "PBS should prioritize revert with lower resulting value for matching bidder when request has multiple media types"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def impPrice = PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) + def currency = USD + def exactRule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: MULTIPLIER, value: firstRulePrice, currency: currency)]]) + def generalRule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: MULTIPLIER, value: secondRulePrice, currency: currency)]]) + def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency + imp.first.audio = Audio.defaultAudio + imp.first.nativeObj = Native.getDefaultNative() + ext.prebid.bidAdjustments = new BidAdjustment(mediaType: [(BANNER): exactRule, (additionalType): generalRule]) + } + + and: "Default bid response" + def originalPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted according to first matched rule" + assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, firstRulePrice, MULTIPLIER) + 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 revert imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, [firstRulePrice, secondRulePrice].max(), MULTIPLIER)] + + where: + additionalType | firstRulePrice | secondRulePrice + BANNER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + AUDIO | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + NATIVE | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + ANY | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + + BANNER | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) + AUDIO | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) + NATIVE | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) + ANY | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) + } + + def "PBS should convert CPM currency before adjustment when it different from original response currency"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def adjustmentRule = new AdjustmentRule(adjustmentType: CPM, value: PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE), currency: GBP) + def bidAdjustmentMultyRule = new BidAdjustmentRule(generic: [(WILDCARD): [adjustmentRule]]) + def currency = EUR + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + cur = [EUR] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency + ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, bidAdjustmentMultyRule) + } + + and: "Default bid response" + def originalPrice = PBSUtils.randomDecimal + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = USD + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Get currency rates" + def currencyRatesResponse = pbsService.sendCurrencyRatesRequest() + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted" + def convertedAdjustment = getPriceAfterCurrencyConversion(adjustmentRule.value, adjustmentRule.currency, bidResponse.cur, currencyRatesResponse) + def adjustedBidPrice = getAdjustedPrice(originalPrice, convertedAdjustment, adjustmentRule.adjustmentType) + assert response.seatbid.first.bid.first.price == getPriceAfterCurrencyConversion(adjustedBidPrice, bidResponse.cur, currency, currencyRatesResponse) + + 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 original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + def convertedReverseAdjustment = getPriceAfterCurrencyConversion(adjustmentRule.value, adjustmentRule.currency, currency, currencyRatesResponse) + def reversedAdjustBidPrice = getReverseAdjustedPrice(impPrice, convertedReverseAdjustment, adjustmentRule.adjustmentType) + assert bidderRequest.imp.bidFloor == [reversedAdjustBidPrice] + } + + def "PBS should change original currency when static bidAdjustments and original response have different currencies"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def adjustmentRule = new AdjustmentRule(adjustmentType: STATIC, value: PBSUtils.randomDecimal, currency: GBP) + def bidAdjustmentMultyRule = new BidAdjustmentRule(generic: [(WILDCARD): [adjustmentRule]]) + def currency = EUR + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency + ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, bidAdjustmentMultyRule) + } + + and: "Default bid response with JPY currency" + def originalPrice = PBSUtils.randomDecimal + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = USD + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Get currency rates" + def currencyRatesResponse = pbsService.sendCurrencyRatesRequest() + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted and converted to original request cur" + assert response.seatbid.first.bid.first.price == + getPriceAfterCurrencyConversion(adjustmentRule.value, adjustmentRule.currency, currency, currencyRatesResponse) + assert response.cur == bidRequest.cur.first + + 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 original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [impPrice] + } + + def "PBS should apply bidAdjustments revert for imp.floors after bidAdjustmentFactors when both are present"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def bidAdjustmentFactorsPrice = PBSUtils.randomPrice + def adjustmentRule = new AdjustmentRule(adjustmentType: adjustmentType, value: adjustmentValue, currency: currency) + def bidAdjustmentMultyRule = new BidAdjustmentRule(generic: [(WILDCARD): [adjustmentRule]]) + def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency + ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, bidAdjustmentMultyRule) + ext.prebid.bidAdjustmentFactors = new BidAdjustmentFactors(adjustments: [(GENERIC): bidAdjustmentFactorsPrice]) + } + + and: "Default bid response" + def originalPrice = PBSUtils.randomDecimal + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted" + def bidAdjustedPrice = originalPrice * bidAdjustmentFactorsPrice + assert !response.ext.warnings + assert response.seatbid.first.bid.first.price == getAdjustedPrice(bidAdjustedPrice, adjustmentRule.value, adjustmentType) + 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 original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + def reversedBidPrice = impPrice / bidAdjustmentFactorsPrice + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(reversedBidPrice, adjustmentRule.value, adjustmentType)] + + where: + adjustmentType | impPrice | adjustmentValue + MULTIPLIER | PBSUtils.getRandomPrice() | PBSUtils.getRandomPrice() + CPM | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) + STATIC | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + } + + def "PBS shouldn't reverse imp.floors for matching bidder when request has invalid value bidAdjustments config"() { + given: "Start time" + def startTime = Instant.now() + + and: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: ruleValue, currency: currency)]]) + bidRequest.ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(mediaType, rule) + bidRequest.cur = [currency] + bidRequest.imp.first.bidFloor = impPrice + bidRequest.imp.first.bidFloorCur = currency + + and: "Default bid response" + def originalPrice = PBSUtils.randomDecimal + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } as BidResponse + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should ignore bidAdjustments for this request" + assert response.seatbid.first.bid.first.price == originalPrice + assert response.cur == bidResponse.cur + + and: "Should add a warning when in debug mode" + def errorMessage = "bid adjustment from request was invalid: the found rule [adjtype=${adjustmentType}, " + + "value=${ruleValue}, currency=${currency}] in ${mediaType.value}.generic.* is invalid" as String + assert response.ext.warnings[PREBID]?.code == [999] + assert response.ext.warnings[PREBID]?.message == [errorMessage] + + 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: "PBS log should contain error" + def logs = floorsPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, errorMessage) + + and: "Bidder request should contain original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [impPrice] + + where: + adjustmentType | ruleValue | mediaType | bidRequest + MULTIPLIER | MIN_ADJUST_VALUE - 1 | BANNER | getBidRequestWithFloors(MediaType.BANNER) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | ANY | getBidRequestWithFloors(MediaType.NATIVE) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | BANNER | getBidRequestWithFloors(MediaType.BANNER) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | ANY | getBidRequestWithFloors(MediaType.NATIVE) + + CPM | MIN_ADJUST_VALUE - 1 | BANNER | getBidRequestWithFloors(MediaType.BANNER) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + CPM | MIN_ADJUST_VALUE - 1 | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + CPM | MIN_ADJUST_VALUE - 1 | ANY | getBidRequestWithFloors(MediaType.NATIVE) + + STATIC | MIN_ADJUST_VALUE - 1 | BANNER | getBidRequestWithFloors(MediaType.BANNER) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + STATIC | MIN_ADJUST_VALUE - 1 | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + STATIC | MIN_ADJUST_VALUE - 1 | ANY | getBidRequestWithFloors(MediaType.NATIVE) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | BANNER | getBidRequestWithFloors(MediaType.BANNER) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | AUDIO | getBidRequestWithFloors(MediaType.AUDIO) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | NATIVE | getBidRequestWithFloors(MediaType.NATIVE) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | ANY | getBidRequestWithFloors(MediaType.NATIVE) + } + + def "PBS shouldn't reverse imp.floors for matching bidder when request has different bidder name in bidAdjustments config"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def rule = new BidAdjustmentRule(alias: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: PBSUtils.randomPrice, currency: currency)]]) + def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency + ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, rule) + } + + and: "Default bid response" + def originalPrice = PBSUtils.randomDecimal + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should ignore bidAdjustments for this request" + assert response.seatbid.first.bid.first.price == originalPrice + assert response.cur == bidResponse.cur + + and: "Response shouldn't contain any warnings" + assert !response.ext.warnings + + 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 original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [impPrice] + + where: + adjustmentType << [MULTIPLIER, CPM, STATIC] + } + + def "PBS shouldn't reverse imp.floors for matching bidder when cpm or static bidAdjustments doesn't have currency value"() { + given: "Start time" + def startTime = Instant.now() + + and: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def adjustmentPrice = PBSUtils.randomPrice.toDouble() + def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: adjustmentPrice, currency: null)]]) + def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency + ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, rule) + } + + and: "Default bid response" + def originalPrice = PBSUtils.randomDecimal + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should ignore bidAdjustments for this request" + assert response.seatbid.first.bid.first.price == originalPrice + assert response.cur == bidResponse.cur + + and: "Should add a warning when in debug mode" + def errorMessage = "bid adjustment from request was invalid: the found rule [adjtype=${adjustmentType}, " + + "value=${adjustmentPrice}, currency=null] in banner.generic.* is invalid" as String + assert response.ext.warnings[PREBID]?.code == [999] + assert response.ext.warnings[PREBID]?.message == [errorMessage] + + 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: "PBS log should contain error" + def logs = floorsPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, errorMessage) + + and: "Bidder request should contain original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [impPrice] + + where: + adjustmentType << [CPM, STATIC] + } + + def "PBS shouldn't reverse imp.floors for matching bidder when bidAdjustments have unknown mediatype"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def adjustmentPrice = PBSUtils.randomPrice + def currency = USD + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: adjustmentType, value: adjustmentPrice, currency: null)]]) + def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency + ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(UNKNOWN, rule) + } + + and: "Default bid response" + def originalPrice = PBSUtils.randomDecimal + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should ignore bidAdjustments for this request" + assert response.seatbid.first.bid.first.price == originalPrice + assert response.cur == bidResponse.cur + + and: "Response shouldn't contain any warnings" + assert !response.ext.warnings + + 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 original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [impPrice] + + where: + adjustmentType << [MULTIPLIER, CPM, STATIC] + } + + def "PBS shouldn't reverse imp.floors for matching bidder when bidAdjustments have unknown adjustmentType"() { + given: "Start time" + def startTime = Instant.now() + + and: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def adjustmentPrice = PBSUtils.randomPrice.toDouble() + def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: AdjustmentType.UNKNOWN, value: adjustmentPrice, currency: currency)]]) + def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + cur = [currency] + imp.first.bidFloor = impPrice + imp.first.bidFloorCur = currency + ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, rule) + } + + and: "Default bid response" + def originalPrice = impPrice + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should ignore bidAdjustments for this request" + assert response.seatbid.first.bid.first.price == originalPrice + assert response.cur == bidResponse.cur + + and: "Should add a warning when in debug mode" + def errorMessage = "bid adjustment from request was invalid: the found rule [adjtype=UNKNOWN, " + + "value=$adjustmentPrice, currency=$currency] in banner.generic.* is invalid" as String + assert response.ext.warnings[PREBID]?.code == [999] + assert response.ext.warnings[PREBID]?.message == [errorMessage] + + 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: "PBS log should contain error" + def logs = floorsPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, errorMessage) + + and: "Bidder request should contain currency from request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] + } + + def "PBS shouldn't reverse imp.floors for matching bidder when multiplier bidAdjustments doesn't have currency value"() { + given: "Default BidRequest with ext.prebid.bidAdjustments" + def currency = USD + def adjustmentPrice = PBSUtils.randomPrice + def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def rule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: MULTIPLIER, value: adjustmentPrice, currency: null)]]) + def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + cur = [currency] + imp.first.tap { + bidFloor = impPrice + bidFloorCur = currency + } + ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, rule) + } + + and: "Default bid response" + def originalPrice = PBSUtils.randomDecimal + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + cur = currency + seatbid.first.bid.first.price = originalPrice + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "Final bid price should be adjusted" + assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, adjustmentPrice, MULTIPLIER) + 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: "Response shouldn't contain any warnings" + assert !response.ext.warnings + + 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 original imp.floors" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.bidFloorCur == [currency] + assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, adjustmentPrice, MULTIPLIER)] + + where: + adjustmentType << [CPM, STATIC] + } + + private static BidRequest getDefaultVideoRequestWithPlacement(VideoPlacementSubtypes videoPlacementSubtypes) { + getBidRequestWithFloors(MediaType.VIDEO).tap { + imp.first.video.tap { + placement = videoPlacementSubtypes + } + } + } + + private static BidRequest getDefaultVideoRequestWithPlcmt(VideoPlcmtSubtype videoPlcmtSubtype) { + getBidRequestWithFloors(MediaType.VIDEO).tap { + imp.first.video.tap { + plcmt = videoPlcmtSubtype + } + } + } + + private static BidRequest getDefaultVideoRequestWithPlcmtAndPlacement(VideoPlcmtSubtype videoPlcmtSubtype, + VideoPlacementSubtypes videoPlacementSubtypes) { + getBidRequestWithFloors(MediaType.VIDEO).tap { + imp.first.video.tap { + plcmt = videoPlcmtSubtype + placement = videoPlacementSubtypes + } + } + } + + private static BigDecimal getReverseAdjustedPrice(BigDecimal originalPrice, + BigDecimal adjustedValue, + AdjustmentType adjustmentType) { + switch (adjustmentType) { + case MULTIPLIER: + return PBSUtils.roundDecimal(originalPrice / adjustedValue, FLOOR_VALUE_PRECISION) + case CPM: + return PBSUtils.roundDecimal(originalPrice + adjustedValue, FLOOR_VALUE_PRECISION) + case STATIC: + return PBSUtils.roundDecimal(originalPrice, FLOOR_VALUE_PRECISION) + default: + return adjustedValue + } + } + + private static BigDecimal applyReverseAdjustments(BigDecimal originalPrice, List rules) { + if (!rules || rules.any { it.adjustmentType == STATIC }) { + return originalPrice + } + def result = originalPrice + rules.reverseEach { + result = getReverseAdjustedPrice(result, it.value, it.adjustmentType) + } + result + } + + private static BigDecimal getAdjustedPrice(BigDecimal originalPrice, + BigDecimal adjustedValue, + AdjustmentType adjustmentType) { + switch (adjustmentType) { + case MULTIPLIER: + return PBSUtils.roundDecimal(originalPrice * adjustedValue, FLOOR_VALUE_PRECISION) + case CPM: + return PBSUtils.roundDecimal(originalPrice - adjustedValue, FLOOR_VALUE_PRECISION) + case STATIC: + return adjustedValue + default: + return originalPrice + } + } + + private static BidRequest getBidRequestWithFloors(MediaType type, + DistributionChannel channel = SITE) { + def floors = ExtPrebidFloors.extPrebidFloors.tap { + data.modelGroups.first.values = [(new Rule(channel: PBSUtils.randomString) + .getRule([PriceFloorField.CHANNEL])): PBSUtils.randomFloorValue] + } + BidRequest.getDefaultBidRequest(type, channel).tap { + ext.prebid.floors = floors + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy index 8b3f5d936bd..427ba1d10fd 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy @@ -7,6 +7,7 @@ import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountPriceFloorsConfig import org.prebid.server.functional.model.config.PriceFloorsFetch import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse import org.prebid.server.functional.model.pricefloors.Country import org.prebid.server.functional.model.pricefloors.MediaType import org.prebid.server.functional.model.pricefloors.Rule @@ -19,12 +20,16 @@ import org.prebid.server.functional.model.request.auction.Prebid import org.prebid.server.functional.model.request.auction.Video import org.prebid.server.functional.model.response.currencyrates.CurrencyRatesResponse import org.prebid.server.functional.service.PrebidServerService +import org.prebid.server.functional.testcontainers.scaffolding.CurrencyConversion import org.prebid.server.functional.testcontainers.scaffolding.FloorsProvider import org.prebid.server.functional.tests.BaseSpec import org.prebid.server.functional.util.PBSUtils import java.math.RoundingMode +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.request.auction.DebugCondition.ENABLED import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE import static org.prebid.server.functional.model.request.auction.FetchStatus.INPROGRESS @@ -40,9 +45,23 @@ abstract class PriceFloorsBaseSpec extends BaseSpec { protected static final String BASIC_FETCH_URL = networkServiceContainer.rootUri + FloorsProvider.FLOORS_ENDPOINT protected static final int MAX_MODEL_WEIGHT = 100 + protected static final Map> DEFAULT_CURRENCY_RATES = [(USD): [(EUR): 0.9124920156948626, + (GBP): 0.793776804452961], + (GBP): [(USD): 1.2597999770088517, + (EUR): 1.1495574203931487], + (EUR): [(USD): 1.3429368029739777]] + protected static final CurrencyConversion currencyConversion = new CurrencyConversion(networkServiceContainer).tap { + setCurrencyConversionRatesResponse(CurrencyConversionRatesResponse.getDefaultCurrencyConversionRatesResponse(DEFAULT_CURRENCY_RATES)) + } + protected static final Map CURRENCY_CONVERTER_CONFIG = ["auction.ad-server-currency" : "USD", + "currency-converter.external-rates.enabled" : "true", + "currency-converter.external-rates.url" : "$networkServiceContainer.rootUri/currency".toString(), + "currency-converter.external-rates.default-timeout-ms": "4000", + "currency-converter.external-rates.refresh-period-ms" : "900000"] + + protected static final int FLOOR_VALUE_PRECISION = 4 private static final int DEFAULT_MODEL_WEIGHT = 1 private static final int CURRENCY_CONVERSION_PRECISION = 3 - private static final int FLOOR_VALUE_PRECISION = 4 protected static final FloorsProvider floorsProvider = new FloorsProvider(networkServiceContainer) protected final PrebidServerService floorsPbsService = pbsServiceFactory.getService(FLOORS_CONFIG + GENERIC_ALIAS_CONFIG) @@ -94,7 +113,7 @@ abstract class PriceFloorsBaseSpec extends BaseSpec { } static String getRule() { - new Rule(mediaType: MediaType.MULTIPLE, country: Country.MULTIPLE).rule + new Rule(mediaType: MediaType.MULTIPLE, country: Country.BULGARIA).rule } static int getModelWeight() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsCurrencySpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsCurrencySpec.groovy index 69d8dce297d..f6b66721cb0 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsCurrencySpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsCurrencySpec.groovy @@ -1,16 +1,13 @@ package org.prebid.server.functional.tests.pricefloors -import org.prebid.server.functional.model.Currency import org.prebid.server.functional.model.config.AccountPriceFloorsConfig import org.prebid.server.functional.model.config.PriceFloorsFetch -import org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse import org.prebid.server.functional.model.pricefloors.PriceFloorData import org.prebid.server.functional.model.request.auction.ImpExtPrebidFloors 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.service.PrebidServerService -import org.prebid.server.functional.testcontainers.scaffolding.CurrencyConversion import org.prebid.server.functional.util.PBSUtils import static org.prebid.server.functional.model.Currency.BOGUS @@ -22,24 +19,11 @@ import static org.prebid.server.functional.model.request.auction.FetchStatus.NON import static org.prebid.server.functional.model.request.auction.FetchStatus.SUCCESS import static org.prebid.server.functional.model.request.auction.Location.FETCH import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID -import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { - private static final Map> DEFAULT_CURRENCY_RATES = [(USD): [(EUR): 0.9124920156948626, - (GBP): 0.793776804452961], - (GBP): [(USD): 1.2597999770088517, - (EUR): 1.1495574203931487], - (EUR): [(USD): 1.3429368029739777]] - private static final CurrencyConversion currencyConversion = new CurrencyConversion(networkServiceContainer).tap { - setCurrencyConversionRatesResponse(CurrencyConversionRatesResponse.getDefaultCurrencyConversionRatesResponse(DEFAULT_CURRENCY_RATES)) - } private static final String GENERAL_ERROR_METRIC = "price-floors.general.err" - private static final Map CURRENCY_CONVERTER_CONFIG = ["auction.ad-server-currency" : "USD", - "currency-converter.external-rates.enabled" : "true", - "currency-converter.external-rates.url" : "$networkServiceContainer.rootUri/currency".toString(), - "currency-converter.external-rates.default-timeout-ms": "4000", - "currency-converter.external-rates.refresh-period-ms" : "900000"] + private final PrebidServerService currencyFloorsPbsService = pbsServiceFactory.getService(FLOORS_CONFIG + CURRENCY_CONVERTER_CONFIG) From 7cfc840c1064c74500ea3b0825d83d6954c5fe65 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Thu, 22 May 2025 16:23:16 +0300 Subject: [PATCH 3/6] minor update --- .../functional/tests/pricefloors/PriceFloorsBaseSpec.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy index 427ba1d10fd..4b6fdaae4d3 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy @@ -113,7 +113,7 @@ abstract class PriceFloorsBaseSpec extends BaseSpec { } static String getRule() { - new Rule(mediaType: MediaType.MULTIPLE, country: Country.BULGARIA).rule + new Rule(mediaType: MediaType.MULTIPLE, country: Country.MULTIPLE).rule } static int getModelWeight() { From 4f88511a5bbd7c8b313c851050b1fe46e5cbc982 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Fri, 23 May 2025 17:19:37 +0300 Subject: [PATCH 4/6] improve cases --- .../request/auction/BidAdjustmentRule.groovy | 1 + .../model/response/auction/Bid.groovy | 30 +++++++ .../response/auction/BidMediaType.groovy | 13 +++ .../server/functional/tests/BaseSpec.groovy | 6 ++ .../PriceFloorsAdjustmentSpec.groovy | 90 +++++++++++-------- 5 files changed, 101 insertions(+), 39 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..cdffc92e7e5 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 @@ -12,5 +12,6 @@ class BidAdjustmentRule { @JsonProperty('*') Map> wildcardBidder Map> generic + Map> openx Map> alias } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy index 127ed32bfd9..353f5516935 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy @@ -8,6 +8,8 @@ import org.prebid.server.functional.model.request.auction.Imp import org.prebid.server.functional.util.ObjectMapperWrapper import org.prebid.server.functional.util.PBSUtils +import static groovy.lang.Closure.DELEGATE_FIRST + @ToString(includeNames = true, ignoreNulls = true) @EqualsAndHashCode class Bid implements ObjectMapperWrapper { @@ -70,6 +72,23 @@ class Bid implements ObjectMapperWrapper { } } + static List getDefaultMultyTypesBids(Imp imp, @DelegatesTo(Bid) Closure commonInit = null) { + List bids = [] + if (imp.banner) bids << createBid(imp, BidMediaType.BANNER) { adm = null } + if (imp.video) bids << createBid(imp, BidMediaType.VIDEO) + if (imp.nativeObj) bids << createBid(imp, BidMediaType.NATIVE) + if (imp.audio) bids << createBid(imp, BidMediaType.AUDIO) { adm = null } + + if (commonInit) { + bids.each { bid -> + commonInit.delegate = bid + commonInit.resolveStrategy = DELEGATE_FIRST + commonInit() + } + } + bids + } + void setAdm(Object adm) { if (adm instanceof Adm) { this.adm = encode(adm) @@ -79,4 +98,15 @@ class Bid implements ObjectMapperWrapper { this.adm = null } } + + private static Bid createBid(Imp imp, BidMediaType type, @DelegatesTo(Bid) Closure init = null) { + def bid = getDefaultBid(imp) + bid.mediaType = type + if (init) { + init.delegate = bid + init.resolveStrategy = DELEGATE_FIRST + init() + } + bid + } } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidMediaType.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidMediaType.groovy index 76aa2a558f9..84b5a5d9953 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidMediaType.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidMediaType.groovy @@ -1,6 +1,7 @@ package org.prebid.server.functional.model.response.auction import com.fasterxml.jackson.annotation.JsonValue +import org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType enum BidMediaType { @@ -15,4 +16,16 @@ enum BidMediaType { BidMediaType(Integer value) { this.value = value } + + static BidMediaType from(BidAdjustmentMediaType mediaType) { + return switch (mediaType) { + case BidAdjustmentMediaType.BANNER -> BANNER + case BidAdjustmentMediaType.VIDEO -> VIDEO + case BidAdjustmentMediaType.VIDEO_IN_STREAM -> VIDEO + case BidAdjustmentMediaType.VIDEO_OUT_STREAM -> VIDEO + case BidAdjustmentMediaType.AUDIO -> AUDIO + case BidAdjustmentMediaType.NATIVE -> NATIVE + default -> throw new IllegalArgumentException("Unknown media type: " + mediaType); + }; + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy index a4fda13f829..2c329468a7c 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy @@ -2,6 +2,8 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.response.amp.AmpResponse +import org.prebid.server.functional.model.response.auction.Bid +import org.prebid.server.functional.model.response.auction.BidMediaType import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.model.response.auction.BidderCall import org.prebid.server.functional.repository.HibernateRepositoryService @@ -91,6 +93,10 @@ abstract class BaseSpec extends Specification implements ObjectMapperWrapper { } } + protected static List getMediaTypedBids(BidResponse bidResponse, BidMediaType mediaType) { + bidResponse.seatbid*.bid.collectMany { it }.findAll { it.mediaType == mediaType } + } + protected static Map> getRequests(AmpResponse ampResponse) { ampResponse.ext.debug.bidders.collectEntries { bidderName, bidderCalls -> collectRequestByBidderName(bidderName, bidderCalls) diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy index 7b760462008..eeb30c1a404 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy @@ -1,6 +1,6 @@ package org.prebid.server.functional.tests.pricefloors - +import org.prebid.server.functional.model.bidder.Openx import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.db.Account @@ -8,7 +8,7 @@ import org.prebid.server.functional.model.pricefloors.PriceFloorField import org.prebid.server.functional.model.pricefloors.Rule 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.Audio +import org.prebid.server.functional.model.request.auction.Banner 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 @@ -16,9 +16,13 @@ import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.DistributionChannel import org.prebid.server.functional.model.request.auction.ExtPrebidFloors import org.prebid.server.functional.model.request.auction.Imp +import org.prebid.server.functional.model.request.auction.MultiBid import org.prebid.server.functional.model.request.auction.Native 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.Bid +import org.prebid.server.functional.model.response.auction.BidExt +import org.prebid.server.functional.model.response.auction.BidMediaType import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.model.response.auction.MediaType import org.prebid.server.functional.service.PrebidServerService @@ -30,13 +34,14 @@ 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.GENERIC +import static org.prebid.server.functional.model.bidder.BidderName.OPENX import static org.prebid.server.functional.model.request.auction.AdjustmentType.CPM import static org.prebid.server.functional.model.request.auction.AdjustmentType.MULTIPLIER import static org.prebid.server.functional.model.request.auction.AdjustmentType.STATIC import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.ANY import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.AUDIO -import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.NATIVE import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.BANNER +import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.NATIVE import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.UNKNOWN import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.VIDEO_IN_STREAM import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.VIDEO_OUT_STREAM @@ -44,6 +49,7 @@ import static org.prebid.server.functional.model.request.auction.DistributionCha import static org.prebid.server.functional.model.request.auction.VideoPlacementSubtypes.IN_STREAM as IN_PLACEMENT_STREAM import static org.prebid.server.functional.model.request.auction.VideoPlcmtSubtype.IN_STREAM as IN_PLCMT_STREAM import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID +import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { @@ -58,6 +64,8 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { private static final Map config = CURRENCY_CONVERTER_CONFIG + FLOORS_CONFIG + GENERIC_ALIAS_CONFIG + + ["adapters.openx.enabled" : "true", + "adapters.openx.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + ["adapter-defaults.ortb.multiformat-supported": "true"] private static final PrebidServerService pbsService = pbsServiceFactory.getService(config) @@ -84,7 +92,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted" assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType) @@ -155,7 +163,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted" assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType) @@ -234,7 +242,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted for big with dealId" assert response.seatbid.first.bid.findAll() { it.impid == bidRequest.imp.first.id }.price == [getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType)] @@ -289,7 +297,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted for big with dealId" assert response.seatbid.first.bid.findAll() { it.dealid == dealId }.price == [getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType)] @@ -368,7 +376,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { accountDao.save(account) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted" assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType) @@ -448,7 +456,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted according to request config" assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, ruleValue as BigDecimal, adjustmentType) @@ -524,7 +532,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + 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) @@ -565,7 +573,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted" def rawAdjustedBidPrice = getAdjustedPrice(originalPrice, firstRule.value as BigDecimal, firstRule.adjustmentType) @@ -601,30 +609,38 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { given: "Default BidRequest with ext.prebid.bidAdjustments" def impPrice = PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) def currency = USD - def exactRule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: MULTIPLIER, value: firstRulePrice, currency: currency)]]) - def generalRule = new BidAdjustmentRule(generic: [(WILDCARD): [new AdjustmentRule(adjustmentType: MULTIPLIER, value: secondRulePrice, currency: currency)]]) - def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { + def firstRule = new BidAdjustmentRule(openx: [(WILDCARD): [new AdjustmentRule(adjustmentType: MULTIPLIER, value: firstRulePrice, currency: currency)]]) + def secondRule = new BidAdjustmentRule(openx: [(WILDCARD): [new AdjustmentRule(adjustmentType: MULTIPLIER, value: secondRulePrice, currency: currency)]]) + def bidRequest = getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM).tap { cur = [currency] + imp[0].ext.prebid.bidder.openx = Openx.defaultOpenx + imp[0].ext.prebid.bidder.generic = null imp.first.bidFloor = impPrice imp.first.bidFloorCur = currency - imp.first.audio = Audio.defaultAudio + imp.first.banner = Banner.getDefaultBanner() imp.first.nativeObj = Native.getDefaultNative() - ext.prebid.bidAdjustments = new BidAdjustment(mediaType: [(BANNER): exactRule, (additionalType): generalRule]) + ext.prebid.bidAdjustments = new BidAdjustment(mediaType: [(primaryType): firstRule, (BANNER): secondRule]) + ext.prebid.multibid = [new MultiBid(bidder: OPENX, maxBids: 3)] } and: "Default bid response" def originalPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = currency - seatbid.first.bid.first.price = originalPrice + seatbid.first.bid = Bid.getDefaultMultyTypesBids(bidRequest.imp.first) { + price = originalPrice + ext = new BidExt() + } + } bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted according to first matched rule" - assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, firstRulePrice, MULTIPLIER) + getMediaTypedBids(response, BidMediaType.from(primaryType)).price == [getAdjustedPrice(originalPrice, firstRulePrice, MULTIPLIER)] + getMediaTypedBids(response, BidMediaType.BANNER).price == [getAdjustedPrice(originalPrice, secondRulePrice, MULTIPLIER)] assert response.cur == bidResponse.cur and: "Original bid price and currency should be presented in bid.ext" @@ -639,16 +655,12 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, [firstRulePrice, secondRulePrice].max(), MULTIPLIER)] where: - additionalType | firstRulePrice | secondRulePrice - BANNER | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) - AUDIO | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) - NATIVE | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) - ANY | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) - - BANNER | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) - AUDIO | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) - NATIVE | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) - ANY | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) + primaryType | firstRulePrice | secondRulePrice + VIDEO_IN_STREAM | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + NATIVE | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + + VIDEO_IN_STREAM | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) + NATIVE | PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) | PBSUtils.getRandomPrice(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) } def "PBS should convert CPM currency before adjustment when it different from original response currency"() { @@ -764,7 +776,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted" def bidAdjustedPrice = originalPrice * bidAdjustmentFactorsPrice @@ -813,7 +825,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "PBS should ignore bidAdjustments for this request" assert response.seatbid.first.bid.first.price == originalPrice @@ -832,7 +844,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { } and: "PBS log should contain error" - def logs = floorsPbsService.getLogsByTime(startTime) + def logs = pbsService.getLogsByTime(startTime) assert getLogsByText(logs, errorMessage) and: "Bidder request should contain original imp.floors" @@ -917,7 +929,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "PBS should ignore bidAdjustments for this request" assert response.seatbid.first.bid.first.price == originalPrice @@ -966,7 +978,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "PBS should ignore bidAdjustments for this request" assert response.seatbid.first.bid.first.price == originalPrice @@ -985,7 +997,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { } and: "PBS log should contain error" - def logs = floorsPbsService.getLogsByTime(startTime) + def logs = pbsService.getLogsByTime(startTime) assert getLogsByText(logs, errorMessage) and: "Bidder request should contain original imp.floors" @@ -1019,7 +1031,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "PBS should ignore bidAdjustments for this request" assert response.seatbid.first.bid.first.price == originalPrice @@ -1068,7 +1080,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "PBS should ignore bidAdjustments for this request" assert response.seatbid.first.bid.first.price == originalPrice @@ -1087,7 +1099,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { } and: "PBS log should contain error" - def logs = floorsPbsService.getLogsByTime(startTime) + def logs = pbsService.getLogsByTime(startTime) assert getLogsByText(logs, errorMessage) and: "Bidder request should contain currency from request" @@ -1119,7 +1131,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted" assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, adjustmentPrice, MULTIPLIER) From d76ef4e94fc60d00abeb078513cd4e2c3ae5e3a5 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Tue, 27 May 2025 18:50:04 +0300 Subject: [PATCH 5/6] update after review --- .../functional/tests/BidAdjustmentSpec.groovy | 27 +++++++++---------- .../PriceFloorsAdjustmentSpec.groovy | 20 +++++--------- 2 files changed, 18 insertions(+), 29 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 d5490c929a2..a3ef50e599d 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,10 @@ class BidAdjustmentSpec extends BaseSpec { "adapters.amx.endpoint": "$networkServiceContainer.rootUri/auction".toString()] private static final PrebidServerService pbsService = pbsServiceFactory.getService(externalCurrencyConverterConfig + AMX_CONFIG) + 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 { @@ -382,7 +386,7 @@ class BidAdjustmentSpec extends BaseSpec { cur = currency seatbid.first.bid.first.price = originalPrice seatbid.first.bid.first.dealid = dealId - } as BidResponse + } bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" @@ -464,7 +468,7 @@ class BidAdjustmentSpec extends BaseSpec { def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = currency seatbid.first.bid.first.price = originalPrice - } as BidResponse + } bidder.setResponse(bidRequest.id, bidResponse) and: "Account in the DB with bidAdjustments" @@ -559,7 +563,7 @@ class BidAdjustmentSpec extends BaseSpec { def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = currency seatbid.first.bid.first.price = originalPrice - } as BidResponse + } bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" @@ -577,6 +581,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -775,7 +780,7 @@ class BidAdjustmentSpec extends BaseSpec { ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, bidAdjustmentMultyRule) } - and: "Default bid response with JPY currency" + and: "Default bid response with USD currency" def originalPrice = PBSUtils.randomPrice def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = USD @@ -866,7 +871,7 @@ class BidAdjustmentSpec extends BaseSpec { def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = currency seatbid.first.bid.first.price = originalPrice - } as BidResponse + } bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" @@ -1399,19 +1404,11 @@ class BidAdjustmentSpec extends BaseSpec { } private static BidRequest getDefaultVideoRequestWithPlacement(VideoPlacementSubtypes videoPlacementSubtypes) { - BidRequest.defaultVideoRequest.tap { - imp.first.video.tap { - placement = videoPlacementSubtypes - } - } + getDefaultVideoRequestWithPlcmtAndPlacement(null, videoPlacementSubtypes) } private static BidRequest getDefaultVideoRequestWithPlcmt(VideoPlcmtSubtype videoPlcmtSubtype) { - BidRequest.defaultVideoRequest.tap { - imp.first.video.tap { - plcmt = videoPlcmtSubtype - } - } + getDefaultVideoRequestWithPlcmtAndPlacement(videoPlcmtSubtype, null) } private static BidRequest getDefaultVideoRequestWithPlcmtAndPlacement(VideoPlcmtSubtype videoPlcmtSubtype, diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy index eeb30c1a404..dadf700b6ea 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy @@ -61,16 +61,16 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { private static final Integer MAX_STATIC_ADJUST_VALUE = Integer.MAX_VALUE private static final String WILDCARD = '*' - private static final Map config = CURRENCY_CONVERTER_CONFIG + + private static final Map PBS_CONFIG = CURRENCY_CONVERTER_CONFIG + FLOORS_CONFIG + GENERIC_ALIAS_CONFIG + ["adapters.openx.enabled" : "true", "adapters.openx.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + ["adapter-defaults.ortb.multiformat-supported": "true"] - private static final PrebidServerService pbsService = pbsServiceFactory.getService(config) + private static final PrebidServerService pbsService = pbsServiceFactory.getService(PBS_CONFIG) def cleanupSpec() { - pbsServiceFactory.removeContainer(config) + pbsServiceFactory.removeContainer(PBS_CONFIG) } def "PBS should reverse imp.floors for matching bidder when request has bidAdjustments config"() { @@ -722,7 +722,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, bidAdjustmentMultyRule) } - and: "Default bid response with JPY currency" + and: "Default bid response with USD currency" def originalPrice = PBSUtils.randomDecimal def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = USD @@ -1162,19 +1162,11 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { } private static BidRequest getDefaultVideoRequestWithPlacement(VideoPlacementSubtypes videoPlacementSubtypes) { - getBidRequestWithFloors(MediaType.VIDEO).tap { - imp.first.video.tap { - placement = videoPlacementSubtypes - } - } + getDefaultVideoRequestWithPlcmtAndPlacement(null, videoPlacementSubtypes) } private static BidRequest getDefaultVideoRequestWithPlcmt(VideoPlcmtSubtype videoPlcmtSubtype) { - getBidRequestWithFloors(MediaType.VIDEO).tap { - imp.first.video.tap { - plcmt = videoPlcmtSubtype - } - } + getDefaultVideoRequestWithPlcmtAndPlacement(videoPlcmtSubtype, null) } private static BidRequest getDefaultVideoRequestWithPlcmtAndPlacement(VideoPlcmtSubtype videoPlcmtSubtype, From 03a47eca0bcc5a3bc4915cdae00b4dc709e42ac0 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Fri, 30 May 2025 12:00:49 +0300 Subject: [PATCH 6/6] update after review --- .../functional/tests/BidAdjustmentSpec.groovy | 14 +++++++++ .../PriceFloorsAdjustmentSpec.groovy | 31 ++++++++++++++----- 2 files changed, 37 insertions(+), 8 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 a3ef50e599d..0a016703245 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -235,6 +235,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -315,6 +316,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [null] @@ -408,6 +410,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency, currency] assert bidderRequest.imp.bidFloor.sort() == [firstImpPrice, secondImpPrice].sort() @@ -492,6 +495,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -667,6 +671,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] } @@ -710,6 +715,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -763,6 +769,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] } @@ -803,6 +810,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] } @@ -846,6 +854,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -899,6 +908,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -1024,6 +1034,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -1080,6 +1091,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -1126,6 +1138,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -1232,6 +1245,7 @@ class BidAdjustmentSpec extends BaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy index dadf700b6ea..cf0b7bac7d9 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsAdjustmentSpec.groovy @@ -177,6 +177,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [null] @@ -238,7 +239,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { cur = currency seatbid.first.bid.first.price = originalPrice seatbid.first.bid.first.dealid = PBSUtils.randomString - } as BidResponse + } bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" @@ -293,7 +294,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { cur = currency seatbid.first.bid.first.price = originalPrice seatbid.first.bid.first.dealid = dealId - } as BidResponse + } bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" @@ -315,6 +316,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency, currency] assert bidderRequest.imp.bidFloor.sort() == [firstImpPrice, secondImpPrice].sort() @@ -366,7 +368,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = currency seatbid.first.bid.first.price = originalPrice - } as BidResponse + } bidder.setResponse(bidRequest.id, bidResponse) and: "Account in the DB with bidAdjustments" @@ -452,7 +454,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = currency seatbid.first.bid.first.price = originalPrice - } as BidResponse + } bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" @@ -470,6 +472,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, ruleValue as BigDecimal, adjustmentType)] @@ -546,6 +549,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, exactRulePrice, STATIC)] } @@ -589,6 +593,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [applyReverseAdjustments(impPrice, [firstRule, secondRule])] @@ -631,7 +636,6 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { price = originalPrice ext = new BidExt() } - } bidder.setResponse(bidRequest.id, bidResponse) @@ -670,7 +674,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { def currency = EUR def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) def bidRequest = getBidRequestWithFloors(MediaType.BANNER).tap { - cur = [EUR] + cur = [currency] imp.first.bidFloor = impPrice imp.first.bidFloorCur = currency ext.prebid.bidAdjustments = BidAdjustment.getDefaultWithSingleMediaTypeRule(BANNER, bidAdjustmentMultyRule) @@ -703,6 +707,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] def convertedReverseAdjustment = getPriceAfterCurrencyConversion(adjustmentRule.value, adjustmentRule.currency, currency, currencyRatesResponse) def reversedAdjustBidPrice = getReverseAdjustedPrice(impPrice, convertedReverseAdjustment, adjustmentRule.adjustmentType) @@ -749,6 +754,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] } @@ -780,7 +786,6 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { then: "Final bid price should be adjusted" def bidAdjustedPrice = originalPrice * bidAdjustmentFactorsPrice - assert !response.ext.warnings assert response.seatbid.first.bid.first.price == getAdjustedPrice(bidAdjustedPrice, adjustmentRule.value, adjustmentType) assert response.cur == bidResponse.cur @@ -790,8 +795,13 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { origbidcur == bidResponse.cur } + and: "Response shouldn't contain any warnings or errors" + assert !response.ext.warnings + assert !response.ext.errors + and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] def reversedBidPrice = impPrice / bidAdjustmentFactorsPrice assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(reversedBidPrice, adjustmentRule.value, adjustmentType)] @@ -821,7 +831,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = currency seatbid.first.bid.first.price = originalPrice - } as BidResponse + } bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" @@ -849,6 +859,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -946,6 +957,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -1002,6 +1014,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -1048,6 +1061,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [impPrice] @@ -1154,6 +1168,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.cur == [currency] assert bidderRequest.imp.bidFloorCur == [currency] assert bidderRequest.imp.bidFloor == [getReverseAdjustedPrice(impPrice, adjustmentPrice, MULTIPLIER)]