From 52e11d662da1b62a9da27947929d61453bee6c80 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Sun, 4 May 2025 19:31:27 +0300 Subject: [PATCH 1/3] Tests: Increase coverage for floors logs tests --- .../model/response/auction/Bid.groovy | 2 +- .../pricefloors/PriceFloorsBaseSpec.groovy | 20 ++ .../PriceFloorsFetchingSpec.groovy | 260 +++++++++++++++-- .../PriceFloorsSignalingSpec.groovy | 262 ++++++++---------- .../server/functional/util/PBSUtils.groovy | 4 + 5 files changed, 375 insertions(+), 173 deletions(-) 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..be764c64df4 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 ? PBSUtils.getRandomPrice(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/pricefloors/PriceFloorsBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy index 4e1cbbf349b..a8b18661abf 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 @@ -47,6 +47,26 @@ abstract class PriceFloorsBaseSpec extends BaseSpec { protected static final FloorsProvider floorsProvider = new FloorsProvider(networkServiceContainer) protected final PrebidServerService floorsPbsService = pbsServiceFactory.getService(FLOORS_CONFIG + GENERIC_ALIAS_CONFIG) + protected static final Closure INVALID_CONFIG_METRIC = { account -> "alerts.account_config.${account}.price-floors" } + + protected static final Closure URL_EMPTY_ERROR = { url -> "Failed to fetch price floor from provider for fetch.url '${url}'" } + protected static final Closure URL_EMPTY_WARNING_MESSAGE = { url, message -> + "${URL_EMPTY_ERROR(url)}, with a reason: $message" + } + protected static final Closure URL_EMPTY_ERROR_LOG = { bidRequest, message -> + "No price floor data for account ${bidRequest.accountId} and " + + "request ${bidRequest.id}, reason: ${URL_EMPTY_WARNING_MESSAGE("$BASIC_FETCH_URL$bidRequest.accountId", message)}" + } + // Required: Sync no longer logs "in progress"—only "none" or "error" statuses are recorded + protected static final String FETCHING_DISABLED_ERROR = 'Fetching is disabled' + protected static final Closure FETCHING_DISABLED_WARNING_MESSAGE = { message -> + "$FETCHING_DISABLED_ERROR. Following parsing of request price floors is failed: $message" + } + protected static final Closure FETCHING_DISABLED_ERROR_LOG = { bidRequest, message -> + "No price floor data for account ${bidRequest.accountId} and " + + "request ${bidRequest.id}, reason: ${FETCHING_DISABLED_WARNING_MESSAGE(message)}" + } + def setupSpec() { floorsProvider.setResponse() } diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy index 699b9bdcda0..767445c04ce 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy @@ -1,6 +1,10 @@ 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.config.AccountPriceFloorsConfig import org.prebid.server.functional.model.config.PriceFloorsFetch +import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.pricefloors.ModelGroup import org.prebid.server.functional.model.pricefloors.PriceFloorData @@ -23,6 +27,7 @@ import static org.prebid.server.functional.model.pricefloors.MediaType.BANNER import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE import static org.prebid.server.functional.model.request.auction.FetchStatus.ERROR +import static org.prebid.server.functional.model.request.auction.FetchStatus.INPROGRESS import static org.prebid.server.functional.model.request.auction.FetchStatus.NONE import static org.prebid.server.functional.model.request.auction.FetchStatus.SUCCESS import static org.prebid.server.functional.model.request.auction.Location.FETCH @@ -45,22 +50,13 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { private static final int FLOOR_MIN = 0 private static final String FETCH_FAILURE_METRIC = "price-floors.fetch.failure" - private static final String FETCHING_DISABLED_ERROR = 'Fetching is disabled' private static final String PRICE_FLOOR_VALUES_MISSING = 'Price floor rules values can\'t be null or empty, but were null' private static final String MODEL_WEIGHT_INVALID = "Price floor modelGroup modelWeight must be in range(1-100), but was %s" private static final String SKIP_RATE_INVALID = "Price floor modelGroup skipRate must be in range(0-100), but was %s" + private static Instant startTime - private static final Closure INVALID_CONFIG_METRIC = { account -> "alerts.account_config.${account}.price-floors" } - private static final Closure URL_EMPTY_ERROR = { url -> "Failed to fetch price floor from provider for fetch.url '${url}'" } - private static final Closure URL_EMPTY_WARNING_MESSAGE = { url, message -> - "${URL_EMPTY_ERROR(url)}, with a reason: $message" - } - private static final Closure URL_EMPTY_ERROR_LOG = { bidRequest, message -> - "No price floor data for account ${bidRequest.accountId} and " + - "request ${bidRequest.id}, reason: ${URL_EMPTY_WARNING_MESSAGE("$BASIC_FETCH_URL$bidRequest.accountId", message)}" - } - private static final Closure WARNING_MESSAGE = { message -> - "$FETCHING_DISABLED_ERROR. Following parsing of request price floors is failed: $message" + def setupSpec() { + startTime = Instant.now() } def "PBS should activate floors feature when price-floors.enabled = true in PBS config"() { @@ -1309,6 +1305,10 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { } accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1319,7 +1319,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "Response should contain warning" def message = "Price floor floorMin must be positive float, but was $invalidFloorMin" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] } def "PBS should validate rules from request when request doesn't contain modelGroups"() { @@ -1336,6 +1336,10 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { } accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1346,7 +1350,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "Response should contain warning" def message = "Price floor rules should contain at least one model group" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] } def "PBS should validate rules from request when request doesn't contain values"() { @@ -1363,6 +1367,10 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { } accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1372,7 +1380,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "Response should contain warning" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(PRICE_FLOOR_VALUES_MISSING)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(PRICE_FLOOR_VALUES_MISSING)] } def "PBS should validate rules from request when modelWeight from request is invalid"() { @@ -1393,6 +1401,10 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { } accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1402,7 +1414,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "Response should contain warning" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(MODEL_WEIGHT_INVALID.formatted(invalidModelWeight))] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(MODEL_WEIGHT_INVALID.formatted(invalidModelWeight))] where: invalidModelWeight << [0, MAX_MODEL_WEIGHT + 1] @@ -1431,6 +1443,10 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { } accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(ampStoredRequest) + bidder.setResponse(ampStoredRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAmpRequest(ampRequest) @@ -1440,7 +1456,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "Response should contain warning" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(MODEL_WEIGHT_INVALID.formatted(invalidModelWeight))] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(MODEL_WEIGHT_INVALID.formatted(invalidModelWeight))] where: invalidModelWeight << [0, MAX_MODEL_WEIGHT + 1] @@ -1469,6 +1485,10 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "PBS fetch rules from floors provider" cacheFloorsProviderRules(bidRequest) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1479,7 +1499,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "Response should contain warning" def message = "Price floor root skipRate must be in range(0-100), but was $invalidSkipRate" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] where: invalidSkipRate << [SKIP_RATE_MIN - 1, SKIP_RATE_MAX + 1] @@ -1508,6 +1528,10 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "PBS fetch rules from floors provider" cacheFloorsProviderRules(bidRequest) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1518,7 +1542,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "Response should contain warning" def message = "Price floor data skipRate must be in range(0-100), but was $invalidSkipRate" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] where: invalidSkipRate << [SKIP_RATE_MIN - 1, SKIP_RATE_MAX + 1] @@ -1547,6 +1571,10 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "PBS fetch rules from floors provider" cacheFloorsProviderRules(bidRequest) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1556,7 +1584,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "Response should contain warning" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(SKIP_RATE_INVALID.formatted(invalidSkipRate))] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(SKIP_RATE_INVALID.formatted(invalidSkipRate))] where: invalidSkipRate << [SKIP_RATE_MIN - 1, SKIP_RATE_MAX + 1] @@ -1581,6 +1609,10 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { } accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1591,7 +1623,193 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "Response should contain warning" def message = "Price floor modelGroup default must be positive float, but was $invalidDefaultFloorValue" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] + } + + def "PBS shouldn't emit error in log and response when floors is not in request and floors fetching disabled for account"() { + given: "Account with disabled fetching" + def priceFloors = new AccountPriceFloorsConfig(enabled: false, fetch: new PriceFloorsFetch(enabled: false)) + def accountAuctionConfig = new AccountAuctionConfig(priceFloors: priceFloors) + def accountConfig = new AccountConfig(auction: accountAuctionConfig) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(floorsPbsService) + + and: "Default bid response" + def response = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, response) + + when: "PBS processes auction request" + def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "PBS shouldn't log warning or errors" + assert !bidResponse.ext?.warnings + assert !bidResponse.ext?.errors + + and: "PBS shouldn't log a errors" + def message = 'Price floor rules data must be present' + def logs = floorsPbsService.getLogsByTime(startTime) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) + assert !floorsLogs.size() + + and: "PBS request on response object status should be in progress" + assert getRequests(bidResponse)[GENERIC.value]?.first?.ext?.prebid?.floors?.fetchStatus == INPROGRESS + + and: "PBS bidderRequest status should be in progress" + def bidderRequest = bidder.getBidderRequests(bidRequest.id) + assert bidderRequest.ext.prebid.floors.fetchStatus == [INPROGRESS] + + and: "Alerts.general metrics shouldn't be populated" + def metrics = floorsPbsService.sendCollectedMetricsRequest() + assert !metrics[ALERT_GENERAL] + + where: + bidRequest << [BidRequest.getDefaultBidRequest(), getBidRequestWithFloors().tap { it.ext.prebid.floors = null }] + } + + def "PBS should emit error in log and response when data is invalid and floors fetching disabled for account and #requestFloors for request"() { + given: "Default BidRequest with empty floors.data" + def bidRequest = bidRequestWithFloors.tap { + ext.prebid.floors.enabled = requestFloors + ext.prebid.floors.data = null + } + + and: "Flush metrics" + flushMetrics(floorsPbsService) + + and: "Account with disabled fetching" + def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { + config.auction.priceFloors.fetch.enabled = false + } + accountDao.save(account) + + and: "Default bid response" + def response = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, response) + + when: "PBS processes auction request" + def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should log a warning" + def message = 'Price floor rules data must be present' + assert bidResponse.ext?.warnings[PREBID]*.code == [999] + assert bidResponse.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] + + and: "PBS should not add errors" + assert !bidResponse.ext.errors + + and: "PBS should log a errors" + def logs = floorsPbsService.getLogsByTime(startTime) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) + assert floorsLogs.size() == 1 + + and: "PBS request on response object status should be not in progress" + assert getRequests(bidResponse)[GENERIC.value]?.first?.ext?.prebid?.floors?.fetchStatus == NONE + + and: "PBS request status shouldn't be in progress" + def bidderRequest = bidder.getBidderRequests(bidRequest.id) + assert bidderRequest.ext.prebid.floors.fetchStatus == [NONE] + + and: "Alerts.general metrics should be populated" + def metrics = floorsPbsService.sendCollectedMetricsRequest() + assert metrics[ALERT_GENERAL] == 1 + + where: + requestFloors << [null, true] + } + + def "PBS shouldn't emit error in log and response when data is invalid and floors fetching enabled for account"() { + given: "Default BidRequest with empty floors.data" + def bidRequest = bidRequestWithFloors.tap { + ext.prebid.floors.enabled = requestFloors + ext.prebid.floors.data = null + } + + and: "Account with enabled fetching" + def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { + config.auction.priceFloors.enabled = true + config.auction.priceFloors.fetch.enabled = true + } + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(floorsPbsService) + + and: "Default bid response" + def response = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, response) + + when: "PBS processes auction request" + def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "PBS shouldn't log warning or errors" + assert !bidResponse.ext?.warnings + assert !bidResponse.ext?.errors + + and: "PBS shouldn't log a errors" + def message = 'Price floor rules data must be present' + def logs = floorsPbsService.getLogsByTime(startTime) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) + assert !floorsLogs.size() + + and: "PBS request on response object status should be in progress" + assert getRequests(bidResponse)[GENERIC.value]?.first?.ext?.prebid?.floors?.fetchStatus == INPROGRESS + + and: "PBS bidderRequest status should be in progress" + def bidderRequest = bidder.getBidderRequests(bidRequest.id) + assert bidderRequest.ext.prebid.floors.fetchStatus == [INPROGRESS] + + and: "Alerts.general metrics shouldn't be populated" + def metrics = floorsPbsService.sendCollectedMetricsRequest() + assert !metrics[ALERT_GENERAL] + + where: + requestFloors << [null, true] + } + + def "PBS shouldn't emit error in log and response when data is invalid and floors disabled for request"() { + given: "Default BidRequest with empty floors.data" + def bidRequest = bidRequestWithFloors.tap { + ext.prebid.floors.enabled = false + ext.prebid.floors.data = null + } + + and: "Flush metrics" + flushMetrics(floorsPbsService) + + and: "Account with disabled fetching" + def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { + config.auction.priceFloors.enabled = false + config.auction.priceFloors.fetch.enabled = false + } + accountDao.save(account) + + and: "PBS fetch rules from floors provider" + cacheFloorsProviderRules(bidRequest, floorsPbsService) + + and: "Default bid response" + def response = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, response) + + when: "PBS processes auction request" + def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should not add warning or errors" + assert !bidResponse.ext.warnings + assert !bidResponse.ext.errors + + and: "PBS request on response object status should be empty" + assert getRequests(bidResponse)[GENERIC.value]?.first?.ext?.prebid?.floors?.fetchStatus == null + + and: "PBS request status should be empty" + def bidderRequest = bidder.getBidderRequests(bidRequest.id) + assert bidderRequest.ext.prebid.floors.fetchStatus.every { it == null } + + and: "Alerts.general metrics shouldn't be populated" + def metrics = floorsPbsService.sendCollectedMetricsRequest() + assert !metrics[ALERT_GENERAL] } def "PBS should not invalidate previously good fetched data when floors provider return invalid data"() { 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 1de08b44cc9..d31c8223822 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 @@ -1,9 +1,5 @@ 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.config.AccountPriceFloorsConfig -import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.pricefloors.Country import org.prebid.server.functional.model.pricefloors.ModelGroup @@ -33,23 +29,11 @@ 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.request.auction.FetchStatus.INPROGRESS -import static org.prebid.server.functional.model.request.auction.FetchStatus.NONE import static org.prebid.server.functional.model.request.auction.FetchStatus.SUCCESS import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { - // Required: Sync no longer logs "in progress"—only "none" or "error" statuses are recorded - private static final String FETCHING_DISABLED_ERROR = 'Fetching is disabled' - private static final Closure INVALID_CONFIG_METRIC = { account -> "alerts.account_config.${account}.price-floors" } - private static final Closure WARNING_MESSAGE = { message -> - "$FETCHING_DISABLED_ERROR. Following parsing of request price floors is failed: $message" - } - private static final Closure ERROR_LOG = { bidRequest, message -> - "No price floor data for account ${bidRequest.accountId} and " + - "request ${bidRequest.id}, reason: ${WARNING_MESSAGE(message)}" - } private static final int MAX_SCHEMA_DIMENSIONS_SIZE = 1 private static final int MAX_RULES_SIZE = 1 private static Instant startTime @@ -58,7 +42,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { startTime = Instant.now() } - def "PBS should skip signalling for request with rules when ext.prebid.floors.enabled = false in request"() { + def "PBS should skip signaling for request with rules when ext.prebid.floors.enabled = false in request"() { given: "Default BidRequest with disabled floors" def bidRequest = bidRequestWithFloors.tap { ext.prebid.floors.enabled = requestEnabled @@ -70,24 +54,45 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { } accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + and: "PBS fetch rules from floors provider" + cacheFloorsProviderRules(bidRequest, floorsPbsService, GENERIC, SUCCESS) + when: "PBS processes auction request" - floorsPbsService.sendAuctionRequest(bidRequest) + def response = floorsPbsService.sendAuctionRequest(bidRequest) then: "Bidder request bidFloor should correspond request" - def bidderRequest = bidder.getBidderRequest(bidRequest.id) + def bidderRequest = bidder.getBidderRequests(bidRequest.id).last assert bidderRequest.imp[0].bidFloor == bidRequest.imp[0].bidFloor assert !bidderRequest.ext?.prebid?.floors?.enabled and: "PBS should not fetch rules from floors provider" assert floorsProvider.getRequestCount(bidRequest.site.publisher.id) == 0 + and: "PBS should not add warning or errors" + assert !response.ext.warnings + assert !response.ext.errors + + and: "PBS shouldn't log a errors" + def message = 'Price floor rules data must be present' + def logs = floorsPbsService.getLogsByTime(startTime) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) + assert !floorsLogs.size() + + and: "Alerts.general metrics shouldn't be populated" + def metrics = floorsPbsService.sendCollectedMetricsRequest() + assert !metrics[ALERT_GENERAL] + where: requestEnabled | accountEnabled false | true true | false } - def "PBS should skip signalling for request without rules when ext.prebid.floors.enabled = false in request"() { + def "PBS should skip signaling for request without rules when ext.prebid.floors.enabled = false in request"() { given: "Default BidRequest" def bidRequest = BidRequest.getDefaultBidRequest(APP).tap { ext.prebid.floors = new ExtPrebidFloors(enabled: false) @@ -166,7 +171,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { assert floorsProvider.getRequestCount(bidRequest.site.publisher.id) == 1 } - def "PBS should not signalling when neither fetched floors nor ext.prebid.floors exist, imp.bidFloor is not defined"() { + def "PBS should not signaling when neither fetched floors nor ext.prebid.floors exist, imp.bidFloor is not defined"() { given: "Default BidRequest" def bidRequest = BidRequest.defaultBidRequest @@ -194,7 +199,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { assert floorsProvider.getRequestCount(bidRequest.site.publisher.id) == 1 } - def "PBS should make PF signalling when skipRate = #skipRate"() { + def "PBS should make PF signaling when skipRate = #skipRate"() { given: "Default BidRequest with bidFloor, bidFloorCur" def bidRequest = BidRequest.defaultBidRequest.tap { imp[0].bidFloor = PBSUtils.randomFloorValue @@ -230,7 +235,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { skipRate << [0, null] } - def "PBS should not make PF signalling, enforcing when skipRate = 100"() { + def "PBS should not make PF signaling, enforcing when skipRate = 100"() { given: "Default BidRequest with bidFloor, bidFloorCur" def bidRequest = BidRequest.defaultBidRequest.tap { imp[0].bidFloor = 0.8 @@ -264,7 +269,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { assert bidderRequest.ext?.prebid?.floors?.skipRate == 100 assert bidderRequest.ext?.prebid?.floors?.skipped - and: "PBS should not made signalling" + and: "PBS should not made signaling" assert !bidderRequest.imp[0].ext?.prebid?.floors where: @@ -553,6 +558,10 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { def account = getAccountWithEnabledFetch(accountId) accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) @@ -597,7 +606,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { then: "PBS should log a warning" def message = "Price floor rules number ${getRuleSize(bidRequest)} exceeded its maximum number ${MAX_RULES_SIZE}" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "Alerts.general metrics should be populated" def metrics = floorsPbsService.sendCollectedMetricsRequest() @@ -628,13 +637,17 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "Flush metrics" flushMetrics(floorsPbsService) + and: "Default bid 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 warning" def message = "Price floor schema dimensions ${getSchemaSize(bidRequest)} exceeded its maximum number ${MAX_SCHEMA_DIMENSIONS_SIZE}" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "Alerts.general metrics should be populated" def metrics = floorsPbsService.sendCollectedMetricsRequest() @@ -671,17 +684,21 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "Flush metrics" flushMetrics(floorsPbsService) + and: "Default bid 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 warning" def message = "Price floor schema dimensions ${getSchemaSize(bidRequest)} exceeded its maximum number ${MAX_SCHEMA_DIMENSIONS_SIZE}" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) assert floorsLogs.size() == 1 and: "Metrics should be updated" @@ -720,17 +737,21 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { } accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) then: "Response should includer error warning" def message = "Price floor schema dimensions ${getSchemaSize(bidRequest)} exceeded its maximum number ${MAX_SCHEMA_DIMENSIONS_SIZE}" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "PBS shouldn't log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) assert floorsLogs.size() == 1 and: "Metrics should be updated" @@ -757,17 +778,21 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "Flush metrics" flushMetrics(floorsPbsService) + and: "Default bid 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 warning" def message = "Price floor schema dimensions ${getSchemaSize(bidRequest)} exceeded its maximum number ${MAX_SCHEMA_DIMENSIONS_SIZE}" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -797,6 +822,10 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "Flush metrics" flushMetrics(floorsPbsService) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) @@ -804,11 +833,11 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { def message = "Price floor schema dimensions ${floorSchemaFilesSize} " + "exceeded its maximum number ${MAX_SCHEMA_DIMENSIONS_SIZE}" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -828,6 +857,10 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { } accountDao.save(account) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + when: "PBS processes auction request" def response = floorsPbsService.sendAuctionRequest(bidRequest) @@ -873,12 +906,12 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { def message = "Price floor rules number ${getRuleSize(ampStoredRequest)} " + "exceeded its maximum number ${MAX_RULES_SIZE}" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert response.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "No price floor data for account $ampRequest.account and " + - "request $ampStoredRequest.id, reason: ${WARNING_MESSAGE(message)}") + "request $ampStoredRequest.id, reason: ${FETCHING_DISABLED_WARNING_MESSAGE(message)}") assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -891,84 +924,6 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { null | MAX_RULES_SIZE } - def "PBS shouldn't emit error in log and response when data is invalid and floors status is in progress"() { - given: "Default BidRequest with empty floors.data" - def bidRequest = bidRequestWithFloors.tap { - ext.prebid.floors.data = null - } - - and: "Account with enabled fetching" - def account = getAccountWithEnabledFetch(bidRequest.accountId) - accountDao.save(account) - - and: "Flush metrics" - flushMetrics(floorsPbsService) - - when: "PBS processes auction request" - def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) - - then: "Response shouldn't includer error warning" - assert !bidResponse.ext?.warnings - - and: "PBS shouldn't log a errors" - def message = 'Price floor rules data must be present' - def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) - assert !floorsLogs.size() - - and: "PBS request on response object status should be in progress" - assert getRequests(bidResponse)[GENERIC.value]?.first?.ext?.prebid?.floors?.fetchStatus == INPROGRESS - - and: "PBS bidderRequest status should be in progress" - def bidderRequest = bidder.getBidderRequests(bidRequest.id) - assert bidderRequest.ext.prebid.floors.fetchStatus == [INPROGRESS] - - and: "Alerts.general metrics shouldn't be populated" - def metrics = floorsPbsService.sendCollectedMetricsRequest() - assert !metrics[ALERT_GENERAL] - } - - def "PBS should emit error in log and response when data is invalid and floors status not in progress"() { - given: "Default BidRequest with empty floors.data" - def bidRequest = bidRequestWithFloors.tap { - ext.prebid.floors.data = null - } - - and: "Flush metrics" - flushMetrics(floorsPbsService) - - and: "Account with disabled fetching" - def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { - config.auction.priceFloors.fetch.enabled = false - } - accountDao.save(account) - - when: "PBS processes auction request" - def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) - - then: "PBS should log a warning" - def message = 'Price floor rules data must be present' - assert bidResponse.ext?.warnings[PREBID]*.code == [999] - assert bidResponse.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] - - and: "PBS should log a errors" - def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) - assert floorsLogs.size() == 1 - - and: "PBS request on response object status should be none" - assert getRequests(bidResponse)[GENERIC.value]?.first?.ext?.prebid?.floors?.fetchStatus == NONE - - and: "PBS request status shouldn't be in progress" - def bidderRequest = bidder.getBidderRequests(bidRequest.id) - assert bidderRequest.ext.prebid.floors.fetchStatus == [NONE] - - - and: "Alerts.general metrics should be populated" - def metrics = floorsPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL] == 1 - } - def "PBS should emit error in log and response when floors skipRate is out of range"() { given: "BidRequest with invalid skipRate" def bidRequest = bidRequestWithFloors.tap { @@ -981,14 +936,18 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { when: "PBS processes auction request" def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) + and: "Default bid response" + def response = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, response) + then: "PBS should log a warning" def message = "Price floor data skipRate must be in range(0-100), but was $requestSkipRate" assert bidResponse.ext?.warnings[PREBID]*.code == [999] - assert bidResponse.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert bidResponse.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -1008,17 +967,21 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "Flush metrics" flushMetrics(floorsPbsService) + and: "Default bid response" + def response = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, response) + when: "PBS processes auction request" def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) then: "PBS should log a warning" def message = "Price floor rules should contain at least one model group" assert bidResponse.ext?.warnings[PREBID]*.code == [999] - assert bidResponse.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert bidResponse.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -1040,17 +1003,21 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "Flush metrics" flushMetrics(floorsPbsService) + and: "Default bid response" + def response = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, response) + when: "PBS processes auction request" def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) then: "PBS should log a warning" def message = "Price floor modelGroup modelWeight must be in range(1-100), but was $requestModelWeight" assert bidResponse.ext?.warnings[PREBID]*.code == [999] - assert bidResponse.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert bidResponse.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -1073,17 +1040,21 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "Flush metrics" flushMetrics(floorsPbsService) + and: "Default bid response" + def response = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, response) + when: "PBS processes auction request" def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) then: "PBS should log a warning" def message = "Price floor modelGroup skipRate must be in range(0-100), but was $requestModelGroupsSkipRate" assert bidResponse.ext?.warnings[PREBID]*.code == [999] - assert bidResponse.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert bidResponse.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "PBS should log an errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -1104,17 +1075,21 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "Flush metrics" flushMetrics(floorsPbsService) + and: "Default bid response" + def response = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, response) + when: "PBS processes auction request" def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) then: "PBS should log a warning" def message = "Price floor modelGroup default must be positive float, but was $requestModelGroupsSkipRate" assert bidResponse.ext?.warnings[PREBID]*.code == [999] - assert bidResponse.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] + assert bidResponse.ext?.warnings[PREBID]*.message == [FETCHING_DISABLED_WARNING_MESSAGE(message)] and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -1122,33 +1097,6 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { assert metrics[ALERT_GENERAL] == 1 } - def "PBS should use default floors config when original account config is invalid"() { - given: "Bid request with empty floors" - def bidRequest = BidRequest.defaultBidRequest - - and: "Account with disabled price floors" - def accountConfig = new AccountConfig(auction: new AccountAuctionConfig(priceFloors: new AccountPriceFloorsConfig(enabled: false))) - def account = new Account(uuid: bidRequest.accountId, config: accountConfig) - accountDao.save(account) - - and: "Flush metrics" - flushMetrics(floorsPbsService) - - when: "PBS processes auction request" - def response = floorsPbsService.sendAuctionRequest(bidRequest) - - and: "PBS floors validation failure should not reject the entire auction" - assert !response.seatbid?.isEmpty() - - then: "PBS shouldn't log warning or errors" - assert !response.ext?.warnings - assert !response.ext?.errors - - and: "Alerts.general metrics shouldn't be populated" - def metrics = floorsPbsService.sendCollectedMetricsRequest() - assert !metrics[ALERT_GENERAL] - } - def "PBS should emit error in log and response when account have disabled dynamic data config"() { given: "Bid request without floors" def bidRequest = BidRequest.defaultBidRequest @@ -1162,6 +1110,10 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "PBS fetch rules from floors provider" cacheFloorsProviderRules(bidRequest, floorsPbsService, GENERIC, SUCCESS) + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + and: "Flush metrics" flushMetrics(floorsPbsService) @@ -1183,6 +1135,14 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { assert metrics[ALERT_GENERAL] == 1 } + def "PBS shouldn't emit error or warning when floors for account configured correctly"() { + given: "Bid request without floors" + def bidRequest = BidRequest.defaultBidRequest + + and: "PBS fetch rules from floors provider" + cacheFloorsProviderRules(bidRequest, floorsPbsService, GENERIC, SUCCESS) + } + private static int getSchemaSize(BidRequest bidRequest) { bidRequest?.ext?.prebid?.floors?.data?.modelGroups[0].schema.fields.size() } diff --git a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy index e1e7750ea05..1243303fa4f 100644 --- a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy @@ -111,6 +111,10 @@ class PBSUtils implements ObjectMapperWrapper { getRandomDecimal(min, max).setScale(scale, HALF_UP) } + static BigDecimal getRandomPrice(BigDecimal min, BigDecimal max = 10, int scale = 3) { + getRandomDecimal(min, max).setScale(scale, HALF_UP) + } + static > T getRandomEnum(Class anEnum, List exclude = []) { def values = anEnum.enumConstants.findAll { !exclude.contains(it) } as T[] values[getRandomNumber(0, values.size() - 1)] From 44426fa55fa1756e0760fff1a142fcf4b74e932a Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Mon, 5 May 2025 11:44:02 +0300 Subject: [PATCH 2/3] Tests: Increase coverage for floors logs tests --- .../PriceFloorsFetchingSpec.groovy | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy index 767445c04ce..4503485ac0d 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy @@ -1628,7 +1628,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def "PBS shouldn't emit error in log and response when floors is not in request and floors fetching disabled for account"() { given: "Account with disabled fetching" - def priceFloors = new AccountPriceFloorsConfig(enabled: false, fetch: new PriceFloorsFetch(enabled: false)) + def priceFloors = new AccountPriceFloorsConfig(enabled: true, fetch: new PriceFloorsFetch(enabled: false)) def accountAuctionConfig = new AccountAuctionConfig(priceFloors: priceFloors) def accountConfig = new AccountConfig(auction: accountAuctionConfig) def account = new Account(uuid: bidRequest.accountId, config: accountConfig) @@ -1669,10 +1669,10 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { bidRequest << [BidRequest.getDefaultBidRequest(), getBidRequestWithFloors().tap { it.ext.prebid.floors = null }] } - def "PBS should emit error in log and response when data is invalid and floors fetching disabled for account and #requestFloors for request"() { + def "PBS should emit error in log and response when data is invalid and floors fetching disabled for account and enabled for request"() { given: "Default BidRequest with empty floors.data" def bidRequest = bidRequestWithFloors.tap { - ext.prebid.floors.enabled = requestFloors + ext.prebid.floors.enabled = true ext.prebid.floors.data = null } @@ -1715,9 +1715,47 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "Alerts.general metrics should be populated" def metrics = floorsPbsService.sendCollectedMetricsRequest() assert metrics[ALERT_GENERAL] == 1 + } + + def "PBS shouldn't emit error in log and response when data is invalid and floors fetching disabled for account and #requestFloors for request"() { + given: "Default BidRequest with empty floors.data" + def bidRequest = bidRequestWithFloors.tap { + ext.prebid.floors.enabled = requestFloors + ext.prebid.floors.data = null + } + + and: "Flush metrics" + flushMetrics(floorsPbsService) + + and: "Account with disabled fetching" + def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { + config.auction.priceFloors.fetch.enabled = false + } + accountDao.save(account) + + and: "Default bid response" + def response = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, response) + + when: "PBS processes auction request" + def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "PBS shouldn't log warning or errors" + assert !bidResponse.ext?.warnings + assert !bidResponse.ext?.errors + + and: "PBS shouldn't log a errors" + def message = 'Price floor rules data must be present' + def logs = floorsPbsService.getLogsByTime(startTime) + def floorsLogs = getLogsByText(logs, FETCHING_DISABLED_ERROR_LOG(bidRequest, message)) + assert !floorsLogs.size() + + and: "Alerts.general metrics shouldn't be populated" + def metrics = floorsPbsService.sendCollectedMetricsRequest() + assert !metrics[ALERT_GENERAL] where: - requestFloors << [null, true] + requestFloors << [null, false] } def "PBS shouldn't emit error in log and response when data is invalid and floors fetching enabled for account"() { From 98d9edb7dbb8f2d2790a073bf866b3f1ef4de7c7 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Mon, 5 May 2025 15:38:33 +0300 Subject: [PATCH 3/3] update after review --- .../model/response/auction/Bid.groovy | 2 +- .../pricefloors/PriceFloorsBaseSpec.groovy | 3 +- .../PriceFloorsFetchingSpec.groovy | 48 +++++++++---------- .../PriceFloorsSignalingSpec.groovy | 8 ---- .../server/functional/util/PBSUtils.groovy | 4 -- 5 files changed, 24 insertions(+), 41 deletions(-) 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 be764c64df4..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 = imp.bidFloor != null ? PBSUtils.getRandomPrice(imp.bidFloor) : 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/pricefloors/PriceFloorsBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy index a8b18661abf..77b1a0f9325 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 @@ -57,8 +57,7 @@ abstract class PriceFloorsBaseSpec extends BaseSpec { "No price floor data for account ${bidRequest.accountId} and " + "request ${bidRequest.id}, reason: ${URL_EMPTY_WARNING_MESSAGE("$BASIC_FETCH_URL$bidRequest.accountId", message)}" } - // Required: Sync no longer logs "in progress"—only "none" or "error" statuses are recorded - protected static final String FETCHING_DISABLED_ERROR = 'Fetching is disabled' + protected static final String FETCHING_DISABLED_ERROR = "Fetching is disabled" protected static final Closure FETCHING_DISABLED_WARNING_MESSAGE = { message -> "$FETCHING_DISABLED_ERROR. Following parsing of request price floors is failed: $message" } diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy index 4503485ac0d..0b379c8b795 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsFetchingSpec.groovy @@ -1,10 +1,6 @@ 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.config.AccountPriceFloorsConfig import org.prebid.server.functional.model.config.PriceFloorsFetch -import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.pricefloors.ModelGroup import org.prebid.server.functional.model.pricefloors.PriceFloorData @@ -1627,20 +1623,20 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { } def "PBS shouldn't emit error in log and response when floors is not in request and floors fetching disabled for account"() { - given: "Account with disabled fetching" - def priceFloors = new AccountPriceFloorsConfig(enabled: true, fetch: new PriceFloorsFetch(enabled: false)) - def accountAuctionConfig = new AccountAuctionConfig(priceFloors: priceFloors) - def accountConfig = new AccountConfig(auction: accountAuctionConfig) - def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + given: "Account with disabled fetching" + def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { + config.auction.priceFloors.fetch.enabled = false + config.auction.priceFloors.fetch.url = null + } accountDao.save(account) - and: "Flush metrics" - flushMetrics(floorsPbsService) - and: "Default bid response" def response = BidResponse.getDefaultBidResponse(bidRequest) bidder.setResponse(bidRequest.id, response) + and: "Flush metrics" + flushMetrics(floorsPbsService) + when: "PBS processes auction request" def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1669,16 +1665,13 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { bidRequest << [BidRequest.getDefaultBidRequest(), getBidRequestWithFloors().tap { it.ext.prebid.floors = null }] } - def "PBS should emit error in log and response when data is invalid and floors fetching disabled for account and enabled for request"() { + def "PBS should emit error in log and response when floor data is empty and floors fetching disabled for account and enabled for request"() { given: "Default BidRequest with empty floors.data" def bidRequest = bidRequestWithFloors.tap { ext.prebid.floors.enabled = true ext.prebid.floors.data = null } - and: "Flush metrics" - flushMetrics(floorsPbsService) - and: "Account with disabled fetching" def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { config.auction.priceFloors.fetch.enabled = false @@ -1689,6 +1682,9 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def response = BidResponse.getDefaultBidResponse(bidRequest) bidder.setResponse(bidRequest.id, response) + and: "Flush metrics" + flushMetrics(floorsPbsService) + when: "PBS processes auction request" def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1717,16 +1713,13 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { assert metrics[ALERT_GENERAL] == 1 } - def "PBS shouldn't emit error in log and response when data is invalid and floors fetching disabled for account and #requestFloors for request"() { + def "PBS shouldn't emit error in log and response when floor data is empty and floors fetching disabled for account and #requestFloors for request"() { given: "Default BidRequest with empty floors.data" def bidRequest = bidRequestWithFloors.tap { ext.prebid.floors.enabled = requestFloors ext.prebid.floors.data = null } - and: "Flush metrics" - flushMetrics(floorsPbsService) - and: "Account with disabled fetching" def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { config.auction.priceFloors.fetch.enabled = false @@ -1737,6 +1730,9 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def response = BidResponse.getDefaultBidResponse(bidRequest) bidder.setResponse(bidRequest.id, response) + and: "Flush metrics" + flushMetrics(floorsPbsService) + when: "PBS processes auction request" def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1772,13 +1768,13 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { } accountDao.save(account) - and: "Flush metrics" - flushMetrics(floorsPbsService) - and: "Default bid response" def response = BidResponse.getDefaultBidResponse(bidRequest) bidder.setResponse(bidRequest.id, response) + and: "Flush metrics" + flushMetrics(floorsPbsService) + when: "PBS processes auction request" def bidResponse = floorsPbsService.sendAuctionRequest(bidRequest) @@ -1814,9 +1810,6 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { ext.prebid.floors.data = null } - and: "Flush metrics" - flushMetrics(floorsPbsService) - and: "Account with disabled fetching" def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { config.auction.priceFloors.enabled = false @@ -1831,6 +1824,9 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def response = BidResponse.getDefaultBidResponse(bidRequest) bidder.setResponse(bidRequest.id, response) + and: "Flush metrics" + flushMetrics(floorsPbsService) + when: "PBS processes auction request" def bidResponse = floorsPbsService.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 d31c8223822..335502bd7fc 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 @@ -1135,14 +1135,6 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { assert metrics[ALERT_GENERAL] == 1 } - def "PBS shouldn't emit error or warning when floors for account configured correctly"() { - given: "Bid request without floors" - def bidRequest = BidRequest.defaultBidRequest - - and: "PBS fetch rules from floors provider" - cacheFloorsProviderRules(bidRequest, floorsPbsService, GENERIC, SUCCESS) - } - private static int getSchemaSize(BidRequest bidRequest) { bidRequest?.ext?.prebid?.floors?.data?.modelGroups[0].schema.fields.size() } diff --git a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy index 1243303fa4f..e1e7750ea05 100644 --- a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy @@ -111,10 +111,6 @@ class PBSUtils implements ObjectMapperWrapper { getRandomDecimal(min, max).setScale(scale, HALF_UP) } - static BigDecimal getRandomPrice(BigDecimal min, BigDecimal max = 10, int scale = 3) { - getRandomDecimal(min, max).setScale(scale, HALF_UP) - } - static > T getRandomEnum(Class anEnum, List exclude = []) { def values = anEnum.enumConstants.findAll { !exclude.contains(it) } as T[] values[getRandomNumber(0, values.size() - 1)]