diff --git a/src/test/groovy/org/prebid/server/functional/model/privacy/Metric.groovy b/src/test/groovy/org/prebid/server/functional/model/privacy/Metric.groovy index 490c1456e53..80d779e069d 100644 --- a/src/test/groovy/org/prebid/server/functional/model/privacy/Metric.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/privacy/Metric.groovy @@ -7,7 +7,6 @@ import org.prebid.server.functional.model.request.setuid.SetuidRequest enum Metric { - ALERT_GENERAL("alerts.general"), PROCESSED_ACTIVITY_RULES_COUNT("requests.activity.processedrules.count"), ACCOUNT_PROCESSED_RULES_COUNT("requests.activity.processedrules.count"), TEMPLATE_ADAPTER_DISALLOWED_COUNT("adapter.{bidderName}.activity.{activityType}.disallowed.count"), diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy index 22e29b76908..127ed32bfd9 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy @@ -60,7 +60,7 @@ class Bid implements ObjectMapperWrapper { new Bid().tap { id = UUID.randomUUID() impid = imp.id - price = PBSUtils.getRandomPrice() + price = imp.bidFloor != null ? imp.bidFloor : PBSUtils.getRandomPrice() crid = 1 height = imp.banner && imp.banner.format ? imp.banner.format.first().height : null weight = imp.banner && imp.banner.format ? imp.banner.format.first().weight : null diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsBaseSpec.groovy index 4e1cbbf349b..a604264b264 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 @@ -37,14 +37,32 @@ abstract class PriceFloorsBaseSpec extends BaseSpec { public static final BigDecimal FLOOR_MAX = 2 public static final Map FLOORS_CONFIG = ["price-floors.enabled": "true"] + protected static final FloorsProvider floorsProvider = new FloorsProvider(networkServiceContainer) + protected static final String BASIC_FETCH_URL = networkServiceContainer.rootUri + FloorsProvider.FLOORS_ENDPOINT protected static final int MAX_MODEL_WEIGHT = 100 + 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 String FETCHING_DISABLED_ERROR = "Fetching is disabled" + protected static final Closure PRICE_FLOORS_ERROR_LOG = { bidRequest, reason, warningMessage -> + "Price Floors can't be resolved for account ${bidRequest.accountId} and request ${bidRequest.id}, reason: ${PRICE_FLOORS_WARNING_MESSAGE(reason, warningMessage)}" + } + protected static final Closure WARNING_MESSAGE = { message -> + "Price floors processing failed: parsing of request price floors is failed: $message" + } + protected static final Closure FETCHING_FLOORS_ERROR_LOG = { bidRequest, warningMessage -> + "Price floor fetching failed for account ${bidRequest.accountId}: ${URL_EMPTY_ERROR("$BASIC_FETCH_URL${bidRequest.accountId}")}, with a reason: $warningMessage" + } + private static final Closure PRICE_FLOORS_WARNING_MESSAGE = { reason, details -> + "Price floors processing failed: $reason. Following parsing of request price floors is failed: $details" + } private static final int DEFAULT_MODEL_WEIGHT = 1 private static final int CURRENCY_CONVERSION_PRECISION = 3 private static final int FLOOR_VALUE_PRECISION = 4 - protected static final FloorsProvider floorsProvider = new FloorsProvider(networkServiceContainer) protected final PrebidServerService floorsPbsService = pbsServiceFactory.getService(FLOORS_CONFIG + GENERIC_ALIAS_CONFIG) def setupSpec() { 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..b8cbd595f2e 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 @@ -23,6 +23,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 +46,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"() { @@ -138,8 +130,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, bidRequest.accountId) assert floorsLogs.size() == 1 - assert floorsLogs.first().contains("No price floor data for account $bidRequest.accountId and request $bidRequest.id, " + - "reason: Malformed fetch.url 'null' passed") + assert floorsLogs.first().contains("alformed fetch.url: 'null' passed for account $bidRequest.accountId") and: "PBS floors validation failure should not reject the entire auction" assert !response.seatbid.isEmpty() @@ -543,7 +534,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def startTime = Instant.now() and: "Default BidRequest" - def bidRequest = BidRequest.defaultBidRequest + def bidRequest = BidRequest.getDefaultBidRequest() and: "Account with enabled fetch and fetch.url in the DB" def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { @@ -575,12 +566,16 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "PBS should fetch data" assert floorsProvider.getRequestCount(bidRequest.accountId) == 1 + and: "PBS should not add warning or errors" + assert !response.ext.warnings + assert !response.ext.errors + and: "PBS log should contain error" def logs = floorsPbsService.getLogsByTime(startTime) def message = "Price floor data useFetchDataRate must be in range(0-100), but was $accounntUseFetchDataRate" def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, message)) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, message)) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -608,6 +603,90 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { ] } + def "PBS should log merged error and increase metrics when useFetchDataRate have invalid value from provider and request"() { + given: "BidRequest with invalid floors" + def requestUseFetchDataRate = PBSUtils.getRandomNegativeNumber() + def bidRequest = getBidRequestWithFloors().tap { + it.ext.prebid.floors.data.useFetchDataRate = requestUseFetchDataRate + } + + and: "Account with enabled fetch and fetch.url in the DB" + def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { + config.auction.priceFloors.useDynamicData = true + } + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(floorsPbsService) + + and: "Set Floors Provider response" + def providerUseFetchDataRate = PBSUtils.getRandomNegativeNumber() + def floorValue = PBSUtils.randomFloorValue + def floorsResponse = PriceFloorData.priceFloorData.tap { + modelGroups[0].values = [(rule): floorValue] + useFetchDataRate = providerUseFetchDataRate + } + floorsProvider.setResponse(bidRequest.accountId, floorsResponse) + + 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, ERROR) + + and: "Test start time" + def startTime = Instant.now() + + when: "PBS processes auction request" + def response = floorsPbsService.sendAuctionRequest(bidRequest) + + then: "metric should be updated" + def metrics = floorsPbsService.sendCollectedMetricsRequest() + assert metrics[FETCH_FAILURE_METRIC] == 1 + + and: "PBS should add single warning" + assert response.ext?.warnings[PREBID]*.code == [999] + assert response.ext?.warnings[PREBID]*.message == + [WARNING_MESSAGE("Price floor data useFetchDataRate must be in range(0-100), but was $requestUseFetchDataRate")] + + and: "PBS should not add error to request" + assert !response.ext.errors + + and: "PBS should fetch data" + assert floorsProvider.getRequestCount(bidRequest.accountId) == 1 + + and: "PBS log should contain combined error log" + def logs = floorsPbsService.getLogsByTime(startTime) + def fetchingErrorLogs = getLogsByText(logs, "Price Floors can't be resolved for account $bidRequest.accountId " + + "and request $bidRequest.id, reason: Price floors processing failed: Failed to fetch price floor from provider " + + "for fetch.url '$BASIC_FETCH_URL${bidRequest.accountId}', with a reason: " + + "Price floor data useFetchDataRate must be in range(0-100), but was $providerUseFetchDataRate. " + + "Following parsing of request price floors is failed: " + + "Price floor data useFetchDataRate must be in range(0-100), but was $requestUseFetchDataRate") + assert fetchingErrorLogs.size() == 1 + + and: "Floors validation failure cannot reject the entire auction" + assert !response.seatbid?.isEmpty() + + and: "Bidder request should contain floors data from floors provider" + def bidderRequest = bidder.getBidderRequests(bidRequest.id).last + verifyAll(bidderRequest) { + imp.bidFloor == bidRequest.imp.bidFloor + imp.bidFloorCur == bidRequest.imp.bidFloorCur + + !imp[0].ext?.prebid?.floors?.floorRule + !imp[0].ext?.prebid?.floors?.floorRuleValue + !imp[0].ext?.prebid?.floors?.floorValue + + !ext?.prebid?.floors?.floorProvider + !ext?.prebid?.floors?.skipRate + !ext?.prebid?.floors?.data + ext?.prebid?.floors?.location == NO_DATA + ext?.prebid?.floors?.fetchStatus == ERROR + } + } + def "PBS should process floors from request when use-dynamic-data = false"() { given: "Pbs with PF configuration with useDynamicData" def defaultAccountConfigSettings = defaultAccountConfigSettings.tap { @@ -680,7 +759,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, message)) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, message)) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -722,7 +801,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, message)) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, message)) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -763,7 +842,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, message)) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, message)) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -807,7 +886,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, message)) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, message)) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -850,7 +929,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, PRICE_FLOOR_VALUES_MISSING)) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, PRICE_FLOOR_VALUES_MISSING)) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -897,7 +976,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, message)) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, message)) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -942,10 +1021,9 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { and: "PBS log should contain error" def logs = pbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, BASIC_FETCH_URL) + def floorsLogs = getLogsByText(logs, "Price floor fetching failed for account $bidRequest.accountId: " + + "Fetch price floor request timeout for fetch.url '$BASIC_FETCH_URL$accountId' exceeded") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains("No price floor data for account $bidRequest.accountId and request $bidRequest.id, " + - "reason: Fetch price floor request timeout for fetch.url '$BASIC_FETCH_URL$accountId' exceeded") and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -991,7 +1069,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, message)) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, message)) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -1309,6 +1387,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) @@ -1336,6 +1418,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) @@ -1363,6 +1449,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) @@ -1393,6 +1483,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) @@ -1431,6 +1525,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) @@ -1469,6 +1567,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) @@ -1508,6 +1610,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) @@ -1547,6 +1653,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) @@ -1581,6 +1691,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) @@ -1594,6 +1708,226 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { assert response.ext?.warnings[PREBID]*.message == [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 account = getAccountWithEnabledFetch(bidRequest.accountId).tap { + config.auction.priceFloors.fetch.enabled = false + config.auction.priceFloors.fetch.url = null + } + accountDao.save(account) + + 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) + + 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, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, 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 == NONE + + and: "PBS bidderRequest status should be in progress" + def bidderRequest = bidder.getBidderRequests(bidRequest.id) + assert bidderRequest.ext.prebid.floors.fetchStatus == [NONE] + + 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 floor data is empty and floors fetching disabled for account and #requestFloorEnabled for request"() { + given: "Default BidRequest with empty floors.data" + def bidRequest = bidRequestWithFloors.tap { + it.ext.prebid.floors.enabled = requestFloorEnabled + it.ext.prebid.floors.data = null + } + + and: "Account with disabled fetching" + def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { + it.config.auction.priceFloors.fetch.enabled = false + } + accountDao.save(account) + + 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) + + 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 not add errors" + assert !bidResponse.ext.errors + + and: "PBS should log a errors" + def logs = floorsPbsService.getLogsByTime(startTime) + def floorsLogs = getLogsByText(logs, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, 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: + requestFloorEnabled << [null, true] + } + + def "PBS shouldn't emit error in log and response when floor data is empty and floors fetching disabled for account and floors disabled for request"() { + given: "Default BidRequest with empty floors.data" + def bidRequest = bidRequestWithFloors.tap { + it.ext.prebid.floors.enabled = false + it.ext.prebid.floors.data = null + } + + and: "Account with disabled fetching" + def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { + it.config.auction.priceFloors.fetch.enabled = false + } + accountDao.save(account) + + 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) + + 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, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, message)) + assert !floorsLogs.size() + + and: "Alerts.general metrics shouldn't be populated" + def metrics = floorsPbsService.sendCollectedMetricsRequest() + assert !metrics[ALERT_GENERAL] + } + + 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 = requestEnabledFloors + ext.prebid.floors.data = null + } + + and: "Account with enabled fetching" + def account = getAccountWithEnabledFetch(bidRequest.accountId) + accountDao.save(account) + + 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) + + 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, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, 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: + requestEnabledFloors << [null, true] + } + + def "PBS shouldn't emit error in log and response when data is invalid and floors disabled for account"() { + given: "Default BidRequest with empty floors.data" + def bidRequest = bidRequestWithFloors.tap { + ext.prebid.floors.data = null + } + + 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) + + and: "Flush metrics" + flushMetrics(floorsPbsService) + + 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"() { given: "PBS with PF configuration with minMaxAgeSec" def pbsService = pbsServiceFactory.getService(FLOORS_CONFIG + ["price-floors.minMaxAgeSec": "3", @@ -1691,7 +2025,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, MODEL_WEIGHT_INVALID.formatted(invalidModelWeight))) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, MODEL_WEIGHT_INVALID.formatted(invalidModelWeight))) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -1749,7 +2083,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, message)) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, message)) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -1806,7 +2140,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, SKIP_RATE_INVALID.formatted(invalidSkipRate))) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, SKIP_RATE_INVALID.formatted(invalidSkipRate))) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() @@ -1864,7 +2198,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec { def logs = floorsPbsService.getLogsByTime(startTime) def floorsLogs = getLogsByText(logs, "$BASIC_FETCH_URL$bidRequest.accountId") assert floorsLogs.size() == 1 - assert floorsLogs[0].contains(URL_EMPTY_ERROR_LOG(bidRequest, message)) + assert floorsLogs[0].contains(FETCHING_FLOORS_ERROR_LOG(bidRequest, message)) and: "Floors validation failure cannot reject the entire auction" assert !response.seatbid?.isEmpty() 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..7d430e37f78 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, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, 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) @@ -628,6 +637,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) @@ -671,6 +684,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) @@ -681,7 +698,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, message)) assert floorsLogs.size() == 1 and: "Metrics should be updated" @@ -720,6 +737,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) @@ -730,7 +751,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "PBS shouldn't log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, message)) assert floorsLogs.size() == 1 and: "Metrics should be updated" @@ -757,6 +778,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) @@ -767,7 +792,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, 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) @@ -808,7 +837,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, 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) @@ -877,8 +910,9 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { 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)}") + def floorsLogs = getLogsByText(logs, "Price Floors can't be resolved for account $ampRequest.account and " + + "request $ampStoredRequest.id, reason: Price floors processing failed: Fetching is disabled. " + + "Following parsing of request price floors is failed: $message") assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -891,84 +925,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,6 +937,10 @@ 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] @@ -988,7 +948,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -1008,6 +968,10 @@ 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) @@ -1018,7 +982,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -1040,6 +1004,10 @@ 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) @@ -1050,7 +1018,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -1073,6 +1041,10 @@ 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) @@ -1083,7 +1055,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "PBS should log an errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -1104,6 +1076,10 @@ 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) @@ -1114,7 +1090,7 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, ERROR_LOG(bidRequest, message)) + def floorsLogs = getLogsByText(logs, PRICE_FLOORS_ERROR_LOG(bidRequest, FETCHING_DISABLED_ERROR, message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" @@ -1122,36 +1098,11 @@ 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 + def bidRequest = getBidRequestWithFloors().tap { + ext.prebid.floors.data = null + } and: "Account with disabled dynamic data" def account = getAccountWithEnabledFetch(bidRequest.accountId).tap { @@ -1162,6 +1113,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) @@ -1169,13 +1124,13 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec { def response = floorsPbsService.sendAuctionRequest(bidRequest) then: "PBS should log a warning" + def message = "Price floor rules data must be present" assert response.ext?.warnings[PREBID]*.code == [999] - assert response.ext?.warnings[PREBID]*.message == ['Using dynamic data is not allowed'] + assert response.ext?.warnings[PREBID]*.message == [WARNING_MESSAGE(message)] and: "PBS should log a errors" def logs = floorsPbsService.getLogsByTime(startTime) - def floorsLogs = getLogsByText(logs, "No price floor data for account ${bidRequest.accountId} " + - "and request ${bidRequest.id}, reason: Using dynamic data is not allowed") + def floorsLogs = getLogsByText(logs, PRICE_FLOORS_ERROR_LOG(bidRequest, 'Using dynamic data is not allowed', message)) assert floorsLogs.size() == 1 and: "Alerts.general metrics should be populated" diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppFetchBidActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppFetchBidActivitiesSpec.groovy index 46634fda48f..2dd15697102 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppFetchBidActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppFetchBidActivitiesSpec.groovy @@ -60,7 +60,6 @@ import static org.prebid.server.functional.model.pricefloors.Country.USA import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ACCOUNT_DISALLOWED_COUNT import static org.prebid.server.functional.model.privacy.Metric.ACCOUNT_PROCESSED_RULES_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ADAPTER_DISALLOWED_COUNT -import static org.prebid.server.functional.model.privacy.Metric.ALERT_GENERAL import static org.prebid.server.functional.model.privacy.Metric.PROCESSED_ACTIVITY_RULES_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_REQUEST_DISALLOWED_COUNT import static org.prebid.server.functional.model.request.GppSectionId.USP_V1 @@ -694,7 +693,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 where: gppAccountsConfig << [[new AccountGppConfig(code: IAB_US_GENERAL, config: new GppModuleConfig(skipSids: [US_NAT_V1]), enabled: false), @@ -868,7 +867,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS auction call when custom privacy regulation with normalizing should ignore call to bidder"() { @@ -1425,7 +1424,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS amp call when privacy module contain invalid property should respond with an error"() { @@ -1624,7 +1623,7 @@ class GppFetchBidActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS amp call when custom privacy regulation with normalizing should ignore call to bidder"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy index 657e0fef1e5..973d6bde700 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy @@ -59,7 +59,6 @@ import static org.prebid.server.functional.model.config.UsNationalPrivacySection import static org.prebid.server.functional.model.pricefloors.Country.CAN import static org.prebid.server.functional.model.pricefloors.Country.USA import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ADAPTER_DISALLOWED_COUNT -import static org.prebid.server.functional.model.privacy.Metric.ALERT_GENERAL import static org.prebid.server.functional.model.privacy.Metric.PROCESSED_ACTIVITY_RULES_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_REQUEST_DISALLOWED_COUNT import static org.prebid.server.functional.model.request.GppSectionId.USP_V1 @@ -657,7 +656,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS cookie sync call when privacy module contain invalid code should include proper responded with bidders URLs"() { @@ -817,7 +816,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS cookie sync when custom privacy regulation with normalizing should exclude bidders URLs"() { @@ -1550,7 +1549,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS setuid request call when privacy module contain invalid code should respond with valid bidders UIDs cookies"() { @@ -1725,7 +1724,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS setuid call when custom privacy regulation with normalizing should reject bidders with status code invalidStatusCode"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy index edd720700ca..0efdb70fd7d 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy @@ -59,7 +59,6 @@ import static org.prebid.server.functional.model.pricefloors.Country.USA import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ACCOUNT_DISALLOWED_COUNT import static org.prebid.server.functional.model.privacy.Metric.ACCOUNT_PROCESSED_RULES_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ADAPTER_DISALLOWED_COUNT -import static org.prebid.server.functional.model.privacy.Metric.ALERT_GENERAL import static org.prebid.server.functional.model.privacy.Metric.PROCESSED_ACTIVITY_RULES_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_REQUEST_DISALLOWED_COUNT import static org.prebid.server.functional.model.request.GppSectionId.USP_V1 @@ -1023,7 +1022,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS auction call when privacy module contain invalid property should respond with an error"() { @@ -1185,7 +1184,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS auction call when custom privacy regulation with normalizing that match custom config should have empty EIDS fields"() { @@ -2097,7 +2096,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS amp call when privacy module contain invalid property should respond with an error"() { @@ -2295,7 +2294,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS amp call when custom privacy regulation with normalizing should change request consent and call to bidder"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitPreciseGeoActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitPreciseGeoActivitiesSpec.groovy index c0c9495bfc3..4707633d01d 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitPreciseGeoActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitPreciseGeoActivitiesSpec.groovy @@ -58,7 +58,6 @@ import static org.prebid.server.functional.model.pricefloors.Country.USA import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ACCOUNT_DISALLOWED_COUNT import static org.prebid.server.functional.model.privacy.Metric.ACCOUNT_PROCESSED_RULES_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ADAPTER_DISALLOWED_COUNT -import static org.prebid.server.functional.model.privacy.Metric.ALERT_GENERAL import static org.prebid.server.functional.model.privacy.Metric.PROCESSED_ACTIVITY_RULES_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_REQUEST_DISALLOWED_COUNT import static org.prebid.server.functional.model.request.GppSectionId.USP_V1 @@ -1359,7 +1358,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS auction call when privacy module contain invalid code should respond with an error"() { @@ -1573,7 +1572,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS auction call when custom privacy regulation with normalizing should change request consent and call to bidder"() { @@ -2613,7 +2612,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS amp call when privacy module contain invalid code should respond with an error"() { @@ -2863,7 +2862,7 @@ class GppTransmitPreciseGeoActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS amp call when custom privacy regulation with normalizing should change request consent and call to bidder"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy index 0eaafecc6f0..d33681cc395 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy @@ -63,7 +63,6 @@ import static org.prebid.server.functional.model.pricefloors.Country.USA import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ACCOUNT_DISALLOWED_COUNT import static org.prebid.server.functional.model.privacy.Metric.ACCOUNT_PROCESSED_RULES_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ADAPTER_DISALLOWED_COUNT -import static org.prebid.server.functional.model.privacy.Metric.ALERT_GENERAL import static org.prebid.server.functional.model.privacy.Metric.PROCESSED_ACTIVITY_RULES_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_REQUEST_DISALLOWED_COUNT import static org.prebid.server.functional.model.request.GppSectionId.USP_V1 @@ -1378,7 +1377,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS auction call when privacy module contain invalid property should respond with an error"() { @@ -1573,7 +1572,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS auction call when custom privacy regulation with normalizing that match custom config should have empty UFPD fields"() { @@ -2748,7 +2747,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS amp call when privacy module contain invalid property should respond with an error"() { @@ -2979,7 +2978,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() - assert metrics[ALERT_GENERAL.getValue()] == 1 + assert metrics[ALERT_GENERAL] == 1 } def "PBS amp call when custom privacy regulation with normalizing should change request consent and call to bidder"() {