diff --git a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy index 31df1efc8d5..b9c173baa54 100644 --- a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy +++ b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy @@ -30,6 +30,7 @@ import org.prebid.server.functional.model.response.setuid.SetuidResponse import org.prebid.server.functional.model.response.status.StatusResponse import org.prebid.server.functional.testcontainers.container.PrebidServerContainer import org.prebid.server.functional.util.ObjectMapperWrapper +import org.prebid.server.functional.util.PBSUtils import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -412,7 +413,12 @@ class PrebidServerService implements ObjectMapperWrapper { } Boolean isContainLogsByValue(String value) { - getPbsLogsByValue(value) != null + try { + PBSUtils.waitUntil({ getPbsLogsByValue(value) != null }) + true + } catch (IllegalStateException ignored) { + false + } } private String getPbsLogsByValue(String value) { diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy index 91ec927b1c2..052bcf2f69f 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy @@ -6,6 +6,7 @@ import org.testcontainers.containers.PostgreSQLContainer import static org.prebid.server.functional.testcontainers.Dependencies.networkServiceContainer import static org.prebid.server.functional.testcontainers.container.PrebidServerContainer.ADMIN_ENDPOINT_PASSWORD import static org.prebid.server.functional.testcontainers.container.PrebidServerContainer.ADMIN_ENDPOINT_USERNAME +import static org.prebid.server.functional.util.CurrencyUtil.DEFAULT_CURRENCY final class PbsConfig { @@ -29,7 +30,7 @@ LIMIT 1 static final Map DEFAULT_ENV = [ "logging.sampling-rate" : "1.0", - "auction.ad-server-currency" : "USD", + "auction.ad-server-currency" : DEFAULT_CURRENCY.value, "auction.stored-requests-timeout-ms" : "1000", "metrics.prefix" : "prebid", "status-response" : "ok", @@ -136,5 +137,13 @@ LIMIT 1 "adapters.generic.aliases.adrino.meta-info.site-media-types" : ""] } + static Map getCurrencyConverterConfig() { + ["auction.ad-server-currency" : DEFAULT_CURRENCY.value, + "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 PbsConfig() {} } diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/CurrencyConversion.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/CurrencyConversion.groovy index 6f5b74bda61..6246f8c9f4d 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/CurrencyConversion.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/CurrencyConversion.groovy @@ -7,16 +7,18 @@ import org.testcontainers.containers.MockServerContainer import static org.mockserver.model.HttpRequest.request import static org.mockserver.model.HttpResponse.response import static org.mockserver.model.HttpStatusCode.OK_200 +import static org.prebid.server.functional.util.CurrencyUtil.DEFAULT_CURRENCY_RATES class CurrencyConversion extends NetworkScaffolding { static final String CURRENCY_ENDPOINT_PATH = "/currency" + private static final CurrencyConversionRatesResponse DEFAULT_RATES_RESPONSE = CurrencyConversionRatesResponse.getDefaultCurrencyConversionRatesResponse(DEFAULT_CURRENCY_RATES) CurrencyConversion(MockServerContainer mockServerContainer) { super(mockServerContainer, CURRENCY_ENDPOINT_PATH) } - void setCurrencyConversionRatesResponse(CurrencyConversionRatesResponse conversionRatesResponse) { + void setCurrencyConversionRatesResponse(CurrencyConversionRatesResponse conversionRatesResponse = DEFAULT_RATES_RESPONSE) { setResponse(request, conversionRatesResponse) } 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 0a016703245..4a32f637e6f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -1,13 +1,12 @@ package org.prebid.server.functional.tests -import org.prebid.server.functional.model.Currency + import org.prebid.server.functional.model.bidder.Generic import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AlternateBidderCodes import org.prebid.server.functional.model.config.BidderConfig import org.prebid.server.functional.model.db.Account -import org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse import org.prebid.server.functional.model.request.auction.AdjustmentRule import org.prebid.server.functional.model.request.auction.AdjustmentType import org.prebid.server.functional.model.request.auction.Amx @@ -22,12 +21,11 @@ import org.prebid.server.functional.model.response.auction.BidExt import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.service.PrebidServerService +import org.prebid.server.functional.testcontainers.PbsConfig import org.prebid.server.functional.testcontainers.scaffolding.CurrencyConversion +import org.prebid.server.functional.util.CurrencyUtil import org.prebid.server.functional.util.PBSUtils -import java.math.RoundingMode -import java.time.Instant - import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST import static org.prebid.server.functional.model.Currency.EUR import static org.prebid.server.functional.model.Currency.GBP @@ -62,25 +60,21 @@ class BidAdjustmentSpec extends BaseSpec { private static final BigDecimal MAX_MULTIPLIER_ADJUST_VALUE = 99 private static final BigDecimal MAX_CPM_ADJUST_VALUE = Integer.MAX_VALUE private static final BigDecimal MAX_STATIC_ADJUST_VALUE = Integer.MAX_VALUE - private static final Currency DEFAULT_CURRENCY = USD private static final int BID_ADJUST_PRECISION = 4 - private static final int PRICE_PRECISION = 3 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 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 CurrencyConversion currencyConversion = new CurrencyConversion(networkServiceContainer) private static final Map AMX_CONFIG = ["adapters.amx.enabled" : "true", "adapters.amx.endpoint": "$networkServiceContainer.rootUri/auction".toString()] - private static final PrebidServerService pbsService = pbsServiceFactory.getService(externalCurrencyConverterConfig + AMX_CONFIG) + private static PrebidServerService pbsService + + def setupSpec() { + currencyConversion.setCurrencyConversionRatesResponse() + pbsService = pbsServiceFactory.getService(PbsConfig.currencyConverterConfig + AMX_CONFIG) + } def cleanupSpec() { - pbsServiceFactory.removeContainer(externalCurrencyConverterConfig + AMX_CONFIG) + pbsServiceFactory.removeContainer(PbsConfig.currencyConverterConfig + AMX_CONFIG) } def "PBS should adjust bid price for matching bidder when request has per-bidder bid adjustment factors"() { @@ -757,9 +751,9 @@ class BidAdjustmentSpec extends BaseSpec { def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted" - def convertedAdjustment = convertCurrency(adjustmentRule.value, adjustmentRule.currency, bidResponse.cur) + def convertedAdjustment = CurrencyUtil.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, currency) + assert response.seatbid.first.bid.first.price == CurrencyUtil.convertCurrency(adjustedBidPrice, bidResponse.cur, currency) and: "Original bid price and currency should be presented in bid.ext" verifyAll(response.seatbid.first.bid.first.ext) { @@ -799,7 +793,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, currency) + assert response.seatbid.first.bid.first.price == CurrencyUtil.convertCurrency(adjustmentRule.value, adjustmentRule.currency, currency) assert response.cur == bidRequest.cur.first and: "Original bid price and currency should be presented in bid.ext" @@ -863,10 +857,7 @@ class BidAdjustmentSpec extends BaseSpec { } def "PBS shouldn't adjust bid price for matching bidder when request has invalid value bidAdjustments config"() { - given: "Start time" - def startTime = Instant.now() - - and: "Default BidRequest with ext.prebid.bidAdjustments" + 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)]]) @@ -903,8 +894,7 @@ class BidAdjustmentSpec extends BaseSpec { } and: "PBS log should contain error" - def logs = pbsService.getLogsByTime(startTime) - assert getLogsByText(logs, errorMessage) + assert pbsService.isContainLogsByValue(errorMessage) and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) @@ -1043,10 +1033,7 @@ class BidAdjustmentSpec extends BaseSpec { } def "PBS shouldn't adjust bid price 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" + given: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD def impPrice = PBSUtils.randomPrice def adjustmentPrice = PBSUtils.randomPrice.toDouble() @@ -1086,8 +1073,7 @@ class BidAdjustmentSpec extends BaseSpec { } and: "PBS log should contain error" - def logs = pbsService.getLogsByTime(startTime) - assert getLogsByText(logs, errorMessage) + assert pbsService.isContainLogsByValue(errorMessage) and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) @@ -1147,10 +1133,7 @@ class BidAdjustmentSpec extends BaseSpec { } def "PBS shouldn't adjust bid price for matching bidder when bidAdjustments have unknown adjustmentType"() { - given: "Start time" - def startTime = Instant.now() - - and: "Default BidRequest with ext.prebid.bidAdjustments" + given: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD def impPrice = PBSUtils.randomPrice def adjustmentPrice = PBSUtils.randomPrice.toDouble() @@ -1190,8 +1173,7 @@ class BidAdjustmentSpec extends BaseSpec { } and: "PBS log should contain error" - def logs = pbsService.getLogsByTime(startTime) - assert getLogsByText(logs, errorMessage) + assert pbsService.isContainLogsByValue(errorMessage) and: "Bidder request should contain currency from request" def bidderRequest = bidder.getBidderRequest(bidRequest.id) @@ -1363,45 +1345,6 @@ class BidAdjustmentSpec extends BaseSpec { bidAdjustmentFactor << [0.9, 1.1] } - private static Map getExternalCurrencyConverterConfig() { - ["auction.ad-server-currency" : DEFAULT_CURRENCY as String, - "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 static BigDecimal convertCurrency(BigDecimal price, Currency fromCurrency, Currency toCurrency) { - return (price * getConversionRate(fromCurrency, toCurrency)).setScale(PRICE_PRECISION, RoundingMode.HALF_EVEN) - } - - private static BigDecimal getConversionRate(Currency fromCurrency, Currency toCurrency) { - def conversionRate - if (fromCurrency == toCurrency) { - conversionRate = 1 - } else if (toCurrency in DEFAULT_CURRENCY_RATES?[fromCurrency]) { - conversionRate = DEFAULT_CURRENCY_RATES[fromCurrency][toCurrency] - } else if (fromCurrency in DEFAULT_CURRENCY_RATES?[toCurrency]) { - conversionRate = 1 / DEFAULT_CURRENCY_RATES[toCurrency][fromCurrency] - } else { - conversionRate = getCrossConversionRate(fromCurrency, toCurrency) - } - conversionRate - } - - private static BigDecimal getCrossConversionRate(Currency fromCurrency, Currency toCurrency) { - for (Map rates : DEFAULT_CURRENCY_RATES.values()) { - def fromRate = rates?[fromCurrency] - def toRate = rates?[toCurrency] - - if (fromRate && toRate) { - return toRate / fromRate - } - } - - null - } - private static BigDecimal getAdjustedPrice(BigDecimal originalPrice, BigDecimal adjustedValue, AdjustmentType adjustmentType) { diff --git a/src/test/groovy/org/prebid/server/functional/tests/CurrencySpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/CurrencySpec.groovy index df5bba70028..662f9423848 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/CurrencySpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/CurrencySpec.groovy @@ -1,13 +1,11 @@ package org.prebid.server.functional.tests -import org.prebid.server.functional.model.Currency -import org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.service.PrebidServerService +import org.prebid.server.functional.testcontainers.PbsConfig import org.prebid.server.functional.testcontainers.scaffolding.CurrencyConversion - -import java.math.RoundingMode +import org.prebid.server.functional.util.CurrencyUtil import static org.prebid.server.functional.model.Currency.CAD import static org.prebid.server.functional.model.Currency.CHF @@ -16,21 +14,17 @@ import static org.prebid.server.functional.model.Currency.JPY import static org.prebid.server.functional.model.Currency.USD import static org.prebid.server.functional.model.response.auction.ErrorType.GENERIC import static org.prebid.server.functional.testcontainers.Dependencies.networkServiceContainer +import static org.prebid.server.functional.util.CurrencyUtil.DEFAULT_CURRENCY class CurrencySpec extends BaseSpec { - private static final Currency DEFAULT_CURRENCY = USD - private static final int PRICE_PRECISION = 3 - private static final Map> DEFAULT_CURRENCY_RATES = [(USD): [(USD): 1, - (EUR): 0.9249838127832763, - (CHF): 0.9033391915641477, - (JPY): 151.1886041994265, - (CAD): 1.357136250115623], - (EUR): [(USD): 1.3429368029739777]] - private static final CurrencyConversion currencyConversion = new CurrencyConversion(networkServiceContainer).tap { - setCurrencyConversionRatesResponse(CurrencyConversionRatesResponse.getDefaultCurrencyConversionRatesResponse(DEFAULT_CURRENCY_RATES)) + private static final CurrencyConversion currencyConversion = new CurrencyConversion(networkServiceContainer) + private static PrebidServerService pbsService + + def setupSpec() { + currencyConversion.setCurrencyConversionRatesResponse() + pbsService = pbsServiceFactory.getService(PbsConfig.currencyConverterConfig) } - private static final PrebidServerService pbsService = pbsServiceFactory.getService(externalCurrencyConverterConfig) def "PBS should return currency rates"() { when: "PBS processes bidders params request" @@ -85,7 +79,7 @@ class CurrencySpec extends BaseSpec { then: "Auction response should contain bid in #requestCurrency currency" assert bidResponse.cur == requestCurrency def bidPrice = bidResponse.seatbid[0].bid[0].price - assert bidPrice == convertCurrency(bidderResponse.seatbid[0].bid[0].price, bidCurrency, requestCurrency) + assert bidPrice == CurrencyUtil.convertCurrency(bidderResponse.seatbid[0].bid[0].price, bidCurrency, requestCurrency) assert bidResponse.seatbid[0].bid[0].ext.origbidcpm == bidderResponse.seatbid[0].bid[0].price assert bidResponse.seatbid[0].bid[0].ext.origbidcur == bidCurrency @@ -109,7 +103,7 @@ class CurrencySpec extends BaseSpec { then: "Auction response should contain bid in #requestCurrency currency" assert bidResponse.cur == requestCurrency def bidPrice = bidResponse.seatbid[0].bid[0].price - assert bidPrice == convertCurrency(bidderResponse.seatbid[0].bid[0].price, bidCurrency, requestCurrency) + assert bidPrice == CurrencyUtil.convertCurrency(bidderResponse.seatbid[0].bid[0].price, bidCurrency, requestCurrency) assert bidResponse.seatbid[0].bid[0].ext.origbidcpm == bidderResponse.seatbid[0].bid[0].price assert bidResponse.seatbid[0].bid[0].ext.origbidcur == bidCurrency @@ -133,7 +127,7 @@ class CurrencySpec extends BaseSpec { then: "Auction response should contain bid in #requestCurrency currency" assert bidResponse.cur == requestCurrency def bidPrice = bidResponse.seatbid[0].bid[0].price - assert bidPrice == convertCurrency(bidderResponse.seatbid[0].bid[0].price, bidCurrency, requestCurrency) + assert bidPrice == CurrencyUtil.convertCurrency(bidderResponse.seatbid[0].bid[0].price, bidCurrency, requestCurrency) assert bidResponse.seatbid[0].bid[0].ext.origbidcpm == bidderResponse.seatbid[0].bid[0].price assert bidResponse.seatbid[0].bid[0].ext.origbidcur == bidCurrency @@ -189,43 +183,4 @@ class CurrencySpec extends BaseSpec { and: "Bid response shouldn't contain warnings" assert !bidResponse.ext.warnings } - - private static Map getExternalCurrencyConverterConfig() { - ["auction.ad-server-currency" : DEFAULT_CURRENCY as String, - "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 static BigDecimal convertCurrency(BigDecimal price, Currency fromCurrency, Currency toCurrency) { - return (price * getConversionRate(fromCurrency, toCurrency)).setScale(PRICE_PRECISION, RoundingMode.HALF_EVEN) - } - - private static BigDecimal getConversionRate(Currency fromCurrency, Currency toCurrency) { - def conversionRate - if (fromCurrency == toCurrency) { - conversionRate = 1 - } else if (toCurrency in DEFAULT_CURRENCY_RATES?[fromCurrency]) { - conversionRate = DEFAULT_CURRENCY_RATES[fromCurrency][toCurrency] - } else if (fromCurrency in DEFAULT_CURRENCY_RATES?[toCurrency]) { - conversionRate = 1 / DEFAULT_CURRENCY_RATES[toCurrency][fromCurrency] - } else { - conversionRate = getCrossConversionRate(fromCurrency, toCurrency) - } - conversionRate - } - - private static BigDecimal getCrossConversionRate(Currency fromCurrency, Currency toCurrency) { - for (Map rates : DEFAULT_CURRENCY_RATES.values()) { - def fromRate = rates?[fromCurrency] - def toRate = rates?[toCurrency] - - if (fromRate && toRate) { - return toRate / fromRate - } - } - - null - } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/StoredResponseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/StoredResponseSpec.groovy index 767c4b8e544..0b6f62923c7 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/StoredResponseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/StoredResponseSpec.groovy @@ -372,10 +372,14 @@ class StoredResponseSpec extends BaseSpec { } } - private static final List convertToComparableSeatBid(List seatBid) { - seatBid*.tap { - it.bid*.ext = null - it.group = null + private static List convertToComparableSeatBid(List seatBids) { + seatBids*.tap { seatBid -> + seatBid.bid*.tap { bid -> + bid.ext = null + bid.price = bid.price.setScale(3) + } + seatBid.group = null } + seatBids } } 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 cf0b7bac7d9..a1fa69e487e 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 @@ -26,10 +26,10 @@ 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 +import org.prebid.server.functional.testcontainers.PbsConfig +import org.prebid.server.functional.util.CurrencyUtil 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 @@ -61,13 +61,17 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { private static final Integer MAX_STATIC_ADJUST_VALUE = Integer.MAX_VALUE private static final String WILDCARD = '*' - private static final Map PBS_CONFIG = CURRENCY_CONVERTER_CONFIG + + private static final Map PBS_CONFIG = PbsConfig.currencyConverterConfig + 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(PBS_CONFIG) + private static PrebidServerService pbsService + + def setupSpec() { + pbsService = pbsServiceFactory.getService(PBS_CONFIG) + } def cleanupSpec() { pbsServiceFactory.removeContainer(PBS_CONFIG) @@ -629,7 +633,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { } and: "Default bid response" - def originalPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) + def originalPrice = PBSUtils.getRandomDecimal() def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = currency seatbid.first.bid = Bid.getDefaultMultyTypesBids(bidRequest.imp.first) { @@ -695,9 +699,9 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { def response = pbsService.sendAuctionRequest(bidRequest) then: "Final bid price should be adjusted" - def convertedAdjustment = getPriceAfterCurrencyConversion(adjustmentRule.value, adjustmentRule.currency, bidResponse.cur, currencyRatesResponse) + def convertedAdjustment = CurrencyUtil.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) + assert response.seatbid.first.bid.first.price == CurrencyUtil.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) { @@ -709,7 +713,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { 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 convertedReverseAdjustment = CurrencyUtil.getPriceAfterCurrencyConversion(adjustmentRule.value, adjustmentRule.currency, currency, currencyRatesResponse) def reversedAdjustBidPrice = getReverseAdjustedPrice(impPrice, convertedReverseAdjustment, adjustmentRule.adjustmentType) assert bidderRequest.imp.bidFloor == [reversedAdjustBidPrice] } @@ -743,7 +747,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { 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) + CurrencyUtil.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" @@ -814,10 +818,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { } 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" + 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)]]) @@ -854,8 +855,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { } and: "PBS log should contain error" - def logs = pbsService.getLogsByTime(startTime) - assert getLogsByText(logs, errorMessage) + assert pbsService.isContainLogsByValue(errorMessage) and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) @@ -966,10 +966,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { } 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" + given: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) def adjustmentPrice = PBSUtils.randomPrice.toDouble() @@ -1009,8 +1006,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { } and: "PBS log should contain error" - def logs = pbsService.getLogsByTime(startTime) - assert getLogsByText(logs, errorMessage) + pbsService.isContainLogsByValue(errorMessage) and: "Bidder request should contain original imp.floors" def bidderRequest = bidder.getBidderRequest(bidRequest.id) @@ -1070,10 +1066,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { } 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" + given: "Default BidRequest with ext.prebid.bidAdjustments" def currency = USD def impPrice = PBSUtils.getRandomPrice(MAX_CPM_ADJUST_VALUE) def adjustmentPrice = PBSUtils.randomPrice.toDouble() @@ -1113,8 +1106,7 @@ class PriceFloorsAdjustmentSpec extends PriceFloorsBaseSpec { } and: "PBS log should contain error" - def logs = pbsService.getLogsByTime(startTime) - assert getLogsByText(logs, errorMessage) + pbsService.isContainLogsByValue(errorMessage) and: "Bidder request should contain currency from request" def bidderRequest = bidder.getBidderRequest(bidRequest.id) 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 6edccfceddf..95ef1318637 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 @@ -1,13 +1,11 @@ package org.prebid.server.functional.tests.pricefloors -import org.prebid.server.functional.model.Currency import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.config.AccountAuctionConfig 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,7 +17,6 @@ import org.prebid.server.functional.model.request.auction.ExtPrebidFloors import org.prebid.server.functional.model.request.auction.FetchStatus 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 @@ -28,9 +25,6 @@ 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 @@ -64,27 +58,15 @@ abstract class PriceFloorsBaseSpec extends BaseSpec { "Price floors processing failed: $reason. Following parsing of request price floors is failed: $details" } - 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 CurrencyConversion currencyConversion = new CurrencyConversion(networkServiceContainer) protected static final int FLOOR_VALUE_PRECISION = 4 private static final int DEFAULT_MODEL_WEIGHT = 1 - private static final int CURRENCY_CONVERSION_PRECISION = 3 protected final PrebidServerService floorsPbsService = pbsServiceFactory.getService(FLOORS_CONFIG + GENERIC_ALIAS_CONFIG) def setupSpec() { + currencyConversion.setCurrencyConversionRatesResponse() floorsProvider.setResponse() } @@ -138,11 +120,6 @@ abstract class PriceFloorsBaseSpec extends BaseSpec { PBSUtils.getRandomNumber(DEFAULT_MODEL_WEIGHT, MAX_MODEL_WEIGHT) } - static BigDecimal getAdjustedValue(BigDecimal floorValue, BigDecimal bidAdjustment) { - def adjustedValue = floorValue / bidAdjustment - PBSUtils.roundDecimal(adjustedValue, FLOOR_VALUE_PRECISION) - } - static BidRequest getBidRequestWithMultipleMediaTypes() { BidRequest.defaultBidRequest.tap { imp[0].video = Video.defaultVideo } } @@ -177,12 +154,4 @@ abstract class PriceFloorsBaseSpec extends BaseSpec { protected BigDecimal getRoundedFloorValue(BigDecimal floorValue) { floorValue.setScale(FLOOR_VALUE_PRECISION, RoundingMode.HALF_EVEN) } - - protected BigDecimal getPriceAfterCurrencyConversion(BigDecimal value, - Currency currencyFrom, Currency currencyTo, - CurrencyRatesResponse currencyRatesResponse) { - def currencyRate = currencyRatesResponse.rates[currencyFrom.value][currencyTo.value] - def convertedValue = value * currencyRate - convertedValue.setScale(CURRENCY_CONVERSION_PRECISION, RoundingMode.HALF_EVEN) - } } 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 f6b66721cb0..b569514d0c4 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 @@ -8,6 +8,8 @@ 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.PbsConfig +import org.prebid.server.functional.util.CurrencyUtil import org.prebid.server.functional.util.PBSUtils import static org.prebid.server.functional.model.Currency.BOGUS @@ -24,8 +26,15 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { private static final String GENERAL_ERROR_METRIC = "price-floors.general.err" - private final PrebidServerService currencyFloorsPbsService = pbsServiceFactory.getService(FLOORS_CONFIG + - CURRENCY_CONVERTER_CONFIG) + private static PrebidServerService currencyFloorsPbsService + + def setupSpec() { + currencyFloorsPbsService = pbsServiceFactory.getService(FLOORS_CONFIG + PbsConfig.currencyConverterConfig) + } + + def cleanupSpec() { + pbsServiceFactory.removeContainer(FLOORS_CONFIG + PbsConfig.currencyConverterConfig) + } def "PBS should update bidFloor, bidFloorCur for signalling when request.cur is specified"() { given: "Default BidRequest with cur" @@ -37,6 +46,10 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id) accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + and: "Set Floors Provider response" def floorValue = PBSUtils.randomFloorValue def floorsResponse = PriceFloorData.priceFloorData.tap { @@ -84,7 +97,7 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { def currencyRatesResponse = currencyFloorsPbsService.sendCurrencyRatesRequest() and: "Bid response with 2 bids: price < floorMin, price = floorMin" - def convertedMinFloorValue = getPriceAfterCurrencyConversion(floorValue, + def convertedMinFloorValue = CurrencyUtil.getPriceAfterCurrencyConversion(floorValue, floorsResponse.modelGroups[0].currency, bidRequest.cur[0], currencyRatesResponse) def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { cur = EUR @@ -121,9 +134,13 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { and: "Get currency rates" def currencyRatesResponse = currencyFloorsPbsService.sendCurrencyRatesRequest() + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + and: "Set Floors Provider response with a currency different from the floorMinCur, floorValur lower then floorMin" def floorProviderCur = EUR - def convertedMinFloorValue = getPriceAfterCurrencyConversion(floorMin, + def convertedMinFloorValue = CurrencyUtil.getPriceAfterCurrencyConversion(floorMin, bidRequest.ext.prebid.floors.floorMinCur, floorProviderCur, currencyRatesResponse) def floorsResponse = PriceFloorData.priceFloorData.tap { @@ -168,6 +185,10 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id) accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + and: "Set Floors Provider response with a currency different from the floorMinCur" def floorsProviderCur = EUR def floorsResponse = PriceFloorData.priceFloorData.tap { @@ -231,6 +252,10 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { } accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" currencyFloorsPbsService.sendAuctionRequest(bidRequest) @@ -267,6 +292,10 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { } accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" currencyFloorsPbsService.sendAuctionRequest(bidRequest) @@ -307,7 +336,7 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { and: "Bid response with 2 bids: price < floorMin, price = floorMin" def bidResponseCur = GBP - def convertedMinFloorValueGbp = getPriceAfterCurrencyConversion(floorValue, + def convertedMinFloorValueGbp = CurrencyUtil.getPriceAfterCurrencyConversion(floorValue, floorCur, bidResponseCur, currencyRatesResponse) def winBidPrice = convertedMinFloorValueGbp + 0.1 def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { @@ -329,7 +358,7 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { } and: "PBS should suppress bids lower than floorRuleValue" - def convertedFloorValueEur = getPriceAfterCurrencyConversion(winBidPrice, + def convertedFloorValueEur = CurrencyUtil.getPriceAfterCurrencyConversion(winBidPrice, bidResponseCur, requestCur, currencyRatesResponse) assert response.seatbid?.first()?.bid?.collect { it.price } == [convertedFloorValueEur] assert response.cur == bidRequest.cur[0] @@ -351,6 +380,10 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id) accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + and: "Set Floors Provider response with a currency different from the floorMinCur" def floorsProviderCur = BOGUS def floorsResponse = PriceFloorData.priceFloorData.tap { @@ -397,6 +430,10 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id) accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" currencyFloorsPbsService.sendAuctionRequest(bidRequest) @@ -454,6 +491,10 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id) accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" currencyFloorsPbsService.sendAuctionRequest(bidRequest) @@ -480,6 +521,10 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec { def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id) accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" currencyFloorsPbsService.sendAuctionRequest(bidRequest) diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsSignalingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsSignalingSpec.groovy index a885da9e86b..b06c2530242 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsSignalingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsSignalingSpec.groovy @@ -18,6 +18,7 @@ import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.model.response.auction.MediaType import org.prebid.server.functional.util.PBSUtils +import java.math.RoundingMode import java.time.Instant import static org.mockserver.model.HttpStatusCode.BAD_REQUEST_400 @@ -1145,4 +1146,8 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { private static int getRuleSize(BidRequest bidRequest) { bidRequest?.ext?.prebid?.floors?.data?.modelGroups[0].values.size() } + + private static BigDecimal getAdjustedValue(BigDecimal floorValue, BigDecimal bidAdjustment) { + floorValue.divide(bidAdjustment, FLOOR_VALUE_PRECISION, RoundingMode.HALF_UP) + } } diff --git a/src/test/groovy/org/prebid/server/functional/util/CurrencyUtil.groovy b/src/test/groovy/org/prebid/server/functional/util/CurrencyUtil.groovy new file mode 100644 index 00000000000..172478ad5ba --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/CurrencyUtil.groovy @@ -0,0 +1,74 @@ +package org.prebid.server.functional.util + +import org.prebid.server.functional.model.Currency +import org.prebid.server.functional.model.response.currencyrates.CurrencyRatesResponse + +import java.math.RoundingMode + +import static org.prebid.server.functional.model.Currency.CAD +import static org.prebid.server.functional.model.Currency.CHF +import static org.prebid.server.functional.model.Currency.EUR +import static org.prebid.server.functional.model.Currency.GBP +import static org.prebid.server.functional.model.Currency.JPY +import static org.prebid.server.functional.model.Currency.USD + +class CurrencyUtil { + + public static final Currency DEFAULT_CURRENCY = USD + public static final Map> DEFAULT_CURRENCY_RATES = [(USD): [(EUR): 0.9249838127832763, + (GBP): 0.793776804452961, + (EUR): 0.9249838127832763, + (CHF): 0.9033391915641477, + (JPY): 151.1886041994265, + (CAD): 1.357136250115623], + (GBP): [(USD): 1.2597999770088517, + (EUR): 1.1495574203931487], + (EUR): [(USD): 1.3429368029739777]] + public static final int PRICE_PRECISION = 3 + public static final int CURRENCY_CONVERSION_PRECISION = 3 + + static BigDecimal getPriceAfterCurrencyConversion(BigDecimal value, + Currency from, + Currency to, + CurrencyRatesResponse currencyRatesResponse) { + (value * currencyRatesResponse.rates[from.value][to.value]) + .setScale(CURRENCY_CONVERSION_PRECISION, RoundingMode.HALF_EVEN) + } + + static BigDecimal convertCurrency(BigDecimal price, + Currency fromCurrency, + Currency toCurrency, + Map> rates = DEFAULT_CURRENCY_RATES) { + return (price * getConversionRate(fromCurrency, toCurrency, rates)).setScale(PRICE_PRECISION, RoundingMode.HALF_EVEN) + } + + private static BigDecimal getConversionRate(Currency fromCurrency, + Currency toCurrency, + Map> rates = DEFAULT_CURRENCY_RATES) { + def conversionRate + if (fromCurrency == toCurrency) { + conversionRate = 1 + } else if (toCurrency in DEFAULT_CURRENCY_RATES?[fromCurrency]) { + conversionRate = DEFAULT_CURRENCY_RATES[fromCurrency][toCurrency] + } else if (fromCurrency in DEFAULT_CURRENCY_RATES?[toCurrency]) { + conversionRate = 1 / DEFAULT_CURRENCY_RATES[toCurrency][fromCurrency] + } else { + conversionRate = getCrossConversionRate(fromCurrency, toCurrency, rates) + } + conversionRate + } + + private static BigDecimal getCrossConversionRate(Currency fromCurrency, + Currency toCurrency, + Map> rates) { + + for (Map rate : rates.values()) { + def fromRate = rate?[fromCurrency] + def toRate = rate?[toCurrency] + if (fromRate && toRate) { + return toRate / fromRate + } + } + null + } +}