values = modelGroup.getValues();
+ if (MapUtils.isEmpty(values)) {
+ throw new PreBidException(String.format("Price floor rules values can't be null or empty, but were %s",
+ values));
+ }
+
+ if (maxRules != null && values.size() > maxRules) {
+ throw new PreBidException(String.format("Price floor rules number %s exceeded its maximum number %s",
+ values.size(), maxRules));
+ }
+ }
+}
diff --git a/src/main/java/org/prebid/server/floors/model/PriceFloorRules.java b/src/main/java/org/prebid/server/floors/model/PriceFloorRules.java
index 993de9c39e1..ccc5e05bb5e 100644
--- a/src/main/java/org/prebid/server/floors/model/PriceFloorRules.java
+++ b/src/main/java/org/prebid/server/floors/model/PriceFloorRules.java
@@ -7,22 +7,10 @@
import java.math.BigDecimal;
-/**
- * This model is a trade-off.
- *
- * It defines both:
- * 1. The contract for prebid server bidrequest.ext.prebid.floors field.
- * 2. The contract for floors provider (assuming prebid server specific fields will not be overridden).
- *
- * To make things better, it should be divided in two separate models:
- * for prebid request and floors provider.
- */
@Value
@Builder(toBuilder = true)
public class PriceFloorRules {
- // prebid server and floors provider fields
-
@JsonProperty("floorMin")
BigDecimal floorMin;
@@ -38,8 +26,6 @@ public class PriceFloorRules {
PriceFloorData data;
- // prebid server specific fields
-
Boolean enabled;
@JsonProperty("fetchStatus")
diff --git a/src/main/java/org/prebid/server/floors/proto/FetchResult.java b/src/main/java/org/prebid/server/floors/proto/FetchResult.java
index 774f7e23932..36c4fda58e0 100644
--- a/src/main/java/org/prebid/server/floors/proto/FetchResult.java
+++ b/src/main/java/org/prebid/server/floors/proto/FetchResult.java
@@ -1,12 +1,12 @@
package org.prebid.server.floors.proto;
import lombok.Value;
-import org.prebid.server.floors.model.PriceFloorRules;
+import org.prebid.server.floors.model.PriceFloorData;
@Value(staticConstructor = "of")
public class FetchResult {
- PriceFloorRules rules;
+ PriceFloorData rulesData;
FetchStatus fetchStatus;
}
diff --git a/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java b/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java
index 6647b2ef09f..fc4fb11a9a2 100644
--- a/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java
+++ b/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java
@@ -17,16 +17,19 @@
public class EnrichingApplicationSettings implements ApplicationSettings {
+ private final boolean enforceValidAccount;
private final ApplicationSettings delegate;
private final PriceFloorsConfigResolver priceFloorsConfigResolver;
private final JsonMerger jsonMerger;
private final Account defaultAccount;
- public EnrichingApplicationSettings(String defaultAccountConfig,
+ public EnrichingApplicationSettings(boolean enforceValidAccount,
+ String defaultAccountConfig,
ApplicationSettings delegate,
PriceFloorsConfigResolver priceFloorsConfigResolver,
JsonMerger jsonMerger) {
+ this.enforceValidAccount = enforceValidAccount;
this.delegate = Objects.requireNonNull(delegate);
this.jsonMerger = Objects.requireNonNull(jsonMerger);
this.priceFloorsConfigResolver = Objects.requireNonNull(priceFloorsConfigResolver);
@@ -42,10 +45,13 @@ public Future getAccountById(String accountId, Timeout timeout) {
if (defaultAccount == null) {
return accountFuture;
}
+ final Future mergedWithDefaultAccount = accountFuture
+ .map(this::mergeAccounts);
- return accountFuture
- .map(this::mergeAccounts)
- .otherwise(mergeAccounts(Account.empty(accountId)));
+ // In case of invalid account return failed future
+ return enforceValidAccount
+ ? mergedWithDefaultAccount
+ : mergedWithDefaultAccount.otherwise(mergeAccounts(Account.empty(accountId)));
}
@Override
diff --git a/src/main/java/org/prebid/server/spring/config/PriceFloorsConfiguration.java b/src/main/java/org/prebid/server/spring/config/PriceFloorsConfiguration.java
index 4e881101264..20f9a9f81cf 100644
--- a/src/main/java/org/prebid/server/spring/config/PriceFloorsConfiguration.java
+++ b/src/main/java/org/prebid/server/spring/config/PriceFloorsConfiguration.java
@@ -79,10 +79,9 @@ PriceFloorResolver noOpPriceFloorResolver() {
@ConditionalOnProperty(prefix = "price-floors", name = "enabled", havingValue = "true")
PriceFloorProcessor basicPriceFloorProcessor(PriceFloorFetcher floorFetcher,
PriceFloorResolver floorResolver,
- CurrencyConversionService conversionService,
JacksonMapper mapper) {
- return new BasicPriceFloorProcessor(floorFetcher, floorResolver, conversionService, mapper);
+ return new BasicPriceFloorProcessor(floorFetcher, floorResolver, mapper);
}
@Bean
diff --git a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java
index 72ceea87e01..7ef4ae5fbdd 100644
--- a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java
+++ b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java
@@ -340,12 +340,14 @@ static class EnrichingSettingsConfiguration {
@Bean
EnrichingApplicationSettings enrichingApplicationSettings(
+ @Value("${settings.enforce-valid-account}") boolean enforceValidAccount,
@Value("${settings.default-account-config:#{null}}") String defaultAccountConfig,
CompositeApplicationSettings compositeApplicationSettings,
PriceFloorsConfigResolver priceFloorsConfigResolver,
JsonMerger jsonMerger) {
- return new EnrichingApplicationSettings(defaultAccountConfig,
+ return new EnrichingApplicationSettings(enforceValidAccount,
+ defaultAccountConfig,
compositeApplicationSettings,
priceFloorsConfigResolver,
jsonMerger);
diff --git a/src/main/java/org/prebid/server/spring/config/bidder/AaxConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/AaxConfiguration.java
new file mode 100644
index 00000000000..4a232c39bec
--- /dev/null
+++ b/src/main/java/org/prebid/server/spring/config/bidder/AaxConfiguration.java
@@ -0,0 +1,47 @@
+package org.prebid.server.spring.config.bidder;
+
+import org.prebid.server.bidder.BidderDeps;
+import org.prebid.server.bidder.GenericBidder;
+import org.prebid.server.json.JacksonMapper;
+import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
+import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
+import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
+import org.prebid.server.spring.env.YamlPropertySourceFactory;
+import org.prebid.server.util.HttpUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+import javax.validation.constraints.NotBlank;
+
+@Configuration
+@PropertySource(value = "classpath:/bidder-config/aax.yaml", factory = YamlPropertySourceFactory.class)
+public class AaxConfiguration {
+
+ private static final String BIDDER_NAME = "aax";
+ private static final String EXTERNAL_URL_MACRO = "{{PREBID_SERVER_ENDPOINT}}";
+
+ @Bean("aaxConfigurationProperties")
+ @ConfigurationProperties("adapters.aax")
+ BidderConfigurationProperties configurationProperties() {
+ return new BidderConfigurationProperties();
+ }
+
+ @Bean
+ BidderDeps aaxBidderDeps(BidderConfigurationProperties aaxConfigurationProperties,
+ @NotBlank @Value("${external-url}") String externalUrl,
+ JacksonMapper mapper) {
+
+ return BidderDepsAssembler.forBidder(BIDDER_NAME)
+ .withConfig(aaxConfigurationProperties)
+ .usersyncerCreator(UsersyncerCreator.create(externalUrl))
+ .bidderCreator(config -> new GenericBidder(resolveEndpoint(config.getEndpoint(), externalUrl), mapper))
+ .assemble();
+ }
+
+ private String resolveEndpoint(String configEndpoint, String externalUrl) {
+ return configEndpoint.replace(EXTERNAL_URL_MACRO, HttpUtil.encodeUrl(externalUrl));
+ }
+}
diff --git a/src/main/resources/bidder-config/aax.yaml b/src/main/resources/bidder-config/aax.yaml
new file mode 100644
index 00000000000..e6bda296d94
--- /dev/null
+++ b/src/main/resources/bidder-config/aax.yaml
@@ -0,0 +1,21 @@
+adapters:
+ aax:
+ endpoint: https://prebid.aaxads.com/rtb/pb/aax-prebid?src={{PREBID_SERVER_ENDPOINT}}
+ meta-info:
+ maintainer-email: product@aax.media
+ app-media-types:
+ - banner
+ - video
+ - native
+ site-media-types:
+ - banner
+ - video
+ - native
+ supported-vendors:
+ vendor-id: 720
+ usersync:
+ url: https://c.aaxads.com/aacxc.php?fv=1&wbsh=psa&ryvlg=setstatuscode&redirect=
+ redirect-url: /setuid?bidder=aax&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&uid=
+ cookie-family-name: aax
+ type: redirect
+ support-cors: false
diff --git a/src/main/resources/static/bidder-params/aax.json b/src/main/resources/static/bidder-params/aax.json
new file mode 100644
index 00000000000..83cdfc59406
--- /dev/null
+++ b/src/main/resources/static/bidder-params/aax.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Aax Adapter Params",
+ "description": "A schema which validates params accepted by the Aax adapter",
+ "type": "object",
+ "properties": {
+ "cid": {
+ "type": "string",
+ "description": "The customer id provided by AAX."
+ },
+ "crid": {
+ "type": "string",
+ "description": "The placement id provided by AAX."
+ }
+ },
+ "required": [
+ "cid",
+ "crid"
+ ]
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy
index f344c203618..3d88cb51248 100644
--- a/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy
@@ -18,4 +18,8 @@ class AccountConfig {
AccountAnalyticsConfig analytics
AccountCookieSyncConfig cookieSync
AccountHooksConfiguration hooks
+
+ static getDefaultAccountConfig() {
+ new AccountConfig(status: AccountStatus.ACTIVE)
+ }
}
diff --git a/src/test/groovy/org/prebid/server/functional/model/mock/services/floorsprovider/PriceFloorEndpoint.groovy b/src/test/groovy/org/prebid/server/functional/model/mock/services/floorsprovider/PriceFloorEndpoint.groovy
deleted file mode 100644
index 6f99c65502a..00000000000
--- a/src/test/groovy/org/prebid/server/functional/model/mock/services/floorsprovider/PriceFloorEndpoint.groovy
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.prebid.server.functional.model.mock.services.floorsprovider
-
-import groovy.transform.ToString
-
-@ToString(includeNames = true, ignoreNulls = true)
-class PriceFloorEndpoint {
-
- String url
-}
diff --git a/src/test/groovy/org/prebid/server/functional/model/mock/services/floorsprovider/PriceFloorRules.groovy b/src/test/groovy/org/prebid/server/functional/model/mock/services/floorsprovider/PriceFloorRules.groovy
deleted file mode 100644
index ff3af553ded..00000000000
--- a/src/test/groovy/org/prebid/server/functional/model/mock/services/floorsprovider/PriceFloorRules.groovy
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.prebid.server.functional.model.mock.services.floorsprovider
-
-import groovy.transform.ToString
-import org.prebid.server.functional.model.ResponseModel
-import org.prebid.server.functional.model.pricefloors.PriceFloorData
-import org.prebid.server.functional.model.pricefloors.PriceFloorEnforcement
-import org.prebid.server.functional.util.PBSUtils
-
-import static org.prebid.server.functional.tests.pricefloors.PriceFloorsBaseSpec.FLOOR_MIN
-
-@ToString(includeNames = true, ignoreNulls = true)
-class PriceFloorRules implements ResponseModel {
-
- BigDecimal floorMin
- String floorProvider
- PriceFloorEnforcement enforcement
- Integer skipRate
- PriceFloorEndpoint endpoint
- PriceFloorData data
-
- static PriceFloorRules getPriceFloorRules() {
- new PriceFloorRules(floorMin: FLOOR_MIN,
- floorProvider: PBSUtils.randomString,
- data: PriceFloorData.priceFloorData)
- }
-}
diff --git a/src/test/groovy/org/prebid/server/functional/model/pricefloors/PriceFloorData.groovy b/src/test/groovy/org/prebid/server/functional/model/pricefloors/PriceFloorData.groovy
index 4498bb3e30d..b9af0cd1dc7 100644
--- a/src/test/groovy/org/prebid/server/functional/model/pricefloors/PriceFloorData.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/pricefloors/PriceFloorData.groovy
@@ -3,13 +3,14 @@ package org.prebid.server.functional.model.pricefloors
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import org.prebid.server.functional.model.Currency
+import org.prebid.server.functional.model.ResponseModel
import org.prebid.server.functional.util.PBSUtils
import static org.prebid.server.functional.model.Currency.USD
@EqualsAndHashCode
@ToString(includeNames = true, ignoreNulls = true)
-class PriceFloorData {
+class PriceFloorData implements ResponseModel {
String floorProvider
Currency currency
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy
index acae8b3e586..4ce40176d72 100644
--- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy
@@ -44,7 +44,7 @@ class Imp {
}
}
- private static Imp getDefaultImp() {
+ private static Imp getDefaultImp() {
new Imp().tap {
id = UUID.randomUUID()
ext = ImpExt.defaultImpExt
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Video.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Video.groovy
index de67c5b3664..e009b5a1741 100644
--- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Video.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Video.groovy
@@ -32,6 +32,6 @@ class Video {
List companiontype
static Video getDefaultVideo() {
- new Video(mimes: ["video/mp4"], w: 300, h: 200)
+ new Video(mimes: ["video/mp4"], w: 300, h: 200)
}
}
diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/FloorsProvider.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/FloorsProvider.groovy
index 935a20b80a7..00bbf9c3fe4 100644
--- a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/FloorsProvider.groovy
+++ b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/FloorsProvider.groovy
@@ -4,7 +4,7 @@ import org.mockserver.matchers.TimeToLive
import org.mockserver.matchers.Times
import org.mockserver.model.HttpRequest
import org.mockserver.model.HttpResponse
-import org.prebid.server.functional.model.mock.services.floorsprovider.PriceFloorRules
+import org.prebid.server.functional.model.pricefloors.PriceFloorData
import org.prebid.server.functional.util.ObjectMapperWrapper
import org.testcontainers.containers.MockServerContainer
@@ -39,6 +39,6 @@ class FloorsProvider extends NetworkScaffolding {
}
private String getDefaultResponse() {
- mapper.encode(PriceFloorRules.priceFloorRules)
+ mapper.encode(PriceFloorData.priceFloorData)
}
}
diff --git a/src/test/groovy/org/prebid/server/functional/tests/AccountSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AccountSpec.groovy
new file mode 100644
index 00000000000..925a2f78794
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/tests/AccountSpec.groovy
@@ -0,0 +1,256 @@
+package org.prebid.server.functional.tests
+
+import org.prebid.server.functional.model.AccountStatus
+import org.prebid.server.functional.model.config.AccountConfig
+import org.prebid.server.functional.model.db.Account
+import org.prebid.server.functional.model.db.StoredRequest
+import org.prebid.server.functional.model.request.amp.AmpRequest
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.model.request.auction.Site
+import org.prebid.server.functional.service.PrebidServerException
+import org.prebid.server.functional.util.PBSUtils
+
+import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED
+
+class AccountSpec extends BaseSpec {
+
+ def "PBS should reject request with inactive account"() {
+ given: "Pbs config with enforce-valid-account and default-account-config"
+ def pbsService = pbsServiceFactory.getService(
+ ["settings.enforce-valid-account": enforceValidAccount as String])
+
+ and: "Inactive account id"
+ def accountId = PBSUtils.randomNumber
+ def account = new Account(uuid: accountId, config: new AccountConfig(status: AccountStatus.INACTIVE))
+ accountDao.save(account)
+
+ and: "Default basic BidRequest with inactive account id"
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ site.publisher.id = accountId
+ }
+
+ when: "PBS processes auction request"
+ pbsService.sendAuctionRequest(bidRequest)
+
+ then: "PBS should reject the entire auction"
+ def exception = thrown(PrebidServerException)
+ assert exception.statusCode == UNAUTHORIZED.code()
+ assert exception.responseBody == "Account $accountId is inactive"
+
+ where:
+ enforceValidAccount << [true, false]
+ }
+
+ def "PBS should reject request with unknown account when settings.enforce-valid-account = true"() {
+ given: "Pbs config with enforce-valid-account and default-account-config"
+ def pbsService = pbsServiceFactory.getService(
+ ["settings.enforce-valid-account" : "true",
+ "settings.default-account-config": mapper.encode(defaultAccountConfig)])
+
+ and: "Non-existing account id"
+ def accountId = PBSUtils.randomNumber
+
+ and: "Default basic BidRequest with non-existing account id"
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ site.publisher.id = accountId
+ }
+
+ when: "PBS processes auction request"
+ pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Request should fail with an error"
+ def exception = thrown(PrebidServerException)
+ assert exception.statusCode == UNAUTHORIZED.code()
+ assert exception.responseBody == "Unauthorized account id: $accountId"
+
+ where:
+ defaultAccountConfig << [null, AccountConfig.defaultAccountConfig]
+ }
+
+ def "PBS should reject request without account when settings.enforce-valid-account = true"() {
+ given: "Pbs config with enforce-valid-account and default-account-config"
+ def pbsService = pbsServiceFactory.getService(
+ ["settings.enforce-valid-account" : "true",
+ "settings.default-account-config": mapper.encode(defaultAccountConfig)])
+
+ and: "Default basic BidRequest without account"
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ site.publisher.id = null
+ }
+
+ when: "PBS processes auction request"
+ pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Request should fail with an error"
+ def exception = thrown(PrebidServerException)
+ assert exception.statusCode == UNAUTHORIZED.code()
+ assert exception.responseBody == "Unauthorized account id: "
+
+ where:
+ defaultAccountConfig << [null, AccountConfig.defaultAccountConfig]
+ }
+
+ def "PBS should not reject request with unknown account when settings.enforce-valid-account = false"() {
+ given: "Pbs config with enforce-valid-account and default-account-config"
+ def pbsService = pbsServiceFactory.getService(
+ ["settings.enforce-valid-account" : "false",
+ "settings.default-account-config": mapper.encode(defaultAccountConfig)])
+
+ and: "Default basic BidRequest with non-existing account id"
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ site.publisher.id = accountId
+ }
+
+ when: "PBS processes auction request"
+ def response = pbsService.sendAuctionRequest(bidRequest)
+
+ then: "PBS should not reject the entire auction"
+ assert !response.seatbid?.isEmpty()
+
+ where:
+ defaultAccountConfig || accountId
+ null || null
+ null || PBSUtils.randomNumber
+ AccountConfig.defaultAccountConfig || null
+ AccountConfig.defaultAccountConfig || PBSUtils.randomNumber
+ }
+
+ def "PBS AMP should reject request with unknown account when settings.enforce-valid-account = true"() {
+ given: "Pbs config with enforce-valid-account and default-account-config"
+ def pbsService = pbsServiceFactory.getService(
+ ["settings.enforce-valid-account" : "true",
+ "settings.default-account-config": mapper.encode(defaultAccountConfig)])
+
+ and: "Default AMP request with non-existing account"
+ def ampRequest = AmpRequest.defaultAmpRequest.tap {
+ account = requestAccount
+ }
+
+ and: "Default stored request with non-existing account"
+ def ampStoredRequest = BidRequest.defaultStoredRequest.tap {
+ site = Site.defaultSite
+ site.publisher.id = storedRequestAccount
+ }
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ when: "PBS processes amp request"
+ pbsService.sendAmpRequest(ampRequest)
+
+ then: "Request should fail with an error"
+ def exception = thrown(PrebidServerException)
+ def resolvedAccount = requestAccount ?: storedRequestAccount
+ assert exception.statusCode == UNAUTHORIZED.code()
+ assert exception.responseBody == "Unauthorized account id: $resolvedAccount"
+
+ where:
+ defaultAccountConfig || requestAccount || storedRequestAccount
+ null || PBSUtils.randomNumber || null
+ null || null || PBSUtils.randomNumber
+ AccountConfig.defaultAccountConfig || PBSUtils.randomNumber || null
+ AccountConfig.defaultAccountConfig || null || PBSUtils.randomNumber
+ }
+
+ def "PBS AMP should reject request without account when settings.enforce-valid-account = true"() {
+ given: "Pbs config with enforce-valid-account and default-account-config"
+ def pbsService = pbsServiceFactory.getService(
+ ["settings.enforce-valid-account" : "true",
+ "settings.default-account-config": mapper.encode(defaultAccountConfig)])
+
+ and: "Default AMP request without account"
+ def ampRequest = AmpRequest.defaultAmpRequest.tap {
+ account = null
+ }
+
+ and: "Default stored request without account"
+ def ampStoredRequest = BidRequest.defaultStoredRequest.tap {
+ site = Site.defaultSite
+ site.publisher.id = null
+ }
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ when: "PBS processes amp request"
+ pbsService.sendAmpRequest(ampRequest)
+
+ then: "Request should fail with an error"
+ def exception = thrown(PrebidServerException)
+ assert exception.statusCode == UNAUTHORIZED.code()
+ assert exception.responseBody == "Unauthorized account id: "
+
+ where:
+ defaultAccountConfig << [null, AccountConfig.defaultAccountConfig]
+ }
+
+ def "PBS AMP should not reject request with unknown account when settings.enforce-valid-account = false"() {
+ given: "Pbs config with enforce-valid-account and default-account-config"
+ def pbsService = pbsServiceFactory.getService(
+ ["settings.enforce-valid-account" : "false",
+ "settings.default-account-config": mapper.encode(defaultAccountConfig)])
+
+ and: "Default AMP request with non-existing account"
+ def ampRequest = AmpRequest.defaultAmpRequest.tap {
+ account = requestAccount
+ }
+
+ and: "Default stored request with non-existing account"
+ def ampStoredRequest = BidRequest.defaultStoredRequest.tap {
+ site = Site.defaultSite
+ site.publisher.id = storedRequestAccount
+ }
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ when: "PBS processes amp request"
+ def response = pbsService.sendAmpRequest(ampRequest)
+
+ then: "PBS should not reject request"
+ assert response.targeting
+ assert response.ext?.debug?.httpcalls
+
+ where:
+ defaultAccountConfig || requestAccount || storedRequestAccount
+ null || PBSUtils.randomNumber || null
+ null || null || PBSUtils.randomNumber
+ AccountConfig.defaultAccountConfig || PBSUtils.randomNumber || null
+ AccountConfig.defaultAccountConfig || null || PBSUtils.randomNumber
+ }
+
+ def "PBS AMP should not reject request without account when settings.enforce-valid-account = false"() {
+ given: "Pbs config with enforce-valid-account and default-account-config"
+ def pbsService = pbsServiceFactory.getService(
+ ["settings.enforce-valid-account" : "false",
+ "settings.default-account-config": mapper.encode(defaultAccountConfig)])
+
+ and: "Default AMP request without account"
+ def ampRequest = AmpRequest.defaultAmpRequest.tap {
+ account = null
+ }
+
+ and: "Default stored request without account"
+ def ampStoredRequest = BidRequest.defaultStoredRequest.tap {
+ site = Site.defaultSite
+ site.publisher.id = null
+ }
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ when: "PBS processes amp request"
+ def response = pbsService.sendAmpRequest(ampRequest)
+
+ then: "PBS should not reject request"
+ assert response.targeting
+ assert response.ext?.debug?.httpcalls
+
+ where:
+ defaultAccountConfig << [null, AccountConfig.defaultAccountConfig]
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy
index f80a88d7183..239e6a690e3 100644
--- a/src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy
+++ b/src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy
@@ -54,8 +54,8 @@ abstract class BaseSpec extends Specification {
PBSUtils.getRandomNumber(MIN_TIMEOUT, MAX_TIMEOUT)
}
- protected static Number getCurrentMetricValue(String name) {
- def response = defaultPbsService.sendCollectedMetricsRequest()
+ protected static Number getCurrentMetricValue(PrebidServerService pbsService = defaultPbsService, String name) {
+ def response = pbsService.sendCollectedMetricsRequest()
response[name] ?: 0
}
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 edc6953fbfd..14d5058a688 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
@@ -26,6 +26,7 @@ import org.prebid.server.functional.util.PBSUtils
import java.math.RoundingMode
import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE
+import static org.prebid.server.functional.model.request.auction.FetchStatus.INPROGRESS
@PBSTest
abstract class PriceFloorsBaseSpec extends BaseSpec {
@@ -39,8 +40,8 @@ abstract class PriceFloorsBaseSpec extends BaseSpec {
FloorsProvider.FLOORS_ENDPOINT
protected static final FloorsProvider floorsProvider = new FloorsProvider(Dependencies.networkServiceContainer, Dependencies.objectMapperWrapper)
+ protected static final int MAX_MODEL_WEIGHT = 100
private static final int DEFAULT_MODEL_WEIGHT = 1
- private static final int MAX_MODEL_WEIGHT = 1000000
private static final int CURRENCY_CONVERSION_PRECISION = 3
private static final int FLOOR_VALUE_PRECISION = 4
@@ -113,8 +114,9 @@ abstract class PriceFloorsBaseSpec extends BaseSpec {
}
protected void cacheFloorsProviderRules(PrebidServerService pbsService = floorsPbsService, BidRequest bidRequest) {
- pbsService.sendAuctionRequest(bidRequest)
- Thread.sleep(1000)
+ PBSUtils.waitUntil({ pbsService.sendAuctionRequest(bidRequest).ext?.debug?.resolvedRequest?.ext?.prebid?.floors?.fetchStatus != INPROGRESS },
+ 5000,
+ 1000)
}
protected void cacheFloorsProviderRules(PrebidServerService pbsService = floorsPbsService,
diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsCurrencySpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsCurrencySpec.groovy
index e9e4a52165e..be81051b597 100644
--- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsCurrencySpec.groovy
+++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsCurrencySpec.groovy
@@ -1,7 +1,7 @@
package org.prebid.server.functional.tests.pricefloors
import org.prebid.server.functional.model.Currency
-import org.prebid.server.functional.model.mock.services.floorsprovider.PriceFloorRules
+import org.prebid.server.functional.model.pricefloors.PriceFloorData
import org.prebid.server.functional.model.request.auction.BidRequest
import org.prebid.server.functional.model.response.auction.Bid
import org.prebid.server.functional.model.response.auction.BidResponse
@@ -33,10 +33,9 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- floorMin = floorValue
- data.modelGroups[0].values = [(rule): floorValue]
- data.modelGroups[0].currency = USD
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
+ modelGroups[0].currency = USD
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -50,7 +49,7 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec {
def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
verifyAll(bidderRequest) {
imp[0].bidFloor == floorValue
- imp[0].bidFloorCur == floorsResponse.data.modelGroups[0].currency
+ imp[0].bidFloorCur == floorsResponse.modelGroups[0].currency
}
}
@@ -66,10 +65,9 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response with a currency different from the request.cur"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- floorMin = floorValue
- data.modelGroups[0].values = [(rule): floorValue]
- data.modelGroups[0].currency = USD
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
+ modelGroups[0].currency = USD
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -78,7 +76,7 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec {
and: "Bid response with 2 bids: price < floorMin, price = floorMin"
def convertedMinFloorValue = getPriceAfterCurrencyConversion(floorValue,
- floorsResponse.data.modelGroups[0].currency, bidRequest.cur[0])
+ floorsResponse.modelGroups[0].currency, bidRequest.cur[0])
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap {
cur = EUR
seatbid.first().bid << Bid.getDefaultBid(bidRequest.imp.first())
@@ -116,9 +114,9 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec {
def convertedMinFloorValue = getPriceAfterCurrencyConversion(floorMin,
bidRequest.ext.prebid.floors.floorMinCur, floorProviderCur)
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): convertedMinFloorValue - 0.1]
- data.modelGroups[0].currency = floorProviderCur
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): convertedMinFloorValue - 0.1]
+ modelGroups[0].currency = floorProviderCur
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -160,15 +158,18 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response with a currency different from the floorMinCur"
def floorsProviderCur = EUR
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): PBSUtils.randomFloorValue]
- data.modelGroups[0].currency = floorsProviderCur
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): PBSUtils.randomFloorValue]
+ modelGroups[0].currency = floorsProviderCur
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
and: "PBS fetch rules from floors provider"
cacheFloorsProviderRules(pbsService, bidRequest)
+ and: "Flush metrics"
+ flushMetrics(pbsService)
+
when: "PBS processes auction request"
def response = pbsService.sendAuctionRequest(bidRequest)
@@ -176,7 +177,7 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec {
assert response.ext?.errors[ErrorType.GENERIC]*.code == [999]
assert response.ext?.errors[ErrorType.GENERIC]*.message ==
["Unable to convert from currency $bidRequest.ext.prebid.floors.floorMinCur to desired ad server" +
- " currency ${floorsResponse.data.modelGroups[0].currency}" as String]
+ " currency ${floorsResponse.modelGroups[0].currency}" as String]
and: "PBS should log a warning"
assert response.ext?.warnings[PREBID]*.code == [999]
@@ -185,8 +186,7 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec {
"to convert from currency $requestFloorCur to desired ad server currency $floorsProviderCur" as String]
and: "Metric #GENERAL_ERROR_METRIC should be update"
- def metrics = pbsService.sendCollectedMetricsRequest()
- assert metrics[GENERAL_ERROR_METRIC] == 1
+ assert getCurrentMetricValue(pbsService, GENERAL_ERROR_METRIC) == 1
and: "Bidder request should contain bidFloor, bidFloorCur from request"
def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
@@ -254,10 +254,9 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response with a currency different from the request.cur"
def floorValue = PBSUtils.randomFloorValue
def floorCur = USD
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- floorMin = floorValue
- data.modelGroups[0].values = [(rule): floorValue]
- data.modelGroups[0].currency = floorCur
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
+ modelGroups[0].currency = floorCur
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -310,15 +309,18 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response with a currency different from the floorMinCur"
def floorsProviderCur = BOGUS
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): PBSUtils.randomFloorValue]
- data.modelGroups[0].currency = floorsProviderCur
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): PBSUtils.randomFloorValue]
+ modelGroups[0].currency = floorsProviderCur
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
and: "PBS fetch rules from floors provider"
cacheFloorsProviderRules(bidRequest)
+ and: "Flush metrics"
+ flushMetrics(floorsPbsService)
+
when: "PBS processes auction request"
def response = floorsPbsService.sendAuctionRequest(bidRequest)
@@ -329,8 +331,7 @@ class PriceFloorsCurrencySpec extends PriceFloorsBaseSpec {
"to convert from currency $requestFloorCur to desired ad server currency $floorsProviderCur" as String]
and: "Metric #GENERAL_ERROR_METRIC should be update"
- def metrics = floorsPbsService.sendCollectedMetricsRequest()
- assert metrics[GENERAL_ERROR_METRIC] == 1
+ assert getCurrentMetricValue(floorsPbsService, GENERAL_ERROR_METRIC) == 1
and: "Bidder request should contain bidFloor, bidFloorCur from request"
def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsEnforcementSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsEnforcementSpec.groovy
index f5c5a1ff711..bf6e6624de5 100644
--- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsEnforcementSpec.groovy
+++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsEnforcementSpec.groovy
@@ -3,8 +3,7 @@ package org.prebid.server.functional.tests.pricefloors
import org.prebid.server.functional.model.Currency
import org.prebid.server.functional.model.bidder.Generic
import org.prebid.server.functional.model.db.StoredRequest
-import org.prebid.server.functional.model.mock.services.floorsprovider.PriceFloorRules
-import org.prebid.server.functional.model.pricefloors.PriceFloorEnforcement
+import org.prebid.server.functional.model.pricefloors.PriceFloorData
import org.prebid.server.functional.model.request.amp.AmpRequest
import org.prebid.server.functional.model.request.auction.BidAdjustmentFactors
import org.prebid.server.functional.model.request.auction.BidRequest
@@ -46,8 +45,8 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(ampRequest.account as String, floorsResponse)
@@ -79,7 +78,7 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
}
and: "PBS should log warning about bid suppression"
- assert response.ext?.warnings[ErrorType.GENERIC_ALIAS]*.code == [999]
+ assert response.ext?.warnings[ErrorType.GENERIC_ALIAS]*.code == [6]
assert response.ext?.warnings[ErrorType.GENERIC_ALIAS]*.message ==
["Bid with id '${aliasBidResponse.seatbid[0].bid[0].id}' was rejected by floor enforcement: " +
"price $lowerPrice is below the floor $floorValue" as String]
@@ -103,8 +102,8 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -128,7 +127,7 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
assert response.seatbid?.first()?.bid?.collect { it.price } == [floorValue]
and: "PBS should log warning about suppression all bids below the floor value "
- assert response.ext?.warnings[ErrorType.GENERIC]*.code == [999, 999]
+ assert response.ext?.warnings[ErrorType.GENERIC]*.code == [6, 6]
assert response.ext?.warnings[ErrorType.GENERIC]*.message ==
["Bid with id '${bidResponse.seatbid[0].bid[1].id}' was rejected by floor enforcement: " +
"price ${bidResponse.seatbid[0].bid[1].price} is below the floor $floorValue" as String,
@@ -143,6 +142,7 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
given: "Default BidRequest"
def bidRequest = BidRequest.defaultBidRequest.tap {
ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: 2)]
+ ext.prebid.floors = new ExtPrebidFloors(enforcement: new ExtPrebidPriceFloorEnforcement(enforcePbs: false))
}
and: "Account with enabled fetch, fetch.url in the DB"
@@ -151,9 +151,8 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
- enforcement = new PriceFloorEnforcement(enforcePbs: false)
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -231,6 +230,7 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
and: "Default basic BidRequest with generic bidder with preferdeals = true"
def bidRequest = BidRequest.defaultBidRequest.tap {
ext.prebid.targeting = new Targeting(preferdeals: true)
+ ext.prebid.floors = new ExtPrebidFloors(enforcement: new ExtPrebidPriceFloorEnforcement(floorDeals: true))
}
and: "Account with enabled fetch, fetch.url,enforceDealFloors in the DB"
@@ -241,9 +241,8 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
- enforcement = new PriceFloorEnforcement(floorDeals: true)
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -280,6 +279,7 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
and: "Default basic BidRequest with generic bidder with preferdeals = true"
def bidRequest = BidRequest.defaultBidRequest.tap {
ext.prebid.targeting = new Targeting(preferdeals: true)
+ ext.prebid.floors = new ExtPrebidFloors(enforcement: new ExtPrebidPriceFloorEnforcement(floorDeals: floorDeals, enforcePbs: enforcePbs))
}
and: "Account with enabled fetch, fetch.url, enforceDealFloors in the DB"
@@ -290,9 +290,8 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
- enforcement = new PriceFloorEnforcement(floorDeals: floorDeals, enforcePbs: enforcePbs)
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -347,9 +346,8 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
- enforcement = new PriceFloorEnforcement(floorDeals: true)
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -403,9 +401,8 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
- enforcement = new PriceFloorEnforcement(floorDeals: true)
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -452,8 +449,8 @@ class PriceFloorsEnforcementSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
and: "Set Floors Provider response"
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(bidRequest.app.publisher.id, floorsResponse)
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 9781999348f..3eed5329d8c 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
@@ -2,8 +2,8 @@ package org.prebid.server.functional.tests.pricefloors
import org.prebid.server.functional.model.config.PriceFloorsFetch
import org.prebid.server.functional.model.db.StoredRequest
-import org.prebid.server.functional.model.mock.services.floorsprovider.PriceFloorRules
import org.prebid.server.functional.model.pricefloors.ModelGroup
+import org.prebid.server.functional.model.pricefloors.PriceFloorData
import org.prebid.server.functional.model.pricefloors.Rule
import org.prebid.server.functional.model.request.amp.AmpRequest
import org.prebid.server.functional.model.request.auction.BidRequest
@@ -15,23 +15,29 @@ import org.prebid.server.functional.util.PBSUtils
import java.time.Instant
import static org.mockserver.model.HttpStatusCode.BAD_REQUEST_400
-import static org.prebid.server.functional.model.Currency.USD
+import static org.prebid.server.functional.model.Currency.EUR
+import static org.prebid.server.functional.model.Currency.JPY
import static org.prebid.server.functional.model.pricefloors.Country.MULTIPLE
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.FetchStatus.ERROR
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
import static org.prebid.server.functional.model.request.auction.Location.REQUEST
+import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID
class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
- private static final int maxEnforceFloorsRate = 100
-
+ private static final int MAX_ENFORCE_FLOORS_RATE = 100
private static final int DEFAULT_MAX_AGE_SEC = 600
private static final int DEFAULT_PERIOD_SEC = 300
private static final int MIN_TIMEOUT_MS = 10
private static final int MAX_TIMEOUT_MS = 10000
+ private static final int MIN_SKIP_RATE = 0
+ private static final int MAX_SKIP_RATE = 100
+ private static final int MIN_DEFAULT_FLOOR_VALUE = 0
+ private static final int MIN_FLOOR_MIN = 0
private static final Closure INVALID_CONFIG_METRIC = { account -> "alerts.account_config.${account}.price-floors" }
private static final String FETCH_FAILURE_METRIC = "price-floors.fetch.failure"
@@ -51,7 +57,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
cacheFloorsProviderRules(pbsService, bidRequest)
when: "PBS processes auction request"
- floorsPbsService.sendAuctionRequest(bidRequest)
+ pbsService.sendAuctionRequest(bidRequest)
then: "PBS should fetch data"
assert floorsProvider.getRequestCount(bidRequest.app.publisher.id) == 1
@@ -256,7 +262,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
assert !response.seatbid?.isEmpty()
where:
- enforceFloorsRate << [PBSUtils.randomNegativeNumber, maxEnforceFloorsRate + 1]
+ enforceFloorsRate << [PBSUtils.randomNegativeNumber, MAX_ENFORCE_FLOORS_RATE + 1]
}
def "PBS should fetch data from provider when price-floors.fetch.enabled = true in account config"() {
@@ -330,8 +336,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(bidRequest.app.publisher.id, floorsResponse)
@@ -527,8 +533,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
and: "Set Floors Provider response without modelGroups"
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups = null
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups = null
}
floorsProvider.setResponse(accountId, floorsResponse)
@@ -569,8 +575,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
and: "Set Floors Provider response without rules"
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = null
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = null
}
floorsProvider.setResponse(accountId, floorsResponse)
@@ -614,8 +620,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
and: "Set Floors Provider response with 2 rules"
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values.put(new Rule(mediaType: BANNER, country: MULTIPLE).rule, 0.7)
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values.put(new Rule(mediaType: BANNER, country: MULTIPLE).rule, 0.7)
}
floorsProvider.setResponse(accountId, floorsResponse)
@@ -694,14 +700,14 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Account with maxFileSizeKb in the DB"
def accountId = bidRequest.app.publisher.id
- def maxSize = PBSUtils.getRandomNumber(0, 10)
+ def maxSize = PBSUtils.getRandomNumber(1, 10)
def account = getAccountWithEnabledFetch(accountId).tap {
config.auction.priceFloors.fetch.maxFileSizeKb = maxSize
}
accountDao.save(account)
and: "Set Floors Provider response with Content-Length"
- def floorsResponse = PriceFloorRules.priceFloorRules
+ def floorsResponse = PriceFloorData.priceFloorData
def responseSize = convertKilobyteSizeToByte(maxSize) + 100
floorsProvider.setResponse(accountId, floorsResponse, ["Content-Length": responseSize as String])
@@ -764,7 +770,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
ext?.prebid?.floors?.location == REQUEST
ext?.prebid?.floors?.fetchStatus == NONE
ext?.prebid?.floors?.floorMin == storedRequestModel.ext.prebid.floors.floorMin
- ext?.prebid?.floors?.floorProvider == storedRequestModel.ext.prebid.floors.floorProvider
+ ext?.prebid?.floors?.floorProvider == storedRequestModel.ext.prebid.floors.data.floorProvider
ext?.prebid?.floors?.data == storedRequestModel.ext.prebid.floors.data
}
@@ -807,7 +813,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
ext?.prebid?.floors?.location == REQUEST
ext?.prebid?.floors?.fetchStatus == NONE
ext?.prebid?.floors?.floorMin == bidRequest.ext.prebid.floors.floorMin
- ext?.prebid?.floors?.floorProvider == bidRequest.ext.prebid.floors.floorProvider
+ ext?.prebid?.floors?.floorProvider == bidRequest.ext.prebid.floors.data.floorProvider
ext?.prebid?.floors?.data == bidRequest.ext.prebid.floors.data
}
}
@@ -846,7 +852,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
ext?.prebid?.floors?.location == REQUEST
ext?.prebid?.floors?.fetchStatus == NONE
ext?.prebid?.floors?.floorMin == ampStoredRequest.ext.prebid.floors.floorMin
- ext?.prebid?.floors?.floorProvider == ampStoredRequest.ext.prebid.floors.floorProvider
+ ext?.prebid?.floors?.floorProvider == ampStoredRequest.ext.prebid.floors.data.floorProvider
ext?.prebid?.floors?.data == ampStoredRequest.ext.prebid.floors.data
}
}
@@ -855,6 +861,7 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
given: "BidRequest with storedRequest"
def bidRequest = bidRequestWithFloors.tap {
ext.prebid.storedRequest = new PrebidStoredRequest(id: PBSUtils.randomNumber)
+ ext.prebid.floors.floorMin = FLOOR_MIN
}
and: "Default stored request with floors"
@@ -870,8 +877,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -882,19 +889,19 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
verifyAll(bidderRequest) {
imp[0].bidFloor == floorValue
- imp[0].bidFloorCur == floorsResponse.data.modelGroups[0].currency
+ imp[0].bidFloorCur == floorsResponse.modelGroups[0].currency
- imp[0].ext?.prebid?.floors?.floorRule == floorsResponse.data.modelGroups[0].values.keySet()[0]
+ imp[0].ext?.prebid?.floors?.floorRule == floorsResponse.modelGroups[0].values.keySet()[0]
imp[0].ext?.prebid?.floors?.floorRuleValue == floorValue
imp[0].ext?.prebid?.floors?.floorValue == floorValue
ext?.prebid?.floors?.location == FETCH
ext?.prebid?.floors?.fetchStatus == SUCCESS
- ext?.prebid?.floors?.floorMin == floorsResponse.floorMin
+ ext?.prebid?.floors?.floorMin == bidRequest.ext.prebid.floors.floorMin
ext?.prebid?.floors?.floorProvider == floorsResponse.floorProvider
ext?.prebid?.floors?.skipRate == floorsResponse.skipRate
- ext?.prebid?.floors?.data == floorsResponse.data
+ ext?.prebid?.floors?.data == floorsResponse
}
}
@@ -903,7 +910,9 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
def ampRequest = AmpRequest.defaultAmpRequest
and: "Default stored request with floors "
- def ampStoredRequest = storedRequestWithFloors
+ def ampStoredRequest = storedRequestWithFloors.tap {
+ ext.prebid.floors.floorMin = FLOOR_MIN
+ }
def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
storedRequestDao.save(storedRequest)
@@ -913,8 +922,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(ampRequest.account as String, floorsResponse)
@@ -925,19 +934,19 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
def bidderRequest = bidder.getBidderRequests(ampStoredRequest.id).last()
verifyAll(bidderRequest) {
imp[0].bidFloor == floorValue
- imp[0].bidFloorCur == floorsResponse.data.modelGroups[0].currency
+ imp[0].bidFloorCur == floorsResponse.modelGroups[0].currency
- imp[0].ext?.prebid?.floors?.floorRule == floorsResponse.data.modelGroups[0].values.keySet()[0]
+ imp[0].ext?.prebid?.floors?.floorRule == floorsResponse.modelGroups[0].values.keySet()[0]
imp[0].ext?.prebid?.floors?.floorRuleValue == floorValue
imp[0].ext?.prebid?.floors?.floorValue == floorValue
ext?.prebid?.floors?.location == FETCH
ext?.prebid?.floors?.fetchStatus == SUCCESS
- ext?.prebid?.floors?.floorMin == floorsResponse.floorMin
+ ext?.prebid?.floors?.floorMin == ampStoredRequest.ext.prebid.floors.floorMin
ext?.prebid?.floors?.floorProvider == floorsResponse.floorProvider
ext?.prebid?.floors?.skipRate == floorsResponse.skipRate
- ext?.prebid?.floors?.data == floorsResponse.data
+ ext?.prebid?.floors?.data == floorsResponse
}
}
@@ -968,8 +977,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
where:
description | floorsResponse
- "valid" | PriceFloorRules.priceFloorRules
- "invalid" | PriceFloorRules.priceFloorRules.tap { data.modelGroups = null }
+ "valid" | PriceFloorData.priceFloorData
+ "invalid" | PriceFloorData.priceFloorData.tap { modelGroups = null }
}
def "PBS should continue to hold onto previously fetched rules when fetch.enabled = false in account config"() {
@@ -989,8 +998,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider #description response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(bidRequest.app.publisher.id, floorsResponse)
@@ -1012,29 +1021,115 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
verifyAll(bidderRequest) {
imp[0].bidFloor == floorValue
- imp[0].bidFloorCur == floorsResponse.data.modelGroups[0].currency
- imp[0].ext?.prebid?.floors?.floorRule == floorsResponse.data.modelGroups[0].values.keySet()[0]
+ imp[0].bidFloorCur == floorsResponse.modelGroups[0].currency
+ imp[0].ext?.prebid?.floors?.floorRule == floorsResponse.modelGroups[0].values.keySet()[0]
imp[0].ext?.prebid?.floors?.floorRuleValue == floorValue
imp[0].ext?.prebid?.floors?.floorValue == floorValue
ext?.prebid?.floors?.location == FETCH
ext?.prebid?.floors?.fetchStatus == SUCCESS
- ext?.prebid?.floors?.floorMin == floorsResponse.floorMin
+ !ext?.prebid?.floors?.floorMin
ext?.prebid?.floors?.floorProvider == floorsResponse.floorProvider
ext?.prebid?.floors?.skipRate == floorsResponse.skipRate
- ext?.prebid?.floors?.data == floorsResponse.data
+ ext?.prebid?.floors?.data == floorsResponse
+ }
+ }
+
+ def "PBS should validate rules from request when floorMin from request is invalid"() {
+ given: "Default BidRequest with floorMin"
+ def floorValue = PBSUtils.randomFloorValue
+ def invalidFloorMin = MIN_FLOOR_MIN - 1
+ def bidRequest = bidRequestWithFloors.tap {
+ imp[0].bidFloor = floorValue
+ ext.prebid.floors.floorMin = invalidFloorMin
+ }
+
+ and: "Account with disabled fetch in the DB"
+ def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id).tap {
+ config.auction.priceFloors.fetch.enabled = false
}
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request bidFloor should correspond to request.imp.bidFloor"
+ def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
+ assert bidderRequest.imp[0].bidFloor == floorValue
+
+ and: "Response should contain error"
+ assert response.ext?.errors[PREBID]*.code == [999]
+ assert response.ext?.errors[PREBID]*.message ==
+ ["Failed to parse price floors from request, with a reason : Price floor floorMin " +
+ "must be positive float, but was $invalidFloorMin "]
+ }
+
+ def "PBS should validate rules from request when request doesn't contain modelGroups"() {
+ given: "Default BidRequest without modelGroups"
+ def floorValue = PBSUtils.randomFloorValue
+ def bidRequest = bidRequestWithFloors.tap {
+ imp[0].bidFloor = floorValue
+ ext.prebid.floors.data.modelGroups = null
+ }
+
+ and: "Account with disabled fetch in the DB"
+ def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id).tap {
+ config.auction.priceFloors.fetch.enabled = false
+ }
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request bidFloor should correspond to request.imp.bidFloor"
+ def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
+ assert bidderRequest.imp[0].bidFloor == floorValue
+
+ and: "Response should contain error"
+ assert response.ext?.errors[PREBID]*.code == [999]
+ assert response.ext?.errors[PREBID]*.message ==
+ ["Failed to parse price floors from request, with a reason : Price floor rules " +
+ "should contain at least one model group "]
+ }
+
+ def "PBS should validate rules from request when request doesn't contain values"() {
+ given: "Default BidRequest without rules"
+ def floorValue = PBSUtils.randomFloorValue
+ def bidRequest = bidRequestWithFloors.tap {
+ imp[0].bidFloor = floorValue
+ ext.prebid.floors.data.modelGroups[0].values = null
+ }
+
+ and: "Account with disabled fetch in the DB"
+ def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id).tap {
+ config.auction.priceFloors.fetch.enabled = false
+ }
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request bidFloor should correspond to request.imp.bidFloor"
+ def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
+ assert bidderRequest.imp[0].bidFloor == floorValue
+
+ and: "Response should contain error"
+ assert response.ext?.errors[PREBID]*.code == [999]
+ assert response.ext?.errors[PREBID]*.message ==
+ ["Failed to parse price floors from request, with a reason : Price floor rules values " +
+ "can't be null or empty, but were null "]
}
def "PBS should validate rules from request when modelWeight from request is invalid"() {
given: "Default BidRequest with floors"
def floorValue = PBSUtils.randomFloorValue
def bidRequest = bidRequestWithFloors.tap {
+ imp[0].bidFloor = floorValue
ext.prebid.floors.data.modelGroups << ModelGroup.modelGroup
ext.prebid.floors.data.modelGroups.first().values = [(rule): floorValue + 0.1]
ext.prebid.floors.data.modelGroups.first().modelWeight = invalidModelWeight
- ext.prebid.floors.data.modelGroups.last().values = [(rule): floorValue]
+ ext.prebid.floors.data.modelGroups.last().values = [(rule): floorValue + 0.2]
ext.prebid.floors.data.modelGroups.last().modelWeight = modelWeight
}
@@ -1045,14 +1140,19 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
when: "PBS processes auction request"
- floorsPbsService.sendAuctionRequest(bidRequest)
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
- then: "Bidder request bidFloor should correspond to valid modelGroup"
+ then: "Bidder request bidFloor should correspond to request.imp.bidFloor"
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
assert bidderRequest.imp[0].bidFloor == floorValue
+ and: "Response should contain error"
+ assert response.ext?.errors[PREBID]*.code == [999]
+ assert response.ext?.errors[PREBID]*.message ==
+ ["Failed to parse price floors from request, with a reason : Price floor modelGroup modelWeight " +
+ "must be in range(1-100), but was $invalidModelWeight "]
where:
- invalidModelWeight << [0, -1, 1000000]
+ invalidModelWeight << [0, MAX_MODEL_WEIGHT + 1]
}
def "PBS should validate rules from amp request when modelWeight from request is invalid"() {
@@ -1062,10 +1162,11 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Default stored request with floors"
def floorValue = PBSUtils.randomFloorValue
def ampStoredRequest = storedRequestWithFloors.tap {
+ imp[0].bidFloor = floorValue
ext.prebid.floors.data.modelGroups << ModelGroup.modelGroup
ext.prebid.floors.data.modelGroups.first().values = [(rule): floorValue + 0.1]
ext.prebid.floors.data.modelGroups.first().modelWeight = invalidModelWeight
- ext.prebid.floors.data.modelGroups.last().values = [(rule): floorValue]
+ ext.prebid.floors.data.modelGroups.last().values = [(rule): floorValue + 0.2]
ext.prebid.floors.data.modelGroups.last().modelWeight = modelWeight
}
def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
@@ -1078,14 +1179,173 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
when: "PBS processes auction request"
- floorsPbsService.sendAmpRequest(ampRequest)
+ def response = floorsPbsService.sendAmpRequest(ampRequest)
then: "Bidder request bidFloor should correspond to valid modelGroup"
- def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id)
+ def bidderRequest = bidder.getBidderRequests(ampStoredRequest.id).last()
+ assert bidderRequest.imp[0].bidFloor == floorValue
+
+ and: "Response should contain error"
+ assert response.ext?.errors[PREBID]*.code == [999]
+ assert response.ext?.errors[PREBID]*.message ==
+ ["Failed to parse price floors from request, with a reason : Price floor modelGroup modelWeight " +
+ "must be in range(1-100), but was $invalidModelWeight "]
+
+ where:
+ invalidModelWeight << [0, MAX_MODEL_WEIGHT + 1]
+ }
+
+ def "PBS should reject fetch when root skipRate from request is invalid"() {
+ given: "Default BidRequest with skipRate"
+ def floorValue = PBSUtils.randomFloorValue
+ def bidRequest = bidRequestWithFloors.tap {
+ imp[0].bidFloor = floorValue
+ ext.prebid.floors.data.modelGroups << ModelGroup.modelGroup
+ ext.prebid.floors.data.modelGroups.first().values = [(rule): floorValue + 0.1]
+ ext.prebid.floors.data.modelGroups[0].skipRate = 0
+ ext.prebid.floors.data.skipRate = 0
+ ext.prebid.floors.skipRate = invalidSkipRate
+ ext.prebid.floors.data.modelGroups.last().values = [(rule): floorValue + 0.2]
+ ext.prebid.floors.data.modelGroups.last().skipRate = 0
+ }
+
+ and: "Account with disabled fetch in the DB"
+ def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id).tap {
+ config.auction.priceFloors.fetch.enabled = false
+ }
+ accountDao.save(account)
+
+ and: "PBS fetch rules from floors provider"
+ cacheFloorsProviderRules(bidRequest)
+
+ when: "PBS processes auction request"
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request bidFloor should correspond to request.imp.bidFloor"
+ def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
+ assert bidderRequest.imp[0].bidFloor == floorValue
+
+ and: "Response should contain error"
+ assert response.ext?.errors[PREBID]*.code == [999]
+ assert response.ext?.errors[PREBID]*.message ==
+ ["Failed to parse price floors from request, with a reason : Price floor root skipRate " +
+ "must be in range(0-100), but was $invalidSkipRate "]
+
+ where:
+ invalidSkipRate << [MIN_SKIP_RATE - 1, MAX_SKIP_RATE + 1]
+ }
+
+ def "PBS should reject fetch when data skipRate from request is invalid"() {
+ given: "Default BidRequest with skipRate"
+ def floorValue = PBSUtils.randomFloorValue
+ def bidRequest = bidRequestWithFloors.tap {
+ imp[0].bidFloor = floorValue
+ ext.prebid.floors.data.modelGroups << ModelGroup.modelGroup
+ ext.prebid.floors.data.modelGroups.first().values = [(rule): floorValue + 0.1]
+ ext.prebid.floors.data.modelGroups[0].skipRate = 0
+ ext.prebid.floors.data.skipRate = invalidSkipRate
+ ext.prebid.floors.skipRate = 0
+ ext.prebid.floors.data.modelGroups.last().values = [(rule): floorValue + 0.2]
+ ext.prebid.floors.data.modelGroups.last().skipRate = 0
+ }
+
+ and: "Account with disabled fetch in the DB"
+ def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id).tap {
+ config.auction.priceFloors.fetch.enabled = false
+ }
+ accountDao.save(account)
+
+ and: "PBS fetch rules from floors provider"
+ cacheFloorsProviderRules(bidRequest)
+
+ when: "PBS processes auction request"
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request bidFloor should correspond to request.imp.bidFloor"
+ def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
assert bidderRequest.imp[0].bidFloor == floorValue
+ and: "Response should contain error"
+ assert response.ext?.errors[PREBID]*.code == [999]
+ assert response.ext?.errors[PREBID]*.message ==
+ ["Failed to parse price floors from request, with a reason : Price floor data skipRate " +
+ "must be in range(0-100), but was $invalidSkipRate "]
+
where:
- invalidModelWeight << [0, -1, 1000000]
+ invalidSkipRate << [MIN_SKIP_RATE - 1, MAX_SKIP_RATE + 1]
+ }
+
+ def "PBS should reject fetch when modelGroup skipRate from request is invalid"() {
+ given: "Default BidRequest with skipRate"
+ def floorValue = PBSUtils.randomFloorValue
+ def bidRequest = bidRequestWithFloors.tap {
+ imp[0].bidFloor = floorValue
+ ext.prebid.floors.data.modelGroups << ModelGroup.modelGroup
+ ext.prebid.floors.data.modelGroups.first().values = [(rule): floorValue + 0.1]
+ ext.prebid.floors.data.modelGroups[0].skipRate = invalidSkipRate
+ ext.prebid.floors.data.skipRate = 0
+ ext.prebid.floors.skipRate = 0
+ ext.prebid.floors.data.modelGroups.last().values = [(rule): floorValue + 0.2]
+ ext.prebid.floors.data.modelGroups.last().skipRate = 0
+ }
+
+ and: "Account with disabled fetch in the DB"
+ def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id).tap {
+ config.auction.priceFloors.fetch.enabled = false
+ }
+ accountDao.save(account)
+
+ and: "PBS fetch rules from floors provider"
+ cacheFloorsProviderRules(bidRequest)
+
+ when: "PBS processes auction request"
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request bidFloor should correspond to request.imp.bidFloor"
+ def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
+ assert bidderRequest.imp[0].bidFloor == floorValue
+
+ and: "Response should contain error"
+ assert response.ext?.errors[PREBID]*.code == [999]
+ assert response.ext?.errors[PREBID]*.message ==
+ ["Failed to parse price floors from request, with a reason : Price floor modelGroup skipRate " +
+ "must be in range(0-100), but was $invalidSkipRate "]
+
+ where:
+ invalidSkipRate << [MIN_SKIP_RATE - 1, MAX_SKIP_RATE + 1]
+ }
+
+ def "PBS should validate rules from request when default floor value from request is invalid"() {
+ given: "Default BidRequest with default floor value"
+ def floorValue = PBSUtils.randomFloorValue
+ def invalidDefaultFloorValue = MIN_DEFAULT_FLOOR_VALUE - 1
+ def bidRequest = bidRequestWithFloors.tap {
+ imp[0].bidFloor = floorValue
+ ext.prebid.floors.data.modelGroups << ModelGroup.modelGroup
+ ext.prebid.floors.data.modelGroups.first().values = [(rule): floorValue + 0.1]
+ ext.prebid.floors.data.modelGroups[0].defaultFloor = invalidDefaultFloorValue
+ ext.prebid.floors.data.modelGroups.last().values = [(rule): floorValue + 0.2]
+ ext.prebid.floors.data.modelGroups.last().defaultFloor = MIN_DEFAULT_FLOOR_VALUE
+ }
+
+ and: "Account with disabled fetch in the DB"
+ def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id).tap {
+ config.auction.priceFloors.fetch.enabled = false
+ }
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request bidFloor should correspond to request.imp.bidFloor"
+ def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
+ assert bidderRequest.imp[0].bidFloor == floorValue
+
+ and: "Response should contain error"
+ assert response.ext?.errors[PREBID]*.code == [999]
+ assert response.ext?.errors[PREBID]*.message ==
+ ["Failed to parse price floors from request, with a reason : Price floor modelGroup default " +
+ "must be positive float, but was $invalidDefaultFloorValue "]
}
def "PBS should not invalidate previously good fetched data when floors provider return invalid data"() {
@@ -1104,8 +1364,8 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider #description response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(accountId, floorsResponse)
@@ -1124,40 +1384,103 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
verifyAll(bidderRequest) {
imp[0].bidFloor == floorValue
- imp[0].bidFloorCur == floorsResponse.data.modelGroups[0].currency
- imp[0].ext?.prebid?.floors?.floorRule == floorsResponse.data.modelGroups[0].values.keySet()[0]
+ imp[0].bidFloorCur == floorsResponse.modelGroups[0].currency
+ imp[0].ext?.prebid?.floors?.floorRule == floorsResponse.modelGroups[0].values.keySet()[0]
imp[0].ext?.prebid?.floors?.floorRuleValue == floorValue
imp[0].ext?.prebid?.floors?.floorValue == floorValue
ext?.prebid?.floors?.location == FETCH
ext?.prebid?.floors?.fetchStatus == SUCCESS
- ext?.prebid?.floors?.floorMin == floorsResponse.floorMin
+ !ext?.prebid?.floors?.floorMin
ext?.prebid?.floors?.floorProvider == floorsResponse.floorProvider
ext?.prebid?.floors?.skipRate == floorsResponse.skipRate
- ext?.prebid?.floors?.data == floorsResponse.data
+ ext?.prebid?.floors?.data == floorsResponse
}
}
- def "PBS should prefer floorMin from request over floorMin from fetched data"() {
- given: "Default BidRequest"
- def floorMin = PBSUtils.randomFloorValue
- def floorMinCur = USD
- def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
- ext.prebid.floors = new ExtPrebidFloors(floorMin: floorMin, floorMinCur: floorMinCur)
+ def "PBS should reject fetch when modelWeight from floors provider is invalid"() {
+ given: "Test start time"
+ def startTime = Instant.now()
+
+ and: "Flush metrics"
+ flushMetrics(floorsPbsService)
+
+ and: "Default BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ and: "Account with enabled fetch, fetch.url in the DB"
+ def accountId = bidRequest.site.publisher.id
+ def account = getAccountWithEnabledFetch(accountId)
+ accountDao.save(account)
+
+ and: "Set Floors Provider response"
+ def floorValue = PBSUtils.randomFloorValue
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups << ModelGroup.modelGroup
+ modelGroups.first().values = [(rule): floorValue + 0.1]
+ modelGroups.first().modelWeight = invalidModelWeight
+ modelGroups.last().values = [(rule): floorValue]
+ modelGroups.last().modelWeight = modelWeight
}
+ floorsProvider.setResponse(accountId, floorsResponse)
+
+ and: "PBS fetch rules from floors provider"
+ cacheFloorsProviderRules(bidRequest)
+
+ when: "PBS processes auction request"
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
+
+ and: "PBS processes collected metrics request"
+ def metrics = floorsPbsService.sendCollectedMetricsRequest()
+
+ then: "Bidder request bidFloor should not be passed"
+ def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
+ assert !bidderRequest.imp[0].bidFloor
+ assert bidderRequest.ext?.prebid?.floors?.fetchStatus == ERROR
+
+ and: "#FETCH_FAILURE_METRIC should be update"
+ assert metrics[FETCH_FAILURE_METRIC] == 1
+
+ and: "PBS log should contain error"
+ def logs = floorsPbsService.getLogsByTime(startTime)
+ def floorsLogs = getLogsByText(logs, basicFetchUrl)
+ assert floorsLogs.size() == 1
+ assert floorsLogs[0].contains("Failed to fetch price floor from provider for fetch.url: " +
+ "'$basicFetchUrl$accountId', account = $accountId with a reason : Price floor modelGroup modelWeight" +
+ " must be in range(1-100), but was $invalidModelWeight")
+
+ and: "Floors validation failure cannot reject the entire auction"
+ assert !response.seatbid?.isEmpty()
+
+ where:
+ invalidModelWeight << [0, MAX_MODEL_WEIGHT + 1]
+ }
+
+ def "PBS should reject fetch when data skipRate from floors provider is invalid"() {
+ given: "Test start time"
+ def startTime = Instant.now()
+
+ and: "Flush metrics"
+ flushMetrics(floorsPbsService)
+
+ and: "Default BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
and: "Account with enabled fetch, fetch.url in the DB"
- def accountId = bidRequest.app.publisher.id
+ def accountId = bidRequest.site.publisher.id
def account = getAccountWithEnabledFetch(accountId)
accountDao.save(account)
- and: "Set Floors Provider #description response"
- def floorValue = floorMin - 0.1
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
- data.modelGroups[0].currency = floorMinCur
- it.floorMin = floorValue
+ and: "Set Floors Provider response"
+ def floorValue = PBSUtils.randomFloorValue
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups << ModelGroup.modelGroup
+ modelGroups.first().values = [(rule): floorValue + 0.1]
+ modelGroups[0].skipRate = 0
+ skipRate = invalidSkipRate
+ modelGroups.last().values = [(rule): floorValue]
+ modelGroups.last().skipRate = 0
}
floorsProvider.setResponse(accountId, floorsResponse)
@@ -1165,63 +1488,163 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
cacheFloorsProviderRules(bidRequest)
when: "PBS processes auction request"
- floorsPbsService.sendAuctionRequest(bidRequest)
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
- then: "Bidder request floorMin should correspond to floorMin from request"
- assert bidder.getRequestCount(bidRequest.id) == 2
+ and: "PBS processes collected metrics request"
+ def metrics = floorsPbsService.sendCollectedMetricsRequest()
+
+ then: "Bidder request bidFloor should not be passed"
def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
- assert bidderRequest.ext?.prebid?.floors?.floorMin == floorMin
+ assert !bidderRequest.imp[0].bidFloor
+ assert bidderRequest.ext?.prebid?.floors?.fetchStatus == ERROR
+
+ and: "#FETCH_FAILURE_METRIC should be update"
+ assert metrics[FETCH_FAILURE_METRIC] == 1
+
+ and: "PBS log should contain error"
+ def logs = floorsPbsService.getLogsByTime(startTime)
+ def floorsLogs = getLogsByText(logs, basicFetchUrl)
+ assert floorsLogs.size() == 1
+ assert floorsLogs[0].contains("Failed to fetch price floor from provider for fetch.url: " +
+ "'$basicFetchUrl$accountId', account = $accountId with a reason : Price floor data skipRate" +
+ " must be in range(0-100), but was $invalidSkipRate")
+
+ and: "Floors validation failure cannot reject the entire auction"
+ assert !response.seatbid?.isEmpty()
+
+ where:
+ invalidSkipRate << [MIN_SKIP_RATE - 1, MAX_SKIP_RATE + 1]
}
- def "PBS should reject entire ruleset when modelWeight from floors provider is invalid"() {
- given: "Default BidRequest"
+ def "PBS should reject fetch when modelGroup skipRate from floors provider is invalid"() {
+ given: "Test start time"
+ def startTime = Instant.now()
+
+ and: "Flush metrics"
+ flushMetrics(floorsPbsService)
+
+ and: "Default BidRequest"
def bidRequest = BidRequest.defaultBidRequest
and: "Account with enabled fetch, fetch.url in the DB"
- def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id)
+ def accountId = bidRequest.site.publisher.id
+ def account = getAccountWithEnabledFetch(accountId)
accountDao.save(account)
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups << ModelGroup.modelGroup
- data.modelGroups.first().values = [(rule): floorValue + 0.1]
- data.modelGroups.first().modelWeight = invalidModelWeight
- data.modelGroups.last().values = [(rule): floorValue]
- data.modelGroups.last().modelWeight = modelWeight
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups << ModelGroup.modelGroup
+ modelGroups.first().values = [(rule): floorValue + 0.1]
+ modelGroups[0].skipRate = invalidSkipRate
+ skipRate = 0
+ modelGroups.last().values = [(rule): floorValue]
+ modelGroups.last().skipRate = 0
}
- floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
+ floorsProvider.setResponse(accountId, floorsResponse)
and: "PBS fetch rules from floors provider"
cacheFloorsProviderRules(bidRequest)
when: "PBS processes auction request"
- floorsPbsService.sendAuctionRequest(bidRequest)
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
+
+ and: "PBS processes collected metrics request"
+ def metrics = floorsPbsService.sendCollectedMetricsRequest()
- then: "Bidder request bidFloor should correspond to rule from valid modelGroup"
+ then: "Bidder request bidFloor should not be passed"
def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
- assert bidderRequest.imp[0].bidFloor == floorValue
+ assert !bidderRequest.imp[0].bidFloor
+ assert bidderRequest.ext?.prebid?.floors?.fetchStatus == ERROR
+
+ and: "#FETCH_FAILURE_METRIC should be update"
+ assert metrics[FETCH_FAILURE_METRIC] == 1
+
+ and: "PBS log should contain error"
+ def logs = floorsPbsService.getLogsByTime(startTime)
+ def floorsLogs = getLogsByText(logs, basicFetchUrl)
+ assert floorsLogs.size() == 1
+ assert floorsLogs[0].contains("Failed to fetch price floor from provider for fetch.url: " +
+ "'$basicFetchUrl$accountId', account = $accountId with a reason : Price floor modelGroup skipRate" +
+ " must be in range(0-100), but was $invalidSkipRate")
+
+ and: "Floors validation failure cannot reject the entire auction"
+ assert !response.seatbid?.isEmpty()
where:
- invalidModelWeight << [0, -1, 1000000]
+ invalidSkipRate << [MIN_SKIP_RATE - 1, MAX_SKIP_RATE + 1]
}
- def "PBS should reject entire ruleset when skipRate from floors provider is invalid"() {
- given: "Default BidRequest"
+ def "PBS should reject fetch when default floor value from floors provider is invalid"() {
+ given: "Test start time"
+ def startTime = Instant.now()
+
+ and: "Flush metrics"
+ flushMetrics(floorsPbsService)
+
+ and: "Default BidRequest"
def bidRequest = BidRequest.defaultBidRequest
+ and: "Account with enabled fetch, fetch.url in the DB"
+ def accountId = bidRequest.site.publisher.id
+ def account = getAccountWithEnabledFetch(accountId)
+ accountDao.save(account)
+
+ and: "Set Floors Provider response"
+ def floorValue = PBSUtils.randomFloorValue
+ def invalidDefaultFloor = MIN_DEFAULT_FLOOR_VALUE - 1
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups << ModelGroup.modelGroup
+ modelGroups.first().values = [(rule): floorValue + 0.1]
+ modelGroups[0].defaultFloor = invalidDefaultFloor
+ modelGroups.last().values = [(rule): floorValue]
+ modelGroups.last().defaultFloor = MIN_DEFAULT_FLOOR_VALUE
+ }
+ floorsProvider.setResponse(accountId, floorsResponse)
+
+ and: "PBS fetch rules from floors provider"
+ cacheFloorsProviderRules(bidRequest)
+
+ when: "PBS processes auction request"
+ def response = floorsPbsService.sendAuctionRequest(bidRequest)
+
+ and: "PBS processes collected metrics request"
+ def metrics = floorsPbsService.sendCollectedMetricsRequest()
+
+ then: "Bidder request bidFloor should not be passed"
+ def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
+ assert !bidderRequest.imp[0].bidFloor
+ assert bidderRequest.ext?.prebid?.floors?.fetchStatus == ERROR
+
+ and: "#FETCH_FAILURE_METRIC should be update"
+ assert metrics[FETCH_FAILURE_METRIC] == 1
+
+ and: "PBS log should contain error"
+ def logs = floorsPbsService.getLogsByTime(startTime)
+ def floorsLogs = getLogsByText(logs, basicFetchUrl)
+ assert floorsLogs.size() == 1
+ assert floorsLogs[0].contains("Failed to fetch price floor from provider for fetch.url: " +
+ "'$basicFetchUrl$accountId', account = $accountId with a reason : Price floor modelGroup default" +
+ " must be positive float, but was $invalidDefaultFloor")
+
+ and: "Floors validation failure cannot reject the entire auction"
+ assert !response.seatbid?.isEmpty()
+ }
+
+ def "PBS should give preference to currency from modelGroups when signalling"() {
+ given: "Default BidRequest with floors"
+ def bidRequest = bidRequestWithFloors
+
and: "Account with enabled fetch, fetch.url in the DB"
def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id)
accountDao.save(account)
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups << ModelGroup.modelGroup
- data.modelGroups.first().values = [(rule): floorValue + 0.1]
- data.modelGroups.first().skipRate = invalidSkipRate
- data.modelGroups.last().values = [(rule): floorValue]
- data.modelGroups.last().skipRate = 0
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
+ modelGroups[0].currency = modelGroupCurrency
+ currency = dataCurrency
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -1231,12 +1654,14 @@ class PriceFloorsFetchingSpec extends PriceFloorsBaseSpec {
when: "PBS processes auction request"
floorsPbsService.sendAuctionRequest(bidRequest)
- then: "Bidder request bidFloor should correspond to rule from valid modelGroup"
+ then: "Bidder request should contain bidFloorCur from floors provider according to priority"
def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
- assert bidderRequest.imp[0].bidFloor == floorValue
+ assert bidderRequest.imp[0].bidFloorCur == JPY
where:
- invalidSkipRate << [-1, 101]
+ modelGroupCurrency | dataCurrency
+ JPY | EUR
+ null | JPY
}
static int convertKilobyteSizeToByte(int kilobyteSize) {
diff --git a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsRulesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsRulesSpec.groovy
index 6521cda5a57..1c6d149b824 100644
--- a/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsRulesSpec.groovy
+++ b/src/test/groovy/org/prebid/server/functional/tests/pricefloors/PriceFloorsRulesSpec.groovy
@@ -1,9 +1,10 @@
package org.prebid.server.functional.tests.pricefloors
-import org.prebid.server.functional.model.mock.services.floorsprovider.PriceFloorRules
+
import org.prebid.server.functional.model.pricefloors.Country
import org.prebid.server.functional.model.pricefloors.MediaType
import org.prebid.server.functional.model.pricefloors.ModelGroup
+import org.prebid.server.functional.model.pricefloors.PriceFloorData
import org.prebid.server.functional.model.pricefloors.PriceFloorSchema
import org.prebid.server.functional.model.pricefloors.Rule
import org.prebid.server.functional.model.request.auction.App
@@ -56,9 +57,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
def floorValue = 0.8
def invalidRule = new Rule(mediaType: BANNER, country: Country.MULTIPLE,
siteDomain: PBSUtils.randomString).rule
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [MEDIA_TYPE, COUNTRY])
- data.modelGroups[0].values = [(rule) : floorValue,
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [MEDIA_TYPE, COUNTRY])
+ modelGroups[0].values = [(rule) : floorValue,
(invalidRule): floorValue + 0.1]
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -86,9 +87,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [MEDIA_TYPE, COUNTRY], delimiter: delimiter)
- data.modelGroups[0].values = [(new Rule(delimiter: delimiter, mediaType: MediaType.MULTIPLE, country: Country.MULTIPLE).rule) : PBSUtils.randomFloorValue,
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [MEDIA_TYPE, COUNTRY], delimiter: delimiter)
+ modelGroups[0].values = [(new Rule(delimiter: delimiter, mediaType: MediaType.MULTIPLE, country: Country.MULTIPLE).rule) : PBSUtils.randomFloorValue,
(new Rule(delimiter: delimiter, mediaType: BANNER, country: Country.MULTIPLE).rule): floorValue]}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -133,9 +134,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [DOMAIN])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [DOMAIN])
+ modelGroups[0].values =
[(new Rule(domain: domain).rule.toUpperCase()) : floorValue,
(new Rule(domain: PBSUtils.randomString).rule.toUpperCase()): floorValue + 0.1]
}
@@ -166,12 +167,12 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups << ModelGroup.modelGroup
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [BOGUS])
- data.modelGroups[0].values = [(new Rule(domain: domain).rule) : floorValue + 0.1]
- data.modelGroups[1].schema = new PriceFloorSchema(fields: [DOMAIN])
- data.modelGroups[1].values = [(new Rule(domain: domain).rule) : floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups << ModelGroup.modelGroup
+ modelGroups[0].schema = new PriceFloorSchema(fields: [BOGUS])
+ modelGroups[0].values = [(new Rule(domain: domain).rule) : floorValue + 0.1]
+ modelGroups[1].schema = new PriceFloorSchema(fields: [DOMAIN])
+ modelGroups[1].values = [(new Rule(domain: domain).rule) : floorValue]
}
floorsProvider.setResponse(accountId, floorsResponse)
@@ -215,9 +216,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.getRoundedFractionalNumber(PBSUtils.getFractionalRandomNumber(FLOOR_MIN, 2), 6) as BigDecimal
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [DOMAIN])
- data.modelGroups[0].values = [(new Rule(domain: domain).rule) : floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [DOMAIN])
+ modelGroups[0].values = [(new Rule(domain: domain).rule) : floorValue]
}
floorsProvider.setResponse(accountId, floorsResponse)
@@ -238,9 +239,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
and: "Set Floors Provider response"
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [MEDIA_TYPE])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [MEDIA_TYPE])
+ modelGroups[0].values =
[(new Rule(mediaType: MediaType.MULTIPLE).rule): bothFloorValue,
(new Rule(mediaType: BANNER).rule) : bannerFloorValue,
(new Rule(mediaType: VIDEO).rule) : videoFloorValue]
@@ -282,9 +283,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def requestFloorValue = 0.8
def floorsProviderFloorValue = requestFloorValue + 0.1
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [SIZE])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [SIZE])
+ modelGroups[0].values =
[(new Rule(size: "*").rule) : floorsProviderFloorValue,
(new Rule(size: "${lowerWidth}x${lowerHigh}").rule) : floorsProviderFloorValue + 0.1,
(new Rule(size: "${higherWidth}x${higherHigh}").rule): floorsProviderFloorValue + 0.2]
@@ -315,9 +316,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def requestFloorValue = 0.8
def floorsProviderFloorValue = requestFloorValue + 0.1
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [SIZE])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [SIZE])
+ modelGroups[0].values =
[(new Rule(size: "*").rule) : floorsProviderFloorValue + 0.1,
(new Rule(size: "${width}x${height}").rule): floorsProviderFloorValue]
}
@@ -358,9 +359,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [DOMAIN])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [DOMAIN])
+ modelGroups[0].values =
[(new Rule(domain: domain).rule) : floorValue,
(new Rule(domain: PBSUtils.randomString).rule): floorValue + 0.1]
}
@@ -407,9 +408,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [SITE_DOMAIN])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [SITE_DOMAIN])
+ modelGroups[0].values =
[(new Rule(siteDomain: domain).rule) : floorValue,
(new Rule(siteDomain: PBSUtils.randomString).rule): floorValue + 0.1]
}
@@ -448,9 +449,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [PUB_DOMAIN])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [PUB_DOMAIN])
+ modelGroups[0].values =
[(new Rule(pubDomain: domain).rule) : floorValue,
(new Rule(pubDomain: PBSUtils.randomString).rule): floorValue + 0.1]
}
@@ -490,9 +491,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [BUNDLE])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [BUNDLE])
+ modelGroups[0].values =
[(new Rule(bundle: bundle).rule) : floorValue,
(new Rule(bundle: PBSUtils.randomString).rule): floorValue + 0.1]
}
@@ -522,9 +523,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [CHANNEL])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [CHANNEL])
+ modelGroups[0].values =
[(new Rule(channel: channel).rule) : floorValue,
(new Rule(channel: APP).rule): floorValue + 0.1]
}
@@ -552,9 +553,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [GPT_SLOT])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [GPT_SLOT])
+ modelGroups[0].values =
[(new Rule(gptSlot: gptSlot).rule) : floorValue,
(new Rule(gptSlot: PBSUtils.randomString).rule): floorValue + 0.1]
}
@@ -590,9 +591,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [PB_AD_SLOT])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [PB_AD_SLOT])
+ modelGroups[0].values =
[(new Rule(pbAdSlot: pbAdSlot).rule) : floorValue,
(new Rule(pbAdSlot: PBSUtils.randomString).rule): floorValue + 0.1]
}
@@ -622,9 +623,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [COUNTRY])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [COUNTRY])
+ modelGroups[0].values =
[(new Rule(country: country).rule) : floorValue,
(new Rule(country: Country.MULTIPLE).rule): floorValue + 0.1]
}
@@ -652,9 +653,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
and: "Set Floors Provider response"
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [DEVICE_TYPE])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [DEVICE_TYPE])
+ modelGroups[0].values =
[(new Rule(deviceType: PHONE).rule): phoneFloorValue,
(new Rule(deviceType: TABLET).rule): tabletFloorValue,
(new Rule(deviceType: DESKTOP).rule): desktopFloorValue,
@@ -698,9 +699,9 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response with wildcard deviceType rule"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [DEVICE_TYPE])
- data.modelGroups[0].values =
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [DEVICE_TYPE])
+ modelGroups[0].values =
[(new Rule(deviceType: PHONE).rule): floorValue + 0.1,
(new Rule(deviceType: TABLET).rule): floorValue + 0.2,
(new Rule(deviceType: DESKTOP).rule): floorValue + 0.3,
@@ -729,10 +730,10 @@ class PriceFloorsRulesSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [MEDIA_TYPE])
- data.modelGroups[0].values = [(new Rule(mediaType: VIDEO).rule): floorValue + 0.1]
- data.modelGroups[0].defaultFloor = floorValue
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [MEDIA_TYPE])
+ modelGroups[0].values = [(new Rule(mediaType: VIDEO).rule): floorValue + 0.1]
+ modelGroups[0].defaultFloor = floorValue
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
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 5436daa9245..36dd199a9e3 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,9 @@
package org.prebid.server.functional.tests.pricefloors
import org.prebid.server.functional.model.db.StoredRequest
-import org.prebid.server.functional.model.mock.services.floorsprovider.PriceFloorRules
import org.prebid.server.functional.model.pricefloors.Country
import org.prebid.server.functional.model.pricefloors.ModelGroup
+import org.prebid.server.functional.model.pricefloors.PriceFloorData
import org.prebid.server.functional.model.pricefloors.PriceFloorSchema
import org.prebid.server.functional.model.pricefloors.Rule
import org.prebid.server.functional.model.request.amp.AmpRequest
@@ -32,11 +32,13 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
def "PBS should skip signalling 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 = false
+ ext.prebid.floors.enabled = requestEnabled
}
and: "Account with enabled fetch, fetch.url in the DB"
- def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id)
+ def account = getAccountWithEnabledFetch(bidRequest.site.publisher.id).tap {
+ config.auction.priceFloors.enabled = accountEnabled
+ }
accountDao.save(account)
when: "PBS processes auction request"
@@ -45,10 +47,15 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
then: "Bidder request bidFloor should correspond request"
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
assert bidderRequest.imp[0].bidFloor == bidRequest.imp[0].bidFloor
- assert bidderRequest.ext?.prebid?.floors?.enabled == bidRequest.ext.prebid.floors.enabled
+ assert !bidderRequest.ext?.prebid?.floors?.enabled
and: "PBS should not fetch rules from floors provider"
assert floorsProvider.getRequestCount(bidRequest.site.publisher.id) == 0
+
+ where:
+ requestEnabled | accountEnabled
+ false | true
+ true | false
}
def "PBS should skip signalling for request without rules when ext.prebid.floors.enabled = false in request"() {
@@ -85,8 +92,8 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
and: "Set Floors Provider response"
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorsProviderFloorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorsProviderFloorValue]
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -111,8 +118,8 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
and: "Set invalid Floors Provider response"
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = null
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = null
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -139,8 +146,8 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
and: "Set invalid Floors Provider response"
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = null
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = null
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -171,10 +178,10 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response with skipRate"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
- data.modelGroups[0].currency = USD
- data.modelGroups[0].skipRate = skipRate
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
+ modelGroups[0].currency = USD
+ modelGroups[0].skipRate = skipRate
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -187,7 +194,8 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
then: "Bidder request bidFloor should correspond to floors provider"
def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
assert bidderRequest.imp[0].bidFloor == floorValue
- assert bidderRequest.imp[0].bidFloorCur == floorsResponse.data.modelGroups[0].currency
+ assert bidderRequest.imp[0].bidFloorCur == floorsResponse.modelGroups[0].currency
+ assert !bidderRequest.ext?.prebid?.floors?.skipped
where:
skipRate << [0, null]
@@ -206,10 +214,11 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response with skipRate"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
- data.modelGroups[0].currency = USD
- data.modelGroups[0].skipRate = 100
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
+ modelGroups[0].currency = USD
+ modelGroups[0].skipRate = modelGroupSkipRate
+ skipRate = dataSkipRate
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
@@ -223,9 +232,16 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
assert bidderRequest.imp[0].bidFloor == bidRequest.imp[0].bidFloor
assert bidderRequest.imp[0].bidFloorCur == bidRequest.imp[0].bidFloorCur
+ assert bidderRequest.ext?.prebid?.floors?.skipRate == 100
+ assert bidderRequest.ext?.prebid?.floors?.skipped
and: "PBS should not made signalling"
assert !bidderRequest.imp[0].ext?.prebid?.floors
+
+ where:
+ modelGroupSkipRate | dataSkipRate
+ 100 | 0
+ null | 100
}
def "PBS should not emit error when request has more rules than fetch.max-rules"() {
@@ -326,8 +342,8 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
and: "Set Floors Provider response"
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorsProviderFloorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorsProviderFloorValue]
}
floorsProvider.setResponse(bidRequest.app.publisher.id, floorsResponse)
@@ -373,8 +389,8 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
accountDao.save(account)
and: "Set Floors Provider response"
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorsProviderFloorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorsProviderFloorValue]
}
floorsProvider.setResponse(accountId, floorsResponse)
@@ -410,8 +426,8 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].values = [(rule): floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].values = [(rule): floorValue]
}
floorsProvider.setResponse(bidRequest.app.publisher.id, floorsResponse)
@@ -441,16 +457,16 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def floorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups << ModelGroup.modelGroup
- data.modelGroups.first().values = [(rule): floorValue + 0.1]
- data.modelGroups.last().schema = new PriceFloorSchema(fields: [SITE_DOMAIN])
- data.modelGroups.last().values = [(new Rule(siteDomain: domain).rule): floorValue]
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups << ModelGroup.modelGroup
+ modelGroups.first().values = [(rule): floorValue + 0.1]
+ modelGroups.last().schema = new PriceFloorSchema(fields: [SITE_DOMAIN])
+ modelGroups.last().values = [(new Rule(siteDomain: domain).rule): floorValue]
}
floorsProvider.setResponse(bidRequest.site.publisher.id, floorsResponse)
when: "PBS cache rules and processes auction request"
- cacheFloorsProviderRules(bidRequest, floorValue)
+ cacheFloorsProviderRules(bidRequest)
then: "Bidder request should contain 1 modelGroup"
def bidderRequest = bidder.getBidderRequests(bidRequest.id).last()
@@ -470,9 +486,9 @@ class PriceFloorsSignalingSpec extends PriceFloorsBaseSpec {
and: "Set Floors Provider response"
def bannerFloorValue = PBSUtils.randomFloorValue
def videoFloorValue = PBSUtils.randomFloorValue
- def floorsResponse = PriceFloorRules.priceFloorRules.tap {
- data.modelGroups[0].schema = new PriceFloorSchema(fields: [MEDIA_TYPE])
- data.modelGroups[0].values = [(new Rule(mediaType: BANNER).rule): bannerFloorValue,
+ def floorsResponse = PriceFloorData.priceFloorData.tap {
+ modelGroups[0].schema = new PriceFloorSchema(fields: [MEDIA_TYPE])
+ modelGroups[0].values = [(new Rule(mediaType: BANNER).rule): bannerFloorValue,
(new Rule(mediaType: VIDEO).rule) : videoFloorValue]
}
floorsProvider.setResponse(bidRequest.app.publisher.id, floorsResponse)
diff --git a/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java b/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java
index b76a2b3214e..9eb4da71b24 100644
--- a/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java
+++ b/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java
@@ -32,6 +32,7 @@
import io.vertx.core.http.HttpMethod;
import lombok.AllArgsConstructor;
import lombok.Value;
+import org.assertj.core.groups.Tuple;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -559,7 +560,7 @@ public void makeHttpRequestsShouldNotSetBidFloorCurrencyToUSDIfNull() {
.extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
.flatExtracting(BidRequest::getImp).doesNotContainNull()
.extracting(Imp::getBidfloor, Imp::getBidfloorcur)
- .containsOnly(tuple(BigDecimal.ONE, null));
+ .containsOnly(tuple(BigDecimal.ONE, "USD"));
}
@Test
@@ -2643,8 +2644,9 @@ public void makeHttpRequestsShouldReturnOnlyLineItemRequestsWithExpectedFieldsWh
@Test
public void makeHttpRequestsShouldFillImpExtWithFloors() {
// given
- final PriceFloorResult priceFloorResult = PriceFloorResult.of("video", BigDecimal.TEN, BigDecimal.TEN, "USD");
-
+ final PriceFloorResult priceFloorResult = PriceFloorResult.of("video", BigDecimal.TEN, BigDecimal.TEN, "JPY");
+ when(currencyConversionService.convertCurrency(any(), any(), any(), any()))
+ .thenReturn(BigDecimal.ONE);
when(priceFloorResolver.resolve(any(), any(), any(), any(), any(), any())).thenReturn(priceFloorResult);
final BidRequest bidRequest = givenBidRequest(
@@ -2663,6 +2665,43 @@ public void makeHttpRequestsShouldFillImpExtWithFloors() {
// then
assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1).doesNotContainNull()
+ .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
+ .flatExtracting(BidRequest::getImp).doesNotContainNull()
+ .extracting(Imp::getExt).doesNotContainNull()
+ .extracting(ext -> mapper.treeToValue(ext, RubiconImpExt.class))
+ .containsOnly(RubiconImpExt.of(RubiconImpExtRp.of(4001,
+ mapper.valueToTree(Inventory.of(singletonList("5-star"), singletonList("tech"))),
+ RubiconImpExtRpTrack.of("", "")), null, 1, null,
+ RubiconImpExtPrebid.of(ExtImpPrebidFloors.of("video", BigDecimal.ONE, BigDecimal.ONE))));
+ }
+
+ @Test
+ public void makeHttpRequestsShouldAssumeDefaultIpfCurrencyAsUSD() {
+ // given
+ final PriceFloorResult priceFloorResult = PriceFloorResult.of("video", BigDecimal.TEN, BigDecimal.TEN, null);
+ when(currencyConversionService.convertCurrency(any(), any(), any(), any()))
+ .thenReturn(BigDecimal.ONE);
+ when(priceFloorResolver.resolve(any(), any(), any(), any(), any(), any())).thenReturn(priceFloorResult);
+
+ final BidRequest bidRequest = givenBidRequest(
+ builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder()
+ .debug(1)
+ .floors(givenFloors(floors -> floors.data(givenFloorData(
+ floorData -> floorData.modelGroups(singletonList(
+ givenModelGroup(UnaryOperator.identity())))))))
+ .build())),
+ builder -> builder.id("123").video(Video.builder().build()),
+ builder -> builder
+ .zoneId(4001)
+ .inventory(mapper.valueToTree(Inventory.of(singletonList("5-star"), singletonList("tech")))));
+
+ // when
+ final Result>> result = rubiconBidder.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).containsExactly(
+ BidderError.badInput("Ipf for imp `123` provided floor with no currency, assuming USD"));
assertThat(result.getValue()).hasSize(1).doesNotContainNull()
.extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
.flatExtracting(BidRequest::getImp).doesNotContainNull()
@@ -2714,9 +2753,10 @@ public void makeHttpRequestsShouldConvertBidFloor() {
@Test
public void makeHttpRequestsShouldFillImpExtWithFloorsWhenBothVideoAndBanner() {
// given
- final PriceFloorResult priceFloorResult = PriceFloorResult.of("video", BigDecimal.TEN, BigDecimal.TEN, "USD");
-
+ final PriceFloorResult priceFloorResult = PriceFloorResult.of("video", BigDecimal.TEN, BigDecimal.TEN, "JPY");
when(priceFloorResolver.resolve(any(), any(), any(), any(), any(), any())).thenReturn(priceFloorResult);
+ when(currencyConversionService.convertCurrency(any(), any(), any(), any()))
+ .thenReturn(BigDecimal.ONE);
final BidRequest bidRequest = givenBidRequest(
builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder()
@@ -2752,7 +2792,7 @@ public void makeHttpRequestsShouldFillImpExtWithFloorsWhenBothVideoAndBanner() {
.containsOnly(RubiconImpExt.of(RubiconImpExtRp.of(4001,
mapper.valueToTree(Inventory.of(singletonList("5-star"), singletonList("tech"))),
RubiconImpExtRpTrack.of("", "")), null, 1, null,
- RubiconImpExtPrebid.of(ExtImpPrebidFloors.of("video", BigDecimal.TEN, BigDecimal.TEN))));
+ RubiconImpExtPrebid.of(ExtImpPrebidFloors.of("video", BigDecimal.ONE, BigDecimal.ONE))));
}
@Test
@@ -2944,7 +2984,8 @@ public void makeBidsShouldReturnBannerBidIfRequestImpHasNoVideo() throws JsonPro
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
- .containsOnly(BidderBid.of(Bid.builder().price(ONE).build(), banner, "USD"));
+ .extracting(BidderBid::getBid, BidderBid::getType)
+ .containsOnly(Tuple.tuple(Bid.builder().price(ONE).build(), banner));
}
@Test
@@ -2963,7 +3004,8 @@ public void makeBidsShouldReturnBannerBidIfRequestImpHasBannerAndVideoButNoRequi
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
- .containsOnly(BidderBid.of(Bid.builder().price(ONE).build(), banner, "USD"));
+ .extracting(BidderBid::getBid, BidderBid::getType)
+ .containsOnly(Tuple.tuple(Bid.builder().price(ONE).build(), banner));
}
@Test
@@ -2983,7 +3025,8 @@ public void makeBidsShouldReturnVideoBidIfRequestImpHasBannerAndVideoButAllRequi
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
- .containsOnly(BidderBid.of(Bid.builder().price(ONE).build(), video, "USD"));
+ .extracting(BidderBid::getBid, BidderBid::getType)
+ .containsOnly(Tuple.tuple(Bid.builder().price(ONE).build(), video));
}
@Test
@@ -2999,7 +3042,8 @@ public void makeBidsShouldReturnVideoBidIfRequestImpHasVideo() throws JsonProces
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
- .containsOnly(BidderBid.of(Bid.builder().price(ONE).build(), video, "USD"));
+ .extracting(BidderBid::getBid, BidderBid::getType)
+ .containsOnly(Tuple.tuple(Bid.builder().price(ONE).build(), video));
}
@Test
@@ -3116,7 +3160,8 @@ public void makeBidsShouldReturnBidWithBidIdFieldFromBidResponseIfZero() throws
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
- .containsOnly(BidderBid.of(Bid.builder().id("bidid1").price(ONE).build(), banner, null));
+ .extracting(BidderBid::getBid, BidderBid::getType)
+ .containsOnly(Tuple.tuple(Bid.builder().id("bidid1").price(ONE).build(), banner));
}
@Test
@@ -3136,7 +3181,8 @@ public void makeBidsShouldReturnBidWithOriginalBidIdFieldFromBidResponseIfNotZer
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
- .containsOnly(BidderBid.of(Bid.builder().id("non-zero").price(ONE).build(), banner, null));
+ .extracting(BidderBid::getBid, BidderBid::getType)
+ .containsOnly(Tuple.tuple(Bid.builder().id("non-zero").price(ONE).build(), banner));
}
@Test
diff --git a/src/test/java/org/prebid/server/floors/BasicPriceFloorEnforcerTest.java b/src/test/java/org/prebid/server/floors/BasicPriceFloorEnforcerTest.java
index e4e6f1d4de8..e46e215b411 100644
--- a/src/test/java/org/prebid/server/floors/BasicPriceFloorEnforcerTest.java
+++ b/src/test/java/org/prebid/server/floors/BasicPriceFloorEnforcerTest.java
@@ -297,7 +297,7 @@ public void shouldRejectBidsHavingPriceBelowFloor() {
singletonList(BidderBid.of(
Bid.builder().id("bidId2").impid("impId").price(BigDecimal.TEN).build(), null, null)),
singletonList(BidderError.of("Bid with id 'bidId1' was rejected by floor enforcement: "
- + "price 0 is below the floor 1", BidderError.Type.generic)));
+ + "price 0 is below the floor 1", BidderError.Type.rejected_ipf)));
}
@Test
diff --git a/src/test/java/org/prebid/server/floors/BasicPriceFloorProcessorTest.java b/src/test/java/org/prebid/server/floors/BasicPriceFloorProcessorTest.java
index dac9ca871b3..fa639324124 100644
--- a/src/test/java/org/prebid/server/floors/BasicPriceFloorProcessorTest.java
+++ b/src/test/java/org/prebid/server/floors/BasicPriceFloorProcessorTest.java
@@ -6,12 +6,12 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.prebid.server.VertxTest;
import org.prebid.server.auction.model.AuctionContext;
-import org.prebid.server.currency.CurrencyConversionService;
import org.prebid.server.floors.model.PriceFloorData;
import org.prebid.server.floors.model.PriceFloorEnforcement;
import org.prebid.server.floors.model.PriceFloorLocation;
@@ -36,7 +36,6 @@
import static java.util.function.UnaryOperator.identity;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.same;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -50,8 +49,6 @@ public class BasicPriceFloorProcessorTest extends VertxTest {
private PriceFloorFetcher priceFloorFetcher;
@Mock
private PriceFloorResolver floorResolver;
- @Mock
- private CurrencyConversionService conversionService;
private BasicPriceFloorProcessor priceFloorProcessor;
@@ -60,12 +57,11 @@ public void setUp() {
priceFloorProcessor = new BasicPriceFloorProcessor(
priceFloorFetcher,
floorResolver,
- conversionService,
jacksonMapper);
}
@Test
- public void shouldDoNothingIfPriceFloorsDisabledForAccount() {
+ public void shouldSetRulesEnabledFieldToFalseIfPriceFloorsDisabledForAccount() {
// given
final AuctionContext auctionContext = givenAuctionContext(
givenAccount(floorsConfig -> floorsConfig.enabled(false)),
@@ -79,11 +75,13 @@ public void shouldDoNothingIfPriceFloorsDisabledForAccount() {
// then
verifyNoInteractions(priceFloorFetcher);
- assertThat(result).isSameAs(auctionContext);
+ assertThat(result)
+ .isEqualTo(auctionContext.with(givenBidRequest(
+ identity(), PriceFloorRules.builder().enabled(false).build())));
}
@Test
- public void shouldDoNothingIfPriceFloorsDisabledForRequest() {
+ public void shouldSetRulesEnabledFieldToFalseIfPriceFloorsDisabledForRequest() {
// given
final AuctionContext auctionContext = givenAuctionContext(
givenAccount(identity()),
@@ -97,20 +95,28 @@ public void shouldDoNothingIfPriceFloorsDisabledForRequest() {
// then
verifyNoInteractions(priceFloorFetcher);
- assertThat(result).isSameAs(auctionContext);
+ assertThat(result)
+ .extracting(AuctionContext::getBidRequest)
+ .extracting(BidRequest::getExt)
+ .extracting(ExtRequest::getPrebid)
+ .extracting(ExtRequestPrebid::getFloors)
+ .extracting(PriceFloorRules::getEnabled)
+ .isEqualTo(false);
}
@Test
- public void shouldUseFloorsFromProviderIfPresent() {
+ public void shouldUseFloorsDataFromProviderIfPresent() {
// given
final AuctionContext auctionContext = givenAuctionContext(
givenAccount(identity()),
givenBidRequest(
identity(),
- null));
+ givenFloors(floors -> floors.floorMin(BigDecimal.ONE))));
- final PriceFloorRules providerFloors = givenFloors(floors -> floors.floorMin(BigDecimal.ONE));
- given(priceFloorFetcher.fetch(any())).willReturn(FetchResult.of(providerFloors, FetchStatus.success));
+ final PriceFloorData providerFloorsData =
+ givenFloorData(floors -> floors.floorProvider("provider.com"));
+ given(priceFloorFetcher.fetch(any()))
+ .willReturn(FetchResult.of(providerFloorsData, FetchStatus.success));
// when
final AuctionContext result = priceFloorProcessor.enrichWithPriceFloors(auctionContext);
@@ -118,13 +124,16 @@ public void shouldUseFloorsFromProviderIfPresent() {
// then
assertThat(extractFloors(result))
.isEqualTo(givenFloors(floors -> floors
+ .enabled(true)
+ .floorProvider("provider.com")
.floorMin(BigDecimal.ONE)
+ .data(providerFloorsData)
.fetchStatus(FetchStatus.success)
.location(PriceFloorLocation.fetch)));
}
@Test
- public void shouldNUseFloorsFromProviderIfUseDynamicDataIsNotPresent() {
+ public void shouldUseFloorsFromProviderIfUseDynamicDataIsNotPresent() {
// given
final AuctionContext auctionContext = givenAuctionContext(
givenAccount(floorsConfig -> floorsConfig.useDynamicData(null)),
@@ -132,8 +141,10 @@ public void shouldNUseFloorsFromProviderIfUseDynamicDataIsNotPresent() {
identity(),
null));
- final PriceFloorRules providerFloors = givenFloors(floors -> floors.floorMin(BigDecimal.ONE));
- given(priceFloorFetcher.fetch(any())).willReturn(FetchResult.of(providerFloors, FetchStatus.success));
+ final PriceFloorData providerFloorsData =
+ givenFloorData(floors -> floors.floorProvider("provider.com"));
+ given(priceFloorFetcher.fetch(any()))
+ .willReturn(FetchResult.of(providerFloorsData, FetchStatus.success));
// when
final AuctionContext result = priceFloorProcessor.enrichWithPriceFloors(auctionContext);
@@ -141,22 +152,26 @@ public void shouldNUseFloorsFromProviderIfUseDynamicDataIsNotPresent() {
// then
assertThat(extractFloors(result))
.isEqualTo(givenFloors(floors -> floors
- .floorMin(BigDecimal.ONE)
+ .enabled(true)
+ .floorProvider("provider.com")
+ .data(providerFloorsData)
.fetchStatus(FetchStatus.success)
.location(PriceFloorLocation.fetch)));
}
@Test
- public void shouldNUseFloorsFromProviderIfUseDynamicDataIsTrue() {
+ public void shouldUseFloorsFromProviderIfUseDynamicDataIsTrue() {
// given
final AuctionContext auctionContext = givenAuctionContext(
givenAccount(floorsConfig -> floorsConfig.useDynamicData(true)),
givenBidRequest(
identity(),
- null));
+ givenFloors(floors -> floors.floorMin(BigDecimal.ONE))));
- final PriceFloorRules providerFloors = givenFloors(floors -> floors.floorMin(BigDecimal.ONE));
- given(priceFloorFetcher.fetch(any())).willReturn(FetchResult.of(providerFloors, FetchStatus.success));
+ final PriceFloorData providerFloorsData =
+ givenFloorData(floors -> floors.floorProvider("provider.com"));
+ given(priceFloorFetcher.fetch(any()))
+ .willReturn(FetchResult.of(providerFloorsData, FetchStatus.success));
// when
final AuctionContext result = priceFloorProcessor.enrichWithPriceFloors(auctionContext);
@@ -164,6 +179,9 @@ public void shouldNUseFloorsFromProviderIfUseDynamicDataIsTrue() {
// then
assertThat(extractFloors(result))
.isEqualTo(givenFloors(floors -> floors
+ .enabled(true)
+ .floorProvider("provider.com")
+ .data(providerFloorsData)
.floorMin(BigDecimal.ONE)
.fetchStatus(FetchStatus.success)
.location(PriceFloorLocation.fetch)));
@@ -178,17 +196,21 @@ public void shouldNotUseFloorsFromProviderIfUseDynamicDataIsFalse() {
identity(),
null));
- final PriceFloorRules providerFloors = givenFloors(identity());
- given(priceFloorFetcher.fetch(any())).willReturn(FetchResult.of(providerFloors, FetchStatus.success));
+ final PriceFloorData providerFloorsData =
+ givenFloorData(floors -> floors.floorProvider("provider.com"));
+ given(priceFloorFetcher.fetch(any()))
+ .willReturn(FetchResult.of(providerFloorsData, FetchStatus.success));
// when
final AuctionContext result = priceFloorProcessor.enrichWithPriceFloors(auctionContext);
// then
assertThat(extractFloors(result))
- .isEqualTo(givenFloors(floors -> floors
- .fetchStatus(FetchStatus.success)
- .location(PriceFloorLocation.noData)));
+ .extracting(PriceFloorRules::getFetchStatus)
+ .isEqualTo(FetchStatus.success);
+ assertThat(extractFloors(result))
+ .extracting(PriceFloorRules::getLocation)
+ .isEqualTo(PriceFloorLocation.noData);
}
@Test
@@ -203,8 +225,10 @@ public void shouldMergeProviderWithRequestFloors() {
.enforcement(PriceFloorEnforcement.builder().enforcePbs(false).enforceRate(100).build())
.floorMin(BigDecimal.ONE))));
- final PriceFloorRules providerFloors = givenFloors(floors -> floors.floorMin(BigDecimal.ZERO));
- given(priceFloorFetcher.fetch(any())).willReturn(FetchResult.of(providerFloors, FetchStatus.success));
+ final PriceFloorData providerFloorsData =
+ givenFloorData(floors -> floors.floorProvider("provider.com"));
+ given(priceFloorFetcher.fetch(any()))
+ .willReturn(FetchResult.of(providerFloorsData, FetchStatus.success));
// when
final AuctionContext result = priceFloorProcessor.enrichWithPriceFloors(auctionContext);
@@ -213,12 +237,72 @@ public void shouldMergeProviderWithRequestFloors() {
assertThat(extractFloors(result))
.isEqualTo(givenFloors(floors -> floors
.enabled(true)
- .enforcement(PriceFloorEnforcement.builder().enforceRate(100).build())
+ .floorProvider("provider.com")
+ .enforcement(PriceFloorEnforcement.builder()
+ .enforcePbs(false)
+ .enforceRate(100
+ ).build())
+ .data(providerFloorsData)
.floorMin(BigDecimal.ONE)
.fetchStatus(FetchStatus.success)
.location(PriceFloorLocation.fetch)));
}
+ @Test
+ public void shouldReturnProviderFloorsWhenNotEnabledByRequestAndEnforceRateAndFloorPriceAreAbsent() {
+ // given
+ final AuctionContext auctionContext = givenAuctionContext(
+ givenAccount(floorsConfig -> floorsConfig.enabled(true)),
+ givenBidRequest(
+ identity(),
+ givenFloors(floors -> floors.data(givenFloorData(identity())).enabled(null))));
+
+ final PriceFloorData providerFloorsData =
+ givenFloorData(floors -> floors.floorProvider("provider.com"));
+ given(priceFloorFetcher.fetch(any()))
+ .willReturn(FetchResult.of(providerFloorsData, FetchStatus.success));
+
+ // when
+ final AuctionContext result = priceFloorProcessor.enrichWithPriceFloors(auctionContext);
+
+ // then
+ final PriceFloorRules expectedResult =
+ givenFloors(floors -> floors
+ .enabled(true)
+ .floorProvider("provider.com")
+ .data(providerFloorsData)
+ .fetchStatus(FetchStatus.success)
+ .location(PriceFloorLocation.fetch));
+
+ assertThat(extractFloors(result)).isEqualTo(expectedResult);
+ }
+
+ @Test
+ public void shouldReturnFloorsWithFloorMinAndCurrencyFromRequestWhenPresent() {
+ // given
+ final AuctionContext auctionContext = givenAuctionContext(
+ givenAccount(identity()),
+ givenBidRequest(
+ identity(),
+ givenFloors(floors -> floors
+ .enabled(true)
+ .floorMin(BigDecimal.ONE)
+ .data(givenFloorData(floorsDataConfig -> floorsDataConfig.currency("USD"))))));
+
+ final PriceFloorData providerFloorsData =
+ givenFloorData(floors -> floors.floorProvider("provider.com"));
+ given(priceFloorFetcher.fetch(any()))
+ .willReturn(FetchResult.of(providerFloorsData, FetchStatus.success));
+
+ // when
+ final AuctionContext result = priceFloorProcessor.enrichWithPriceFloors(auctionContext);
+
+ // then
+ assertThat(extractFloors(result))
+ .extracting(PriceFloorRules::getFloorMin, PriceFloorRules::getFloorMinCur)
+ .containsExactly(BigDecimal.ONE, "USD");
+ }
+
@Test
public void shouldUseFloorsFromRequestIfProviderFloorsMissing() {
// given
@@ -236,6 +320,7 @@ public void shouldUseFloorsFromRequestIfProviderFloorsMissing() {
// then
assertThat(extractFloors(result))
.isEqualTo(givenFloors(floors -> floors
+ .enabled(true)
.floorMin(BigDecimal.ONE)
.location(PriceFloorLocation.request)));
}
@@ -256,8 +341,11 @@ public void shouldTolerateMissingRequestAndProviderFloors() {
// then
assertThat(extractFloors(result))
- .isEqualTo(givenFloors(floors -> floors
- .location(PriceFloorLocation.noData)));
+ .extracting(PriceFloorRules::getEnabled)
+ .isEqualTo(true);
+ assertThat(extractFloors(result))
+ .extracting(PriceFloorRules::getLocation)
+ .isEqualTo(PriceFloorLocation.noData);
}
@Test
@@ -275,6 +363,7 @@ public void shouldNotSkipFloorsIfRootSkipRateIsOff() {
// then
assertThat(extractFloors(result))
.isEqualTo(givenFloors(floors -> floors
+ .enabled(true)
.skipRate(0)
.location(PriceFloorLocation.request)));
}
@@ -295,6 +384,7 @@ public void shouldSkipFloorsIfRootSkipRateIsOn() {
assertThat(extractFloors(result))
.isEqualTo(givenFloors(floors -> floors
.skipRate(100)
+ .enabled(false)
.skipped(true)
.location(PriceFloorLocation.request)));
}
@@ -316,7 +406,8 @@ public void shouldSkipFloorsIfDataSkipRateIsOn() {
// then
assertThat(extractFloors(result))
.isEqualTo(givenFloors(floors -> floors
- .skipRate(0)
+ .enabled(false)
+ .skipRate(100)
.data(priceFloorData)
.skipped(true)
.location(PriceFloorLocation.request)));
@@ -342,6 +433,8 @@ public void shouldSkipFloorsIfModelGroupSkipRateIsOn() {
assertThat(extractFloors(result))
.isEqualTo(givenFloors(floors -> floors
.data(priceFloorData)
+ .skipRate(100)
+ .enabled(false)
.skipped(true)
.location(PriceFloorLocation.request)));
}
@@ -385,7 +478,37 @@ public void shouldUseSelectedModelGroup() {
priceFloorProcessor.enrichWithPriceFloors(auctionContext);
// then
- verify(floorResolver).resolve(any(), same(modelGroup), any(), any());
+ final ArgumentCaptor captor = ArgumentCaptor.forClass(PriceFloorRules.class);
+ verify(floorResolver).resolve(any(), captor.capture(), any(), any());
+ assertThat(captor.getValue())
+ .extracting(PriceFloorRules::getData)
+ .extracting(PriceFloorData::getModelGroups)
+ .isEqualTo(singletonList(modelGroup));
+ }
+
+ @Test
+ public void shouldCopyFloorProviderValueFromDataLevel() {
+ // given
+ final AuctionContext auctionContext = givenAuctionContext(
+ givenAccount(identity()),
+ givenBidRequest(
+ identity(),
+ givenFloors(floors -> floors
+ .floorMin(BigDecimal.ONE))));
+
+ final PriceFloorData providerFloorsData =
+ givenFloorData(floors -> floors.floorProvider("provider.com"));
+ given(priceFloorFetcher.fetch(any()))
+ .willReturn(FetchResult.of(providerFloorsData, FetchStatus.success));
+
+ // when
+ final AuctionContext result = priceFloorProcessor.enrichWithPriceFloors(auctionContext);
+
+ // then
+ assertThat(extractFloors(result))
+ .extracting(PriceFloorRules::getData)
+ .extracting(PriceFloorData::getFloorProvider)
+ .isEqualTo("provider.com");
}
@Test
@@ -510,19 +633,30 @@ private static Imp givenImp(UnaryOperator impCustomizer) {
private static PriceFloorRules givenFloors(
UnaryOperator floorsCustomizer) {
- return floorsCustomizer.apply(PriceFloorRules.builder()).build();
+ return floorsCustomizer.apply(PriceFloorRules.builder()
+ .data(PriceFloorData.builder()
+ .modelGroups(singletonList(PriceFloorModelGroup.builder()
+ .value("someKey", BigDecimal.ONE)
+ .build()))
+ .build())
+ ).build();
}
private static PriceFloorData givenFloorData(
UnaryOperator floorDataCustomizer) {
- return floorDataCustomizer.apply(PriceFloorData.builder()).build();
+ return floorDataCustomizer.apply(PriceFloorData.builder()
+ .modelGroups(singletonList(PriceFloorModelGroup.builder()
+ .value("someKey", BigDecimal.ONE)
+ .build()))).build();
}
private static PriceFloorModelGroup givenModelGroup(
UnaryOperator modelGroupCustomizer) {
- return modelGroupCustomizer.apply(PriceFloorModelGroup.builder()).build();
+ return modelGroupCustomizer.apply(PriceFloorModelGroup.builder()
+ .value("someKey", BigDecimal.ONE))
+ .build();
}
private static PriceFloorRules extractFloors(AuctionContext auctionContext) {
diff --git a/src/test/java/org/prebid/server/floors/BasicPriceFloorResolverTest.java b/src/test/java/org/prebid/server/floors/BasicPriceFloorResolverTest.java
index 80b89cf5d4f..c456dfabf51 100644
--- a/src/test/java/org/prebid/server/floors/BasicPriceFloorResolverTest.java
+++ b/src/test/java/org/prebid/server/floors/BasicPriceFloorResolverTest.java
@@ -21,6 +21,7 @@
import org.prebid.server.VertxTest;
import org.prebid.server.currency.CurrencyConversionService;
import org.prebid.server.exception.PreBidException;
+import org.prebid.server.floors.model.PriceFloorData;
import org.prebid.server.floors.model.PriceFloorField;
import org.prebid.server.floors.model.PriceFloorModelGroup;
import org.prebid.server.floors.model.PriceFloorResult;
@@ -83,7 +84,7 @@ public void resolveShouldReturnNullWhenNoModelGroupSchema() {
final BidRequest bidRequest = BidRequest.builder().build();
// when and then
- assertThat(priceFloorResolver.resolve(bidRequest, PriceFloorModelGroup.builder().build(),
+ assertThat(priceFloorResolver.resolve(bidRequest, givenRules(PriceFloorModelGroup.builder().build()),
Imp.builder().build(), null)).isNull();
}
@@ -94,9 +95,9 @@ public void resolveShouldReturnNullWhenModelGroupSchemaFieldsIsEmpty() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", emptyList()))
- .build(),
+ .build()),
Imp.builder().build(), null)).isNull();
}
@@ -107,9 +108,9 @@ public void resolveShouldReturnNullWhenNoModelGroupSchemaFields() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", null))
- .build(),
+ .build()),
Imp.builder().build(), null)).isNull();
}
@@ -120,10 +121,10 @@ public void resolveShouldReturnNullWhenModelGroupValuesIsEmpty() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.channel)))
.values(emptyMap())
- .build(),
+ .build()),
Imp.builder().build(), null)).isNull();
}
@@ -134,9 +135,9 @@ public void resolveShouldReturnNullWhenNoModelGroupValues() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.channel)))
- .build(),
+ .build()),
Imp.builder().build(), null)).isNull();
}
@@ -147,10 +148,10 @@ public void resolveShouldReturnNullWhenNoSiteDomainPresent() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.siteDomain)))
.value("siteDomain", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null)).isNull();
}
@@ -163,10 +164,10 @@ public void resolveShouldReturnPriceFloorForSiteDomainPresentedBySite() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.siteDomain)))
.value("siteDomain", BigDecimal.TEN)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -179,10 +180,10 @@ public void resolveShouldReturnPriceFloorForSiteDomainPresentedByApp() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.siteDomain)))
.value("appDomain", BigDecimal.TEN)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -193,10 +194,10 @@ public void resolveShouldReturnNullWhenNoPubDomainPresent() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.pubDomain)))
.value("pubDomain", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null)).isNull();
}
@@ -211,10 +212,10 @@ public void resolveShouldReturnPriceFloorForPubDomainPresentedBySite() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.pubDomain)))
.value("siteDomain", BigDecimal.TEN)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -229,10 +230,10 @@ public void resolveShouldReturnPriceFloorForPubDomainPresentedByApp() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.pubDomain)))
.value("appDomain", BigDecimal.TEN)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -243,10 +244,10 @@ public void resolveShouldReturnNullWhenNoDomainPresent() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.domain)))
.value("pubDomain", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null)).isNull();
}
@@ -259,10 +260,10 @@ public void resolveShouldReturnPriceFloorForDomainPresentedBySite() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.domain)))
.value("siteDomain", BigDecimal.TEN)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -275,10 +276,10 @@ public void resolveShouldReturnPriceFloorForDomainPresentedByApp() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.domain)))
.value("appDomain", BigDecimal.TEN)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -293,10 +294,10 @@ public void resolveShouldReturnPriceFloorForDomainPresentedBySitePublisher() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.domain)))
.value("siteDomain", BigDecimal.TEN)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -311,10 +312,10 @@ public void resolveShouldReturnPriceFloorForDomainPresentedByAppPublisher() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.domain)))
.value("appDomain", BigDecimal.TEN)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -325,10 +326,10 @@ public void resolveShouldReturnNullWhenNoBundlePresent() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.bundle)))
.value("bundle", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null)).isNull();
}
@@ -343,10 +344,10 @@ public void resolveShouldReturnPriceFloorIfBundlePresent() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.bundle)))
.value("someBundle", BigDecimal.TEN)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -357,10 +358,10 @@ public void resolveShouldReturnNullWhenNoChannelPresent() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.channel)))
.value("channel", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null)).isNull();
}
@@ -378,10 +379,10 @@ public void resolveShouldReturnPriceFloorIfChannelPresent() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.channel)))
.value("someChannelName", BigDecimal.TEN)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -392,10 +393,10 @@ public void resolveShouldReturnNullWhenMediaTypeDoesNotMatchRuleMediaType() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.mediaType)))
.value("video", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null)).isNull();
}
@@ -406,12 +407,12 @@ public void resolveShouldReturnPriceFloorForCatchAllImpMediaTypeWhenImpContainsM
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.mediaType)))
.value("banner", BigDecimal.ONE)
.value("*", BigDecimal.TEN)
.value("video", BigDecimal.ONE)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder
.video(Video.builder().build())), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
@@ -424,10 +425,10 @@ public void resolveShouldReturnPriceFloorWhenImpMediaTypeIsBannerAndRuleMediaTyp
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.mediaType)))
.value("banner", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -439,10 +440,10 @@ public void resolveShouldReturnPriceFloorWhenImpMediaTypeIsVideoInStreamAndRuleM
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.mediaType)))
.value("video", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder
.banner(null)
.video(Video.builder().placement(1).build())),
@@ -458,10 +459,10 @@ public void resolveShouldReturnPriceFloorWhenImpMediaTypeIsVideoByEmptyPlacement
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.mediaType)))
.value("video", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder
.banner(null)
.video(Video.builder().placement(null).build())), null
@@ -476,10 +477,10 @@ public void resolveShouldReturnPriceFloorWhenImpMediaTypeIsVideoInStreamAndRuleM
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.mediaType)))
.value("video-instream", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder
.banner(null)
.video(Video.builder().placement(1).build())), null).getFloorValue())
@@ -493,10 +494,10 @@ public void resolveShouldReturnPriceFloorWhenImpMediaTypeIsNativeAndRuleMediaTyp
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.mediaType)))
.value("native", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder
.banner(null)
.xNative(Native.builder().build())), null).getFloorValue())
@@ -510,10 +511,10 @@ public void resolveShouldReturnPriceFloorWhenImpMediaTypeIsAudioAndRuleMediaType
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.mediaType)))
.value("native", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder
.banner(null)
.xNative(Native.builder().build())), null).getFloorValue())
@@ -527,10 +528,10 @@ public void resolveShouldReturnNullWhenSizeDoesNotMatchRuleSize() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.size)))
.value("250x300", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null)).isNull();
}
@@ -541,11 +542,11 @@ public void resolveShouldReturnPriceFloorWhenMediaTypeIsBannerAndTakePriorityFor
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.size)))
.value("100x150", BigDecimal.ONE)
.value("250x300", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder
.banner(Banner.builder()
.w(100)
@@ -562,12 +563,12 @@ public void resolveShouldReturnPriceFloorForCatchAllWildcardWhenMultipleFormats(
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.size)))
.value("400x500", BigDecimal.ONE)
.value("*", BigDecimal.TEN)
.value("250x300", BigDecimal.ONE)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder
.banner(Banner.builder()
.w(100)
@@ -585,10 +586,10 @@ public void resolveShouldReturnPriceFloorWhenMediaTypeIsBannerAndTakeSizesForFor
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.size)))
.value("250x300", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder
.banner(Banner.builder()
.w(250)
@@ -604,10 +605,10 @@ public void resolveShouldReturnPriceFloorWhenMediaTypeIsVideoAndTakeSizesForForm
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.size)))
.value("250x300", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder
.banner(null)
.video(Video.builder()
@@ -625,10 +626,10 @@ public void resolveShouldReturnNullWhenGptSlotDoesNotMatchRule() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.gptSlot)))
.value("someGptSlot", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null)).isNull();
}
@@ -646,10 +647,10 @@ public void resolveShouldReturnPriceFloorIfAdserverNameIsGamAndAdSlotMatchesRule
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.gptSlot)))
.value("someGptSlot", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.ext(impExt)), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -668,10 +669,10 @@ public void resolveShouldReturnNullIfAdserverNameIsNotGamAndAdSlotMatchesRule()
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.gptSlot)))
.value("someGptSlot", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.ext(impExt)), null))
.isNull();
}
@@ -687,10 +688,10 @@ public void resolveShouldReturnPriceFloorFromPbAdSlotIfAdserverNameIsNotGamAndAd
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.gptSlot)))
.value("somePbAdSlot", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.ext(impExt)), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -702,10 +703,10 @@ public void resolveShouldReturnNullWhenPbAdSlotDoesNotMatchRule() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.pbAdSlot)))
.value("somePbAdSlot", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null)).isNull();
}
@@ -720,10 +721,10 @@ public void resolveShouldReturnPriceFloorIfPbAdSlotMatchesRule() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.pbAdSlot)))
.value("somePbAdSlot", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.ext(impExt)), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -735,10 +736,10 @@ public void resolveShouldReturnNullWhenCountryDoesNotMatchRule() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.country)))
.value("USA", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null)).isNull();
}
@@ -751,10 +752,10 @@ public void resolveShouldReturnPriceFloorWhenCountryMatchesRule() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.country)))
.value("usa", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -769,10 +770,10 @@ public void resolveShouldReturnPriceFloorWhenCountryAlpha3IsForMatchingRuleAlpha
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.country)))
.value("usa", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -784,10 +785,10 @@ public void resolveShouldReturnNullWhenDeviceTypeDoesNotMatchRule() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.deviceType)))
.value("desktop", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null)).isNull();
}
@@ -800,10 +801,10 @@ public void resolveShouldReturnPriceFloorForPhoneTypeWhenUaMatchesPhone() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.deviceType)))
.value("phone", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -817,10 +818,10 @@ public void resolveShouldReturnPriceFloorForPhoneTypeWhenUaMatchesIPhone() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.deviceType)))
.value("phone", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -834,10 +835,10 @@ public void resolveShouldReturnPriceFloorForPhoneTypeWhenUaMatchesAndroidMobile(
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.deviceType)))
.value("phone", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -851,10 +852,10 @@ public void resolveShouldReturnPriceFloorForPhoneTypeWhenUaMatchesMobileAndroid(
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.deviceType)))
.value("phone", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -868,10 +869,10 @@ public void resolveShouldReturnPriceFloorForTabletTypeWhenUaMatchesTablet() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.deviceType)))
.value("tablet", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -885,10 +886,10 @@ public void resolveShouldReturnPriceFloorForTabletTypeWhenUaMatchesIPad() {
// when and then
final BigDecimal floorValue = priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.deviceType)))
.value("tablet", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue();
assertThat(floorValue)
.isEqualTo(BigDecimal.TEN);
@@ -903,10 +904,10 @@ public void resolveShouldReturnPriceFloorForTabletTypeWhenUaMatchesWindowsNt() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.deviceType)))
.value("tablet", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -920,10 +921,10 @@ public void resolveShouldReturnPriceFloorForTabletTypeWhenUaMatchesAndroid() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.deviceType)))
.value("tablet", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -937,10 +938,10 @@ public void resolveShouldReturnPriceFloorForDesktopTypeWhenUaDoesNotMatchTabletO
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.deviceType)))
.value("desktop", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -954,14 +955,14 @@ public void resolveShouldReturnFloorWhenAllFieldsMatchExactly() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|",
List.of(PriceFloorField.deviceType, PriceFloorField.mediaType, PriceFloorField.size)))
.value("desktop|*|300x250", BigDecimal.ONE)
.value("*|banner|300x250", BigDecimal.ONE)
.value("desktop|banner|300x250", BigDecimal.TEN)
.value("desktop|banner|*", BigDecimal.ONE)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.banner(Banner.builder()
.w(300)
.h(250)
@@ -978,13 +979,13 @@ public void resolveShouldReturnFloorWhenMostAccurateWildcardMatchIsPresent() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|",
List.of(PriceFloorField.deviceType, PriceFloorField.mediaType, PriceFloorField.size)))
.value("desktop|*|300x250", BigDecimal.ONE)
.value("desktop|banner|*", BigDecimal.TEN)
.value("*|banner|300x250", BigDecimal.ONE)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.banner(Banner.builder()
.w(300)
.h(250)
@@ -1001,12 +1002,12 @@ public void resolveShouldReturnFloorWhenNarrowerWildcardMatchIsPresent() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|",
List.of(PriceFloorField.deviceType, PriceFloorField.mediaType, PriceFloorField.size)))
.value("desktop|*|*", BigDecimal.ONE)
.value("*|banner|300x250", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.banner(Banner.builder()
.w(300)
.h(250)
@@ -1023,11 +1024,11 @@ public void resolveShouldReturnFloorWhenCaseIsDifferent() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|",
List.of(PriceFloorField.deviceType, PriceFloorField.mediaType, PriceFloorField.size)))
.value("deSKtop|baNNer|300X250", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.banner(Banner.builder()
.w(300)
.h(250)
@@ -1044,11 +1045,11 @@ public void resolveShouldReturnFloorWhenDelimiterIsNullAndDefaultAssumed() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of(null,
List.of(PriceFloorField.deviceType, PriceFloorField.mediaType, PriceFloorField.size)))
.value("desktop|banner|300x250", BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.banner(Banner.builder()
.w(300)
.h(250)
@@ -1065,12 +1066,12 @@ public void resolveShouldReturnDefaultWhenNoMatchingRule() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of(null,
List.of(PriceFloorField.deviceType, PriceFloorField.mediaType, PriceFloorField.size)))
.value("video|banner|300x250", BigDecimal.ONE)
.defaultFloor(BigDecimal.TEN)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.banner(Banner.builder()
.w(300)
.h(250)
@@ -1089,12 +1090,12 @@ public void resolveShouldReturnFloorInRulesCurrency() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.currency("EUR")
.schema(PriceFloorSchema.of("|",
List.of(PriceFloorField.deviceType, PriceFloorField.mediaType, PriceFloorField.size)))
.value("desktop|banner|300x250", BigDecimal.ONE)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.banner(Banner.builder()
.w(300)
.h(250)
@@ -1110,21 +1111,21 @@ public void resolveShouldReturnFloorInRulesCurrencyIfConversionIsNotPossible() {
final BidRequest bidRequest = BidRequest.builder()
.device(Device.builder().ua("potential desktop type").build())
.ext(ExtRequest.of(ExtRequestPrebid.builder()
- .floors(PriceFloorRules.builder()
- .floorMin(BigDecimal.ONE)
- .floorMinCur("UNKNOWN")
- .build())
+ .floors(PriceFloorRules.builder()
+ .floorMin(BigDecimal.ONE)
+ .floorMinCur("UNKNOWN")
+ .build())
.build()))
.build();
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.currency("EUR")
.schema(PriceFloorSchema.of("|",
List.of(PriceFloorField.deviceType, PriceFloorField.mediaType, PriceFloorField.size)))
.value("desktop|banner|300x250", BigDecimal.ONE)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.banner(Banner.builder()
.w(300)
.h(250)
@@ -1142,11 +1143,11 @@ public void resolveShouldReturnFloorRuleThatWasSelected() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|",
List.of(PriceFloorField.deviceType, PriceFloorField.mediaType, PriceFloorField.size)))
.value("desktop|banner|300x250", BigDecimal.ONE)
- .build(),
+ .build()),
givenImp(impBuilder -> impBuilder.banner(Banner.builder()
.w(300)
.h(250)
@@ -1171,10 +1172,10 @@ public void resolveShouldReturnEffectiveFloorMinIfCurrencyIsTheSameAndAllFloorsR
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.pubDomain)))
.value("appDomain", BigDecimal.TEN)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
@@ -1198,11 +1199,11 @@ public void resolveShouldReturnConvertedFloorMinInProvidedCurrencyAndFloorMinMor
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.pubDomain)))
.currency("GUF")
.value("appDomain", BigDecimal.valueOf(5))
- .build(), givenImp(identity()), null))
+ .build()), givenImp(identity()), null))
.isEqualTo(PriceFloorResult.of("appdomain", BigDecimal.valueOf(5), BigDecimal.TEN, "GUF"));
}
@@ -1223,10 +1224,10 @@ public void resolveShouldReturnCorrectValueAfterRoundingUpFifthDecimalNumber() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.pubDomain)))
.value("appDomain", BigDecimal.ZERO)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.valueOf(9.0001D));
}
@@ -1247,13 +1248,22 @@ public void resolveShouldReturnCorrectValueAfterRoundingUpToWhole() {
// when and then
assertThat(priceFloorResolver.resolve(bidRequest,
- PriceFloorModelGroup.builder()
+ givenRules(PriceFloorModelGroup.builder()
.schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.pubDomain)))
.value("appDomain", BigDecimal.ZERO)
- .build(), givenImp(identity()), null).getFloorValue())
+ .build()), givenImp(identity()), null).getFloorValue())
.isEqualTo(BigDecimal.TEN);
}
+ private static PriceFloorRules givenRules(PriceFloorModelGroup modelGroup) {
+
+ return PriceFloorRules.builder()
+ .data(PriceFloorData.builder()
+ .modelGroups(singletonList(modelGroup))
+ .build())
+ .build();
+ }
+
private static Imp givenImp(UnaryOperator impCustomizer) {
return impCustomizer.apply(Imp.builder()
.id("123")
diff --git a/src/test/java/org/prebid/server/floors/PriceFloorFetcherTest.java b/src/test/java/org/prebid/server/floors/PriceFloorFetcherTest.java
index d18eb51f9da..182ad127e54 100644
--- a/src/test/java/org/prebid/server/floors/PriceFloorFetcherTest.java
+++ b/src/test/java/org/prebid/server/floors/PriceFloorFetcherTest.java
@@ -15,11 +15,11 @@
import org.prebid.server.exception.PreBidException;
import org.prebid.server.execution.TimeoutFactory;
import org.prebid.server.floors.model.PriceFloorData;
+import org.prebid.server.floors.model.PriceFloorDebugProperties;
import org.prebid.server.floors.model.PriceFloorField;
import org.prebid.server.floors.model.PriceFloorModelGroup;
import org.prebid.server.floors.model.PriceFloorRules;
import org.prebid.server.floors.model.PriceFloorSchema;
-import org.prebid.server.floors.model.PriceFloorDebugProperties;
import org.prebid.server.floors.proto.FetchResult;
import org.prebid.server.floors.proto.FetchStatus;
import org.prebid.server.metric.Metrics;
@@ -90,7 +90,7 @@ public void fetchShouldReturnPriceFloorFetchedFromProviderAndCache() {
final Account givenAccount = givenAccount(identity());
given(httpClient.get(anyString(), anyLong(), anyLong()))
.willReturn(Future.succeededFuture(HttpClientResponse.of(200, MultiMap.caseInsensitiveMultiMap(),
- jacksonMapper.encodeToString(givenPriceFloorRules()))));
+ jacksonMapper.encodeToString(givenPriceFloorData()))));
// when
final FetchResult fetchResult = priceFloorFetcher.fetch(givenAccount);
@@ -104,7 +104,7 @@ public void fetchShouldReturnPriceFloorFetchedFromProviderAndCache() {
final FetchResult priceFloorRulesCached = priceFloorFetcher.fetch(givenAccount);
assertThat(priceFloorRulesCached.getFetchStatus()).isEqualTo(FetchStatus.success);
- assertThat(priceFloorRulesCached.getRules()).isEqualTo(givenPriceFloorRules());
+ assertThat(priceFloorRulesCached.getRulesData()).isEqualTo(givenPriceFloorData());
}
@@ -118,7 +118,7 @@ public void fetchShouldReturnEmptyRulesAndInProgressStatusForTheFirstInvocation(
final FetchResult fetchResult = priceFloorFetcher.fetch(givenAccount(identity()));
// then
- assertThat(fetchResult.getRules()).isNull();
+ assertThat(fetchResult.getRulesData()).isNull();
assertThat(fetchResult.getFetchStatus()).isEqualTo(FetchStatus.inprogress);
verify(vertx).setTimer(eq(1700000L), any());
}
@@ -133,12 +133,12 @@ public void fetchShouldReturnEmptyRulesAndInProgressStatusForTheFirstInvocationA
final FetchResult firstInvocationResult = priceFloorFetcher.fetch(givenAccount(identity()));
// then
- assertThat(firstInvocationResult.getRules()).isNull();
+ assertThat(firstInvocationResult.getRulesData()).isNull();
assertThat(firstInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.inprogress);
verify(vertx).setTimer(eq(1700000L), any());
final FetchResult secondInvocationResult = priceFloorFetcher.fetch(givenAccount(identity()));
- assertThat(secondInvocationResult.getRules()).isNull();
+ assertThat(secondInvocationResult.getRulesData()).isNull();
assertThat(secondInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.error);
}
@@ -152,12 +152,12 @@ public void fetchShouldReturnEmptyRulesAndInProgressStatusForTheFirstInvocationA
final FetchResult firstInvocationResult = priceFloorFetcher.fetch(givenAccount(identity()));
// then
- assertThat(firstInvocationResult.getRules()).isNull();
+ assertThat(firstInvocationResult.getRulesData()).isNull();
assertThat(firstInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.inprogress);
verify(vertx).setTimer(eq(1700000L), any());
final FetchResult secondInvocationResult = priceFloorFetcher.fetch(givenAccount(identity()));
- assertThat(secondInvocationResult.getRules()).isNull();
+ assertThat(secondInvocationResult.getRulesData()).isNull();
assertThat(secondInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.timeout);
}
@@ -168,7 +168,7 @@ public void fetchShouldCacheResponseForTimeFromResponseCacheControlHeader() {
.willReturn(Future.succeededFuture(
HttpClientResponse.of(200, MultiMap.caseInsensitiveMultiMap()
.add(HttpHeaders.CACHE_CONTROL, "max-age=700"),
- jacksonMapper.encodeToString(givenPriceFloorRules()))));
+ jacksonMapper.encodeToString(givenPriceFloorData()))));
// when
priceFloorFetcher.fetch(givenAccount(identity()));
@@ -185,7 +185,7 @@ public void fetchShouldTakePrecedenceForTestingPropertyToCacheResponse() {
.willReturn(Future.succeededFuture(
HttpClientResponse.of(200, MultiMap.caseInsensitiveMultiMap()
.add(HttpHeaders.CACHE_CONTROL, "max-age=700"),
- jacksonMapper.encodeToString(givenPriceFloorRules()))));
+ jacksonMapper.encodeToString(givenPriceFloorData()))));
// when
priceFloorFetcher.fetch(givenAccount(identity()));
@@ -202,7 +202,7 @@ public void fetchShouldTakePrecedenceForTestingPropertyToCreatePeriodicTimer() {
given(httpClient.get(anyString(), anyLong(), anyLong()))
.willReturn(Future.succeededFuture(
HttpClientResponse.of(200, MultiMap.caseInsensitiveMultiMap(),
- jacksonMapper.encodeToString(givenPriceFloorRules()))));
+ jacksonMapper.encodeToString(givenPriceFloorData()))));
// when
priceFloorFetcher.fetch(givenAccount(identity()));
@@ -219,7 +219,7 @@ public void fetchShouldTakePrecedenceForTestingPropertyToChooseRequestTimeout()
given(httpClient.get(anyString(), anyLong(), anyLong()))
.willReturn(Future.succeededFuture(
HttpClientResponse.of(200, MultiMap.caseInsensitiveMultiMap(),
- jacksonMapper.encodeToString(givenPriceFloorRules()))));
+ jacksonMapper.encodeToString(givenPriceFloorData()))));
// when
priceFloorFetcher.fetch(givenAccount(identity()));
@@ -236,7 +236,7 @@ public void fetchShouldTakePrecedenceForMinTimeoutTestingPropertyToChooseRequest
given(httpClient.get(anyString(), anyLong(), anyLong()))
.willReturn(Future.succeededFuture(
HttpClientResponse.of(200, MultiMap.caseInsensitiveMultiMap(),
- jacksonMapper.encodeToString(givenPriceFloorRules()))));
+ jacksonMapper.encodeToString(givenPriceFloorData()))));
// when
priceFloorFetcher.fetch(givenAccount(identity()));
@@ -251,7 +251,7 @@ public void fetchShouldSetDefaultCacheTimeWhenCacheControlHeaderCantBeParsed() {
given(httpClient.get(anyString(), anyLong(), anyLong()))
.willReturn(Future.succeededFuture(HttpClientResponse.of(200,
MultiMap.caseInsensitiveMultiMap().add(HttpHeaders.CACHE_CONTROL, "invalid"),
- jacksonMapper.encodeToString(givenPriceFloorRules()))));
+ jacksonMapper.encodeToString(givenPriceFloorData()))));
// when
priceFloorFetcher.fetch(givenAccount(identity()));
@@ -267,7 +267,7 @@ public void fetchShouldNotPrepareAnyRequestsWhenFetchUrlIsMalformedAndReturnErro
// then
verifyNoInteractions(httpClient);
- assertThat(fetchResult.getRules()).isNull();
+ assertThat(fetchResult.getRulesData()).isNull();
assertThat(fetchResult.getFetchStatus()).isEqualTo(FetchStatus.error);
verifyNoInteractions(vertx);
}
@@ -279,7 +279,7 @@ public void fetchShouldNotPrepareAnyRequestsWhenFetchUrlIsBlankAndReturnErrorSta
// then
verifyNoInteractions(httpClient);
- assertThat(fetchResult.getRules()).isNull();
+ assertThat(fetchResult.getRulesData()).isNull();
assertThat(fetchResult.getFetchStatus()).isEqualTo(FetchStatus.error);
verifyNoInteractions(vertx);
}
@@ -291,7 +291,7 @@ public void fetchShouldNotPrepareAnyRequestsWhenFetchUrlIsNotProvidedAndReturnEr
// then
verifyNoInteractions(httpClient);
- assertThat(fetchResult.getRules()).isNull();
+ assertThat(fetchResult.getRulesData()).isNull();
assertThat(fetchResult.getFetchStatus()).isEqualTo(FetchStatus.error);
verifyNoInteractions(vertx);
}
@@ -303,7 +303,7 @@ public void fetchShouldNotPrepareAnyRequestsWhenFetchEnabledIsFalseAndReturnNone
// then
verifyNoInteractions(httpClient);
- assertThat(fetchResult.getRules()).isNull();
+ assertThat(fetchResult.getRulesData()).isNull();
assertThat(fetchResult.getFetchStatus()).isEqualTo(FetchStatus.none);
verifyNoInteractions(vertx);
}
@@ -320,12 +320,12 @@ public void fetchShouldReturnEmptyRulesAndErrorStatusForSecondCallAndCreatePerio
// then
verify(httpClient).get(anyString(), anyLong(), anyLong());
- assertThat(firstInvocationResult.getRules()).isNull();
+ assertThat(firstInvocationResult.getRulesData()).isNull();
assertThat(firstInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.inprogress);
verify(vertx).setTimer(eq(1700000L), any());
verify(vertx).setTimer(eq(1500000L), any());
final FetchResult secondInvocationResult = priceFloorFetcher.fetch(givenAccount(identity()));
- assertThat(secondInvocationResult.getRules()).isNull();
+ assertThat(secondInvocationResult.getRulesData()).isNull();
assertThat(secondInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.error);
verifyNoMoreInteractions(vertx);
}
@@ -342,12 +342,12 @@ public void fetchShouldReturnEmptyRulesWithErrorStatusAndCreatePeriodicTimerWhen
// then
verify(httpClient).get(anyString(), anyLong(), anyLong());
- assertThat(firstInvocationResult.getRules()).isNull();
+ assertThat(firstInvocationResult.getRulesData()).isNull();
assertThat(firstInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.inprogress);
verify(vertx).setTimer(eq(1700000L), any());
verify(vertx).setTimer(eq(1500000L), any());
final FetchResult secondInvocationResult = priceFloorFetcher.fetch(givenAccount(identity()));
- assertThat(secondInvocationResult.getRules()).isNull();
+ assertThat(secondInvocationResult.getRulesData()).isNull();
assertThat(secondInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.error);
verifyNoMoreInteractions(vertx);
}
@@ -364,12 +364,12 @@ public void fetchShouldReturnEmptyRulesWithErrorStatusForSecondCallAndCreatePeri
// then
verify(httpClient).get(anyString(), anyLong(), anyLong());
- assertThat(firstInvocationResult.getRules()).isNull();
+ assertThat(firstInvocationResult.getRulesData()).isNull();
assertThat(firstInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.inprogress);
verify(vertx).setTimer(eq(1700000L), any());
verify(vertx).setTimer(eq(1500000L), any());
final FetchResult secondInvocationResult = priceFloorFetcher.fetch(givenAccount(identity()));
- assertThat(secondInvocationResult.getRules()).isNull();
+ assertThat(secondInvocationResult.getRulesData()).isNull();
assertThat(secondInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.error);
verifyNoMoreInteractions(vertx);
}
@@ -386,12 +386,12 @@ public void fetchShouldReturnEmptyRulesWithErrorStatusForSecondCallAndCreatePeri
// then
verify(httpClient).get(anyString(), anyLong(), anyLong());
- assertThat(firstInvocationResult.getRules()).isNull();
+ assertThat(firstInvocationResult.getRulesData()).isNull();
assertThat(firstInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.inprogress);
verify(vertx).setTimer(eq(1700000L), any());
verify(vertx).setTimer(eq(1500000L), any());
final FetchResult secondInvocationResult = priceFloorFetcher.fetch(givenAccount(identity()));
- assertThat(secondInvocationResult.getRules()).isNull();
+ assertThat(secondInvocationResult.getRulesData()).isNull();
assertThat(secondInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.error);
verifyNoMoreInteractions(vertx);
}
@@ -410,16 +410,16 @@ public void fetchShouldNotCallPriceFloorProviderWhileFetchIsAlreadyInProgress()
verify(httpClient).get(anyString(), anyLong(), anyLong());
verifyNoMoreInteractions(httpClient);
- assertThat(secondFetch.getRules()).isNull();
+ assertThat(secondFetch.getRulesData()).isNull();
assertThat(secondFetch.getFetchStatus()).isEqualTo(FetchStatus.inprogress);
fetchPromise.tryComplete(
HttpClientResponse.of(200, MultiMap.caseInsensitiveMultiMap()
.add(HttpHeaders.CACHE_CONTROL, "max-age==3"),
- jacksonMapper.encodeToString(givenPriceFloorRules())));
+ jacksonMapper.encodeToString(givenPriceFloorData())));
- final PriceFloorRules thirdFetch = priceFloorFetcher.fetch(givenAccount(identity())).getRules();
- assertThat(thirdFetch).isEqualTo(givenPriceFloorRules());
+ final PriceFloorData thirdFetch = priceFloorFetcher.fetch(givenAccount(identity())).getRulesData();
+ assertThat(thirdFetch).isEqualTo(givenPriceFloorData());
}
@Test
@@ -428,13 +428,11 @@ public void fetchShouldReturnNullAndCreatePeriodicTimerWhenResponseExceededRules
given(httpClient.get(anyString(), anyLong(), anyLong()))
.willReturn(Future.succeededFuture(HttpClientResponse.of(200,
MultiMap.caseInsensitiveMultiMap(),
- jacksonMapper.encodeToString(givenPriceFloorRules().toBuilder()
- .data(PriceFloorData.builder()
+ jacksonMapper.encodeToString(PriceFloorData.builder()
.modelGroups(singletonList(PriceFloorModelGroup.builder()
.value("video", BigDecimal.ONE).value("banner", BigDecimal.TEN)
.build()))
- .build())
- .build()))));
+ .build()))));
// when
final FetchResult firstInvocationResult =
@@ -442,12 +440,12 @@ public void fetchShouldReturnNullAndCreatePeriodicTimerWhenResponseExceededRules
// then
verify(httpClient).get(anyString(), anyLong(), anyLong());
- assertThat(firstInvocationResult.getRules()).isNull();
+ assertThat(firstInvocationResult.getRulesData()).isNull();
assertThat(firstInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.inprogress);
verify(vertx).setTimer(eq(1700000L), any());
verify(vertx).setTimer(eq(1500000L), any());
final FetchResult secondInvocationResult = priceFloorFetcher.fetch(givenAccount(identity()));
- assertThat(secondInvocationResult.getRules()).isNull();
+ assertThat(secondInvocationResult.getRulesData()).isNull();
assertThat(secondInvocationResult.getFetchStatus()).isEqualTo(FetchStatus.error);
verifyNoMoreInteractions(vertx);
}
@@ -478,17 +476,14 @@ private static AccountPriceFloorsFetchConfig givenFetchConfig(
.build();
}
- private PriceFloorRules givenPriceFloorRules() {
- return PriceFloorRules.builder()
- .data(PriceFloorData.builder()
- .currency("USD")
- .modelGroups(singletonList(PriceFloorModelGroup.builder()
- .modelVersion("model version 1.0")
- .schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.mediaType)))
- .value("banner", BigDecimal.TEN)
- .currency("EUR").build()))
- .build())
- .skipRate(60)
+ private PriceFloorData givenPriceFloorData() {
+ return PriceFloorData.builder()
+ .currency("USD")
+ .modelGroups(singletonList(PriceFloorModelGroup.builder()
+ .modelVersion("model version 1.0")
+ .schema(PriceFloorSchema.of("|", singletonList(PriceFloorField.mediaType)))
+ .value("banner", BigDecimal.TEN)
+ .currency("EUR").build()))
.build();
}
}
diff --git a/src/test/java/org/prebid/server/floors/PriceFloorRulesValidatorTest.java b/src/test/java/org/prebid/server/floors/PriceFloorRulesValidatorTest.java
new file mode 100644
index 00000000000..90689b73ce1
--- /dev/null
+++ b/src/test/java/org/prebid/server/floors/PriceFloorRulesValidatorTest.java
@@ -0,0 +1,200 @@
+package org.prebid.server.floors;
+
+import org.junit.Test;
+import org.prebid.server.VertxTest;
+import org.prebid.server.exception.PreBidException;
+import org.prebid.server.floors.model.PriceFloorData;
+import org.prebid.server.floors.model.PriceFloorModelGroup;
+import org.prebid.server.floors.model.PriceFloorRules;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+public class PriceFloorRulesValidatorTest extends VertxTest {
+
+ @Test
+ public void validateShouldThrowExceptionOnInvalidRootSkipRateWhenPresent() {
+ // given
+ final PriceFloorRules priceFloorRules = givenPriceFloorRules(rulesBuilder -> rulesBuilder.skipRate(-1));
+
+ assertThatExceptionOfType(PreBidException.class)
+ .isThrownBy(() -> PriceFloorRulesValidator.validateRules(priceFloorRules, 100))
+ .withMessage("Price floor root skipRate must be in range(0-100), but was -1");
+ }
+
+ @Test
+ public void validateShouldThrowExceptionWhenFloorMinPresentAndLessThanZero() {
+ // given
+ final PriceFloorRules priceFloorRules = givenPriceFloorRules(
+ rulesBuilder -> rulesBuilder.floorMin(BigDecimal.valueOf(-1)));
+
+ // when and then
+ assertThatExceptionOfType(PreBidException.class)
+ .isThrownBy(() -> PriceFloorRulesValidator.validateRules(priceFloorRules, 100))
+ .withMessage("Price floor floorMin must be positive float, but was -1");
+ }
+
+ @Test
+ public void validateShouldThrowExceptionWhenDataIsAbsent() {
+ // given
+ final PriceFloorRules priceFloorRules = givenPriceFloorRules(rulesBuilder -> rulesBuilder.data(null));
+
+ // when and then
+ assertThatExceptionOfType(PreBidException.class)
+ .isThrownBy(() -> PriceFloorRulesValidator.validateRules(priceFloorRules, 100))
+ .withMessage("Price floor rules data must be present");
+ }
+
+ @Test
+ public void validateShouldThrowExceptionOnInvalidDataSkipRateWhenPresent() {
+ // given
+ final PriceFloorRules priceFloorRules = givenPriceFloorRulesWithData(dataBuilder -> dataBuilder.skipRate(-1));
+
+ // when and then
+ assertThatExceptionOfType(PreBidException.class)
+ .isThrownBy(() -> PriceFloorRulesValidator.validateRules(priceFloorRules, 100))
+ .withMessage("Price floor data skipRate must be in range(0-100), but was -1");
+ }
+
+ @Test
+ public void validateShouldThrowExceptionOnAbsentDataModelGroups() {
+ // given
+ final PriceFloorRules priceFloorRules = givenPriceFloorRulesWithData(
+ dataBuilder -> dataBuilder.modelGroups(null));
+
+ // when and then
+ assertThatExceptionOfType(PreBidException.class)
+ .isThrownBy(() -> PriceFloorRulesValidator.validateRules(priceFloorRules, 100))
+ .withMessage("Price floor rules should contain at least one model group");
+ }
+
+ @Test
+ public void validateShouldThrowExceptionOnEmptyDataModelGroups() {
+ // given
+ final PriceFloorRules priceFloorRules = givenPriceFloorRulesWithData(
+ dataBuilder -> dataBuilder.modelGroups(Collections.emptyList()));
+
+ // when and then
+ assertThatExceptionOfType(PreBidException.class)
+ .isThrownBy(() -> PriceFloorRulesValidator.validateRules(priceFloorRules, 100))
+ .withMessage("Price floor rules should contain at least one model group");
+ }
+
+ @Test
+ public void validateShouldThrowExceptionOnInvalidDataModelGroupModelWeightWhenPresent() {
+ // given
+ final PriceFloorRules priceFloorRules = givenPriceFloorRulesWithDataModelGroups(
+ modelGroupBuilder -> modelGroupBuilder.modelWeight(-1));
+
+ // when and then
+ assertThatExceptionOfType(PreBidException.class)
+ .isThrownBy(() -> PriceFloorRulesValidator.validateRules(priceFloorRules, 100))
+ .withMessage("Price floor modelGroup modelWeight must be in range(1-100), but was -1");
+ }
+
+ @Test
+ public void validateShouldThrowExceptionOnInvalidDataModelGroupSkipRateWhenPresent() {
+ // given
+ final PriceFloorRules priceFloorRules = givenPriceFloorRulesWithDataModelGroups(
+ modelGroupBuilder -> modelGroupBuilder.skipRate(-1));
+
+ // when and then
+ assertThatExceptionOfType(PreBidException.class)
+ .isThrownBy(() -> PriceFloorRulesValidator.validateRules(priceFloorRules, 100))
+ .withMessage("Price floor modelGroup skipRate must be in range(0-100), but was -1");
+ }
+
+ @Test
+ public void validateShouldThrowExceptionOnInvalidDataModelGroupDefaultFloorWhenPresent() {
+ // given
+ final PriceFloorRules priceFloorRules = givenPriceFloorRulesWithDataModelGroups(
+ modelGroupBuilder -> modelGroupBuilder.defaultFloor(BigDecimal.valueOf(-1)));
+
+ // when and then
+ assertThatExceptionOfType(PreBidException.class)
+ .isThrownBy(() -> PriceFloorRulesValidator.validateRules(priceFloorRules, 100))
+ .withMessage("Price floor modelGroup default must be positive float, but was -1");
+ }
+
+ @Test
+ public void validateShouldThrowExceptionOnEmptyModelGroupValues() {
+ // given
+ final PriceFloorRules priceFloorRules = givenPriceFloorRulesWithDataModelGroups(
+ PriceFloorModelGroup.PriceFloorModelGroupBuilder::clearValues);
+
+ // when and then
+ assertThatExceptionOfType(PreBidException.class)
+ .isThrownBy(() -> PriceFloorRulesValidator.validateRules(priceFloorRules, 100))
+ .withMessage("Price floor rules values can't be null or empty, but were {}");
+ }
+
+ @Test
+ public void validateShouldThrowExceptionWhenModelGroupValuesSizeGreaterThanMaxRules() {
+ // given
+ final Map modelGroupValues = Map.of(
+ "v1", BigDecimal.TEN,
+ "v2", BigDecimal.TEN);
+
+ final PriceFloorRules priceFloorRules = givenPriceFloorRulesWithDataModelGroups(
+ modelGroupBuilder -> modelGroupBuilder.clearValues().values(modelGroupValues));
+
+ final int maxRules = modelGroupValues.size() - 1;
+
+ // when and then
+ assertThatExceptionOfType(PreBidException.class)
+ .isThrownBy(() -> PriceFloorRulesValidator.validateRules(priceFloorRules, maxRules))
+ .withMessage(
+ "Price floor rules number %s exceeded its maximum number %s",
+ modelGroupValues.size(),
+ maxRules);
+ }
+
+ private static PriceFloorRules givenPriceFloorRulesWithDataModelGroups(
+ UnaryOperator... modelGroupBuilders) {
+
+ final PriceFloorModelGroup.PriceFloorModelGroupBuilder validModelGroupBuilder =
+ PriceFloorModelGroup.builder()
+ .modelWeight(10)
+ .skipRate(10)
+ .defaultFloor(BigDecimal.TEN)
+ .values(Map.of("value", BigDecimal.TEN));
+
+ final List modelGroups = Arrays.stream(modelGroupBuilders)
+ .map(modelGroupBuilder -> modelGroupBuilder.apply(validModelGroupBuilder).build())
+ .collect(Collectors.toList());
+
+ return givenPriceFloorRulesWithData(dataBuilder -> dataBuilder.modelGroups(modelGroups));
+ }
+
+ private static PriceFloorRules givenPriceFloorRulesWithData(
+ UnaryOperator dataBuilder) {
+
+ return givenPriceFloorRules(UnaryOperator.identity(), dataBuilder);
+ }
+
+ private static PriceFloorRules givenPriceFloorRules(
+ UnaryOperator rulesBuilder) {
+
+ return givenPriceFloorRules(rulesBuilder, UnaryOperator.identity());
+ }
+
+ private static PriceFloorRules givenPriceFloorRules(
+ UnaryOperator rulesBuilder,
+ UnaryOperator dataBuilder) {
+
+ final PriceFloorRules priceFloorRules = PriceFloorRules.builder()
+ .skipRate(10)
+ .floorMin(BigDecimal.TEN)
+ .data(dataBuilder.apply(PriceFloorData.builder()).build())
+ .build();
+
+ return rulesBuilder.apply(priceFloorRules.toBuilder()).build();
+ }
+}
diff --git a/src/test/java/org/prebid/server/floors/PriceFloorsConfigResolverTest.java b/src/test/java/org/prebid/server/floors/PriceFloorsConfigResolverTest.java
index 3490e51d879..450578faf55 100644
--- a/src/test/java/org/prebid/server/floors/PriceFloorsConfigResolverTest.java
+++ b/src/test/java/org/prebid/server/floors/PriceFloorsConfigResolverTest.java
@@ -19,6 +19,7 @@
import static java.util.function.UnaryOperator.identity;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -39,6 +40,13 @@ public void setUp() {
metrics);
}
+ @Test
+ public void priceFloorsConfigResolverShouldNotCreateInstanceIfDefaultAccountIsInvalid() {
+ assertThatIllegalArgumentException().isThrownBy(() -> new PriceFloorsConfigResolver(
+ "{",
+ metrics));
+ }
+
@Test
public void updateFloorsConfigShouldNotChangeAccountIfConfigIsValid() {
// when
@@ -210,6 +218,86 @@ public void updateFloorsConfigShouldReturnDefaultConfigIfMaxFileSizeMoreThanMaxi
verify(metrics).updateAlertsConfigFailed("some-id", MetricName.price_floors);
}
+ @Test
+ public void updateFloorsConfigShouldValidateByDefaultConfigWhenAccountEnforceFloorRateIsNotPresent() {
+ // given
+ final Account givenAccount = Account.builder()
+ .id("some-id")
+ .auction(AccountAuctionConfig.builder()
+ .priceFloors(AccountPriceFloorsConfig.builder()
+ .enforceFloorsRate(null).build())
+ .build())
+ .build();
+
+ // when
+ final Future> future = testingInstance.updateFloorsConfig(givenAccount);
+
+ // then
+ assertThat(future.result())
+ .isEqualTo(withDefaultFloorsConfig(accountBuilder -> accountBuilder.id("some-id")));
+ verify(metrics).updateAlertsConfigFailed("some-id", MetricName.price_floors);
+ }
+
+ @Test
+ public void updateFloorsConfigShouldValidateByDefaultConfigWhenAccountMaxFileSizeIsNotPresent() {
+ // when
+ final Future> future = testingInstance.updateFloorsConfig(
+ accountWithFloorsFetchConfig(config -> config.maxFileSize(null)));
+
+ // then
+ assertThat(future.result())
+ .isEqualTo(withDefaultFloorsConfig(accountBuilder -> accountBuilder.id("some-id")));
+ verify(metrics).updateAlertsConfigFailed("some-id", MetricName.price_floors);
+ }
+
+ @Test
+ public void updateFloorsConfigShouldValidateByDefaultConfigWhenAccountPeriodicSecIsNotPresent() {
+ // when
+ final Future> future = testingInstance.updateFloorsConfig(
+ accountWithFloorsFetchConfig(config -> config.periodSec(null)));
+
+ // then
+ assertThat(future.result())
+ .isEqualTo(withDefaultFloorsConfig(accountBuilder -> accountBuilder.id("some-id")));
+ verify(metrics).updateAlertsConfigFailed("some-id", MetricName.price_floors);
+ }
+
+ @Test
+ public void updateFloorsConfigShouldValidateByDefaultConfigWhenAccountFetchTimeoutIsNotPresent() {
+ // when
+ final Future> future = testingInstance.updateFloorsConfig(
+ accountWithFloorsFetchConfig(config -> config.timeout(null)));
+
+ // then
+ assertThat(future.result())
+ .isEqualTo(withDefaultFloorsConfig(accountBuilder -> accountBuilder.id("some-id")));
+ verify(metrics).updateAlertsConfigFailed("some-id", MetricName.price_floors);
+ }
+
+ @Test
+ public void updateFloorsConfigShouldValidateByDefaultConfigWhenAccountMaxRulesIsNotPresent() {
+ // when
+ final Future> future = testingInstance.updateFloorsConfig(
+ accountWithFloorsFetchConfig(config -> config.maxRules(null)));
+
+ // then
+ assertThat(future.result())
+ .isEqualTo(withDefaultFloorsConfig(accountBuilder -> accountBuilder.id("some-id")));
+ verify(metrics).updateAlertsConfigFailed("some-id", MetricName.price_floors);
+ }
+
+ @Test
+ public void updateFloorsConfigShouldValidateByDefaultConfigWhenAccountMaxAgeSecIsNotPresent() {
+ // when
+ final Future> future = testingInstance.updateFloorsConfig(
+ accountWithFloorsFetchConfig(config -> config.maxAgeSec(null)));
+
+ // then
+ assertThat(future.result())
+ .isEqualTo(withDefaultFloorsConfig(accountBuilder -> accountBuilder.id("some-id")));
+ verify(metrics).updateAlertsConfigFailed("some-id", MetricName.price_floors);
+ }
+
private static Account accountWithFloorsFetchConfig(
UnaryOperator configCustomizer) {
return Account.builder()
@@ -235,7 +323,14 @@ private static Account withDefaultFloorsConfig(UnaryOperator bidders,
Customization... customizations) throws IOException, JSONException {
- final List fullCustomizations = new ArrayList<>(Arrays.asList(customizations));
- fullCustomizations.add(new Customization("ext.prebid.auctiontimestamp", (o1, o2) -> true));
- fullCustomizations.add(new Customization("ext.responsetimemillis.cache", (o1, o2) -> true));
- String expectedRequest = replaceStaticInfo(jsonFrom(file));
- for (String bidder : bidders) {
- expectedRequest = replaceBidderRelatedStaticInfo(expectedRequest, bidder);
- fullCustomizations.add(new Customization(
- String.format("ext.responsetimemillis.%s", bidder), (o1, o2) -> true));
- }
- JSONAssert.assertEquals(expectedRequest, response.asString(),
- new CustomComparator(JSONCompareMode.NON_EXTENSIBLE,
- fullCustomizations.toArray(new Customization[0])));
+ IntegrationTestsUtil.assertJsonEquals(
+ file,
+ response,
+ bidders,
+ (json, bidder) -> replaceBidderRelatedStaticInfo(json, bidder, WIREMOCK_PORT),
+ IntegrationTest::replaceStaticInfo,
+ customizations);
}
private static String replaceStaticInfo(String json) {
-
return json.replaceAll("\\{\\{ cache.endpoint }}", CACHE_ENDPOINT)
.replaceAll("\\{\\{ cache.resource_url }}", CACHE_ENDPOINT + "?uuid=")
.replaceAll("\\{\\{ cache.host }}", HOST_AND_PORT)
@@ -271,12 +258,6 @@ private static String replaceStaticInfo(String json) {
.replaceAll("\\{\\{ event.url }}", "http://localhost:8080/event?");
}
- private static String replaceBidderRelatedStaticInfo(String json, String bidder) {
-
- return json.replaceAll("\\{\\{ " + bidder + "\\.exchange_uri }}",
- "http://" + HOST_AND_PORT + "/" + bidder + "-exchange");
- }
-
static BidCacheRequestPattern equalToBidCacheRequest(String json) {
return new BidCacheRequestPattern(json);
}
diff --git a/src/test/java/org/prebid/server/it/PriceFloorsTest.java b/src/test/java/org/prebid/server/it/PriceFloorsTest.java
new file mode 100644
index 00000000000..8d6620d0869
--- /dev/null
+++ b/src/test/java/org/prebid/server/it/PriceFloorsTest.java
@@ -0,0 +1,113 @@
+package org.prebid.server.it;
+
+import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
+import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
+import org.json.JSONException;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.prebid.server.VertxTest;
+import org.prebid.server.model.Endpoint;
+import org.prebid.server.util.IntegrationTestsUtil;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.io.IOException;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.prebid.server.util.IntegrationTestsUtil.assertJsonEquals;
+import static org.prebid.server.util.IntegrationTestsUtil.jsonFrom;
+import static org.prebid.server.util.IntegrationTestsUtil.responseFor;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
+@RunWith(SpringRunner.class)
+@TestPropertySource(
+ value = {"test-application.properties"},
+ properties = {"price-floors.enabled=true", "http.port=8050", "admin.port=0"}
+)
+public class PriceFloorsTest extends VertxTest {
+
+ private static final int APP_PORT = 8050;
+ private static final int WIREMOCK_PORT = 8090;
+
+ private static final String PRICE_FLOORS = "Price Floors Test";
+ private static final String FLOORS_FROM_REQUEST = "Floors from request";
+ private static final String FLOORS_FROM_PROVIDER = "Floors from provider";
+
+ private static final RequestSpecification SPEC = IntegrationTest.spec(APP_PORT);
+
+ @ClassRule
+ public static final WireMockClassRule WIRE_MOCK_RULE = new WireMockClassRule(options().port(WIREMOCK_PORT));
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ WIRE_MOCK_RULE.stubFor(get(urlPathEqualTo("/periodic-update"))
+ .willReturn(aResponse().withBody(jsonFrom("storedrequests/test-periodic-refresh.json"))));
+ WIRE_MOCK_RULE.stubFor(get(urlPathEqualTo("/currency-rates"))
+ .willReturn(aResponse().withBody(jsonFrom("currency/latest.json"))));
+ WIRE_MOCK_RULE.stubFor(get(urlPathEqualTo("/floors-provider"))
+ .willReturn(aResponse().withBody(jsonFrom("openrtb2/floors/provided-floors.json"))));
+ }
+
+ @Test
+ public void openrtb2AuctionShouldApplyPriceFloorsForTheGenericBidder() throws IOException, JSONException {
+ // given
+ WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/generic-exchange"))
+ .inScenario(PRICE_FLOORS)
+ .whenScenarioStateIs(STARTED)
+ .withRequestBody(equalToJson(jsonFrom("openrtb2/floors/floors-test-bid-request-1.json")))
+ .willReturn(aResponse().withBody(jsonFrom("openrtb2/floors/floors-test-bid-response.json")))
+ .willSetStateTo(FLOORS_FROM_REQUEST));
+
+ // when
+ final Response firstResponse = responseFor(
+ "openrtb2/floors/floors-test-auction-request-1.json",
+ Endpoint.openrtb2_auction,
+ SPEC);
+
+ // then
+ assertJsonEquals(
+ "openrtb2/floors/floors-test-auction-response.json",
+ firstResponse,
+ singletonList("generic"),
+ PriceFloorsTest::replaceBidderRelatedStaticInfo);
+
+ // given
+ final StubMapping stubMapping = WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/generic-exchange"))
+ .inScenario(PRICE_FLOORS)
+ .whenScenarioStateIs(FLOORS_FROM_REQUEST)
+ .withRequestBody(equalToJson(jsonFrom("openrtb2/floors/floors-test-bid-request-2.json")))
+ .willReturn(aResponse().withBody(jsonFrom("openrtb2/floors/floors-test-bid-response.json")))
+ .willSetStateTo(FLOORS_FROM_PROVIDER));
+
+ // when
+ final Response secondResponse = responseFor(
+ "openrtb2/floors/floors-test-auction-request-2.json",
+ Endpoint.openrtb2_auction,
+ SPEC);
+
+ // then
+ assertThat(stubMapping.getNewScenarioState()).isEqualTo(FLOORS_FROM_PROVIDER);
+ assertJsonEquals(
+ "openrtb2/floors/floors-test-auction-response.json",
+ secondResponse,
+ singletonList("generic"),
+ PriceFloorsTest::replaceBidderRelatedStaticInfo);
+ }
+
+ private static String replaceBidderRelatedStaticInfo(String json, String bidder) {
+ return IntegrationTestsUtil.replaceBidderRelatedStaticInfo(json, bidder, WIREMOCK_PORT);
+ }
+}
diff --git a/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java b/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java
index 34aaced63e7..8fe44bad80d 100644
--- a/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java
+++ b/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java
@@ -51,7 +51,7 @@ public void setUp() {
public void getAccountByIdShouldOmitMergingWhenDefaultAccountIsNull() {
// given
enrichingApplicationSettings =
- new EnrichingApplicationSettings(null, delegate, priceFloorsConfigResolver, jsonMerger);
+ new EnrichingApplicationSettings(true, null, delegate, priceFloorsConfigResolver, jsonMerger);
final Account returnedAccount = Account.builder().build();
given(delegate.getAccountById(anyString(), any())).willReturn(Future.succeededFuture(returnedAccount));
@@ -70,6 +70,7 @@ public void getAccountByIdShouldOmitMergingWhenDefaultAccountIsNull() {
public void getAccountByIdShouldOmitMergingWhenDefaultAccountIsEmpty() {
// given
enrichingApplicationSettings = new EnrichingApplicationSettings(
+ true,
"{}",
delegate,
priceFloorsConfigResolver,
@@ -92,6 +93,7 @@ public void getAccountByIdShouldOmitMergingWhenDefaultAccountIsEmpty() {
public void getAccountByIdShouldMergeAccountWithDefaultAccount() {
// given
enrichingApplicationSettings = new EnrichingApplicationSettings(
+ true,
"{\"auction\": {\"banner-cache-ttl\": 100},"
+ "\"privacy\": {\"gdpr\": {\"enabled\": true, \"channel-enabled\": {\"web\": false}}}}",
delegate,
@@ -133,6 +135,7 @@ public void getAccountByIdShouldMergeAccountWithDefaultAccount() {
public void getAccountByIdShouldReturnDefaultAccountWhenDelegateFailed() {
// given
enrichingApplicationSettings = new EnrichingApplicationSettings(
+ false,
"{\"auction\": {\"banner-cache-ttl\": 100}}",
delegate,
priceFloorsConfigResolver,
@@ -152,10 +155,30 @@ public void getAccountByIdShouldReturnDefaultAccountWhenDelegateFailed() {
.build());
}
+ @Test
+ public void getAccountByIdShouldReturnFailedFutureWhenDelegateFailedAndEnforceValidAccountIsTrue() {
+ // given
+ enrichingApplicationSettings = new EnrichingApplicationSettings(
+ true,
+ "{\"auction\": {\"banner-cache-ttl\": 100}}",
+ delegate,
+ priceFloorsConfigResolver,
+ jsonMerger);
+
+ given(delegate.getAccountById(anyString(), any())).willReturn(Future.failedFuture("Exception"));
+
+ // when
+ final Future accountFuture = enrichingApplicationSettings.getAccountById("123", timeout);
+
+ // then
+ assertThat(accountFuture).isFailed();
+ }
+
@Test
public void getAccountByIdShouldPassOnFailureWhenDefaultAccountIsEmpty() {
// given
enrichingApplicationSettings = new EnrichingApplicationSettings(
+ true,
"{}",
delegate,
priceFloorsConfigResolver,
diff --git a/src/test/java/org/prebid/server/util/IntegrationTestsUtil.java b/src/test/java/org/prebid/server/util/IntegrationTestsUtil.java
new file mode 100644
index 00000000000..5c20b3cb6d1
--- /dev/null
+++ b/src/test/java/org/prebid/server/util/IntegrationTestsUtil.java
@@ -0,0 +1,84 @@
+package org.prebid.server.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.restassured.RestAssured;
+import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
+import org.json.JSONException;
+import org.prebid.server.it.IntegrationTest;
+import org.prebid.server.json.ObjectMapperProvider;
+import org.prebid.server.model.Endpoint;
+import org.skyscreamer.jsonassert.Customization;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.skyscreamer.jsonassert.JSONCompareMode;
+import org.skyscreamer.jsonassert.comparator.CustomComparator;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+public class IntegrationTestsUtil {
+
+ private static final ObjectMapper MAPPER = ObjectMapperProvider.mapper();
+
+ private IntegrationTestsUtil() {
+ }
+
+ public static void assertJsonEquals(String file,
+ Response response,
+ List bidders,
+ BiFunction bidderStaticInfoUpdater,
+ Function staticInfoUpdater,
+ Customization... customizations) throws IOException, JSONException {
+
+ final List fullCustomizations = new ArrayList<>(Arrays.asList(customizations));
+ fullCustomizations.add(new Customization("ext.prebid.auctiontimestamp", (o1, o2) -> true));
+ fullCustomizations.add(new Customization("ext.responsetimemillis.cache", (o1, o2) -> true));
+
+ String expectedRequest = staticInfoUpdater.apply(jsonFrom(file));
+ for (String bidder : bidders) {
+ expectedRequest = bidderStaticInfoUpdater.apply(expectedRequest, bidder);
+ fullCustomizations.add(new Customization(
+ String.format("ext.responsetimemillis.%s", bidder), (o1, o2) -> true));
+ }
+
+ JSONAssert.assertEquals(
+ expectedRequest,
+ response.asString(),
+ new CustomComparator(JSONCompareMode.NON_EXTENSIBLE, fullCustomizations.toArray(new Customization[0])));
+ }
+
+ public static void assertJsonEquals(String file,
+ Response response,
+ List bidders,
+ BiFunction bidderStaticInfoUpdater,
+ Customization... customizations) throws IOException, JSONException {
+
+ assertJsonEquals(file, response, bidders, bidderStaticInfoUpdater, Function.identity(), customizations);
+ }
+
+ public static String replaceBidderRelatedStaticInfo(String json, String bidder, int wiremockPort) {
+ return json.replaceAll("\\{\\{ " + bidder + "\\.exchange_uri }}",
+ "http://localhost:" + wiremockPort + "/" + bidder + "-exchange");
+ }
+
+ public static Response responseFor(String file, Endpoint endpoint, RequestSpecification requestSpecification)
+ throws IOException {
+
+ return RestAssured.given(requestSpecification)
+ .header("Referer", "http://www.example.com")
+ .header("X-Forwarded-For", "193.168.244.1")
+ .header("User-Agent", "userAgent")
+ .header("Origin", "http://www.example.com")
+ .body(jsonFrom(file))
+ .post(endpoint.value());
+ }
+
+ public static String jsonFrom(String file) throws IOException {
+ // workaround to clear formatting
+ return MAPPER.writeValueAsString(MAPPER.readTree(IntegrationTest.class.getResourceAsStream(file)));
+ }
+}
diff --git a/src/test/resources/org/prebid/server/functional/floor-rules.json b/src/test/resources/org/prebid/server/functional/floor-rules.json
index 8bd346f25bf..194c83ebc91 100644
--- a/src/test/resources/org/prebid/server/functional/floor-rules.json
+++ b/src/test/resources/org/prebid/server/functional/floor-rules.json
@@ -1,157 +1,155 @@
{
- "data": {
- "floorProvider": "rubicon",
- "modelGroups": [
- {
- "modelWeight": 10,
- "modelVersion": "mlcp-v1@2022-03-02-21",
- "schema": {
- "fields": [
- "domain",
- "mediaType",
- "gptSlot"
- ],
- "delimiter": "|"
- },
- "values": {
- "example.com|banner|/111/k/categorytop/footer_left/300x250": 0.07,
- "s.example.com|banner|/111/ks/itemview/bbs_view/300x250_15": 0.02,
- "s.example.com|banner|/111/ks/categorytop/300x250": 0.02,
- "bbs.example.com|banner|/111/k/itemview/bbs/footer_left/300x250": 0.04,
- "example5.com|banner|/111/t/shop/300x600": 0.02,
- "example5.com|banner|/111/t/list/search_footer_left_300x250": 0.03,
- "example2.com|banner|/111/kinarinopc/article/300x250": 0.18,
- "s.example.com|banner|/111/ks/itemview/h/footer/300x250": 0.03,
- "example6.com|banner|/111/cg/top_3rd_300x250": 0.06,
- "example5.com|banner|/111/t/shop/1st300x250": 0.02,
- "example.com|banner|/111/k/ranking/footer_right/300x250": 0.06,
- "review.example.com|banner|/111/k/itemview/review/footer_left/300x250": 0.05,
- "bbs.example.com|banner|/111/k/itemview/footer_right/300x250": 0.04,
- "example7.com|banner|/111/e/contents/footer_left_300x250": 0.02,
- "s.example.com|banner|/111/ks/news/300x250": 0.03,
- "s.example.com|banner|/111/ks/categorytop/footer/300x250": 0.02,
- "example6.com|banner|/111/cgs/ros/300x250": 0.01,
- "example7.com|banner|/111/e/contents/footer_728x90": 0.06,
- "example.com|banner|/111/k/categorytop/footer_right/300x250": 0.06,
- "s.example.com|banner|/111/ks/top_320x50": 0.02,
- "example5.com|banner|/111/t/map/middle_468x60": 0.04,
- "example4.com|*|*": 0.07,
- "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_6": 0.06,
- "news.example.com|banner|/111/k/news/footer_right/300x250": 0.08,
- "example4.com|banner|/111/kmag/1st_300x250": 0.06,
- "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_4": 0.06,
- "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_1": 0.07,
- "example.com|banner|/111/k/btf/tv/footer_right_300x250": 0.01,
- "s.example5.com|banner|/111/ts/list/300x250": 0.02,
- "bbs.example.com|banner|/111/k/itemview/bbs/middle_right/300x250_5": 0.06,
- "s.example.com|banner|/111/ks/itemview/bbs_view/300x250_10": 0.03,
- "example.com|banner|/111/k/global_search/footer_right/300x250": 0.06,
- "s.akiba-souken.com|banner|/111/as/1st_300x250": 0.03,
- "bbs.example.com|banner|/111/k/itemview/bbs/160x600": 0.06,
- "example.com|banner|/111/k/tv_728x90": 0.06,
- "example3.com|*|*": 0.02,
- "s.example.com|banner|/111/ks/ranking/middle_20/300x250": 0.05,
- "s.example.com|banner|/111/ks/itemview/review/300x250_9": 0.03,
- "example5.com|banner|/111/t/map/middle_left_300x250": 0.04,
- "s.example.com|banner|/111/ks/itemview/review/300x250_12": 0.04,
- "example.com|banner|/111/k/ranking/728x90": 0.06,
- "example6.com|banner|/111/cg/ros/footer_right_300x250": 0.07,
- "example6.com|banner|/111/cg/top_300x250": 0.09,
- "example7.com|banner|/111/es/overlay/320x50": 0.07,
- "s.example.com|banner|/111/ks/itemview/bbs/300x250": 0.08,
- "example5.com|banner|/111/t/list/search_footer_right_300x250": 0.04,
- "example.com|banner|/111/k/pricemenu/728x90": 0.06,
- "s.example.com|banner|/111/ks/itemview/320x50_lazytest": 0.03,
- "search.example.com|banner|/111/ks/itemlist/320x50": 0.03,
- "s.example.com|banner|/111/ks/categorytop/middle/320x50": 0.06,
- "example.com|*|*": 0.04,
- "example.com|banner|/111/k/itemlist/footer_right/300x250": 0.06,
- "bbs.example.com|banner|/111/k/itemview/bbs/footer_right/300x250": 0.04,
- "s.example.com|banner|/111/ks/itemview/review/300x250_3": 0.05,
- "example4.com|banner|/111/kmag/footer_left_300x250": 0.08,
- "s.example.com|banner|/111/ks/itemview/bbs/300x250_2": 0.08,
- "example.com|banner|/111/k/itemlist/728x90": 0.07,
- "example.com|banner|/111/k/ranking/middle/left/300x250": 0.09,
- "search.example.com|banner|/111/k/itemlist/160x600": 0.12,
- "example2.com|banner|/111/kinarinopc/top_300x250": 0.01,
- "s.example.com|banner|/111/ks/itemlist/footer/300x250": 0.02,
- "example2.com|banner|/111/kinarino/login": 0.09,
- "example5.com|banner|/111/t/special/4th_300x250": 0.02,
- "s.example.com|banner|/111/ks/news/320x50": 0.05,
- "s.example.com|banner|/111/ks/itemview/review/300x250_6": 0.03,
- "example.com|banner|/111/k/ranking/footer_left/300x250": 0.03,
- "bbs.example.com|banner|/111/k/itemview/bbs/middle_right/300x250_2": 0.06,
- "example4.com|banner|/111/kmag/3rd_300x250": 0.08,
- "s.example.com|banner|/111/ks/itemview/300x250": 0.04,
- "example.com|banner|/111/k/itemview/footer_left/300x250": 0.05,
- "review.example.com|banner|/111/k/itemview/review/160x600": 0.06,
- "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_5": 0.06,
- "example5.com|banner|/111/t/shop/shop_footer_left_300x250": 0.02,
- "s.example5.com|banner|/111/ts/shop/middle/300x250": 0.04,
- "bbs.example.com|banner|/111/k/itemview/bbs/middle_right/300x250_6": 0.01,
- "s.example.com|banner|/111/ks/ranking/middle_30/300x250": 0.03,
- "example.com|banner|/111/k/top_2nd_300x250": 0.01,
- "search.example.com|banner|/111/ks/itemlist/middle/320x50": 0.07,
- "search.example.com|banner|/111/k/itemlist/footer_right/300x250": 0.06,
- "s.example.com|banner|/111/ks/categorytop/320x50": 0.02,
- "s.example.com|banner|/111/ks/pricemenu/320x50": 0.02,
- "example.com|banner|/111/k/specsearch/footer/728x90": 0.01,
- "example4.com|banner|/111/4ts/ros/video_320x180": 0.03,
- "example5.com|banner|/111/t/matome/article/300x250": 0.04,
- "s.example.com|banner|/111/ks/ranking/middle_10/300x250": 0.07,
- "s.example.com|banner|/111/ks/ranking/320x50": 0.03,
- "example.com|banner|/111/k/categorytop/300x250": 0.08,
- "s.example.com|banner|/111/ks/itemview/h/320x50": 0.03,
- "example2.com|banner|/111/kinarino/article": 0.13,
- "anime.example7.com|banner|/111/ahs/overlay/320x50": 0.09,
- "bbs.example.com|banner|/111/k/itemview/bbs/middle_right/300x250_3": 0.06,
- "example.com|banner|/111/k/pricemenu/footer_right/300x250": 0.06,
- "s.example.com|banner|/111/ks/itemview/bbs/footer/300x250": 0.03,
- "s.example.com|banner|/111/ks/global_search/300x250": 0.06,
- "example2.com|*|*": 0.03,
- "s.example.com|banner|/111/ks/itemview/bbs_view/300x250_5": 0.08,
- "review.example.com|*|*": 0.04,
- "s.example.com|banner|/111/ks/itemview/review/footer/300x250": 0.03,
- "example6.com|banner|/111/cg/ros/footer_left_300x250": 0.04,
- "search.example.com|banner|/111/k/itemlist/footer_left/300x250": 0.04,
- "example.com|banner|/111/k/itemview/h/160x600": 0.06,
- "review.example.com|banner|/111/k/itemview/review/728x90": 0.05,
- "s.example.com|banner|/111/ks/tv/overlay_320x50": 0.04,
- "news.example.com|*|*": 0.07,
- "example.com|banner|/111/k/ranking/middle/right/300x250": 0.03,
- "s.example.com|banner|/111/ks/itemlist/300x250": 0.03,
- "example6.com|*|*": 0.02,
- "example4.com|banner|/111/ksmag/footer_300x250": 0.06,
- "example7.com|banner|/111/e/overlay/728x90": 0.03,
- "example4.com|banner|/111/kmag/2nd_300x250": 0.09,
- "s.example.com|banner|/111/ks/itemview/review/300x250": 0.04,
- "example.com|banner|/111/k/categorytop/728x90": 0.06,
- "example5.com|banner|/111/t/shop/shop_footer_right_300x250": 0.02,
- "bbs.example.com|banner|/111/k/itemview/footer_left/300x250": 0.04,
- "*|*|*": 0.01,
- "bbs.example.com|*|*": 0.02,
- "s.example.com|banner|/111/ks/itemview/bbs_view/300x250_20": 0.06,
- "example3.com|banner|/111/icotto_sp/article/footer_1st300x250": 0.04,
- "s.example.com|banner|/111/ks/itemlist/320x50": 0.04,
- "s.example.com|banner|/111/ks/itemview/footer/300x250": 0.03,
- "s.example.com|banner|/111/ks/itemview/h/300x250": 0.03,
- "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_3": 0.06,
- "example3.com|banner|/111/icotto_pc/2nd_300x250": 0.08,
- "example.com|banner|/111/k/pricemenu/300x250": 0.06,
- "s.example.com|banner|/111/ks/tv/middle_300x250": 0.03,
- "example7.com|banner|/111/es/contents/footer_buzz_300x250": 0.02,
- "example.com|banner|/111/k/itemview/spec/160x600": 0.07,
- "example5.com|banner|/111/t/map/middle_right_300x250": 0.04,
- "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_2": 0.06,
- "bbs.example.com|banner|/111/k/itemview/bbs/middle_right/300x250_4": 0.06,
- "search.example.com|*|*": 0.02
- },
- "default": 0.01
- }
- ],
- "modelTimestamp": 1646254800,
- "currency": "USD",
- "skipRate": 0,
- "floorsSchemaVersion": 2
- }
+ "floorProvider": "rubicon",
+ "modelGroups": [
+ {
+ "modelWeight": 10,
+ "modelVersion": "mlcp-v1@2022-03-02-21",
+ "schema": {
+ "fields": [
+ "domain",
+ "mediaType",
+ "gptSlot"
+ ],
+ "delimiter": "|"
+ },
+ "values": {
+ "example.com|banner|/111/k/categorytop/footer_left/300x250": 0.07,
+ "s.example.com|banner|/111/ks/itemview/bbs_view/300x250_15": 0.02,
+ "s.example.com|banner|/111/ks/categorytop/300x250": 0.02,
+ "bbs.example.com|banner|/111/k/itemview/bbs/footer_left/300x250": 0.04,
+ "example5.com|banner|/111/t/shop/300x600": 0.02,
+ "example5.com|banner|/111/t/list/search_footer_left_300x250": 0.03,
+ "example2.com|banner|/111/kinarinopc/article/300x250": 0.18,
+ "s.example.com|banner|/111/ks/itemview/h/footer/300x250": 0.03,
+ "example6.com|banner|/111/cg/top_3rd_300x250": 0.06,
+ "example5.com|banner|/111/t/shop/1st300x250": 0.02,
+ "example.com|banner|/111/k/ranking/footer_right/300x250": 0.06,
+ "review.example.com|banner|/111/k/itemview/review/footer_left/300x250": 0.05,
+ "bbs.example.com|banner|/111/k/itemview/footer_right/300x250": 0.04,
+ "example7.com|banner|/111/e/contents/footer_left_300x250": 0.02,
+ "s.example.com|banner|/111/ks/news/300x250": 0.03,
+ "s.example.com|banner|/111/ks/categorytop/footer/300x250": 0.02,
+ "example6.com|banner|/111/cgs/ros/300x250": 0.01,
+ "example7.com|banner|/111/e/contents/footer_728x90": 0.06,
+ "example.com|banner|/111/k/categorytop/footer_right/300x250": 0.06,
+ "s.example.com|banner|/111/ks/top_320x50": 0.02,
+ "example5.com|banner|/111/t/map/middle_468x60": 0.04,
+ "example4.com|*|*": 0.07,
+ "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_6": 0.06,
+ "news.example.com|banner|/111/k/news/footer_right/300x250": 0.08,
+ "example4.com|banner|/111/kmag/1st_300x250": 0.06,
+ "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_4": 0.06,
+ "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_1": 0.07,
+ "example.com|banner|/111/k/btf/tv/footer_right_300x250": 0.01,
+ "s.example5.com|banner|/111/ts/list/300x250": 0.02,
+ "bbs.example.com|banner|/111/k/itemview/bbs/middle_right/300x250_5": 0.06,
+ "s.example.com|banner|/111/ks/itemview/bbs_view/300x250_10": 0.03,
+ "example.com|banner|/111/k/global_search/footer_right/300x250": 0.06,
+ "s.akiba-souken.com|banner|/111/as/1st_300x250": 0.03,
+ "bbs.example.com|banner|/111/k/itemview/bbs/160x600": 0.06,
+ "example.com|banner|/111/k/tv_728x90": 0.06,
+ "example3.com|*|*": 0.02,
+ "s.example.com|banner|/111/ks/ranking/middle_20/300x250": 0.05,
+ "s.example.com|banner|/111/ks/itemview/review/300x250_9": 0.03,
+ "example5.com|banner|/111/t/map/middle_left_300x250": 0.04,
+ "s.example.com|banner|/111/ks/itemview/review/300x250_12": 0.04,
+ "example.com|banner|/111/k/ranking/728x90": 0.06,
+ "example6.com|banner|/111/cg/ros/footer_right_300x250": 0.07,
+ "example6.com|banner|/111/cg/top_300x250": 0.09,
+ "example7.com|banner|/111/es/overlay/320x50": 0.07,
+ "s.example.com|banner|/111/ks/itemview/bbs/300x250": 0.08,
+ "example5.com|banner|/111/t/list/search_footer_right_300x250": 0.04,
+ "example.com|banner|/111/k/pricemenu/728x90": 0.06,
+ "s.example.com|banner|/111/ks/itemview/320x50_lazytest": 0.03,
+ "search.example.com|banner|/111/ks/itemlist/320x50": 0.03,
+ "s.example.com|banner|/111/ks/categorytop/middle/320x50": 0.06,
+ "example.com|*|*": 0.04,
+ "example.com|banner|/111/k/itemlist/footer_right/300x250": 0.06,
+ "bbs.example.com|banner|/111/k/itemview/bbs/footer_right/300x250": 0.04,
+ "s.example.com|banner|/111/ks/itemview/review/300x250_3": 0.05,
+ "example4.com|banner|/111/kmag/footer_left_300x250": 0.08,
+ "s.example.com|banner|/111/ks/itemview/bbs/300x250_2": 0.08,
+ "example.com|banner|/111/k/itemlist/728x90": 0.07,
+ "example.com|banner|/111/k/ranking/middle/left/300x250": 0.09,
+ "search.example.com|banner|/111/k/itemlist/160x600": 0.12,
+ "example2.com|banner|/111/kinarinopc/top_300x250": 0.01,
+ "s.example.com|banner|/111/ks/itemlist/footer/300x250": 0.02,
+ "example2.com|banner|/111/kinarino/login": 0.09,
+ "example5.com|banner|/111/t/special/4th_300x250": 0.02,
+ "s.example.com|banner|/111/ks/news/320x50": 0.05,
+ "s.example.com|banner|/111/ks/itemview/review/300x250_6": 0.03,
+ "example.com|banner|/111/k/ranking/footer_left/300x250": 0.03,
+ "bbs.example.com|banner|/111/k/itemview/bbs/middle_right/300x250_2": 0.06,
+ "example4.com|banner|/111/kmag/3rd_300x250": 0.08,
+ "s.example.com|banner|/111/ks/itemview/300x250": 0.04,
+ "example.com|banner|/111/k/itemview/footer_left/300x250": 0.05,
+ "review.example.com|banner|/111/k/itemview/review/160x600": 0.06,
+ "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_5": 0.06,
+ "example5.com|banner|/111/t/shop/shop_footer_left_300x250": 0.02,
+ "s.example5.com|banner|/111/ts/shop/middle/300x250": 0.04,
+ "bbs.example.com|banner|/111/k/itemview/bbs/middle_right/300x250_6": 0.01,
+ "s.example.com|banner|/111/ks/ranking/middle_30/300x250": 0.03,
+ "example.com|banner|/111/k/top_2nd_300x250": 0.01,
+ "search.example.com|banner|/111/ks/itemlist/middle/320x50": 0.07,
+ "search.example.com|banner|/111/k/itemlist/footer_right/300x250": 0.06,
+ "s.example.com|banner|/111/ks/categorytop/320x50": 0.02,
+ "s.example.com|banner|/111/ks/pricemenu/320x50": 0.02,
+ "example.com|banner|/111/k/specsearch/footer/728x90": 0.01,
+ "example4.com|banner|/111/4ts/ros/video_320x180": 0.03,
+ "example5.com|banner|/111/t/matome/article/300x250": 0.04,
+ "s.example.com|banner|/111/ks/ranking/middle_10/300x250": 0.07,
+ "s.example.com|banner|/111/ks/ranking/320x50": 0.03,
+ "example.com|banner|/111/k/categorytop/300x250": 0.08,
+ "s.example.com|banner|/111/ks/itemview/h/320x50": 0.03,
+ "example2.com|banner|/111/kinarino/article": 0.13,
+ "anime.example7.com|banner|/111/ahs/overlay/320x50": 0.09,
+ "bbs.example.com|banner|/111/k/itemview/bbs/middle_right/300x250_3": 0.06,
+ "example.com|banner|/111/k/pricemenu/footer_right/300x250": 0.06,
+ "s.example.com|banner|/111/ks/itemview/bbs/footer/300x250": 0.03,
+ "s.example.com|banner|/111/ks/global_search/300x250": 0.06,
+ "example2.com|*|*": 0.03,
+ "s.example.com|banner|/111/ks/itemview/bbs_view/300x250_5": 0.08,
+ "review.example.com|*|*": 0.04,
+ "s.example.com|banner|/111/ks/itemview/review/footer/300x250": 0.03,
+ "example6.com|banner|/111/cg/ros/footer_left_300x250": 0.04,
+ "search.example.com|banner|/111/k/itemlist/footer_left/300x250": 0.04,
+ "example.com|banner|/111/k/itemview/h/160x600": 0.06,
+ "review.example.com|banner|/111/k/itemview/review/728x90": 0.05,
+ "s.example.com|banner|/111/ks/tv/overlay_320x50": 0.04,
+ "news.example.com|*|*": 0.07,
+ "example.com|banner|/111/k/ranking/middle/right/300x250": 0.03,
+ "s.example.com|banner|/111/ks/itemlist/300x250": 0.03,
+ "example6.com|*|*": 0.02,
+ "example4.com|banner|/111/ksmag/footer_300x250": 0.06,
+ "example7.com|banner|/111/e/overlay/728x90": 0.03,
+ "example4.com|banner|/111/kmag/2nd_300x250": 0.09,
+ "s.example.com|banner|/111/ks/itemview/review/300x250": 0.04,
+ "example.com|banner|/111/k/categorytop/728x90": 0.06,
+ "example5.com|banner|/111/t/shop/shop_footer_right_300x250": 0.02,
+ "bbs.example.com|banner|/111/k/itemview/footer_left/300x250": 0.04,
+ "*|*|*": 0.01,
+ "bbs.example.com|*|*": 0.02,
+ "s.example.com|banner|/111/ks/itemview/bbs_view/300x250_20": 0.06,
+ "example3.com|banner|/111/icotto_sp/article/footer_1st300x250": 0.04,
+ "s.example.com|banner|/111/ks/itemlist/320x50": 0.04,
+ "s.example.com|banner|/111/ks/itemview/footer/300x250": 0.03,
+ "s.example.com|banner|/111/ks/itemview/h/300x250": 0.03,
+ "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_3": 0.06,
+ "example3.com|banner|/111/icotto_pc/2nd_300x250": 0.08,
+ "example.com|banner|/111/k/pricemenu/300x250": 0.06,
+ "s.example.com|banner|/111/ks/tv/middle_300x250": 0.03,
+ "example7.com|banner|/111/es/contents/footer_buzz_300x250": 0.02,
+ "example.com|banner|/111/k/itemview/spec/160x600": 0.07,
+ "example5.com|banner|/111/t/map/middle_right_300x250": 0.04,
+ "bbs.example.com|banner|/111/k/itemview/bbs/middle_left/300x250_2": 0.06,
+ "bbs.example.com|banner|/111/k/itemview/bbs/middle_right/300x250_4": 0.06,
+ "search.example.com|*|*": 0.02
+ },
+ "default": 0.01
+ }
+ ],
+ "modelTimestamp": 1646254800,
+ "currency": "USD",
+ "skipRate": 0,
+ "floorsSchemaVersion": 2
}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/aax/test-aax-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/aax/test-aax-bid-request.json
new file mode 100644
index 00000000000..ba09e5f9846
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/aax/test-aax-bid-request.json
@@ -0,0 +1,42 @@
+{
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "cid": "AAXCID",
+ "crid": "12345678"
+ }
+ }
+ }
+ ],
+ "site": {
+ "domain": "www.example.com",
+ "page": "http://www.example.com",
+ "publisher": {
+ "domain": "example.com"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "userAgent",
+ "ip": "193.168.244.1"
+ },
+ "at": 1,
+ "tmax": 5000,
+ "cur": [
+ "USD"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/aax/test-aax-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/aax/test-aax-bid-response.json
new file mode 100644
index 00000000000..4b1ecda6ece
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/aax/test-aax-bid-response.json
@@ -0,0 +1,22 @@
+ {
+ "id": "tid",
+ "seatbid": [
+ {
+ "seat": "aax",
+ "bid": [
+ {
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.500000,
+ "adid": "12345678",
+ "adm": "some-test-ad",
+ "cid": "987",
+ "crid": "12345678",
+ "h": 250,
+ "w": 300
+ }
+ ]
+ }
+ ],
+ "bidid": "bid01"
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/aax/test-auction-aax-request.json b/src/test/resources/org/prebid/server/it/openrtb2/aax/test-auction-aax-request.json
new file mode 100644
index 00000000000..7e76123f01b
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/aax/test-auction-aax-request.json
@@ -0,0 +1,24 @@
+{
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "aax": {
+ "cid": "AAXCID",
+ "crid": "12345678"
+ }
+ }
+ }
+ ],
+ "tmax": 5000,
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/aax/test-auction-aax-response.json b/src/test/resources/org/prebid/server/it/openrtb2/aax/test-auction-aax-response.json
new file mode 100644
index 00000000000..510834b5e2e
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/aax/test-auction-aax-response.json
@@ -0,0 +1,38 @@
+{
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "adid": "12345678",
+ "cid": "987",
+ "crid": "12345678",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ },
+ "origbidcpm": 0.5
+ }
+ }
+ ],
+ "seat": "aax",
+ "group": 0
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ "responsetimemillis": {
+ "aax": "{{ aax.response_time_ms }}"
+ },
+ "prebid": {
+ "auctiontimestamp": 0
+ },
+ "tmaxrequest": 5000
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-auction-request-1.json b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-auction-request-1.json
new file mode 100644
index 00000000000..a19b6560a42
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-auction-request-1.json
@@ -0,0 +1,67 @@
+{
+ "id": "request_id",
+ "imp": [
+ {
+ "id": "imp_id",
+ "banner": {
+ "w": 320,
+ "h": 250
+ },
+ "bidfloor": 4,
+ "bidfloorcur": "USD",
+ "ext": {
+ "generic": {
+ }
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "12001"
+ }
+ },
+ "tmax": 5000,
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "ext": {
+ "prebid": {
+ "floors": {
+ "data": {
+ "modelGroups": [
+ {
+ "currency": "NZD",
+ "modelWeight": 100,
+ "schema": {
+ "fields": [
+ "domain",
+ "mediaType",
+ "size"
+ ]
+ },
+ "values": {
+ "*|banner|*": 10,
+ "*|banner|320x250": 8,
+ "*|*|320x250": 6,
+ "www.example.com|*|320x250": 4,
+ "www.example.com|banner|320x250": 2,
+ "www.example.com|banner|*": 1,
+ "*|video|*": 800,
+ "*|video|640x480": 333
+ }
+ }
+ ]
+ }
+ },
+ "bidadjustmentfactors": {
+ "mediatypes": {
+ "banner": {
+ "generic": 0.5
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-auction-request-2.json b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-auction-request-2.json
new file mode 100644
index 00000000000..3c47ca50e00
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-auction-request-2.json
@@ -0,0 +1,43 @@
+{
+ "id": "request_id",
+ "imp": [
+ {
+ "id": "imp_id",
+ "banner": {
+ "w": 320,
+ "h": 250
+ },
+ "bidfloor": 4,
+ "bidfloorcur": "USD",
+ "ext": {
+ "generic": {
+ }
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "12001"
+ }
+ },
+ "tmax": 5000,
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "ext": {
+ "prebid": {
+ "floors": {
+ "floorMinCur": "NZD"
+ },
+ "bidadjustmentfactors": {
+ "mediatypes": {
+ "banner": {
+ "generic": 0.5
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-auction-response.json
new file mode 100644
index 00000000000..179a08e5cb1
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-auction-response.json
@@ -0,0 +1,36 @@
+{
+ "id": "request_id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "bid_id",
+ "impid": "imp_id",
+ "price": 8.399,
+ "adid": "2068416",
+ "cid": "8048",
+ "crid": "24080",
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ },
+ "origbidcpm": 13,
+ "origbidcur":"GBP"
+ }
+ }
+ ],
+ "seat": "generic",
+ "group": 0
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ "responsetimemillis": {
+ "generic": "{{ generic.response_time_ms }}"
+ },
+ "prebid": {
+ "auctiontimestamp": 0
+ },
+ "tmaxrequest": 5000
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-bid-request-1.json b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-bid-request-1.json
new file mode 100644
index 00000000000..1cebf4f1ffd
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-bid-request-1.json
@@ -0,0 +1,96 @@
+{
+ "id": "request_id",
+ "imp": [
+ {
+ "id": "imp_id",
+ "banner": {
+ "w": 320,
+ "h": 250
+ },
+ "bidfloor": 4,
+ "bidfloorcur": "NZD",
+ "ext": {
+ "prebid": {
+ "floors": {
+ "floorRule": "www.example.com|banner|320x250",
+ "floorRuleValue": 2,
+ "floorValue": 4
+ }
+ },
+ "bidder": {}
+ }
+ }
+ ],
+ "site": {
+ "domain": "www.example.com",
+ "page": "http://www.example.com",
+ "publisher": {
+ "id": "12001",
+ "domain": "example.com"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "userAgent",
+ "ip": "193.168.244.1"
+ },
+ "at": 1,
+ "tmax": 5000,
+ "cur": [
+ "USD"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "ext": {
+ "prebid": {
+ "bidadjustmentfactors": {
+ "mediatypes": {
+ "banner": {
+ "generic": 0.5
+ }
+ }
+ },
+ "channel": {
+ "name": "web"
+ },
+ "pbs": {
+ "endpoint": "/openrtb2/auction"
+ },
+ "floors": {
+ "data": {
+ "modelGroups": [
+ {
+ "currency": "NZD",
+ "modelWeight": 100,
+ "schema": {
+ "fields": [
+ "domain",
+ "mediaType",
+ "size"
+ ]
+ },
+ "values": {
+ "*|banner|*": 10,
+ "*|banner|320x250": 8,
+ "*|*|320x250": 6,
+ "www.example.com|*|320x250": 4,
+ "www.example.com|banner|320x250": 2,
+ "www.example.com|banner|*": 1,
+ "*|video|*": 800,
+ "*|video|640x480": 333
+ }
+ }
+ ]
+ },
+ "enabled": true,
+ "fetchStatus": "inprogress",
+ "location": "request"
+ }
+ }
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-bid-request-2.json b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-bid-request-2.json
new file mode 100644
index 00000000000..b8f0bf034a4
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-bid-request-2.json
@@ -0,0 +1,96 @@
+{
+ "id": "request_id",
+ "imp": [
+ {
+ "id": "imp_id",
+ "banner": {
+ "w": 320,
+ "h": 250
+ },
+ "bidfloor": 4,
+ "bidfloorcur": "NZD",
+ "ext": {
+ "prebid": {
+ "floors": {
+ "floorRule": "www.example.com|banner|320x250",
+ "floorRuleValue": 2,
+ "floorValue": 4
+ }
+ },
+ "bidder": {}
+ }
+ }
+ ],
+ "site": {
+ "domain": "www.example.com",
+ "page": "http://www.example.com",
+ "publisher": {
+ "id": "12001",
+ "domain": "example.com"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "userAgent",
+ "ip": "193.168.244.1"
+ },
+ "at": 1,
+ "tmax": 5000,
+ "cur": [
+ "USD"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "ext": {
+ "prebid": {
+ "bidadjustmentfactors": {
+ "mediatypes": {
+ "banner": {
+ "generic": 0.5
+ }
+ }
+ },
+ "channel": {
+ "name": "web"
+ },
+ "pbs": {
+ "endpoint": "/openrtb2/auction"
+ },
+ "floors": {
+ "data": {
+ "modelGroups": [
+ {
+ "currency": "NZD",
+ "modelWeight": 100,
+ "schema": {
+ "fields": [
+ "domain",
+ "mediaType",
+ "size"
+ ]
+ },
+ "values": {
+ "*|banner|*": 10,
+ "*|banner|320x250": 8,
+ "*|*|320x250": 6,
+ "www.example.com|*|320x250": 4,
+ "www.example.com|banner|320x250": 2,
+ "www.example.com|banner|*": 1,
+ "*|video|*": 800,
+ "*|video|640x480": 333
+ }
+ }
+ ]
+ },
+ "enabled": true,
+ "fetchStatus": "success",
+ "location": "fetch"
+ }
+ }
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-bid-response.json
new file mode 100644
index 00000000000..f6559262b7d
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/floors/floors-test-bid-response.json
@@ -0,0 +1,19 @@
+{
+ "id": "tid",
+ "cur": "GBP",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "crid": "24080",
+ "adid": "2068416",
+ "price": 13,
+ "id": "bid_id",
+ "impid": "imp_id",
+ "cid": "8048"
+ }
+ ],
+ "type": "banner"
+ }
+ ]
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/floors/provided-floors.json b/src/test/resources/org/prebid/server/it/openrtb2/floors/provided-floors.json
new file mode 100644
index 00000000000..3c4dc6adb9a
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/floors/provided-floors.json
@@ -0,0 +1,25 @@
+{
+ "modelGroups": [
+ {
+ "modelWeight": 100,
+ "currency": "NZD",
+ "schema": {
+ "fields": [
+ "domain",
+ "mediaType",
+ "size"
+ ]
+ },
+ "values": {
+ "*|banner|*": 10,
+ "*|banner|320x250": 8,
+ "*|*|320x250": 6,
+ "www.example.com|*|320x250": 4,
+ "www.example.com|banner|320x250": 2,
+ "www.example.com|banner|*": 1,
+ "*|video|*": 800,
+ "*|video|640x480": 333
+ }
+ }
+ ]
+}
diff --git a/src/test/resources/org/prebid/server/it/test-app-settings.yaml b/src/test/resources/org/prebid/server/it/test-app-settings.yaml
index 7d3c90274ca..ef28a3481be 100644
--- a/src/test/resources/org/prebid/server/it/test-app-settings.yaml
+++ b/src/test/resources/org/prebid/server/it/test-app-settings.yaml
@@ -120,6 +120,12 @@ accounts:
hook-sequence:
- module-code: sample-it-module
hook-impl-code: rejecting-processed-bidder-response
+ - id: 12001
+ auction:
+ price-floors:
+ fetch:
+ url: http://localhost:8090/floors-provider
+ enabled: true
domains:
- rubiconproject.com
- www.rubiconproject.com
diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties
index d9f05a99a54..825bcc910e3 100644
--- a/src/test/resources/org/prebid/server/it/test-application.properties
+++ b/src/test/resources/org/prebid/server/it/test-application.properties
@@ -288,6 +288,8 @@ adapters.yieldone.enabled=true
adapters.yieldone.endpoint=http://localhost:8090/yieldone-exchange
adapters.zeroclickfraud.enabled=true
adapters.zeroclickfraud.endpoint=http://{{Host}}/zeroclickfraud-exchange?sid={{SourceId}}
+adapters.aax.enabled=true
+adapters.aax.endpoint=http://localhost:8090/aax-exchange
http-client.circuit-breaker.enabled=true
http-client.circuit-breaker.idle-expire-hours=24
http-client.circuit-breaker.opening-threshold=1