Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test: Price floors max rules dimensions #3646

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class AccountPriceFloorsConfig {
Boolean adjustForBidAdjustment
Boolean enforceDealFloors
Boolean useDynamicData
Long maxRules
Long maxSchemaDims

@JsonProperty("enforce_floors_rate")
Integer enforceFloorsRateSnakeCase
Expand All @@ -24,4 +26,8 @@ class AccountPriceFloorsConfig {
Boolean enforceDealFloorsSnakeCase
@JsonProperty("use_dynamic_data")
Boolean useDynamicDataSnakeCase
@JsonProperty("max_rules")
Long maxRulesSnakeCase
@JsonProperty("max_schema_dims")
Long maxSchemaDimsSnakeCase
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ class PriceFloorsFetch {
Integer periodSec
@JsonProperty("period_sec")
Integer periodSecSnakeCase
Integer maxSchemaDims
@JsonProperty("max_schema_dims")
Integer maxSchemaDimsSnakeCase
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,15 @@ abstract class PriceFloorsBaseSpec extends BaseSpec {
maxRules: 0,
maxFileSizeKb: 200,
maxAgeSec: 86400,
periodSec: 3600)
periodSec: 3600,
maxSchemaDims: 0)
def floors = new AccountPriceFloorsConfig(enabled: true,
fetch: fetch,
enforceFloorsRate: 100,
enforceDealFloors: true,
adjustForBidAdjustment: true,
useDynamicData: true)
useDynamicData: true,
maxRules: 0)
new AccountConfig(auction: new AccountAuctionConfig(priceFloors: floors))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1299,8 +1299,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Response should contain error"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason : Price floor floorMin " +
"must be positive float, but was $invalidFloorMin "]
["Failed to parse price floors from request, with a reason: Price floor floorMin " +
"must be positive float, but was $invalidFloorMin"]
}

def "PBS should validate rules from request when request doesn't contain modelGroups"() {
Expand All @@ -1327,8 +1327,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Response should contain error"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason : Price floor rules " +
"should contain at least one model group "]
["Failed to parse price floors from request, with a reason: Price floor rules " +
"should contain at least one model group"]
}

def "PBS should validate rules from request when request doesn't contain values"() {
Expand All @@ -1355,8 +1355,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Response should contain error"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason : Price floor rules values " +
"can't be null or empty, but were null "]
["Failed to parse price floors from request, with a reason: Price floor rules values " +
"can't be null or empty, but were null"]
}

def "PBS should validate rules from request when modelWeight from request is invalid"() {
Expand Down Expand Up @@ -1387,8 +1387,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Response should contain error"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason : Price floor modelGroup modelWeight " +
"must be in range(1-100), but was $invalidModelWeight "]
["Failed to parse price floors from request, with a reason: Price floor modelGroup modelWeight " +
"must be in range(1-100), but was $invalidModelWeight"]
where:
invalidModelWeight << [0, MAX_MODEL_WEIGHT + 1]
}
Expand Down Expand Up @@ -1426,8 +1426,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Response should contain error"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason : Price floor modelGroup modelWeight " +
"must be in range(1-100), but was $invalidModelWeight "]
["Failed to parse price floors from request, with a reason: Price floor modelGroup modelWeight " +
"must be in range(1-100), but was $invalidModelWeight"]

where:
invalidModelWeight << [0, MAX_MODEL_WEIGHT + 1]
Expand Down Expand Up @@ -1466,8 +1466,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Response should contain error"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason : Price floor root skipRate " +
"must be in range(0-100), but was $invalidSkipRate "]
["Failed to parse price floors from request, with a reason: Price floor root skipRate " +
"must be in range(0-100), but was $invalidSkipRate"]

where:
invalidSkipRate << [SKIP_RATE_MIN - 1, SKIP_RATE_MAX + 1]
Expand Down Expand Up @@ -1506,8 +1506,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Response should contain error"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason : Price floor data skipRate " +
"must be in range(0-100), but was $invalidSkipRate "]
["Failed to parse price floors from request, with a reason: Price floor data skipRate " +
"must be in range(0-100), but was $invalidSkipRate"]

where:
invalidSkipRate << [SKIP_RATE_MIN - 1, SKIP_RATE_MAX + 1]
Expand Down Expand Up @@ -1546,8 +1546,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Response should contain error"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason : Price floor modelGroup skipRate " +
"must be in range(0-100), but was $invalidSkipRate "]
["Failed to parse price floors from request, with a reason: Price floor modelGroup skipRate " +
"must be in range(0-100), but was $invalidSkipRate"]

where:
invalidSkipRate << [SKIP_RATE_MIN - 1, SKIP_RATE_MAX + 1]
Expand Down Expand Up @@ -1582,8 +1582,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Response should contain error"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason : Price floor modelGroup default " +
"must be positive float, but was $invalidDefaultFloorValue "]
["Failed to parse price floors from request, with a reason: Price floor modelGroup default " +
"must be positive float, but was $invalidDefaultFloorValue"]
}

def "PBS should not invalidate previously good fetched data when floors provider return invalid data"() {
Expand Down Expand Up @@ -2046,6 +2046,91 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
}
}

def "PBS should validate fetch.max-schema-dims from account config"() {
given: "Default BidRequest"
def bidRequest = BidRequest.getDefaultBidRequest(APP)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also why APP?


and: "Account with enabled fetch, maxSchemaDims in the DB"
def account = getAccountWithEnabledFetch(bidRequest.accountId).tap {
config.auction.priceFloors.fetch.maxSchemaDims = maxSchemaDims
config.auction.priceFloors.fetch.maxSchemaDimsSnakeCase = maxSchemaDimsSnakeCase
}
accountDao.save(account)

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

then: "Metric alerts.account_config.ACCOUNT.price-floors should be update"
def metrics = floorsPbsService.sendCollectedMetricsRequest()
assert metrics[INVALID_CONFIG_METRIC(bidRequest.accountId) as String] == 1

and: "PBS floors validation failure should not reject the entire auction"
assert !response.seatbid?.isEmpty()

where:
maxSchemaDims | maxSchemaDimsSnakeCase
null | PBSUtils.randomNegativeNumber
null | PBSUtils.getRandomNumber(20)
PBSUtils.randomNegativeNumber | null
PBSUtils.getRandomNumber(20) | null
}

def "PBS should validate price-floor.max-rules from account config"() {
given: "Default BidRequest"
def bidRequest = BidRequest.getDefaultBidRequest(APP)

and: "Account with enabled fetch, maxRules in the DB"
def account = getAccountWithEnabledFetch(bidRequest.accountId).tap {
config.auction.priceFloors.maxRules = maxRules
config.auction.priceFloors.maxRulesSnakeCase = maxRulesSnakeCase
}
accountDao.save(account)

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

then: "Metric alerts.account_config.ACCOUNT.price-floors should be update"
def metrics = floorsPbsService.sendCollectedMetricsRequest()
assert metrics[INVALID_CONFIG_METRIC(bidRequest.accountId) as String] == 1

and: "PBS floors validation failure should not reject the entire auction"
assert !response.seatbid?.isEmpty()

where:
maxRules | maxRulesSnakeCase
null | PBSUtils.randomNegativeNumber
PBSUtils.randomNegativeNumber | null
}

def "PBS should validate price-floor.max-schema-dims from account config"() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PBS should validate price-floor.max-schema-dims from account config and not reject entire auction same for others

given: "Default BidRequest"
def bidRequest = BidRequest.getDefaultBidRequest(APP)

and: "Account with enabled fetch, maxSchemaDims in the DB"
def account = getAccountWithEnabledFetch(bidRequest.accountId).tap {
config.auction.priceFloors.maxSchemaDims = maxSchemaDims
config.auction.priceFloors.maxSchemaDimsSnakeCase = maxSchemaDimsSnakeCase
}
accountDao.save(account)

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

then: "Metric alerts.account_config.ACCOUNT.price-floors should be update"
def metrics = floorsPbsService.sendCollectedMetricsRequest()
assert metrics[INVALID_CONFIG_METRIC(bidRequest.accountId) as String] == 1

and: "PBS floors validation failure should not reject the entire auction"
assert !response.seatbid?.isEmpty()

where:
maxSchemaDims | maxSchemaDimsSnakeCase
null | PBSUtils.randomNegativeNumber
null | PBSUtils.getRandomNumber(20)
PBSUtils.randomNegativeNumber | null
PBSUtils.getRandomNumber(20) | null
}

static int convertKilobyteSizeToByte(int kilobyteSize) {
kilobyteSize * 1024
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,8 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {

where:
bidRequest | bothFloorValue | bannerFloorValue | videoFloorValue
bidRequestWithMultipleMediaTypes | 0.6 | PBSUtils.randomFloorValue |
PBSUtils.randomFloorValue
BidRequest.defaultBidRequest | PBSUtils.randomFloorValue | 0.6 |
PBSUtils.randomFloorValue
bidRequestWithMultipleMediaTypes | 0.6 | PBSUtils.randomFloorValue | PBSUtils.randomFloorValue
BidRequest.defaultBidRequest | PBSUtils.randomFloorValue | 0.6 | PBSUtils.randomFloorValue
BidRequest.defaultVideoRequest | PBSUtils.randomFloorValue | PBSUtils.randomFloorValue | 0.6
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import static org.prebid.server.functional.model.pricefloors.MediaType.VIDEO
import static org.prebid.server.functional.model.pricefloors.PriceFloorField.MEDIA_TYPE
import static org.prebid.server.functional.model.pricefloors.PriceFloorField.SITE_DOMAIN
import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP
import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID

class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {

Expand Down Expand Up @@ -511,4 +512,141 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
assert bidderRequest.imp.first().bidFloor == bannerFloorValue
assert bidderRequest.imp.last().bidFloor == videoFloorValue
}

def "PBS should emit errors when request has more rules than price-floor.max-rules"() {
given: "BidRequest with 2 rules"
def requestFloorValue = PBSUtils.randomFloorValue
def bidRequest = bidRequestWithFloors.tap {
ext.prebid.floors.data.modelGroups[0].values =
[(rule) : requestFloorValue + 0.1,
(new Rule(mediaType: BANNER, country: Country.MULTIPLE).rule): requestFloorValue]
}

and: "Account with maxRules in the DB"
def accountId = bidRequest.site.publisher.id
def account = getAccountWithEnabledFetch(accountId).tap {
config.auction.priceFloors.maxRules = maxRules
config.auction.priceFloors.maxRulesSnakeCase = maxRulesSnakeCase
}
accountDao.save(account)

and: "Set bidder response"
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
bidResponse.seatbid.first().bid.first().price = requestFloorValue
bidder.setResponse(bidRequest.id, bidResponse)

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

then: "PBS should log a errors"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason: " +
"Price floor rules number 2 exceeded its maximum number 1"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the proper way to configure tests. For any external viewer, it will be hard to understand why it is set to 2 and why we are using 1. A person would be forced to look at the code and into PriceFloorSchema.groovy for the request. This is not right. We should replace 2 with priceFloorSchema.fields.size and 1 with priceFloorSchema.fields.size - 1 (as example). Same for others


where:
maxRules | maxRulesSnakeCase
1 | null
null | 1
}

def "PBS should emit errors when request has more schema.fields than floor-config.max-schema-dims"() {
given: "BidRequest with schema 2 fields"
def bidRequest = bidRequestWithFloors

and: "Account with maxSchemaDims in the DB"
def accountId = bidRequest.site.publisher.id
def account = getAccountWithEnabledFetch(accountId).tap {
config.auction.priceFloors.maxSchemaDims = maxSchemaDims
config.auction.priceFloors.maxSchemaDimsSnakeCase = maxSchemaDimsSnakeCase
}
accountDao.save(account)

and: "Set bidder response"
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
bidder.setResponse(bidRequest.id, bidResponse)

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

then: "PBS should log a errors"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason: " +
"Price floor schema dimensions 2 exceeded its maximum number 1"]

where:
maxSchemaDims | maxSchemaDimsSnakeCase
1 | null
null | 1
}

def "PBS should emit errors when request has more schema.fields than fetch.max-schema-dims"() {
given: "Default BidRequest with floorMin"
def bidRequest = bidRequestWithFloors

and: "Account with disabled fetch in the DB"
def account = getAccountWithEnabledFetch(bidRequest.accountId).tap {
config.auction.priceFloors.fetch.enabled = false
config.auction.priceFloors.maxSchemaDims = maxSchemaDims
config.auction.priceFloors.maxSchemaDimsSnakeCase = maxSchemaDimsSnakeCase
}
accountDao.save(account)

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

then: "PBS should log a errors"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason: " +
"Price floor schema dimensions 2 exceeded its maximum number 1"]

where:
maxSchemaDims | maxSchemaDimsSnakeCase
1 | null
null | 1
}

def "PBS should emit errors when stored request has more rules than price-floor.max-rules for amp request"() {
given: "Default AmpRequest"
def ampRequest = AmpRequest.defaultAmpRequest

and: "Default stored request with 2 rules "
def requestFloorValue = PBSUtils.randomFloorValue
def ampStoredRequest = BidRequest.defaultStoredRequest.tap {
ext.prebid.floors = ExtPrebidFloors.extPrebidFloors
ext.prebid.floors.data.modelGroups[0].values =
[(rule) : requestFloorValue + 0.1,
(new Rule(mediaType: BANNER, country: Country.MULTIPLE).rule): requestFloorValue]
}
def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest)
storedRequestDao.save(storedRequest)

and: "Account with maxRules in the DB"
def account = getAccountWithEnabledFetch(ampRequest.account as String).tap {
config.auction.priceFloors.maxRules = maxRules
config.auction.priceFloors.maxRulesSnakeCase = maxRulesSnakeCase
}
accountDao.save(account)

and: "Set bidder response"
def bidResponse = BidResponse.getDefaultBidResponse(ampStoredRequest)
bidResponse.seatbid.first().bid.first().price = requestFloorValue
bidder.setResponse(ampStoredRequest.id, bidResponse)

when: "PBS processes amp request"
def response = floorsPbsService.sendAmpRequest(ampRequest)

then: "PBS should log a errors"
assert response.ext?.errors[PREBID]*.code == [999]
assert response.ext?.errors[PREBID]*.message ==
["Failed to parse price floors from request, with a reason: " +
"Price floor rules number 2 exceeded its maximum number 1"]

where:
maxRules | maxRulesSnakeCase
1 | null
null | 1
}
}