From aaa4cc7ccaea1e2b6244e109d74f0def9c21576c Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko Date: Mon, 12 Feb 2024 07:27:20 -0500 Subject: [PATCH] Feature: Default account DSA support (#2955) --- .../requestfactory/Ortb2RequestFactory.java | 63 ++- .../EnrichingApplicationSettings.java | 1 + .../settings/model/AccountDsaConfig.java | 14 + .../settings/model/AccountPrivacyConfig.java | 2 + .../server/settings/model/DefaultDsa.java | 21 + .../settings/model/DsaTransparency.java | 15 + .../model/config/AccountDsaConfig.groovy | 16 + .../model/config/AccountPrivacyConfig.groovy | 1 + .../model/request/auction/Dsa.groovy | 25 ++ .../model/request/auction/DsaDataToPub.groovy | 19 + .../model/request/auction/DsaPubRender.groovy | 19 + .../model/request/auction/DsaRequired.groovy | 20 + .../request/auction/DsaTransparency.groovy | 7 +- .../auction/DsaTransparencyParam.groovy | 19 + .../model/request/auction/RegsDsa.groovy | 25 -- .../model/request/auction/RegsExt.groovy | 3 +- .../auction/ReqsDsaRequiredType.groovy | 15 - .../model/response/auction/BidExt.groovy | 3 +- .../model/response/auction/BidExtDsa.groovy | 23 - .../model/response/auction/Dsa.groovy | 28 ++ .../model/response/auction/DsaAdRender.groovy | 18 + .../server/functional/tests/DsaSpec.groovy | 293 ------------ .../functional/tests/privacy/DsaSpec.groovy | 424 ++++++++++++++++++ .../tests/privacy/PrivacyBaseSpec.groovy | 5 + .../ActivityInfrastructureCreatorTest.java | 10 +- ...countActivitiesConfigurationUtilsTest.java | 11 +- .../PrivacyEnforcementServiceTest.java | 11 +- .../Ortb2RequestFactoryTest.java | 302 +++++++++++++ .../server/handler/CookieSyncHandlerTest.java | 2 +- .../server/handler/SetuidHandlerTest.java | 2 +- .../EnrichingApplicationSettingsTest.java | 4 + .../settings/FileApplicationSettingsTest.java | 1 + .../settings/HttpApplicationSettingsTest.java | 2 +- .../settings/JdbcApplicationSettingsTest.java | 1 + 34 files changed, 1055 insertions(+), 370 deletions(-) create mode 100644 src/main/java/org/prebid/server/settings/model/AccountDsaConfig.java create mode 100644 src/main/java/org/prebid/server/settings/model/DefaultDsa.java create mode 100644 src/main/java/org/prebid/server/settings/model/DsaTransparency.java create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/AccountDsaConfig.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/auction/Dsa.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/auction/DsaDataToPub.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/auction/DsaPubRender.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/auction/DsaRequired.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/auction/DsaTransparencyParam.groovy delete mode 100644 src/test/groovy/org/prebid/server/functional/model/request/auction/ReqsDsaRequiredType.groovy delete mode 100644 src/test/groovy/org/prebid/server/functional/model/response/auction/BidExtDsa.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/auction/Dsa.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/auction/DsaAdRender.groovy delete mode 100644 src/test/groovy/org/prebid/server/functional/tests/DsaSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/tests/privacy/DsaSpec.groovy diff --git a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java index b1178d3081c..003e9d2204c 100644 --- a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java +++ b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java @@ -6,6 +6,7 @@ import com.iab.openrtb.request.Dooh; import com.iab.openrtb.request.Geo; import com.iab.openrtb.request.Publisher; +import com.iab.openrtb.request.Regs; import com.iab.openrtb.request.Site; import io.vertx.core.Future; import io.vertx.core.MultiMap; @@ -13,6 +14,7 @@ import io.vertx.core.logging.LoggerFactory; import io.vertx.ext.web.RoutingContext; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.prebid.server.activity.infrastructure.ActivityInfrastructure; @@ -52,6 +54,9 @@ import org.prebid.server.proto.openrtb.ext.FlexibleExtension; import org.prebid.server.proto.openrtb.ext.request.ExtPublisher; import org.prebid.server.proto.openrtb.ext.request.ExtPublisherPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRegs; +import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsa; +import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsaTransparency; import org.prebid.server.proto.openrtb.ext.request.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtRequestTargeting; @@ -59,8 +64,11 @@ import org.prebid.server.settings.ApplicationSettings; import org.prebid.server.settings.model.Account; import org.prebid.server.settings.model.AccountAuctionConfig; +import org.prebid.server.settings.model.AccountDsaConfig; +import org.prebid.server.settings.model.AccountPrivacyConfig; import org.prebid.server.settings.model.AccountStatus; import org.prebid.server.settings.model.AccountTargetingConfig; +import org.prebid.server.settings.model.DefaultDsa; import org.prebid.server.util.HttpUtil; import org.prebid.server.util.ObjectUtil; import org.prebid.server.validation.RequestValidator; @@ -217,16 +225,69 @@ public BidRequest enrichBidRequestWithAccountAndPrivacyData(AuctionContext aucti final Device device = bidRequest.getDevice(); final Device enrichedDevice = enrichDevice(device, privacyContext); - if (enrichedRequestExt != null || enrichedDevice != null) { + final Regs regs = bidRequest.getRegs(); + final Regs enrichedRegs = enrichRegs(regs, privacyContext, account); + + if (enrichedRequestExt != null || enrichedDevice != null || enrichedRegs != null) { return bidRequest.toBuilder() .ext(ObjectUtils.defaultIfNull(enrichedRequestExt, requestExt)) .device(ObjectUtils.defaultIfNull(enrichedDevice, device)) + .regs(ObjectUtils.defaultIfNull(enrichedRegs, regs)) .build(); } return bidRequest; } + private static Regs enrichRegs(Regs regs, PrivacyContext privacyContext, Account account) { + final ExtRegs regsExt = regs != null ? regs.getExt() : null; + final ExtRegsDsa regsExtDsa = regsExt != null ? regsExt.getDsa() : null; + if (regsExtDsa != null) { + return null; + } + + final AccountDsaConfig accountDsaConfig = Optional.ofNullable(account) + .map(Account::getPrivacy) + .map(AccountPrivacyConfig::getDsa) + .orElse(null); + final DefaultDsa defaultDsa = accountDsaConfig != null ? accountDsaConfig.getDefaultDsa() : null; + if (defaultDsa == null) { + return null; + } + + final boolean isGdprOnly = BooleanUtils.isTrue(accountDsaConfig.getGdprOnly()); + if (isGdprOnly && !privacyContext.getTcfContext().isInGdprScope()) { + return null; + } + + return Optional.ofNullable(regs) + .map(Regs::toBuilder) + .orElseGet(Regs::builder) + .ext(mapRegsExtDsa(defaultDsa, regsExt)) + .build(); + } + + private static ExtRegs mapRegsExtDsa(DefaultDsa defaultDsa, ExtRegs regsExt) { + final List enrichedDsaTransparencies = defaultDsa.getTransparency() + .stream() + .map(dsaTransparency -> ExtRegsDsaTransparency.of( + dsaTransparency.getDomain(), dsaTransparency.getDsaParams())) + .toList(); + + final ExtRegsDsa enrichedRegsExtDsa = ExtRegsDsa.of( + defaultDsa.getDsaRequired(), + defaultDsa.getPubRender(), + defaultDsa.getDataToPub(), + enrichedDsaTransparencies); + + final boolean isRegsExtPresent = regsExt != null; + return ExtRegs.of( + isRegsExtPresent ? regsExt.getGdpr() : null, + isRegsExtPresent ? regsExt.getUsPrivacy() : null, + isRegsExtPresent ? regsExt.getGpc() : null, + enrichedRegsExtDsa); + } + public Future executeEntrypointHooks(RoutingContext routingContext, String body, AuctionContext auctionContext) { diff --git a/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java b/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java index 30238a6a761..79d2bcb7ce7 100644 --- a/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java +++ b/src/main/java/org/prebid/server/settings/EnrichingApplicationSettings.java @@ -129,6 +129,7 @@ private Account validateAndModifyAccount(Account account) { .privacy(AccountPrivacyConfig.of( accountPrivacyConfig.getGdpr(), accountPrivacyConfig.getCcpa(), + accountPrivacyConfig.getDsa(), AccountActivitiesConfigurationUtils .removeInvalidRules(accountPrivacyConfig.getActivities()), accountPrivacyConfig.getModules())) diff --git a/src/main/java/org/prebid/server/settings/model/AccountDsaConfig.java b/src/main/java/org/prebid/server/settings/model/AccountDsaConfig.java new file mode 100644 index 00000000000..59d8913340e --- /dev/null +++ b/src/main/java/org/prebid/server/settings/model/AccountDsaConfig.java @@ -0,0 +1,14 @@ +package org.prebid.server.settings.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class AccountDsaConfig { + + @JsonProperty("default") + DefaultDsa defaultDsa; + + @JsonProperty("gdpr-only") + Boolean gdprOnly; +} diff --git a/src/main/java/org/prebid/server/settings/model/AccountPrivacyConfig.java b/src/main/java/org/prebid/server/settings/model/AccountPrivacyConfig.java index 77e83aadda7..4542114aa95 100644 --- a/src/main/java/org/prebid/server/settings/model/AccountPrivacyConfig.java +++ b/src/main/java/org/prebid/server/settings/model/AccountPrivacyConfig.java @@ -16,6 +16,8 @@ public class AccountPrivacyConfig { AccountCcpaConfig ccpa; + AccountDsaConfig dsa; + @JsonProperty("allowactivities") Map activities; diff --git a/src/main/java/org/prebid/server/settings/model/DefaultDsa.java b/src/main/java/org/prebid/server/settings/model/DefaultDsa.java new file mode 100644 index 00000000000..16d6e2cc29f --- /dev/null +++ b/src/main/java/org/prebid/server/settings/model/DefaultDsa.java @@ -0,0 +1,21 @@ +package org.prebid.server.settings.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +import java.util.List; + +@Value(staticConstructor = "of") +public class DefaultDsa { + + @JsonProperty("dsarequired") + Integer dsaRequired; + + @JsonProperty("pubrender") + Integer pubRender; + + @JsonProperty("datatopub") + Integer dataToPub; + + List transparency; +} diff --git a/src/main/java/org/prebid/server/settings/model/DsaTransparency.java b/src/main/java/org/prebid/server/settings/model/DsaTransparency.java new file mode 100644 index 00000000000..c3a4c6d8b22 --- /dev/null +++ b/src/main/java/org/prebid/server/settings/model/DsaTransparency.java @@ -0,0 +1,15 @@ +package org.prebid.server.settings.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +import java.util.List; + +@Value(staticConstructor = "of") +public class DsaTransparency { + + String domain; + + @JsonProperty("dsaparams") + List dsaParams; +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountDsaConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountDsaConfig.groovy new file mode 100644 index 00000000000..ef134c916fd --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountDsaConfig.groovy @@ -0,0 +1,16 @@ +package org.prebid.server.functional.model.config + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.ToString +import org.prebid.server.functional.model.request.auction.Dsa + +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) +@ToString(includeNames = true, ignoreNulls = true) +class AccountDsaConfig { + + @JsonProperty("default") + Dsa defaultDsa + Boolean gdprOnly +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy index 1fdd4ba6cc9..b6f2819adf4 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy @@ -12,6 +12,7 @@ class AccountPrivacyConfig { AccountGdprConfig gdpr AccountCcpaConfig ccpa + AccountDsaConfig dsa @JsonProperty("allowactivities") AllowActivities allowActivities List modules diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Dsa.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Dsa.groovy new file mode 100644 index 00000000000..ed0beafdc31 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Dsa.groovy @@ -0,0 +1,25 @@ +package org.prebid.server.functional.model.request.auction + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString +import org.prebid.server.functional.util.PBSUtils + +@JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) +@EqualsAndHashCode +@ToString(includeNames = true, ignoreNulls = true) +class Dsa { + + DsaRequired dsaRequired + DsaPubRender pubRender + DsaDataToPub dataToPub + List transparency + + static Dsa getDefaultDsa(DsaRequired dsaRequired = PBSUtils.getRandomEnum(DsaRequired)) { + new Dsa(dsaRequired: dsaRequired, + pubRender: PBSUtils.getRandomEnum(DsaPubRender), + dataToPub: PBSUtils.getRandomEnum(DsaDataToPub), + transparency: [DsaTransparency.defaultDsaTransparency]) + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaDataToPub.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaDataToPub.groovy new file mode 100644 index 00000000000..e2039ce523b --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaDataToPub.groovy @@ -0,0 +1,19 @@ +package org.prebid.server.functional.model.request.auction + +import com.fasterxml.jackson.annotation.JsonValue +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +enum DsaDataToPub { + + DO_NOT_SEND_TRANSPARENCY(0), + OPTIONAL_TO_SEND(1), + SEND_TRANSPARENCY(2) + + @JsonValue + final int value + + private DsaDataToPub(int value) { + this.value = value + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaPubRender.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaPubRender.groovy new file mode 100644 index 00000000000..d8c46588e0a --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaPubRender.groovy @@ -0,0 +1,19 @@ +package org.prebid.server.functional.model.request.auction + +import com.fasterxml.jackson.annotation.JsonValue +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +enum DsaPubRender { + + PUB_CANT_RENDER(0), + PUB_MIGHT_RENDER(1), + PUB_WILL_RENDER(2) + + @JsonValue + final int value + + private DsaPubRender(int value) { + this.value = value + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaRequired.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaRequired.groovy new file mode 100644 index 00000000000..e57f6a62e5b --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaRequired.groovy @@ -0,0 +1,20 @@ +package org.prebid.server.functional.model.request.auction + +import com.fasterxml.jackson.annotation.JsonValue +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +enum DsaRequired { + + NOT_REQUIRED(0), + SUPPORTED(1), + REQUIRED(2), + REQUIRED_PUBLISHER_IS_ONLINE_PLATFORM(3) + + @JsonValue + final int value + + private DsaRequired(int value) { + this.value = value + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaTransparency.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaTransparency.groovy index c396f99eeba..f0a0ece5483 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaTransparency.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaTransparency.groovy @@ -2,18 +2,19 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.util.PBSUtils @JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) +@EqualsAndHashCode @ToString(includeNames = true, ignoreNulls = true) class DsaTransparency { String domain - List dsaParams + List dsaParams - static DsaTransparency getDefaultRegsDsaTransparency() { + static DsaTransparency getDefaultDsaTransparency() { new DsaTransparency(domain: PBSUtils.randomString) } } - diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaTransparencyParam.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaTransparencyParam.groovy new file mode 100644 index 00000000000..e567f6d52ef --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/DsaTransparencyParam.groovy @@ -0,0 +1,19 @@ +package org.prebid.server.functional.model.request.auction + +import com.fasterxml.jackson.annotation.JsonValue +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +enum DsaTransparencyParam { + + PROFILING(1), + BASIC_ADVERTISING(2), + PRECISE_GEO(3) + + @JsonValue + final int value + + private DsaTransparencyParam(int value) { + this.value = value + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsDsa.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsDsa.groovy index 98db2f6469c..e69de29bb2d 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsDsa.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsDsa.groovy @@ -1,25 +0,0 @@ -package org.prebid.server.functional.model.request.auction - -import com.fasterxml.jackson.databind.PropertyNamingStrategies -import com.fasterxml.jackson.databind.annotation.JsonNaming -import groovy.transform.ToString -import org.prebid.server.functional.util.PBSUtils - -@JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) -@ToString(includeNames = true, ignoreNulls = true) -class RegsDsa { - - Integer dsaRequired - Integer pubRender - Integer dataToPub - List transparency - - static RegsDsa getDefaultRegsDsa(ReqsDsaRequiredType required) { - new RegsDsa( - dsaRequired: required.value, - pubRender: PBSUtils.getRandomNumber(0, 2), - dataToPub: PBSUtils.getRandomNumber(0, 2), - transparency: [DsaTransparency.defaultRegsDsaTransparency] - ) - } -} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy index 0ec8962c778..f235dfbd600 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy @@ -11,6 +11,5 @@ class RegsExt { Integer gdpr String usPrivacy String gpc - RegsDsa dsa - + Dsa dsa } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/ReqsDsaRequiredType.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/ReqsDsaRequiredType.groovy deleted file mode 100644 index 2cb07b350d4..00000000000 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/ReqsDsaRequiredType.groovy +++ /dev/null @@ -1,15 +0,0 @@ -package org.prebid.server.functional.model.request.auction - -import com.fasterxml.jackson.annotation.JsonValue - -enum ReqsDsaRequiredType { - - NOT_REQUIRED(0), SUPPORTED(1), REQUIRED(2), REQUIRED_PUBLISHER_ONLINE_PLATFORM(3) - - @JsonValue - final Integer value - - ReqsDsaRequiredType(Integer value) { - this.value = value - } -} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExt.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExt.groovy index 769f6ff88ff..216b78b3609 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExt.groovy @@ -9,6 +9,5 @@ class BidExt { Prebid prebid BigDecimal origbidcpm Currency origbidcur - BidExtDsa dsa - + Dsa dsa } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExtDsa.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExtDsa.groovy deleted file mode 100644 index 4712c59e76e..00000000000 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExtDsa.groovy +++ /dev/null @@ -1,23 +0,0 @@ -package org.prebid.server.functional.model.response.auction - -import groovy.transform.ToString -import org.prebid.server.functional.model.request.auction.DsaTransparency -import org.prebid.server.functional.util.PBSUtils - -@ToString(includeNames = true, ignoreNulls = true) -class BidExtDsa { - - String behalf - String paid - List transparency - Integer adrender - - static BidExtDsa getDefaultBidExtDsa() { - new BidExtDsa( - behalf: PBSUtils.randomString, - paid: PBSUtils.randomString, - adrender: PBSUtils.getRandomNumber(0, 2), - transparency: [DsaTransparency.defaultRegsDsaTransparency] - ) - } -} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Dsa.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Dsa.groovy new file mode 100644 index 00000000000..172955e2e18 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Dsa.groovy @@ -0,0 +1,28 @@ +package org.prebid.server.functional.model.response.auction + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString +import org.prebid.server.functional.model.request.auction.DsaTransparency +import org.prebid.server.functional.util.PBSUtils + +@JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) +@EqualsAndHashCode +@ToString(includeNames = true, ignoreNulls = true) +class Dsa { + + String behalf + String paid + List transparency + DsaAdRender adRender + + static Dsa getDefaultDsa() { + new Dsa( + behalf: PBSUtils.randomString, + paid: PBSUtils.randomString, + adRender: PBSUtils.getRandomEnum(DsaAdRender), + transparency: [DsaTransparency.defaultDsaTransparency] + ) + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/DsaAdRender.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/DsaAdRender.groovy new file mode 100644 index 00000000000..555310c9f7b --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/DsaAdRender.groovy @@ -0,0 +1,18 @@ +package org.prebid.server.functional.model.response.auction + +import com.fasterxml.jackson.annotation.JsonValue +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +enum DsaAdRender { + + ADVERTISER_WONT_RENDER(0), + ADVERTISER_WILL_RENDER(1) + + @JsonValue + final int value + + private DsaAdRender(int value) { + this.value = value + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/DsaSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/DsaSpec.groovy deleted file mode 100644 index 90f90c905dd..00000000000 --- a/src/test/groovy/org/prebid/server/functional/tests/DsaSpec.groovy +++ /dev/null @@ -1,293 +0,0 @@ -package org.prebid.server.functional.tests - -import org.prebid.server.functional.model.bidder.BidderName -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.RegsDsa -import org.prebid.server.functional.model.request.auction.ReqsDsaRequiredType -import org.prebid.server.functional.model.response.auction.BidExt -import org.prebid.server.functional.model.response.auction.BidExtDsa -import org.prebid.server.functional.model.response.auction.BidResponse - -import static org.prebid.server.functional.model.response.auction.ErrorType.GENERIC - -class DsaSpec extends BaseSpec { - - def "AMP request should send DSA to bidder and succeed when DSA is not required"() { - given: "Default AmpRequest" - def ampRequest = AmpRequest.defaultAmpRequest - def dsa = RegsDsa.getDefaultRegsDsa(dsaRequired) - - and: "Stored default bid request with DSA" - def ampStoredRequest = BidRequest.defaultBidRequest.tap { - regs.ext.dsa = dsa - setAccountId(ampRequest.account) - } - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - and: "Default bidder response with DSA" - def bidResponse = BidResponse.getDefaultBidResponse(ampStoredRequest).tap { - seatbid[0].bid[0].ext = bidExt - } - - and: "Set bidder response" - bidder.setResponse(ampStoredRequest.id, bidResponse) - - when: "PBS processes amp request" - def response = defaultPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should contain DSA" - def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) - verifyAll { - bidderRequests.regs.ext.dsa.dsaRequired == dsa.dsaRequired - bidderRequests.regs.ext.dsa.dataToPub == dsa.dataToPub - bidderRequests.regs.ext.dsa.pubRender == dsa.pubRender - bidderRequests.regs.ext.dsa.transparency[0].domain == dsa.transparency[0].domain - bidderRequests.regs.ext.dsa.transparency[0].dsaParams == dsa.transparency[0].dsaParams - } - - and: "Bidder response should not contain DSA" - def bidderResponse = decode(response.ext.debug.httpcalls.get(BidderName.GENERIC.value)[0].responseBody, BidResponse) - assert !bidderResponse.seatbid[0].bid[0].ext?.dsa - - and: "PBS should not log warning" - assert !response.ext.warnings - assert !response.ext.errors - - where: - dsaRequired | bidExt - ReqsDsaRequiredType.NOT_REQUIRED | null - ReqsDsaRequiredType.SUPPORTED | new BidExt(dsa: null) - } - - def "AMP request should send DSA to bidder and always succeed when bidder returns DSA"() { - given: "Default AmpRequest" - def ampRequest = AmpRequest.defaultAmpRequest - def dsa = RegsDsa.getDefaultRegsDsa(dsaRequired) - - and: "Stored default bid request with DSA" - def ampStoredRequest = BidRequest.defaultBidRequest.tap { - regs.ext.dsa = dsa - setAccountId(ampRequest.account) - } - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - and: "Default bidder response with DSA" - def bidDsa = BidExtDsa.getDefaultBidExtDsa() - def bidResponse = BidResponse.getDefaultBidResponse(ampStoredRequest).tap { - seatbid[0].bid[0].ext = new BidExt(dsa: bidDsa) - } - - and: "Set bidder response" - bidder.setResponse(ampStoredRequest.id, bidResponse) - - when: "PBS processes amp request" - def response = defaultPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should contain DSA" - def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) - verifyAll { - bidderRequests.regs.ext.dsa.dsaRequired == dsa.dsaRequired - bidderRequests.regs.ext.dsa.dataToPub == dsa.dataToPub - bidderRequests.regs.ext.dsa.pubRender == dsa.pubRender - bidderRequests.regs.ext.dsa.transparency[0].domain == dsa.transparency[0].domain - bidderRequests.regs.ext.dsa.transparency[0].dsaParams == dsa.transparency[0].dsaParams - } - - and: "Bidder response should contain DSA" - def bidderResponse = decode(response.ext.debug.httpcalls.get(BidderName.GENERIC.value)[0].responseBody, BidResponse) - def actualDsa = bidderResponse.seatbid[0].bid[0].ext.dsa - verifyAll { - actualDsa.transparency[0].domain == bidDsa.transparency[0].domain - actualDsa.transparency[0].dsaParams == bidDsa.transparency[0].dsaParams - actualDsa.adrender == bidDsa.adrender - actualDsa.behalf == bidDsa.behalf - actualDsa.paid == bidDsa.paid - } - - and: "PBS should not log warning" - assert !response.ext.warnings - assert !response.ext.errors - - where: - dsaRequired << ReqsDsaRequiredType.values() - } - - def "AMP request should send DSA to bidder and fail on response when DSA is required and bidder does not return DSA"() { - given: "Default AmpRequest" - def ampRequest = AmpRequest.defaultAmpRequest - def dsa = RegsDsa.getDefaultRegsDsa(dsaRequired) - - and: "Stored default bid request with DSA" - def ampStoredRequest = BidRequest.defaultBidRequest.tap { - regs.ext.dsa = dsa - setAccountId(ampRequest.account) - } - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) - - and: "Default bidder response with DSA" - def bidResponse = BidResponse.getDefaultBidResponse(ampStoredRequest).tap { - seatbid[0].bid[0].ext = bidExt - } - - and: "Set bidder response" - bidder.setResponse(ampStoredRequest.id, bidResponse) - - when: "PBS processes amp request" - def response = defaultPbsService.sendAmpRequest(ampRequest) - - then: "Bidder request should contain DSA" - def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) - verifyAll { - bidderRequests.regs.ext.dsa.dsaRequired == dsa.dsaRequired - bidderRequests.regs.ext.dsa.dataToPub == dsa.dataToPub - bidderRequests.regs.ext.dsa.pubRender == dsa.pubRender - bidderRequests.regs.ext.dsa.transparency[0].domain == dsa.transparency[0].domain - bidderRequests.regs.ext.dsa.transparency[0].dsaParams == dsa.transparency[0].dsaParams - } - - and: "Bidder response should not contain DSA" - def bidderResponse = decode(response.ext.debug.httpcalls.get(BidderName.GENERIC.value)[0].responseBody, BidResponse) - assert !bidderResponse.seatbid[0].bid[0].ext?.dsa - - and: "Response should contain error" - def expectedBidId = bidResponse.seatbid[0].bid[0].id - assert response.ext?.errors[GENERIC]*.code == [5] - assert response.ext?.errors[GENERIC]*.message == ["BidId `$expectedBidId` validation messages: Error: Bid \"$expectedBidId\" missing DSA"] - - where: - dsaRequired | bidExt - ReqsDsaRequiredType.REQUIRED | new BidExt(dsa: null) - ReqsDsaRequiredType.REQUIRED_PUBLISHER_ONLINE_PLATFORM | null - } - - def "Auction request should send DSA to bidder and succeeds when DSA is not required and bidder does not return DSA"() { - given: "Default bid request with DSA" - def dsa = RegsDsa.getDefaultRegsDsa(dsaRequired) - def bidRequest = BidRequest.defaultBidRequest.tap { - regs.ext.dsa = dsa - } - - and: "Default bidder response with DSA" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid[0].ext = new BidExt(dsa: null) - } - - and: "Set bidder response" - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request should contain DSA" - def bidderRequests = bidder.getBidderRequest(bidRequest.id) - verifyAll { - bidderRequests.regs.ext.dsa.dsaRequired == dsa.dsaRequired - bidderRequests.regs.ext.dsa.dataToPub == dsa.dataToPub - bidderRequests.regs.ext.dsa.pubRender == dsa.pubRender - bidderRequests.regs.ext.dsa.transparency[0].domain == dsa.transparency[0].domain - bidderRequests.regs.ext.dsa.transparency[0].dsaParams == dsa.transparency[0].dsaParams - } - - and: "DSA is not returned" - assert !response.seatbid[0].bid[0].ext.dsa - - where: - dsaRequired << [ReqsDsaRequiredType.NOT_REQUIRED, ReqsDsaRequiredType.SUPPORTED] - } - - def "Auction request should send DSA to bidder and always succeed when bidder returns DSA"() { - given: "Default bid request with DSA" - def dsa = RegsDsa.getDefaultRegsDsa(dsaRequired) - def bidRequest = BidRequest.defaultBidRequest.tap { - regs.ext.dsa = dsa - } - - and: "Default bidder response with DSA" - def bidDsa = BidExtDsa.getDefaultBidExtDsa() - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid[0].ext = new BidExt(dsa: bidDsa) - } - - and: "Set bidder response" - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request should contain DSA" - def bidderRequests = bidder.getBidderRequest(bidRequest.id) - verifyAll { - bidderRequests.regs.ext.dsa.dsaRequired == dsa.dsaRequired - bidderRequests.regs.ext.dsa.dataToPub == dsa.dataToPub - bidderRequests.regs.ext.dsa.pubRender == dsa.pubRender - bidderRequests.regs.ext.dsa.transparency[0].domain == dsa.transparency[0].domain - bidderRequests.regs.ext.dsa.transparency[0].dsaParams == dsa.transparency[0].dsaParams - } - - and: "DSA is not returned" - def actualDsa = response.seatbid[0].bid[0].ext.dsa - verifyAll { - actualDsa.transparency[0].domain == bidDsa.transparency[0].domain - actualDsa.transparency[0].dsaParams == bidDsa.transparency[0].dsaParams - actualDsa.adrender == bidDsa.adrender - actualDsa.behalf == bidDsa.behalf - actualDsa.paid == bidDsa.paid - } - - where: - dsaRequired << ReqsDsaRequiredType.values() - } - - def "Auction request should send DSA to bidder and fail on response when DSA is required and bidder does not return DSA"() { - given: "Default bid request with DSA" - def dsa = RegsDsa.getDefaultRegsDsa(dsaRequired) - def bidRequest = BidRequest.defaultBidRequest.tap { - regs.ext.dsa = dsa - } - - and: "Default bidder response with DSA" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid[0].ext = bidExt - } - - and: "Set bidder response" - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request should contain DSA" - def bidderRequests = bidder.getBidderRequest(bidRequest.id) - verifyAll { - bidderRequests.regs.ext.dsa.dsaRequired == dsa.dsaRequired - bidderRequests.regs.ext.dsa.dataToPub == dsa.dataToPub - bidderRequests.regs.ext.dsa.pubRender == dsa.pubRender - bidderRequests.regs.ext.dsa.transparency[0].domain == dsa.transparency[0].domain - bidderRequests.regs.ext.dsa.transparency[0].dsaParams == dsa.transparency[0].dsaParams - } - - and: "Response should contain error" - def expectedBidId = bidResponse.seatbid[0].bid[0].id - verifyAll { - response.seatbid.isEmpty() - response.ext?.errors[GENERIC]*.code == [5] - response.ext?.errors[GENERIC]*.message == ["BidId `$expectedBidId` validation messages: Error: Bid \"$expectedBidId\" missing DSA"] - } - - where: - dsaRequired | bidExt - ReqsDsaRequiredType.REQUIRED | new BidExt(dsa: null) - ReqsDsaRequiredType.REQUIRED_PUBLISHER_ONLINE_PLATFORM | null - } -} diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/DsaSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/DsaSpec.groovy new file mode 100644 index 00000000000..76e669f7262 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/DsaSpec.groovy @@ -0,0 +1,424 @@ +package org.prebid.server.functional.tests.privacy + +import org.prebid.server.functional.model.config.AccountDsaConfig +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.Dsa +import org.prebid.server.functional.model.request.auction.Dsa as RequestDsa +import org.prebid.server.functional.model.request.auction.DsaRequired +import org.prebid.server.functional.model.response.auction.BidExt +import org.prebid.server.functional.model.response.auction.BidResponse +import org.prebid.server.functional.model.response.auction.Dsa as BidDsa +import org.prebid.server.functional.util.PBSUtils +import org.prebid.server.functional.util.privacy.TcfConsent + +import static org.prebid.server.functional.model.response.auction.ErrorType.GENERIC +import static org.prebid.server.functional.util.privacy.TcfConsent.GENERIC_VENDOR_ID +import static org.prebid.server.functional.util.privacy.TcfConsent.PurposeId.BASIC_ADS + +class DsaSpec extends PrivacyBaseSpec { + + def "AMP request should always forward DSA to bidders"() { + given: "Default AmpRequest" + def ampRequest = AmpRequest.defaultAmpRequest + + and: "Default stored request with DSA" + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + regs.ext.dsa = dsa + setAccountId(ampRequest.account) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + privacyPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain DSA" + assert bidder.getBidderRequest(ampStoredRequest.id).regs?.ext?.dsa == dsa + + where: + dsa << [null, + new RequestDsa(), + RequestDsa.getDefaultDsa(DsaRequired.NOT_REQUIRED), + RequestDsa.getDefaultDsa(DsaRequired.SUPPORTED), + RequestDsa.getDefaultDsa(DsaRequired.REQUIRED), + RequestDsa.getDefaultDsa(DsaRequired.REQUIRED_PUBLISHER_IS_ONLINE_PLATFORM)] + } + + def "AMP request should always accept bids with DSA"() { + given: "Default AmpRequest" + def ampRequest = AmpRequest.defaultAmpRequest + + and: "Default stored request with DSA" + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + regs.ext.dsa = dsa + setAccountId(ampRequest.account) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Default bidder response with DSA" + def bidDsa = BidDsa.defaultDsa + def bidResponse = BidResponse.getDefaultBidResponse(ampStoredRequest).tap { + seatbid[0].bid[0].ext = new BidExt(dsa: bidDsa) + } + + and: "Set bidder response" + bidder.setResponse(ampStoredRequest.id, bidResponse) + + when: "PBS processes amp request" + def response = privacyPbsService.sendAmpRequest(ampRequest) + + then: "PBS should return bid" + assert response.targeting + + and: "PBS should not log warning" + assert !response.ext.warnings + assert !response.ext.errors + + where: + dsa << [null, + new RequestDsa(), + RequestDsa.getDefaultDsa(DsaRequired.NOT_REQUIRED), + RequestDsa.getDefaultDsa(DsaRequired.SUPPORTED), + RequestDsa.getDefaultDsa(DsaRequired.REQUIRED), + RequestDsa.getDefaultDsa(DsaRequired.REQUIRED_PUBLISHER_IS_ONLINE_PLATFORM)] + } + + def "AMP request should accept bids without DSA when dsarequired is #dsaRequired"() { + given: "Default AmpRequest" + def ampRequest = AmpRequest.defaultAmpRequest + def dsa = RequestDsa.getDefaultDsa(dsaRequired) + + and: "Default stored request with DSA" + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + regs.ext.dsa = dsa + setAccountId(ampRequest.account) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Default bidder response with DSA" + def bidResponse = BidResponse.getDefaultBidResponse(ampStoredRequest).tap { + seatbid[0].bid[0].ext = new BidExt(dsa: null) + } + + and: "Set bidder response" + bidder.setResponse(ampStoredRequest.id, bidResponse) + + when: "PBS processes amp request" + def response = privacyPbsService.sendAmpRequest(ampRequest) + + then: "PBS should return bid" + assert response.targeting + + and: "PBS should not log warning" + assert !response.ext.warnings + assert !response.ext.errors + + where: + dsaRequired << [DsaRequired.NOT_REQUIRED, + DsaRequired.SUPPORTED] + } + + def "AMP request should reject bids without DSA when dsarequired is #dsaRequired"() { + given: "Default AmpRequest" + def ampRequest = AmpRequest.defaultAmpRequest + def dsa = RequestDsa.getDefaultDsa(dsaRequired) + + and: "Default stored bid request with DSA" + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + regs.ext.dsa = dsa + setAccountId(ampRequest.account) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Default bidder response without DSA" + def bidResponse = BidResponse.getDefaultBidResponse(ampStoredRequest).tap { + seatbid[0].bid[0].ext = new BidExt(dsa: null) + } + + and: "Set bidder response" + bidder.setResponse(ampStoredRequest.id, bidResponse) + + when: "PBS processes amp request" + def response = privacyPbsService.sendAmpRequest(ampRequest) + + then: "PBS should reject bid" + assert !response.targeting + + and: "Response should contain an error" + def bidId = bidResponse.seatbid[0].bid[0].id + assert response.ext?.errors[GENERIC]*.code == [5] + assert response.ext?.errors[GENERIC]*.message == ["BidId `$bidId` validation messages: Error: Bid \"$bidId\" missing DSA"] + + where: + dsaRequired << [DsaRequired.REQUIRED, + DsaRequired.REQUIRED_PUBLISHER_IS_ONLINE_PLATFORM] + } + + def "Auction request should always forward DSA to bidders"() { + given: "Default bid request with DSA" + def bidRequest = BidRequest.defaultBidRequest.tap { + regs.ext.dsa = dsa + } + + when: "PBS processes auction request" + privacyPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain DSA" + assert bidder.getBidderRequest(bidRequest.id).regs?.ext?.dsa == dsa + + where: + dsa << [null, + new RequestDsa(), + RequestDsa.getDefaultDsa(DsaRequired.NOT_REQUIRED), + RequestDsa.getDefaultDsa(DsaRequired.SUPPORTED), + RequestDsa.getDefaultDsa(DsaRequired.REQUIRED), + RequestDsa.getDefaultDsa(DsaRequired.REQUIRED_PUBLISHER_IS_ONLINE_PLATFORM)] + } + + def "Auction request should always accept bids with DSA"() { + given: "Default bid request with DSA" + def bidRequest = BidRequest.defaultBidRequest.tap { + regs.ext.dsa = dsa + } + + and: "Default bidder response with DSA" + def bidDsa = BidDsa.defaultDsa + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid[0].ext = new BidExt(dsa: bidDsa) + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = privacyPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should return bid" + assert response.seatbid.bid + + and: "Returned bid should contain DSA" + assert response.seatbid[0].bid[0].ext.dsa == bidDsa + + and: "PBS should not log warning" + assert !response.ext.warnings + assert !response.ext.errors + + where: + dsa << [null, + new RequestDsa(), + RequestDsa.getDefaultDsa(DsaRequired.NOT_REQUIRED), + RequestDsa.getDefaultDsa(DsaRequired.SUPPORTED), + RequestDsa.getDefaultDsa(DsaRequired.REQUIRED), + RequestDsa.getDefaultDsa(DsaRequired.REQUIRED_PUBLISHER_IS_ONLINE_PLATFORM)] + } + + def "Auction request should accept bids without DSA when dsarequired is #dsaRequired"() { + given: "Default bid request with DSA" + def bidRequest = BidRequest.defaultBidRequest.tap { + regs.ext.dsa = RequestDsa.getDefaultDsa(dsaRequired) + } + + and: "Default bidder response with DSA" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid[0].ext = new BidExt(dsa: null) + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = privacyPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should return bid" + assert response.seatbid.bid + + and: "PBS should not log warning" + assert !response.ext.warnings + assert !response.ext.errors + + where: + dsaRequired << [DsaRequired.NOT_REQUIRED, + DsaRequired.SUPPORTED] + } + + def "Auction request should reject bids without DSA when dsarequired is #dsaRequired"() { + given: "Default bid request with DSA" + def bidRequest = BidRequest.defaultBidRequest.tap { + regs.ext.dsa = RequestDsa.getDefaultDsa(dsaRequired) + } + + and: "Default bidder response without DSA" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid[0].ext = new BidExt(dsa: null) + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = privacyPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should reject bid" + assert !response.seatbid + + and: "Response should contain an error" + def bidId = bidResponse.seatbid[0].bid[0].id + assert response.ext?.errors[GENERIC]*.code == [5] + assert response.ext?.errors[GENERIC]*.message == ["BidId `$bidId` validation messages: Error: Bid \"$bidId\" missing DSA"] + + where: + dsaRequired << [DsaRequired.REQUIRED, + DsaRequired.REQUIRED_PUBLISHER_IS_ONLINE_PLATFORM] + } + + def "Auction request should set account DSA when BidRequest DSA is null"() { + given: "Default bid request without DSA" + def accountId = PBSUtils.randomNumber.toString() + def bidRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + regs.ext.dsa = null + } + + and: "Account with default DSA config" + def accountDsa = Dsa.defaultDsa + def account = getAccountWithDsa(accountId, new AccountDsaConfig(defaultDsa: accountDsa)) + accountDao.save(account) + + when: "PBS processes auction request" + privacyPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain DSA from account config" + assert bidder.getBidderRequest(bidRequest.id).regs.ext.dsa == accountDsa + } + + def "Auction request shouldn't set account DSA when BidRequest DSA is not null"() { + given: "Default bid request with DSA" + def accountId = PBSUtils.randomNumber.toString() + def bidRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + regs.ext.dsa = requestDsa + } + + and: "Account with default DSA config" + def account = getAccountWithDsa(accountId, new AccountDsaConfig(defaultDsa: accountDsa)) + accountDao.save(account) + + when: "PBS processes auction request" + privacyPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain DSA from request" + assert bidder.getBidderRequest(bidRequest.id).regs.ext.dsa == requestDsa + + where: + requestDsa || accountDsa + new Dsa() || null + new Dsa() || Dsa.defaultDsa + Dsa.defaultDsa || null + Dsa.defaultDsa || Dsa.defaultDsa + } + + def "Auction request shouldn't populate DSA when account DSA is null and request DSA is null"() { + given: "Default bid request without DSA" + def accountId = PBSUtils.randomNumber.toString() + def bidRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + regs.ext.dsa = null + } + + and: "Account without default DSA config" + def account = getAccountWithDsa(accountId, new AccountDsaConfig(defaultDsa: null)) + accountDao.save(account) + + when: "PBS processes auction request" + privacyPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain DSA" + assert !bidder.getBidderRequest(bidRequest.id)?.regs?.ext?.dsa + } + + def "Auction request should set account DSA when gdpr-only is false and not in GDPR scope"() { + given: "Default bid request not in GDPR scope without DSA" + def accountId = PBSUtils.randomNumber.toString() + def bidRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + regs.ext.dsa = null + regs.ext.gdpr = 0 + } + + and: "Account with default DSA config" + def accountDsa = Dsa.defaultDsa + def account = getAccountWithDsa(accountId, + new AccountDsaConfig(defaultDsa: accountDsa, gdprOnly: false)) + accountDao.save(account) + + when: "PBS processes auction request" + privacyPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain DSA from account config" + assert bidder.getBidderRequest(bidRequest.id).regs.ext.dsa == accountDsa + } + + def "Auction request should set account DSA when gdpr-only is #gdprOnly and in GDPR scope"() { + given: "Default bid request in GDPR scope with DSA" + def consentString = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + + def accountId = PBSUtils.randomNumber.toString() + def bidRequest = getGdprBidRequest(consentString).tap { + setAccountId(accountId) + regs.ext.dsa = null + } + + and: "Account with default DSA config" + def accountDsa = Dsa.defaultDsa + def account = getAccountWithDsa(accountId, + new AccountDsaConfig(defaultDsa: accountDsa, gdprOnly: gdprOnly)) + accountDao.save(account) + + when: "PBS processes auction request" + privacyPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain DSA from account config" + assert bidder.getBidderRequest(bidRequest.id).regs.ext.dsa == accountDsa + + where: + gdprOnly << [true, false] + } + + def "Auction request shouldn't set account DSA when gdpr-only is true and not in GDPR scope"() { + given: "Default bid request not in GDPR scope without DSA" + def accountId = PBSUtils.randomNumber.toString() + def bidRequest = BidRequest.defaultBidRequest.tap { + setAccountId(accountId) + regs.ext.dsa = null + regs.ext.gdpr = 0 + } + + and: "Account with default DSA config" + def accountDsa = Dsa.defaultDsa + def account = getAccountWithDsa(accountId, + new AccountDsaConfig(defaultDsa: accountDsa, gdprOnly: true)) + accountDao.save(account) + + when: "PBS processes auction request" + privacyPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain DSA" + assert !bidder.getBidderRequest(bidRequest.id)?.regs?.ext?.dsa + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy index 408e6e890a2..0b917c50055 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy @@ -4,6 +4,7 @@ import org.prebid.server.functional.model.config.AccountCcpaConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountCookieSyncConfig import org.prebid.server.functional.model.config.AccountCoopSyncConfig +import org.prebid.server.functional.model.config.AccountDsaConfig import org.prebid.server.functional.model.config.AccountGdprConfig import org.prebid.server.functional.model.config.AccountGppConfig import org.prebid.server.functional.model.config.AccountPrivacyConfig @@ -162,6 +163,10 @@ abstract class PrivacyBaseSpec extends BaseSpec { getAccountWithPrivacy(accountId, new AccountPrivacyConfig(ccpa: ccpaConfig)) } + protected static Account getAccountWithDsa(String accountId, AccountDsaConfig dsaConfig) { + getAccountWithPrivacy(accountId, new AccountPrivacyConfig(dsa: dsaConfig)) + } + private static Account getAccountWithPrivacy(String accountId, AccountPrivacyConfig privacy) { new Account(uuid: accountId, config: new AccountConfig(privacy: privacy)) } diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreatorTest.java index 0931afe953e..ddef30e4900 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreatorTest.java @@ -73,7 +73,13 @@ public void parseShouldReturnExpectedResultIfAccountPrivacyNull() { @Test public void parseShouldReturnExpectedResultIfAccountPrivacyActivitiesNull() { // given - final Account account = Account.builder().privacy(AccountPrivacyConfig.of(null, null, null, null)).build(); + final Account account = Account.builder().privacy(AccountPrivacyConfig.of( + null, + null, + null, + null, + null)) + .build(); // when final Map controllers = creator.parse(account, null, debug); @@ -87,6 +93,7 @@ public void parseShouldSkipPrivacyModulesDuplicatesAndEmitWarnings() { // given final Account account = Account.builder() .privacy(AccountPrivacyConfig.of( + null, null, null, Map.of(Activity.SYNC_USER, AccountActivityConfiguration.of( @@ -109,6 +116,7 @@ public void parseShouldReturnExpectedResult() { // given final Account account = Account.builder() .privacy(AccountPrivacyConfig.of( + null, null, null, Map.of( diff --git a/src/test/java/org/prebid/server/activity/utils/AccountActivitiesConfigurationUtilsTest.java b/src/test/java/org/prebid/server/activity/utils/AccountActivitiesConfigurationUtilsTest.java index 020524a3b0f..38e85b72616 100644 --- a/src/test/java/org/prebid/server/activity/utils/AccountActivitiesConfigurationUtilsTest.java +++ b/src/test/java/org/prebid/server/activity/utils/AccountActivitiesConfigurationUtilsTest.java @@ -42,7 +42,13 @@ public void isInvalidActivitiesConfigurationShouldReturnFalseIfAccountPrivacyNul @Test public void isInvalidActivitiesConfigurationShouldReturnFalseIfAccountPrivacyActivitiesNull() { // given - final Account account = Account.builder().privacy(AccountPrivacyConfig.of(null, null, null, null)).build(); + final Account account = Account.builder().privacy(AccountPrivacyConfig.of( + null, + null, + null, + null, + null)) + .build(); // when final boolean result = AccountActivitiesConfigurationUtils.isInvalidActivitiesConfiguration(account); @@ -56,6 +62,7 @@ public void isInvalidActivitiesConfigurationShouldReturnFalseIfConfigurationVali // given final Account account = Account.builder() .privacy(AccountPrivacyConfig.of( + null, null, null, Map.of( @@ -98,6 +105,7 @@ public void isInvalidActivitiesConfigurationShouldReturnTrueOnInvalidComponentRu // given final Account account = Account.builder() .privacy(AccountPrivacyConfig.of( + null, null, null, Map.of(Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, singletonList( @@ -119,6 +127,7 @@ public void isInvalidActivitiesConfigurationShouldReturnTrueOnInvalidGeoRule() { // given final Account account = Account.builder() .privacy(AccountPrivacyConfig.of( + null, null, null, Map.of(Activity.CALL_BIDDER, AccountActivityConfiguration.of(null, singletonList( diff --git a/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java b/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java index 6b96611d462..7300468568a 100644 --- a/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java +++ b/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java @@ -532,6 +532,7 @@ public void shouldMaskForCcpaWhenAccountHasCppaConfigEnabledForRequestType() { EnabledForRequestType.of(false, false, true, false, false)) .build(), null, + null, null)) .build()) .requestTypeMetric(MetricName.openrtb2app) @@ -585,6 +586,7 @@ public void shouldMaskForCcpaWhenAccountHasCppaEnforcedTrue() { null, AccountCcpaConfig.builder().enabled(true).build(), null, + null, null)) .build()) .requestTypeMetric(MetricName.openrtb2app) @@ -638,6 +640,7 @@ public void shouldMaskForCcpaWhenAccountHasCcpaConfigEnabled() { null, AccountCcpaConfig.builder().enabled(true).build(), null, + null, null)) .build()) .requestTypeMetric(null) @@ -1518,7 +1521,12 @@ public void isCcpaEnforcedShouldReturnFalseWhenEnforcedPropertyIsTrueInConfigura final Ccpa ccpa = Ccpa.of("1YYY"); final Account account = Account.builder() - .privacy(AccountPrivacyConfig.of(null, AccountCcpaConfig.builder().enabled(false).build(), null, null)) + .privacy(AccountPrivacyConfig.of( + null, + AccountCcpaConfig.builder().enabled(false).build(), + null, + null, + null)) .build(); // when and then @@ -1552,6 +1560,7 @@ public void isCcpaEnforcedShouldReturnFalseWhenAccountCcpaConfigHasEnabledTrue() null, AccountCcpaConfig.builder().enabled(true).build(), null, + null, null)) .build(); diff --git a/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java b/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java index a65a86cd29a..9b508cdbfd1 100644 --- a/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java +++ b/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java @@ -7,6 +7,7 @@ import com.iab.openrtb.request.Dooh; import com.iab.openrtb.request.Geo; import com.iab.openrtb.request.Publisher; +import com.iab.openrtb.request.Regs; import com.iab.openrtb.request.Site; import io.vertx.core.Future; import io.vertx.core.MultiMap; @@ -59,6 +60,9 @@ import org.prebid.server.privacy.model.PrivacyContext; import org.prebid.server.proto.openrtb.ext.request.ExtPublisher; import org.prebid.server.proto.openrtb.ext.request.ExtPublisherPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRegs; +import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsa; +import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsaTransparency; import org.prebid.server.proto.openrtb.ext.request.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtRequestTargeting; @@ -66,8 +70,12 @@ import org.prebid.server.settings.ApplicationSettings; import org.prebid.server.settings.model.Account; import org.prebid.server.settings.model.AccountAuctionConfig; +import org.prebid.server.settings.model.AccountDsaConfig; +import org.prebid.server.settings.model.AccountPrivacyConfig; import org.prebid.server.settings.model.AccountStatus; import org.prebid.server.settings.model.AccountTargetingConfig; +import org.prebid.server.settings.model.DefaultDsa; +import org.prebid.server.settings.model.DsaTransparency; import org.prebid.server.validation.RequestValidator; import org.prebid.server.validation.model.ValidationResult; @@ -1452,6 +1460,300 @@ public void updateTimeoutShouldReturnContextWithUpdatedTimeoutAndBidRequestTmax( assertThat(result.getTimeout()).isEqualTo(updatedTimeout); } + @Test + public void enrichBidRequestWithAccountAndPrivacyDataShouldSetDsaFromAccountWhenRequestLacksDsa() { + // given + final String accountId = "accId"; + final BidRequest bidRequest = givenBidRequest(builder -> builder + .imp(emptyList()) + .site(Site.builder() + .publisher(Publisher.builder().id(accountId).build()) + .build())); + + final PrivacyContext privacyContext = PrivacyContext.of( + Privacy.builder() + .gdpr("") + .consentString("") + .ccpa(Ccpa.EMPTY) + .coppa(0) + .build(), + TcfContext.empty(), + ""); + + final Account account = Account.builder() + .id(accountId) + .privacy(AccountPrivacyConfig.of(null, + null, + AccountDsaConfig.of(DefaultDsa.of(0, + 1, + 2, + List.of(DsaTransparency.of("", + List.of(0)))), + null), + null, + null)) + .build(); + given(applicationSettings.getAccountById(any(), any())).willReturn(Future.succeededFuture(account)); + + final AuctionContext auctionContext = AuctionContext.builder() + .bidRequest(bidRequest) + .account(account) + .privacyContext(privacyContext) + .build(); + + // when + final BidRequest result = target.enrichBidRequestWithAccountAndPrivacyData(auctionContext); + + // then + assertThat(result) + .extracting(BidRequest::getRegs) + .extracting(Regs::getExt) + .extracting(ExtRegs::getDsa) + .satisfies(dsa -> { + assertThat(dsa.getDsaRequired()).isEqualTo(0); + assertThat(dsa.getPubRender()).isEqualTo(1); + assertThat(dsa.getDataToPub()).isEqualTo(2); + assertThat(dsa.getTransparency()).satisfies(transparencies -> + assertThat(transparencies).isEqualTo(List.of(ExtRegsDsaTransparency.of("", + List.of(0))))); + }); + } + + @Test + public void enrichBidRequestWithAccountAndPrivacyDataShouldNotSetDsaFromAccountWhenAccountLacksDefaultDsa() { + // given + final String accountId = "accId"; + final Regs regs = Regs.builder().build(); + final BidRequest bidRequest = givenBidRequest(builder -> builder + .imp(emptyList()) + .site(Site.builder() + .publisher(Publisher.builder().id(accountId).build()) + .build()) + .regs(regs)); + + final PrivacyContext privacyContext = PrivacyContext.of( + Privacy.builder() + .gdpr("") + .consentString("") + .ccpa(Ccpa.EMPTY) + .coppa(0) + .build(), + TcfContext.empty(), + ""); + + final Account account = Account.builder() + .id(accountId) + .privacy(AccountPrivacyConfig.of(null, + null, + AccountDsaConfig.of(null, + null), + null, + null)) + .build(); + given(applicationSettings.getAccountById(any(), any())).willReturn(Future.succeededFuture(account)); + + final AuctionContext auctionContext = AuctionContext.builder() + .bidRequest(bidRequest) + .account(account) + .privacyContext(privacyContext) + .build(); + + // when + final BidRequest result = target.enrichBidRequestWithAccountAndPrivacyData(auctionContext); + + // then + assertThat(result) + .extracting(BidRequest::getRegs) + .extracting(Regs::getExt) + .isNull(); + assertThat(result) + .extracting(BidRequest::getRegs) + .isSameAs(regs); + } + + @Test + public void enrichBidRequestWithAccountAndPrivacyDataShouldNotSetDsaFromAccountWhenRequestContainsDsa() { + // given + final String accountId = "accId"; + final BidRequest bidRequest = givenBidRequest(builder -> builder + .imp(emptyList()) + .site(Site.builder() + .publisher(Publisher.builder().id(accountId).build()) + .build()) + .regs(Regs.builder().ext(ExtRegs.of(null, + null, + null, + ExtRegsDsa.of(0, + 1, + 2, + List.of(ExtRegsDsaTransparency.of("", List.of(0)))))) + .build()) + ); + + final PrivacyContext privacyContext = PrivacyContext.of( + Privacy.builder() + .gdpr("") + .consentString("") + .ccpa(Ccpa.EMPTY) + .coppa(0) + .build(), + TcfContext.empty(), + ""); + + final Account account = Account.builder() + .id(accountId) + .privacy(AccountPrivacyConfig.of(null, + null, + AccountDsaConfig.of(DefaultDsa.of(3, + 4, + 5, + List.of(DsaTransparency.of("domain", + List.of(1)))), + null), + null, + null)) + .build(); + given(applicationSettings.getAccountById(any(), any())).willReturn(Future.succeededFuture(account)); + + final AuctionContext auctionContext = AuctionContext.builder() + .bidRequest(bidRequest) + .account(account) + .privacyContext(privacyContext) + .build(); + + // when + final BidRequest result = target.enrichBidRequestWithAccountAndPrivacyData(auctionContext); + + // then + assertThat(result) + .extracting(BidRequest::getRegs) + .extracting(Regs::getExt) + .extracting(ExtRegs::getDsa) + .satisfies(dsa -> { + assertThat(dsa.getDsaRequired()).isEqualTo(0); + assertThat(dsa.getPubRender()).isEqualTo(1); + assertThat(dsa.getDataToPub()).isEqualTo(2); + assertThat(dsa.getTransparency()).satisfies(transparencies -> + assertThat(transparencies).isEqualTo(List.of(ExtRegsDsaTransparency.of("", + List.of(0))))); + }); + } + + @Test + public void enrichBidRequestWithAccountAndPrivacyDataShouldSetDsaFromAccountWhenGdprScopeMatches() { + // given + final String accountId = "accId"; + final BidRequest bidRequest = givenBidRequest(builder -> builder + .imp(emptyList()) + .site(Site.builder() + .publisher(Publisher.builder().id(accountId).build()) + .build())); + + final PrivacyContext privacyContext = PrivacyContext.of( + Privacy.builder() + .gdpr("") + .consentString("") + .ccpa(Ccpa.EMPTY) + .coppa(0) + .build(), + TcfContext.builder().inGdprScope(true).build(), + ""); + + final Account account = Account.builder() + .id(accountId) + .privacy(AccountPrivacyConfig.of(null, + null, + AccountDsaConfig.of(DefaultDsa.of(0, + 1, + 2, + List.of(DsaTransparency.of("", + List.of(0)))), + true), + null, + null)) + .build(); + given(applicationSettings.getAccountById(any(), any())).willReturn(Future.succeededFuture(account)); + + final AuctionContext auctionContext = AuctionContext.builder() + .bidRequest(bidRequest) + .account(account) + .privacyContext(privacyContext) + .build(); + + // when + final BidRequest result = target.enrichBidRequestWithAccountAndPrivacyData(auctionContext); + + // then + assertThat(result) + .extracting(BidRequest::getRegs) + .extracting(Regs::getExt) + .extracting(ExtRegs::getDsa) + .satisfies(dsa -> { + assertThat(dsa.getDsaRequired()).isEqualTo(0); + assertThat(dsa.getPubRender()).isEqualTo(1); + assertThat(dsa.getDataToPub()).isEqualTo(2); + assertThat(dsa.getTransparency()).satisfies(transparencies -> + assertThat(transparencies).isEqualTo(List.of(ExtRegsDsaTransparency.of("", + List.of(0))))); + }); + } + + @Test + public void enrichBidRequestWithAccountAndPrivacyDataShouldNotSetDsaFromAccountWhenGdprScopeDoesntMatch() { + // given + final String accountId = "accId"; + final Regs regs = Regs.builder().build(); + final BidRequest bidRequest = givenBidRequest(builder -> builder + .imp(emptyList()) + .site(Site.builder() + .publisher(Publisher.builder().id(accountId).build()) + .build()) + .regs(regs)); + + final PrivacyContext privacyContext = PrivacyContext.of( + Privacy.builder() + .gdpr("") + .consentString("") + .ccpa(Ccpa.EMPTY) + .coppa(0) + .build(), + TcfContext.builder().inGdprScope(false).build(), + ""); + + final Account account = Account.builder() + .id(accountId) + .privacy(AccountPrivacyConfig.of(null, + null, + AccountDsaConfig.of(DefaultDsa.of(0, + 1, + 2, + List.of(DsaTransparency.of("", + List.of(0)))), + true), + null, + null)) + .build(); + given(applicationSettings.getAccountById(any(), any())).willReturn(Future.succeededFuture(account)); + + final AuctionContext auctionContext = AuctionContext.builder() + .bidRequest(bidRequest) + .account(account) + .privacyContext(privacyContext) + .build(); + + // when + final BidRequest result = target.enrichBidRequestWithAccountAndPrivacyData(auctionContext); + + // then + assertThat(result) + .extracting(BidRequest::getRegs) + .extracting(Regs::getExt) + .isNull(); + assertThat(result) + .extracting(BidRequest::getRegs) + .isSameAs(regs); + } + private static String bidRequestToString(BidRequest bidRequest) { try { return mapper.writeValueAsString(bidRequest); diff --git a/src/test/java/org/prebid/server/handler/CookieSyncHandlerTest.java b/src/test/java/org/prebid/server/handler/CookieSyncHandlerTest.java index 84a8d3a76a3..b6536dec25c 100644 --- a/src/test/java/org/prebid/server/handler/CookieSyncHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/CookieSyncHandlerTest.java @@ -317,7 +317,7 @@ public void shouldPassAccountToPrivacyEnforcementServiceWhenAccountIsFound() { final AccountGdprConfig accountGdprConfig = AccountGdprConfig.builder() .enabledForRequestType(EnabledForRequestType.of(true, true, true, true, true)).build(); final Account account = Account.builder() - .privacy(AccountPrivacyConfig.of(accountGdprConfig, null, null, null)) + .privacy(AccountPrivacyConfig.of(accountGdprConfig, null, null, null, null)) .build(); given(applicationSettings.getAccountById(any(), any())).willReturn(Future.succeededFuture(account)); diff --git a/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java b/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java index da2885f42d2..0f99716f44b 100644 --- a/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java @@ -394,7 +394,7 @@ public void shouldPassAccountToPrivacyEnforcementServiceWhenAccountIsFound() { .enabledForRequestType(EnabledForRequestType.of(true, true, true, true, true)) .build(); final Account account = Account.builder() - .privacy(AccountPrivacyConfig.of(accountGdprConfig, null, null, null)) + .privacy(AccountPrivacyConfig.of(accountGdprConfig, null, null, null, null)) .build(); final Future accountFuture = Future.succeededFuture(account); given(applicationSettings.getAccountById(any(), any())).willReturn(accountFuture); diff --git a/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java b/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java index 683a430edca..9de1879aab9 100644 --- a/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java +++ b/src/test/java/org/prebid/server/settings/EnrichingApplicationSettingsTest.java @@ -124,6 +124,7 @@ public void getAccountByIdShouldMergeAccountWithDefaultAccount() { .build(), null, null, + null, null)) .build())); @@ -144,6 +145,7 @@ public void getAccountByIdShouldMergeAccountWithDefaultAccount() { .build(), null, null, + null, null)) .build()); } @@ -230,6 +232,7 @@ public void getAccountByIdShouldRemoveInvalidRulesFromAccountActivitiesConfigura given(delegate.getAccountById(anyString(), any())).willReturn(Future.succeededFuture(Account.builder() .privacy(AccountPrivacyConfig.of( + null, null, null, Map.of( @@ -257,6 +260,7 @@ public void getAccountByIdShouldRemoveInvalidRulesFromAccountActivitiesConfigura // then assertThat(accountFuture).succeededWith(Account.builder() .privacy(AccountPrivacyConfig.of( + null, null, null, Map.of( diff --git a/src/test/java/org/prebid/server/settings/FileApplicationSettingsTest.java b/src/test/java/org/prebid/server/settings/FileApplicationSettingsTest.java index 505b779c8f7..711442be41e 100644 --- a/src/test/java/org/prebid/server/settings/FileApplicationSettingsTest.java +++ b/src/test/java/org/prebid/server/settings/FileApplicationSettingsTest.java @@ -173,6 +173,7 @@ public void getAccountByIdShouldReturnPresentAccount() { .build(), null, null, + null, null)) .analytics(AccountAnalyticsConfig.of( expectedEventsConfig, diff --git a/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java b/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java index 628b4feb936..cd4d0c557c2 100644 --- a/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java +++ b/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java @@ -107,7 +107,7 @@ public void getAccountByIdShouldReturnFetchedAccount() throws JsonProcessingExce .auction(AccountAuctionConfig.builder() .priceGranularity("testPriceGranularity") .build()) - .privacy(AccountPrivacyConfig.of(null, null, null, null)) + .privacy(AccountPrivacyConfig.of(null, null, null, null, null)) .build(); final HttpAccountsResponse response = HttpAccountsResponse.of(Collections.singletonMap("someId", account)); givenHttpClientReturnsResponse(200, mapper.writeValueAsString(response)); diff --git a/src/test/java/org/prebid/server/settings/JdbcApplicationSettingsTest.java b/src/test/java/org/prebid/server/settings/JdbcApplicationSettingsTest.java index a086dc27a20..53b9e518605 100644 --- a/src/test/java/org/prebid/server/settings/JdbcApplicationSettingsTest.java +++ b/src/test/java/org/prebid/server/settings/JdbcApplicationSettingsTest.java @@ -251,6 +251,7 @@ public void getAccountByIdShouldReturnAccountWithAllFieldsPopulated(TestContext .build(), null, null, + null, null)) .analytics(AccountAnalyticsConfig.of( expectedEventsConfig,