Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming
import groovy.transform.ToString
import org.prebid.server.functional.model.bidder.BidderName
import org.prebid.server.functional.model.request.auction.BidAdjustment
import org.prebid.server.functional.model.request.auction.BidRounding
import org.prebid.server.functional.model.request.auction.PaaFormat
import org.prebid.server.functional.model.request.auction.Targeting
import org.prebid.server.functional.model.response.auction.MediaType
Expand All @@ -31,6 +32,7 @@ class AccountAuctionConfig {
PrivacySandbox privacySandbox
@JsonProperty("bidadjustments")
BidAdjustment bidAdjustments
BidRounding bidRounding

@JsonProperty("price_granularity")
PriceGranularityType priceGranularitySnakeCase
Expand All @@ -48,4 +50,6 @@ class AccountAuctionConfig {
AccountBidValidationConfig bidValidationsSnakeCase
@JsonProperty("price_floors")
AccountPriceFloorsConfig priceFloorsSnakeCase
@JsonProperty("bid_rounding")
BidRounding bidRoundingSnakeCase
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.prebid.server.functional.model.request.auction

import com.fasterxml.jackson.annotation.JsonValue

enum BidRounding {

UP("up"),
DOWN("down"),
TRUE("true"),
TIME_SPLIT("timesplit"),
UNKNOWN("unknown"),

private String value

BidRounding(String value) {
this.value = value
}

@Override
@JsonValue
String toString() {
return value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class AmpSpec extends BaseSpec {

then: "Response should contain information from stored response"
def price = storedAuctionResponse.bid[0].price
assert response.targeting["hb_pb"] == getRoundedTargetingValueWithDefaultPrecision(price)
assert response.targeting["hb_pb"] == getRoundedTargetingValueWithDownPrecision(price)
assert response.targeting["hb_size"] == "${storedAuctionResponse.bid[0].weight}x${storedAuctionResponse.bid[0].height}"

and: "PBS not send request to bidder"
Expand Down
20 changes: 18 additions & 2 deletions src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ import org.prebid.server.functional.util.ObjectMapperWrapper
import org.prebid.server.functional.util.PBSUtils
import spock.lang.Specification

import java.math.RoundingMode

import static java.math.RoundingMode.DOWN
import static java.math.RoundingMode.HALF_UP
import static java.math.RoundingMode.UP
import static org.prebid.server.functional.testcontainers.Dependencies.networkServiceContainer
import static org.prebid.server.functional.util.SystemProperties.DEFAULT_TIMEOUT

Expand Down Expand Up @@ -80,8 +84,16 @@ abstract class BaseSpec extends Specification implements ObjectMapperWrapper {
logs.findAll { it.contains(text) }
}

protected static String getRoundedTargetingValueWithDefaultPrecision(BigDecimal value) {
"${value.setScale(DEFAULT_TARGETING_PRECISION, DOWN)}0"
protected static String getRoundedTargetingValueWithDownPrecision(BigDecimal value) {
roundWithDefaultPrecisionAndRoundingType(value, DOWN)
}

protected static String getRoundedTargetingValueWithHalfUpPrecision(BigDecimal value) {
roundWithDefaultPrecisionAndRoundingType(value, HALF_UP)
}

protected static String getRoundedTargetingValueWithUpPrecision(BigDecimal value) {
roundWithDefaultPrecisionAndRoundingType(value, UP)
}

protected static Map<String, List<BidderRequest>> getRequests(BidResponse bidResponse) {
Expand All @@ -100,4 +112,8 @@ abstract class BaseSpec extends Specification implements ObjectMapperWrapper {
List<BidderCall> bidderCalls) {
[(bidderName): bidderCalls.collect { bidderCall -> decode(bidderCall.requestBody as String, BidderRequest) }]
}

private static GString roundWithDefaultPrecisionAndRoundingType(BigDecimal value, RoundingMode roundingMode) {
"${value.setScale(DEFAULT_TARGETING_PRECISION, roundingMode)}0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package org.prebid.server.functional.tests

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.request.auction.BidRequest
import org.prebid.server.functional.model.response.auction.BidResponse
import org.prebid.server.functional.util.PBSUtils

import static org.prebid.server.functional.model.AccountStatus.ACTIVE
import static org.prebid.server.functional.model.request.auction.BidRounding.DOWN
import static org.prebid.server.functional.model.request.auction.BidRounding.TRUE
import static org.prebid.server.functional.model.request.auction.BidRounding.UNKNOWN
import static org.prebid.server.functional.model.request.auction.BidRounding.UP

class BidRoundingSpec extends BaseSpec {

def "PBS should round bid value to the down when account bid rounding setting is #bidRoundingValue"() {
given: "Default bid request"
def bidRequest = BidRequest.getDefaultBidRequest().tap {
enableCache()
}

and: "Account in the DB"
def account = getAccountWithBidRounding(bidRequest.accountId, bidRoundingValue)
accountDao.save(account)

and: "Default bid response"
def bidPrice = PBSUtils.randomFloorValue
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap {
seatbid[0].bid[0].price = bidPrice
}
bidder.setResponse(bidRequest.id, bidResponse)

when: "PBS processes auction request"
def response = defaultPbsService.sendAuctionRequest(bidRequest)

then: "Targeting hb_pb should be round"
def targeting = response.seatbid[0].bid[0].ext.prebid.targeting
assert targeting["hb_pb"] == getRoundedTargetingValueWithDownPrecision(bidPrice)

where:
bidRoundingValue << [new AccountAuctionConfig(bidRounding: null),
new AccountAuctionConfig(bidRounding: UNKNOWN),
new AccountAuctionConfig(bidRounding: DOWN),
new AccountAuctionConfig(bidRoundingSnakeCase: DOWN)]
}

def "PBS should round bid value to the up when account bid rounding setting is #bidRoundingValue"() {
given: "Default bid request"
def bidRequest = BidRequest.getDefaultBidRequest().tap {
enableCache()
}

and: "Account in the DB"
def account = getAccountWithBidRounding(bidRequest.accountId, bidRoundingValue)
accountDao.save(account)

and: "Default bid response"
def bidPrice = PBSUtils.getRandomFloorValue()
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap {
seatbid[0].bid[0].price = bidPrice
}
bidder.setResponse(bidRequest.id, bidResponse)

when: "PBS processes auction request"
def response = defaultPbsService.sendAuctionRequest(bidRequest)

then: "Targeting hb_pb should be round"
def targeting = response.seatbid[0].bid[0].ext.prebid.targeting
assert targeting["hb_pb"] == getRoundedTargetingValueWithUpPrecision(bidPrice)

where:
bidRoundingValue << [new AccountAuctionConfig(bidRounding: UP),
new AccountAuctionConfig(bidRoundingSnakeCase: UP)]
}

def "PBS should round bid value to the up or down when account bid rounding setting is #bidRoundingValue"() {
given: "Default bid request"
def bidRequest = BidRequest.getDefaultBidRequest().tap {
enableCache()
}

and: "Account in the DB"
def account = getAccountWithBidRounding(bidRequest.accountId, bidRoundingValue)
accountDao.save(account)

and: "Default bid response"
def bidPrice = PBSUtils.getRandomFloorValue()
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap {
seatbid[0].bid[0].price = bidPrice
}
bidder.setResponse(bidRequest.id, bidResponse)

when: "PBS processes auction request"
def response = defaultPbsService.sendAuctionRequest(bidRequest)

then: "Targeting hb_pb should be round"
def targeting = response.seatbid[0].bid[0].ext.prebid.targeting
assert targeting["hb_pb"] == getRoundedTargetingValueWithHalfUpPrecision(bidPrice)

where:
bidRoundingValue << [new AccountAuctionConfig(bidRounding: TRUE),
new AccountAuctionConfig(bidRoundingSnakeCase: TRUE)]
}

private static final Account getAccountWithBidRounding(String accountId, AccountAuctionConfig accountAuctionConfig) {
def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig)
new Account(uuid: accountId, config: accountConfig)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
def response = floorsPbsService.sendAmpRequest(ampRequest)

then: "PBS should suppress bids lower than floorRuleValue"
def bidPrice = getRoundedTargetingValueWithDefaultPrecision(floorValue)
def bidPrice = getRoundedTargetingValueWithDownPrecision(floorValue)
verifyAll(response) {
targeting["hb_pb_generic"] == bidPrice
targeting["hb_pb"] == bidPrice
Expand Down