From 8ec774bcbfa8478eec0cc501df77fecddcb60fdb Mon Sep 17 00:00:00 2001 From: antonbabak Date: Thu, 23 Jan 2025 14:02:50 +0100 Subject: [PATCH 1/4] Support testing the USGen privacy module --- .../rule/PrivacyModulesRuleCreator.java | 14 +++++++++ .../privacy/AbstainPrivacyModule.java | 30 +++++++++++++++++++ .../privacy/AccountPrivacyModuleConfig.java | 3 ++ .../AccountUSCustomLogicModuleConfig.java | 2 ++ .../privacy/AccountUSNatModuleConfig.java | 2 ++ .../ActivityInfrastructureCreatorTest.java | 4 +-- .../USCustomLogicModuleCreatorTest.java | 2 +- .../privacy/usnat/USNatModuleCreatorTest.java | 2 +- .../rule/PrivacyModulesRuleCreatorTest.java | 29 +++++++++++++----- 9 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/prebid/server/activity/infrastructure/privacy/AbstainPrivacyModule.java diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java index bb68ff60df0..db8cd01036d 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java @@ -5,6 +5,7 @@ import org.prebid.server.activity.infrastructure.creator.ActivityControllerCreationContext; import org.prebid.server.activity.infrastructure.creator.PrivacyModuleCreationContext; import org.prebid.server.activity.infrastructure.creator.privacy.PrivacyModuleCreator; +import org.prebid.server.activity.infrastructure.privacy.AbstainPrivacyModule; import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; import org.prebid.server.activity.infrastructure.rule.AndRule; @@ -17,12 +18,16 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; import java.util.function.UnaryOperator; import java.util.stream.Collectors; public class PrivacyModulesRuleCreator extends AbstractRuleCreator { private static final String WILDCARD = "*"; + private static final int SKIP_RATE_MIN = 0; + private static final int SKIP_RATE_MAX = 100; private final Map privacyModulesCreators; @@ -82,6 +87,15 @@ private static boolean isModuleEnabled(AccountPrivacyModuleConfig accountPrivacy private PrivacyModule createPrivacyModule(PrivacyModuleQualifier privacyModuleQualifier, ActivityControllerCreationContext creationContext) { + final Integer skipRate = Optional.ofNullable(creationContext.getPrivacyModulesConfigs()) + .map(configs -> configs.get(privacyModuleQualifier)) + .map(AccountPrivacyModuleConfig::getSkipRate) + .orElse(SKIP_RATE_MIN); + + if (ThreadLocalRandom.current().nextInt(SKIP_RATE_MAX) < skipRate) { + return new AbstainPrivacyModule(privacyModuleQualifier); + } + return privacyModulesCreators.get(privacyModuleQualifier) .from(creationContext(privacyModuleQualifier, creationContext)); } diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/AbstainPrivacyModule.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/AbstainPrivacyModule.java new file mode 100644 index 00000000000..b1b183cc08b --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/AbstainPrivacyModule.java @@ -0,0 +1,30 @@ +package org.prebid.server.activity.infrastructure.privacy; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.prebid.server.activity.infrastructure.debug.Loggable; +import org.prebid.server.activity.infrastructure.payload.ActivityInvocationPayload; + +import java.util.Objects; + +public class AbstainPrivacyModule implements PrivacyModule, Loggable { + + private final PrivacyModuleQualifier privacyModuleQualifier; + + public AbstainPrivacyModule(PrivacyModuleQualifier privacyModuleQualifier) { + this.privacyModuleQualifier = Objects.requireNonNull(privacyModuleQualifier); + } + + @Override + public Result proceed(ActivityInvocationPayload activityInvocationPayload) { + return Result.ABSTAIN; + } + + @Override + public JsonNode asLogEntry(ObjectMapper mapper) { + return mapper.createObjectNode() + .put("privacy_module", privacyModuleQualifier.moduleName()) + .put("skipped", true) + .put("result", Result.ABSTAIN.name()); + } +} diff --git a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountPrivacyModuleConfig.java b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountPrivacyModuleConfig.java index eea8b4f644e..6c2f0183286 100644 --- a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountPrivacyModuleConfig.java +++ b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountPrivacyModuleConfig.java @@ -23,6 +23,9 @@ public sealed interface AccountPrivacyModuleConfig permits PrivacyModuleQualifier getCode(); + @JsonProperty("skipRate") + Integer getSkipRate(); + @JsonProperty Boolean enabled(); } diff --git a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSCustomLogicModuleConfig.java b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSCustomLogicModuleConfig.java index 2506321e031..9473cf67a6b 100644 --- a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSCustomLogicModuleConfig.java +++ b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSCustomLogicModuleConfig.java @@ -17,6 +17,8 @@ public class AccountUSCustomLogicModuleConfig implements AccountPrivacyModuleCon @Accessors(fluent = true) Boolean enabled; + Integer skipRate; + Config config; @Override diff --git a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java index fa51135791b..c7889a670f9 100644 --- a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java +++ b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java @@ -14,6 +14,8 @@ public class AccountUSNatModuleConfig implements AccountPrivacyModuleConfig { @Accessors(fluent = true) Boolean enabled; + Integer skipRate; + Config config; @Override 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 661d63eaa4d..1cd14a6c610 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 @@ -91,8 +91,8 @@ public void parseShouldSkipPrivacyModulesDuplicatesAndEmitWarnings() { .activities(Map.of(Activity.SYNC_USER, AccountActivityConfiguration.of( null, singletonList(AccountActivityConditionsRuleConfig.of(null, null))))) .modules(asList( - AccountUSNatModuleConfig.of(null, null), - AccountUSNatModuleConfig.of(null, null))) + AccountUSNatModuleConfig.of(null, null, null), + AccountUSNatModuleConfig.of(null, null, null))) .build()) .build(); diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreatorTest.java index d18a7b15e4b..674e0654d31 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreatorTest.java @@ -248,7 +248,7 @@ private static PrivacyModuleCreationContext givenCreationContext(List s return PrivacyModuleCreationContext.of( Activity.CALL_BIDDER, - AccountUSCustomLogicModuleConfig.of(true, config), + AccountUSCustomLogicModuleConfig.of(true, null, config), GppContextCreator.from(null, sectionsIds).build().getGppContext()); } diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreatorTest.java index 270f0f47d61..5b8d4e61bb2 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreatorTest.java @@ -126,7 +126,7 @@ private static PrivacyModuleCreationContext givenCreationContext(List s return PrivacyModuleCreationContext.of( Activity.CALL_BIDDER, - AccountUSNatModuleConfig.of(true, AccountUSNatModuleConfig.Config.of(skipSectionsIds)), + AccountUSNatModuleConfig.of(true, null, AccountUSNatModuleConfig.Config.of(skipSectionsIds)), GppContextCreator.from(null, sectionsIds).build().getGppContext()); } } diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java index 2484824be9f..0e9c55bc00f 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java @@ -56,7 +56,7 @@ public void fromShouldCreateDefaultRuleIfNoneOfConfiguredPrivacyModulesMatches() final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList("not_configured")); final ActivityControllerCreationContext creationContext = creationContext(Map.of( - PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(null, null))); + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(null, null, null))); // when final Rule rule = target.from(config, creationContext); @@ -70,7 +70,7 @@ public void fromShouldCreateRuleWithAllConfiguredPrivacyModules() { // given final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList("*")); - final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null); + final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null, null); final ActivityControllerCreationContext creationContext = creationContext( Map.of(PrivacyModuleQualifier.US_NAT, moduleConfig)); @@ -89,7 +89,7 @@ public void fromShouldCreateRuleWithAllConfiguredPrivacyModulesThatMatches() { // given final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList("iab.*")); - final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null); + final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null, null); final ActivityControllerCreationContext creationContext = creationContext( Map.of(PrivacyModuleQualifier.US_NAT, moduleConfig)); @@ -108,7 +108,7 @@ public void fromShouldCreateRuleAndModifyContextWithUsedPrivacyModules() { // given final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); - final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null); + final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null, null); final ActivityControllerCreationContext creationContext = creationContext( Map.of(PrivacyModuleQualifier.US_NAT, moduleConfig)); @@ -129,7 +129,7 @@ public void fromShouldSkipAlreadyUsedPrivacyModule() { final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); final ActivityControllerCreationContext creationContext = creationContext(Map.of( - PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(true, null))); + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(true, null, null))); creationContext.use(PrivacyModuleQualifier.US_NAT); // when @@ -145,7 +145,22 @@ public void fromShouldSkipDisabledPrivacyModule() { final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); final ActivityControllerCreationContext creationContext = creationContext(Map.of( - PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(false, null))); + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(false, null, null))); + + // when + final Rule rule = target.from(config, creationContext); + + // then + assertThat(rule.proceed(null)).isEqualTo(Rule.Result.ABSTAIN); + } + + @Test + public void fromShouldDisableSkippedPrivacyModule() { + // given + final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( + singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); + final ActivityControllerCreationContext creationContext = creationContext(Map.of( + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(true, 100, null))); // when final Rule rule = target.from(config, creationContext); @@ -162,7 +177,7 @@ public void fromShouldSkipPrivacyModuleWithoutCreator() { final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); final ActivityControllerCreationContext creationContext = creationContext(Map.of( - PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(null, null))); + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(null, null, null))); // when final Rule rule = target.from(config, creationContext); From 066e403f7f2d1f907cce72e56540beac4b6b5f56 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Tue, 4 Feb 2025 13:45:41 +0100 Subject: [PATCH 2/4] Fix --- .../ActivityControllerCreationContext.java | 2 ++ .../ActivityInfrastructureCreator.java | 14 +++++++++ .../rule/PrivacyModulesRuleCreator.java | 10 ++----- .../ActivityInfrastructureCreatorTest.java | 29 +++++++++++++++++++ .../rule/ConditionsRuleCreatorTest.java | 2 +- .../rule/PrivacyModulesRuleCreatorTest.java | 9 ++++-- 6 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityControllerCreationContext.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityControllerCreationContext.java index 1651d1bc3a6..d39625d23ee 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityControllerCreationContext.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityControllerCreationContext.java @@ -20,6 +20,8 @@ public class ActivityControllerCreationContext { Map privacyModulesConfigs; + Map skipModuleConfigs; + @Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE) Set usedPrivacyModules = EnumSet.noneOf(PrivacyModuleQualifier.class); diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java index 49e24a06183..84f38e28fdd 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; @@ -41,6 +42,8 @@ public class ActivityInfrastructureCreator { private static final Logger logger = LoggerFactory.getLogger(ActivityInfrastructureCreator.class); + private static final int MODULE_MAX_SKIP_RATE = 100; + private final ActivityRuleFactory activityRuleFactory; private final Purpose defaultPurpose4; private final Metrics metrics; @@ -144,9 +147,13 @@ private ActivityController from(Activity activity, debug); } + final Map skipModulesConfigs = modulesConfigs.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> shouldSkipAllActivities(entry.getValue()))); + final ActivityControllerCreationContext creationContext = ActivityControllerCreationContext.of( activity, modulesConfigs, + skipModulesConfigs, gppContext); final boolean allow = allowFromConfig(activityConfiguration.getAllow()); @@ -158,6 +165,13 @@ private ActivityController from(Activity activity, return ActivityController.of(allow, rules, debug); } + private static boolean shouldSkipAllActivities(AccountPrivacyModuleConfig config) { + return Optional.ofNullable(config) + .map(AccountPrivacyModuleConfig::getSkipRate) + .map(skipRate -> ThreadLocalRandom.current().nextInt(MODULE_MAX_SKIP_RATE) < skipRate) + .orElse(false); + } + private static boolean allowFromConfig(Boolean configValue) { return configValue != null ? configValue : ActivityInfrastructure.ALLOW_ACTIVITY_BY_DEFAULT; } diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java index db8cd01036d..cc0b0020491 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java @@ -19,15 +19,12 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.ThreadLocalRandom; import java.util.function.UnaryOperator; import java.util.stream.Collectors; public class PrivacyModulesRuleCreator extends AbstractRuleCreator { private static final String WILDCARD = "*"; - private static final int SKIP_RATE_MIN = 0; - private static final int SKIP_RATE_MAX = 100; private final Map privacyModulesCreators; @@ -87,12 +84,11 @@ private static boolean isModuleEnabled(AccountPrivacyModuleConfig accountPrivacy private PrivacyModule createPrivacyModule(PrivacyModuleQualifier privacyModuleQualifier, ActivityControllerCreationContext creationContext) { - final Integer skipRate = Optional.ofNullable(creationContext.getPrivacyModulesConfigs()) + final boolean isSkipped = Optional.ofNullable(creationContext.getSkipModuleConfigs()) .map(configs -> configs.get(privacyModuleQualifier)) - .map(AccountPrivacyModuleConfig::getSkipRate) - .orElse(SKIP_RATE_MIN); + .orElse(false); - if (ThreadLocalRandom.current().nextInt(SKIP_RATE_MAX) < skipRate) { + if (isSkipped) { return new AbstainPrivacyModule(privacyModuleQualifier); } 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 1cd14a6c610..0a4ec9a15f8 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 @@ -3,6 +3,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.activity.Activity; @@ -22,6 +23,7 @@ import org.prebid.server.settings.model.PurposeEid; import org.prebid.server.settings.model.Purposes; import org.prebid.server.settings.model.activity.AccountActivityConfiguration; +import org.prebid.server.settings.model.activity.privacy.AccountUSCustomLogicModuleConfig; import org.prebid.server.settings.model.activity.privacy.AccountUSNatModuleConfig; import org.prebid.server.settings.model.activity.rule.AccountActivityConditionsRuleConfig; @@ -30,12 +32,15 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; +import static org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier.US_CUSTOM_LOGIC; +import static org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier.US_NAT; @ExtendWith(MockitoExtension.class) public class ActivityInfrastructureCreatorTest { @@ -104,6 +109,30 @@ null, singletonList(AccountActivityConditionsRuleConfig.of(null, null))))) verify(metrics).updateAlertsMetrics(eq(MetricName.general)); } + @Test + public void parseShouldPopulateSkipConfigForModules() { + // given + final Account account = Account.builder() + .privacy(AccountPrivacyConfig.builder() + .activities(Map.of(Activity.SYNC_USER, AccountActivityConfiguration.of( + null, singletonList(AccountActivityConditionsRuleConfig.of(null, null))))) + .modules(asList( + AccountUSNatModuleConfig.of(null, 100, null), + AccountUSCustomLogicModuleConfig.of(null, 0, null))) + .build()) + .build(); + + // when + creator.parse(account, null, debug); + + // then + final ArgumentCaptor captor = + ArgumentCaptor.forClass(ActivityControllerCreationContext.class); + verify(activityRuleFactory).from(any(), captor.capture()); + assertThat(captor.getValue().getSkipModuleConfigs()) + .containsOnly(entry(US_NAT, true), entry(US_CUSTOM_LOGIC, false)); + } + @Test public void parseShouldReturnExpectedResult() { // given diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/ConditionsRuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/ConditionsRuleCreatorTest.java index 2bd8609ee8e..b935ccf46c4 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/ConditionsRuleCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/ConditionsRuleCreatorTest.java @@ -85,6 +85,6 @@ private static BidRequest givenBidRequest(String country, String region, String } private static ActivityControllerCreationContext creationContext(GppContext gppContext) { - return ActivityControllerCreationContext.of(null, null, gppContext); + return ActivityControllerCreationContext.of(null, null, null, gppContext); } } diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java index 0e9c55bc00f..5290c44a524 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java @@ -159,8 +159,11 @@ public void fromShouldDisableSkippedPrivacyModule() { // given final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); - final ActivityControllerCreationContext creationContext = creationContext(Map.of( - PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(true, 100, null))); + final ActivityControllerCreationContext creationContext = ActivityControllerCreationContext.of( + null, + Map.of(PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(true, null, null)), + Map.of(PrivacyModuleQualifier.US_NAT, true), + null); // when final Rule rule = target.from(config, creationContext); @@ -189,6 +192,6 @@ public void fromShouldSkipPrivacyModuleWithoutCreator() { private static ActivityControllerCreationContext creationContext( Map modulesConfigs) { - return ActivityControllerCreationContext.of(null, modulesConfigs, null); + return ActivityControllerCreationContext.of(null, modulesConfigs, null, null); } } From 190a0707da463414fa29b585263ad0a05be12c5f Mon Sep 17 00:00:00 2001 From: antonbabak Date: Wed, 5 Feb 2025 09:41:28 +0100 Subject: [PATCH 3/4] Fix comments --- .../ActivityControllerCreationContext.java | 2 +- .../ActivityInfrastructureCreator.java | 27 +++++++++++-------- .../rule/PrivacyModulesRuleCreator.java | 7 +---- .../privacy/AccountPrivacyModuleConfig.java | 2 +- .../AccountUSCustomLogicModuleConfig.java | 2 +- .../privacy/AccountUSNatModuleConfig.java | 2 +- .../ActivityInfrastructureCreatorTest.java | 9 +++---- .../USCustomLogicModuleCreatorTest.java | 2 +- .../privacy/usnat/USNatModuleCreatorTest.java | 2 +- .../rule/PrivacyModulesRuleCreatorTest.java | 22 ++++++++------- 10 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityControllerCreationContext.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityControllerCreationContext.java index d39625d23ee..1f199646857 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityControllerCreationContext.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityControllerCreationContext.java @@ -20,7 +20,7 @@ public class ActivityControllerCreationContext { Map privacyModulesConfigs; - Map skipModuleConfigs; + Set skipPrivacyModules; @Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE) diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java index 84f38e28fdd..ae139798dcf 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/ActivityInfrastructureCreator.java @@ -27,10 +27,12 @@ import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; +import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.function.BinaryOperator; import java.util.function.Function; @@ -78,15 +80,22 @@ Map parse(Account account, GppContext gppContext, final Map activitiesConfiguration = accountPrivacyConfig .map(AccountPrivacyConfig::getActivities) .orElseGet(Collections::emptyMap); + final Map modulesConfigs = accountPrivacyConfig .map(AccountPrivacyConfig::getModules) .orElseGet(Collections::emptyList) .stream() + .filter(Objects::nonNull) .collect(Collectors.toMap( AccountPrivacyModuleConfig::getCode, UnaryOperator.identity(), takeFirstAndLogDuplicates(account.getId()))); + final Set skipPrivacyModules = modulesConfigs.entrySet().stream() + .filter(entry -> shouldSkipPrivacyModule(entry.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(PrivacyModuleQualifier.class))); + return Arrays.stream(Activity.values()).collect(Collectors.toMap( UnaryOperator.identity(), fallbackActivity( @@ -96,6 +105,7 @@ Map parse(Account account, GppContext gppContext, activity, activitiesConfiguration.get(activity), modulesConfigs, + skipPrivacyModules, gppContext, debug)), (oldValue, newValue) -> oldValue, @@ -134,9 +144,14 @@ private Function fallbackActivity( : activityControllerCreator.apply(originalActivity); } + private static boolean shouldSkipPrivacyModule(AccountPrivacyModuleConfig config) { + return ThreadLocalRandom.current().nextInt(MODULE_MAX_SKIP_RATE) < config.getSkipRate(); + } + private ActivityController from(Activity activity, AccountActivityConfiguration activityConfiguration, Map modulesConfigs, + Set skipPrivacyModules, GppContext gppContext, ActivityInfrastructureDebug debug) { @@ -147,13 +162,10 @@ private ActivityController from(Activity activity, debug); } - final Map skipModulesConfigs = modulesConfigs.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, entry -> shouldSkipAllActivities(entry.getValue()))); - final ActivityControllerCreationContext creationContext = ActivityControllerCreationContext.of( activity, modulesConfigs, - skipModulesConfigs, + skipPrivacyModules, gppContext); final boolean allow = allowFromConfig(activityConfiguration.getAllow()); @@ -165,13 +177,6 @@ private ActivityController from(Activity activity, return ActivityController.of(allow, rules, debug); } - private static boolean shouldSkipAllActivities(AccountPrivacyModuleConfig config) { - return Optional.ofNullable(config) - .map(AccountPrivacyModuleConfig::getSkipRate) - .map(skipRate -> ThreadLocalRandom.current().nextInt(MODULE_MAX_SKIP_RATE) < skipRate) - .orElse(false); - } - private static boolean allowFromConfig(Boolean configValue) { return configValue != null ? configValue : ActivityInfrastructure.ALLOW_ACTIVITY_BY_DEFAULT; } diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java index cc0b0020491..f1854fa7ee9 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreator.java @@ -18,7 +18,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.function.UnaryOperator; import java.util.stream.Collectors; @@ -84,11 +83,7 @@ private static boolean isModuleEnabled(AccountPrivacyModuleConfig accountPrivacy private PrivacyModule createPrivacyModule(PrivacyModuleQualifier privacyModuleQualifier, ActivityControllerCreationContext creationContext) { - final boolean isSkipped = Optional.ofNullable(creationContext.getSkipModuleConfigs()) - .map(configs -> configs.get(privacyModuleQualifier)) - .orElse(false); - - if (isSkipped) { + if (creationContext.getSkipPrivacyModules().contains(privacyModuleQualifier)) { return new AbstainPrivacyModule(privacyModuleQualifier); } diff --git a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountPrivacyModuleConfig.java b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountPrivacyModuleConfig.java index 6c2f0183286..98352cf6bee 100644 --- a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountPrivacyModuleConfig.java +++ b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountPrivacyModuleConfig.java @@ -24,7 +24,7 @@ public sealed interface AccountPrivacyModuleConfig permits PrivacyModuleQualifier getCode(); @JsonProperty("skipRate") - Integer getSkipRate(); + int getSkipRate(); @JsonProperty Boolean enabled(); diff --git a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSCustomLogicModuleConfig.java b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSCustomLogicModuleConfig.java index 9473cf67a6b..92ccff4c3e9 100644 --- a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSCustomLogicModuleConfig.java +++ b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSCustomLogicModuleConfig.java @@ -17,7 +17,7 @@ public class AccountUSCustomLogicModuleConfig implements AccountPrivacyModuleCon @Accessors(fluent = true) Boolean enabled; - Integer skipRate; + int skipRate; Config config; diff --git a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java index c7889a670f9..1c6adf7c00d 100644 --- a/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java +++ b/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java @@ -14,7 +14,7 @@ public class AccountUSNatModuleConfig implements AccountPrivacyModuleConfig { @Accessors(fluent = true) Boolean enabled; - Integer skipRate; + int skipRate; Config config; 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 0a4ec9a15f8..bdfa42035da 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 @@ -32,14 +32,12 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; -import static org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier.US_CUSTOM_LOGIC; import static org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier.US_NAT; @ExtendWith(MockitoExtension.class) @@ -96,8 +94,8 @@ public void parseShouldSkipPrivacyModulesDuplicatesAndEmitWarnings() { .activities(Map.of(Activity.SYNC_USER, AccountActivityConfiguration.of( null, singletonList(AccountActivityConditionsRuleConfig.of(null, null))))) .modules(asList( - AccountUSNatModuleConfig.of(null, null, null), - AccountUSNatModuleConfig.of(null, null, null))) + AccountUSNatModuleConfig.of(null, 0, null), + AccountUSNatModuleConfig.of(null, 0, null))) .build()) .build(); @@ -129,8 +127,7 @@ null, singletonList(AccountActivityConditionsRuleConfig.of(null, null))))) final ArgumentCaptor captor = ArgumentCaptor.forClass(ActivityControllerCreationContext.class); verify(activityRuleFactory).from(any(), captor.capture()); - assertThat(captor.getValue().getSkipModuleConfigs()) - .containsOnly(entry(US_NAT, true), entry(US_CUSTOM_LOGIC, false)); + assertThat(captor.getValue().getSkipPrivacyModules()).containsOnly(US_NAT); } @Test diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreatorTest.java index 674e0654d31..3d10dd5b9a4 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreatorTest.java @@ -248,7 +248,7 @@ private static PrivacyModuleCreationContext givenCreationContext(List s return PrivacyModuleCreationContext.of( Activity.CALL_BIDDER, - AccountUSCustomLogicModuleConfig.of(true, null, config), + AccountUSCustomLogicModuleConfig.of(true, 0, config), GppContextCreator.from(null, sectionsIds).build().getGppContext()); } diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreatorTest.java index 5b8d4e61bb2..5dce2c5fb18 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat/USNatModuleCreatorTest.java @@ -126,7 +126,7 @@ private static PrivacyModuleCreationContext givenCreationContext(List s return PrivacyModuleCreationContext.of( Activity.CALL_BIDDER, - AccountUSNatModuleConfig.of(true, null, AccountUSNatModuleConfig.Config.of(skipSectionsIds)), + AccountUSNatModuleConfig.of(true, 0, AccountUSNatModuleConfig.Config.of(skipSectionsIds)), GppContextCreator.from(null, sectionsIds).build().getGppContext()); } } diff --git a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java index 5290c44a524..095c52ed14e 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/creator/rule/PrivacyModulesRuleCreatorTest.java @@ -16,8 +16,10 @@ import org.prebid.server.settings.model.activity.rule.AccountActivityPrivacyModulesRuleConfig; import java.util.Map; +import java.util.Set; import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; @@ -56,7 +58,7 @@ public void fromShouldCreateDefaultRuleIfNoneOfConfiguredPrivacyModulesMatches() final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList("not_configured")); final ActivityControllerCreationContext creationContext = creationContext(Map.of( - PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(null, null, null))); + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(null, 0, null))); // when final Rule rule = target.from(config, creationContext); @@ -70,7 +72,7 @@ public void fromShouldCreateRuleWithAllConfiguredPrivacyModules() { // given final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList("*")); - final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null, null); + final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, 0, null); final ActivityControllerCreationContext creationContext = creationContext( Map.of(PrivacyModuleQualifier.US_NAT, moduleConfig)); @@ -89,7 +91,7 @@ public void fromShouldCreateRuleWithAllConfiguredPrivacyModulesThatMatches() { // given final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList("iab.*")); - final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null, null); + final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, 0, null); final ActivityControllerCreationContext creationContext = creationContext( Map.of(PrivacyModuleQualifier.US_NAT, moduleConfig)); @@ -108,7 +110,7 @@ public void fromShouldCreateRuleAndModifyContextWithUsedPrivacyModules() { // given final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); - final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, null, null); + final AccountPrivacyModuleConfig moduleConfig = AccountUSNatModuleConfig.of(null, 0, null); final ActivityControllerCreationContext creationContext = creationContext( Map.of(PrivacyModuleQualifier.US_NAT, moduleConfig)); @@ -129,7 +131,7 @@ public void fromShouldSkipAlreadyUsedPrivacyModule() { final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); final ActivityControllerCreationContext creationContext = creationContext(Map.of( - PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(true, null, null))); + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(true, 0, null))); creationContext.use(PrivacyModuleQualifier.US_NAT); // when @@ -145,7 +147,7 @@ public void fromShouldSkipDisabledPrivacyModule() { final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); final ActivityControllerCreationContext creationContext = creationContext(Map.of( - PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(false, null, null))); + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(false, 0, null))); // when final Rule rule = target.from(config, creationContext); @@ -161,8 +163,8 @@ public void fromShouldDisableSkippedPrivacyModule() { singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); final ActivityControllerCreationContext creationContext = ActivityControllerCreationContext.of( null, - Map.of(PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(true, null, null)), - Map.of(PrivacyModuleQualifier.US_NAT, true), + Map.of(PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(true, 0, null)), + Set.of(PrivacyModuleQualifier.US_NAT), null); // when @@ -180,7 +182,7 @@ public void fromShouldSkipPrivacyModuleWithoutCreator() { final AccountActivityPrivacyModulesRuleConfig config = AccountActivityPrivacyModulesRuleConfig.of( singletonList(PrivacyModuleQualifier.US_NAT.moduleName())); final ActivityControllerCreationContext creationContext = creationContext(Map.of( - PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(null, null, null))); + PrivacyModuleQualifier.US_NAT, AccountUSNatModuleConfig.of(null, 0, null))); // when final Rule rule = target.from(config, creationContext); @@ -192,6 +194,6 @@ public void fromShouldSkipPrivacyModuleWithoutCreator() { private static ActivityControllerCreationContext creationContext( Map modulesConfigs) { - return ActivityControllerCreationContext.of(null, modulesConfigs, null, null); + return ActivityControllerCreationContext.of(null, modulesConfigs, emptySet(), null); } } From f3cd04d2bb58dc5c31f3c877fefda56238a9ce61 Mon Sep 17 00:00:00 2001 From: osulzhenko <125548596+osulzhenko@users.noreply.github.com> Date: Thu, 6 Feb 2025 11:41:01 +0200 Subject: [PATCH 4/4] Tests: Support testing the USGen privacy module (#3722) * Add functional tests for GPP USNat v2 --- .../model/config/AccountGppConfig.groovy | 1 + .../auction/ActivityInfrastructure.groovy | 2 +- .../model/response/auction/And.groovy | 7 + .../model/response/auction/RuleResult.groovy | 6 + .../tests/privacy/ActivityTraceLogSpec.groovy | 300 +++++++++++++++++- 5 files changed, 309 insertions(+), 7 deletions(-) create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/auction/RuleResult.groovy diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountGppConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountGppConfig.groovy index add9834d5c5..bb4e0ab0b74 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountGppConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountGppConfig.groovy @@ -8,5 +8,6 @@ class AccountGppConfig { PrivacyModule code Boolean enabled + Integer skipRate GppModuleConfig config } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/ActivityInfrastructure.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/ActivityInfrastructure.groovy index 1325a65e219..0d1a3ba16ad 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/ActivityInfrastructure.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/ActivityInfrastructure.groovy @@ -17,7 +17,7 @@ class ActivityInfrastructure { RuleConfiguration ruleConfiguration Boolean allowByDefault Boolean allowed - String result + RuleResult result String region String country } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/And.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/And.groovy index 4072d3e1f62..73799db84d5 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/And.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/And.groovy @@ -1,11 +1,18 @@ 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.PrivacyModule @ToString(includeNames = true, ignoreNulls = true) @EqualsAndHashCode +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) class And { List and + PrivacyModule privacyModule + Boolean skipped + RuleResult result } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/RuleResult.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/RuleResult.groovy new file mode 100644 index 00000000000..62b4256bb66 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/RuleResult.groovy @@ -0,0 +1,6 @@ +package org.prebid.server.functional.model.response.auction + +enum RuleResult { + + ALLOW, DISALLOW, ABSTAIN +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/ActivityTraceLogSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/ActivityTraceLogSpec.groovy index e0c3b279200..e42fa3fb594 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/ActivityTraceLogSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/ActivityTraceLogSpec.groovy @@ -23,14 +23,21 @@ import static org.prebid.server.functional.model.pricefloors.Country.CAN import static org.prebid.server.functional.model.pricefloors.Country.USA import static org.prebid.server.functional.model.request.GppSectionId.US_CA_V1 import static org.prebid.server.functional.model.request.GppSectionId.US_CO_V1 +import static org.prebid.server.functional.model.request.GppSectionId.US_NAT_V1 import static org.prebid.server.functional.model.request.auction.ActivityType.FETCH_BIDS +import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_EIDS import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_PRECISE_GEO import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_TID import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_UFPD import static org.prebid.server.functional.model.request.auction.Condition.ConditionType.BIDDER +import static org.prebid.server.functional.model.request.auction.PrivacyModule.ALL +import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_US_CUSTOM_LOGIC import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_US_GENERAL import static org.prebid.server.functional.model.request.auction.TraceLevel.BASIC import static org.prebid.server.functional.model.request.auction.TraceLevel.VERBOSE +import static org.prebid.server.functional.model.response.auction.RuleResult.ABSTAIN +import static org.prebid.server.functional.model.response.auction.RuleResult.ALLOW +import static org.prebid.server.functional.model.response.auction.RuleResult.DISALLOW import static org.prebid.server.functional.util.privacy.model.State.ALABAMA import static org.prebid.server.functional.util.privacy.model.State.ARIZONA @@ -42,6 +49,9 @@ class ActivityTraceLogSpec extends PrivacyBaseSpec { private static final def PROCESSING_ACTIVITY_TRACE = ["Processing rule."] + private final static Integer MIN_PERCENT_AB = 0 + private final static Integer MAX_PERCENT_AB = 100 + def "PBS auction shouldn't log info about activity in response when ext.prebid.trace=null"() { given: "Default basic generic BidRequest" def accountId = PBSUtils.randomNumber as String @@ -97,7 +107,7 @@ class ActivityTraceLogSpec extends PrivacyBaseSpec { country: USA.ISOAlpha3)) assert fetchBidsActivity.ruleConfiguration.every { it == null } assert fetchBidsActivity.allowByDefault.contains(activity.defaultAction) - assert fetchBidsActivity.result.contains("DISALLOW") + assert fetchBidsActivity.result.contains(DISALLOW) assert fetchBidsActivity.country.every { it == null } assert fetchBidsActivity.region.every { it == null } } @@ -137,7 +147,7 @@ class ActivityTraceLogSpec extends PrivacyBaseSpec { country: USA.ISOAlpha3)) assert fetchBidsActivity.allowByDefault.contains(activity.defaultAction) assert fetchBidsActivity.ruleConfiguration.every { it == null } - assert fetchBidsActivity.result.contains("ALLOW") + assert fetchBidsActivity.result.contains(ALLOW) assert fetchBidsActivity.country.every { it == null } assert fetchBidsActivity.region.every { it == null } @@ -237,7 +247,7 @@ class ActivityTraceLogSpec extends PrivacyBaseSpec { gpc: gpc, geoCodes: [new GeoCode(country: CAN, region: ARIZONA.abbreviation)])) assert fetchBidsActivity.allowByDefault.contains(activity.defaultAction) - assert fetchBidsActivity.result.contains("ABSTAIN") + assert fetchBidsActivity.result.contains(ABSTAIN) assert fetchBidsActivity.country.every { it == null } assert fetchBidsActivity.region.every { it == null } @@ -343,7 +353,7 @@ class ActivityTraceLogSpec extends PrivacyBaseSpec { assert fetchBidsActivity.ruleConfiguration.contains(new RuleConfiguration( and: [new And(and: ["USNatDefault. Precomputed result: ABSTAIN."])])) assert fetchBidsActivity.allowByDefault.contains(activity.defaultAction) - assert fetchBidsActivity.result.contains("ABSTAIN") + assert fetchBidsActivity.result.contains(ABSTAIN) assert fetchBidsActivity.country.every { it == null } assert fetchBidsActivity.region.every { it == null } @@ -398,10 +408,288 @@ class ActivityTraceLogSpec extends PrivacyBaseSpec { allow << [false, true] } - private List getActivityByName(List activityInfrastructures, - ActivityType activity) { + def "PBS auction should log info about activity in response when ext.prebid.trace=verbose and skipRate=#skipRate"() { + given: "Default bid request" + def accountId = PBSUtils.randomNumber as String + def bidRequest = getBidRequestWithPersonalData(accountId).tap { + ext.prebid.trace = VERBOSE + regs.gpp = SIMPLE_GPC_DISALLOW_LOGIC + regs.gppSid = [US_NAT_V1.intValue] + } + + and: "Set up activities" + def condition = Condition.baseCondition.tap { + it.gppSid = [US_NAT_V1.intValue] + } + def activityRule = ActivityRule.getDefaultActivityRule(condition).tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + def activity = Activity.getDefaultActivity([activityRule]) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_EIDS, activity) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true, skipRate: skipRate) + + and: "Save account with allow activities setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + def bidResponse = activityPbsService.sendAuctionRequest(bidRequest) + + then: "Generic bidder request should have empty EIDS fields" + def genericBidderRequest = bidder.getBidderRequest(bidRequest.id) + + verifyAll { + !genericBidderRequest.user.eids + !genericBidderRequest.user?.ext?.eids + } + + and: "Bid response should contain info about triggered activity in debug" + def infrastructure = bidResponse.ext.debug.trace.activityInfrastructure + def ruleConfigurations = findProcessingRule(infrastructure, TRANSMIT_EIDS).ruleConfiguration.and + assert ruleConfigurations.size() == 1 + assert ruleConfigurations.first.and.every { it.contains(DISALLOW.toString()) } + + and: "Should not contain information that module was skipped" + verifyAll(ruleConfigurations.first) { + !it.privacyModule + !it.skipped + !it.result + } + + where: + skipRate << [null, MIN_PERCENT_AB] + } + + def "PBS auction should log info about module skip in response when ext.prebid.trace=verbose and skipRate is max"() { + given: "Default bid request" + def accountId = PBSUtils.randomNumber as String + def bidRequest = getBidRequestWithPersonalData(accountId).tap { + ext.prebid.trace = VERBOSE + regs.gpp = SIMPLE_GPC_DISALLOW_LOGIC + regs.gppSid = [US_NAT_V1.intValue] + } + + and: "Set up activities" + def condition = Condition.baseCondition.tap { + it.gppSid = [US_NAT_V1.intValue] + } + def activityRule = ActivityRule.getDefaultActivityRule(condition).tap { + it.privacyRegulation = [ALL] + } + def activity = Activity.getDefaultActivity([activityRule]) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_EIDS, activity) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: code, enabled: true, skipRate: MAX_PERCENT_AB) + + and: "Save account with allow activities setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + def bidResponse = activityPbsService.sendAuctionRequest(bidRequest) + + then: "Generic bidder request should have data in EIDS fields" + def genericBidderRequest = bidder.getBidderRequest(bidRequest.id) + assert genericBidderRequest.user.eids[0].source == bidRequest.user.eids[0].source + + and: "Bid response should not contain info about triggered activity in debug" + def infrastructure = bidResponse.ext.debug.trace.activityInfrastructure + def ruleConfigurations = findProcessingRule(infrastructure, TRANSMIT_EIDS).ruleConfiguration.and + assert ruleConfigurations.size() == 1 + assert ruleConfigurations.first.and.every { it == null } + + and: "Should contain information that module was skipped" + verifyAll(ruleConfigurations.first) { + it.privacyModule == code + it.skipped == true + it.result == ABSTAIN + } + + where: + code | defaultAction + IAB_US_GENERAL | false + IAB_US_GENERAL | true + IAB_US_CUSTOM_LOGIC | false + IAB_US_CUSTOM_LOGIC | true + } + + def "PBS auction should log consistently for each activity about skips modules in response"() { + given: "Default bid request" + def accountId = PBSUtils.randomNumber as String + def bidRequest = getBidRequestWithPersonalData(accountId).tap { + ext.prebid.trace = VERBOSE + regs.gpp = SIMPLE_GPC_DISALLOW_LOGIC + regs.gppSid = [US_NAT_V1.intValue] + } + + and: "Set up activities" + def condition = Condition.baseCondition.tap { + it.gppSid = [US_NAT_V1.intValue] + } + def activityRule = ActivityRule.getDefaultActivityRule(condition).tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + def activity = Activity.getDefaultActivity([activityRule]) + def activities = new AllowActivities( + syncUser: activity, + fetchBids: activity, + enrichUfpd: activity, + reportAnalytics: activity, + transmitUfpd: activity, + transmitEids: activity, + transmitPreciseGeo: activity, + transmitTid: activity, + ) + + and: "Account gpp configuration" + def skipRate = PBSUtils.getRandomNumber(MIN_PERCENT_AB, MAX_PERCENT_AB) + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true, skipRate: skipRate) + + and: "Save account with allow activities setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + def bidResponse = activityPbsService.sendAuctionRequest(bidRequest) + + then: "Bid response should log consistently for each activity about skips" + def infrastructure = bidResponse.ext.debug.trace.activityInfrastructure + def fetchBidsLogs = findProcessingRule(infrastructure, FETCH_BIDS).ruleConfiguration.and + def transmitUfpdLogs = findProcessingRule(infrastructure, TRANSMIT_UFPD).ruleConfiguration.and + def transmitEidsLogs = findProcessingRule(infrastructure, TRANSMIT_EIDS).ruleConfiguration.and + def transmitPreciseGeoLogs = findProcessingRule(infrastructure, TRANSMIT_PRECISE_GEO).ruleConfiguration.and + def transmitTidLogs = findProcessingRule(infrastructure, TRANSMIT_TID).ruleConfiguration.and + verifyAll ([fetchBidsLogs, transmitUfpdLogs, transmitEidsLogs, transmitPreciseGeoLogs, transmitTidLogs]) { + it.privacyModule.toSet().size() == 1 + it.skipped.toSet().size() == 1 + it.result.toSet().size() == 1 + } + } + + def "PBS auction shouldn't emit errors or warnings when skip rate is at minimum boundary"() { + given: "A bid request with verbose tracing and GPC disallow logic" + def accountId = PBSUtils.randomNumber as String + def bidRequest = getBidRequestWithPersonalData(accountId).tap { + ext.prebid.trace = VERBOSE + regs.gpp = SIMPLE_GPC_DISALLOW_LOGIC + regs.gppSid = [US_NAT_V1.intValue] + } + + and: "An activity rule with GPP SID and privacy regulation setup" + def condition = Condition.baseCondition.tap { + it.gppSid = [US_NAT_V1.intValue] + } + def activityRule = ActivityRule.getDefaultActivityRule(condition).tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + def activity = Activity.getDefaultActivity([activityRule]) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_EIDS, activity) + + and: "Account GPP configuration with minimum skip rate" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true, skipRate: Integer.MIN_VALUE) + + and: "Save the account with configured activities and privacy module" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes the auction request" + def bidResponse = activityPbsService.sendAuctionRequest(bidRequest) + + then: "Response should not contain errors or warnings" + assert !bidResponse.ext?.errors + assert !bidResponse.ext?.warnings + + and: "Bid response should contain info about triggered activity in debug" + def infrastructure = bidResponse.ext.debug.trace.activityInfrastructure + def ruleConfigurations = findProcessingRule(infrastructure, TRANSMIT_EIDS).ruleConfiguration.and + assert ruleConfigurations.size() == 1 + assert ruleConfigurations.first.and.every { it.contains(DISALLOW.toString()) } + + and: "Should not contain information that module was skipped" + verifyAll(ruleConfigurations.first) { + !it.privacyModule + !it.skipped + !it.result + } + + and: "Generic bidder request should have empty EIDS fields" + def genericBidderRequest = bidder.getBidderRequest(bidRequest.id) + + verifyAll { + !genericBidderRequest.user.eids + !genericBidderRequest.user?.ext?.eids + } + } + + def "PBS auction shouldn't emit errors or warnings when skip rate is at maximum boundary"() { + given: "A bid request with verbose tracing and GPC disallow logic" + def accountId = PBSUtils.randomNumber as String + def bidRequest = getBidRequestWithPersonalData(accountId).tap { + ext.prebid.trace = VERBOSE + regs.gpp = SIMPLE_GPC_DISALLOW_LOGIC + regs.gppSid = [US_NAT_V1.intValue] + } + + and: "An activity rule with GPP SID and privacy regulation setup" + def condition = Condition.baseCondition.tap { + it.gppSid = [US_NAT_V1.intValue] + } + def activityRule = ActivityRule.getDefaultActivityRule(condition).tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + def activity = Activity.getDefaultActivity([activityRule]) + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_EIDS, activity) + + and: "Account GPP configuration with maximum skip rate" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true, skipRate: Integer.MAX_VALUE) + + and: "Save the account with configured activities and privacy module" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes the auction request" + def bidResponse = activityPbsService.sendAuctionRequest(bidRequest) + + then: "Response should not contain errors or warnings" + assert !bidResponse.ext?.errors + assert !bidResponse.ext?.warnings + + and: "Bid response should not contain info about triggered activity in debug" + def infrastructure = bidResponse.ext.debug.trace.activityInfrastructure + def ruleConfigurations = findProcessingRule(infrastructure, TRANSMIT_EIDS).ruleConfiguration.and + assert ruleConfigurations.size() == 1 + assert ruleConfigurations.first.and.every { it == null } + + and: "Should contain information that module was skipped" + verifyAll(ruleConfigurations.first) { + it.privacyModule == IAB_US_GENERAL + it.skipped == true + it.result == ABSTAIN + } + + and: "Generic bidder request should have data in EIDS fields" + def genericBidderRequest = bidder.getBidderRequest(bidRequest.id) + assert genericBidderRequest.user.eids[0].source == bidRequest.user.eids[0].source + } + + private static List getActivityByName(List activityInfrastructures, + ActivityType activity) { def firstIndex = activityInfrastructures.findLastIndexOf { it -> it.activity == activity } def lastIndex = activityInfrastructures.findIndexOf { it -> it.activity == activity } activityInfrastructures[new IntRange(true, firstIndex, lastIndex)] } + + private static ActivityInfrastructure findProcessingRule(List infrastructures, ActivityType activity) { + def matchingActivities = getActivityByName(infrastructures, activity) + .findAll { PROCESSING_ACTIVITY_TRACE.contains(it.description) } + + if (matchingActivities.size() != 1) { + throw new IllegalStateException("Expected a single processing activity, but found ${matchingActivities.size()}") + } + + matchingActivities.first() + } }