diff --git a/src/main/java/org/prebid/server/activity/infrastructure/ActivityInfrastructure.java b/src/main/java/org/prebid/server/activity/infrastructure/ActivityInfrastructure.java index e4b6c54994c..007bf469d1a 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/ActivityInfrastructure.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/ActivityInfrastructure.java @@ -5,11 +5,13 @@ import org.prebid.server.activity.ComponentType; import org.prebid.server.activity.infrastructure.debug.ActivityInfrastructureDebug; import org.prebid.server.activity.infrastructure.payload.ActivityInvocationPayload; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; import org.prebid.server.proto.openrtb.ext.response.ExtTraceActivityInfrastructure; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; public class ActivityInfrastructure { @@ -48,4 +50,8 @@ public void updateActivityMetrics(Activity activity, ComponentType componentType public List debugTrace() { return debug.trace(); } + + public Set skippedPrivacyModules() { + return debug.skippedPrivacyModules(); + } } 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 f1854fa7ee9..2550e2fc95f 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,7 +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.SkippedPrivacyModule; import org.prebid.server.activity.infrastructure.privacy.PrivacyModule; import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; import org.prebid.server.activity.infrastructure.rule.AndRule; @@ -84,7 +84,7 @@ private PrivacyModule createPrivacyModule(PrivacyModuleQualifier privacyModuleQu ActivityControllerCreationContext creationContext) { if (creationContext.getSkipPrivacyModules().contains(privacyModuleQualifier)) { - return new AbstainPrivacyModule(privacyModuleQualifier); + return new SkippedPrivacyModule(privacyModuleQualifier); } return privacyModulesCreators.get(privacyModuleQualifier) diff --git a/src/main/java/org/prebid/server/activity/infrastructure/debug/ActivityInfrastructureDebug.java b/src/main/java/org/prebid/server/activity/infrastructure/debug/ActivityInfrastructureDebug.java index 9d15023d559..6c7d083975d 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/debug/ActivityInfrastructureDebug.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/debug/ActivityInfrastructureDebug.java @@ -3,6 +3,9 @@ import org.prebid.server.activity.Activity; import org.prebid.server.activity.ComponentType; import org.prebid.server.activity.infrastructure.payload.ActivityInvocationPayload; +import org.prebid.server.activity.infrastructure.privacy.SkippedPrivacyModule; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; +import org.prebid.server.activity.infrastructure.rule.AndRule; import org.prebid.server.activity.infrastructure.rule.Rule; import org.prebid.server.json.JacksonMapper; import org.prebid.server.metric.Metrics; @@ -15,14 +18,17 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.Objects; +import java.util.Set; public class ActivityInfrastructureDebug { private final String accountId; private final TraceLevel traceLevel; private final List traceLog; + private final Set skippedPrivacyModules; private final Metrics metrics; private final JacksonMapper jacksonMapper; @@ -34,6 +40,7 @@ public ActivityInfrastructureDebug(String accountId, this.accountId = accountId; this.traceLevel = traceLevel; this.traceLog = new ArrayList<>(); + this.skippedPrivacyModules = EnumSet.noneOf(PrivacyModuleQualifier.class); this.metrics = Objects.requireNonNull(metrics); this.jacksonMapper = Objects.requireNonNull(jacksonMapper); } @@ -56,6 +63,8 @@ public void emitActivityInvocationDefaultResult(boolean defaultResult) { } public void emitProcessedRule(Rule rule, Rule.Result result) { + collectSkippedPrivacyModules(rule); + if (atLeast(TraceLevel.basic)) { traceLog.add(ExtTraceActivityRule.of( "Processing rule.", @@ -69,6 +78,17 @@ public void emitProcessedRule(Rule rule, Rule.Result result) { } } + private void collectSkippedPrivacyModules(Rule rule) { + if (rule instanceof SkippedPrivacyModule module) { + skippedPrivacyModules.add(module.skippedModule()); + } else if (rule instanceof AndRule andRule) { + andRule.rules().stream() + .filter(SkippedPrivacyModule.class::isInstance) + .map(SkippedPrivacyModule.class::cast) + .forEach(module -> skippedPrivacyModules.add(module.skippedModule())); + } + } + public void emitActivityInvocationResult(Activity activity, ActivityInvocationPayload activityInvocationPayload, boolean result) { @@ -102,6 +122,10 @@ public List trace() { return Collections.unmodifiableList(traceLog); } + public Set skippedPrivacyModules() { + return Collections.unmodifiableSet(skippedPrivacyModules); + } + private boolean atLeast(TraceLevel minTraceLevel) { return traceLevel != null && traceLevel.ordinal() >= minTraceLevel.ordinal(); } diff --git a/src/main/java/org/prebid/server/activity/infrastructure/privacy/AbstainPrivacyModule.java b/src/main/java/org/prebid/server/activity/infrastructure/privacy/SkippedPrivacyModule.java similarity index 80% rename from src/main/java/org/prebid/server/activity/infrastructure/privacy/AbstainPrivacyModule.java rename to src/main/java/org/prebid/server/activity/infrastructure/privacy/SkippedPrivacyModule.java index b1b183cc08b..fc73a5f67f7 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/privacy/AbstainPrivacyModule.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/privacy/SkippedPrivacyModule.java @@ -7,11 +7,11 @@ import java.util.Objects; -public class AbstainPrivacyModule implements PrivacyModule, Loggable { +public class SkippedPrivacyModule implements PrivacyModule, Loggable { private final PrivacyModuleQualifier privacyModuleQualifier; - public AbstainPrivacyModule(PrivacyModuleQualifier privacyModuleQualifier) { + public SkippedPrivacyModule(PrivacyModuleQualifier privacyModuleQualifier) { this.privacyModuleQualifier = Objects.requireNonNull(privacyModuleQualifier); } @@ -27,4 +27,8 @@ public JsonNode asLogEntry(ObjectMapper mapper) { .put("skipped", true) .put("result", Result.ABSTAIN.name()); } + + public PrivacyModuleQualifier skippedModule() { + return privacyModuleQualifier; + } } diff --git a/src/main/java/org/prebid/server/activity/infrastructure/rule/AndRule.java b/src/main/java/org/prebid/server/activity/infrastructure/rule/AndRule.java index 40585181541..31163933d68 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/rule/AndRule.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/rule/AndRule.java @@ -8,6 +8,7 @@ import org.prebid.server.activity.infrastructure.debug.Loggable; import org.prebid.server.activity.infrastructure.payload.ActivityInvocationPayload; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -45,4 +46,8 @@ public JsonNode asLogEntry(ObjectMapper mapper) { return andNode; } + + public List rules() { + return Collections.unmodifiableList(rules); + } } 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 e42fa3fb594..7dc412d1780 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 @@ -509,11 +509,7 @@ class ActivityTraceLogSpec extends PrivacyBaseSpec { } where: - code | defaultAction - IAB_US_GENERAL | false - IAB_US_GENERAL | true - IAB_US_CUSTOM_LOGIC | false - IAB_US_CUSTOM_LOGIC | true + code << [IAB_US_GENERAL, IAB_US_CUSTOM_LOGIC] } def "PBS auction should log consistently for each activity about skips modules in response"() { @@ -675,6 +671,81 @@ class ActivityTraceLogSpec extends PrivacyBaseSpec { assert genericBidderRequest.user.eids[0].source == bidRequest.user.eids[0].source } + def "PBS auction shouldn't log info about module skip in response when ext.prebid.trace=basic and skipRate is max"() { + given: "Default bid request" + def accountId = PBSUtils.randomNumber as String + def bidRequest = getBidRequestWithPersonalData(accountId).tap { + ext.prebid.trace = BASIC + 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: IAB_US_GENERAL, 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 rule configuration in debug" + def infrastructure = bidResponse.ext.debug.trace.activityInfrastructure + assert !findProcessingRule(infrastructure, TRANSMIT_EIDS).ruleConfiguration + } + + def "PBS auction shouldn't log info about module skip in response when ext.prebid.trace=null and skipRate is max"() { + given: "Default bid request" + def accountId = PBSUtils.randomNumber as String + def bidRequest = getBidRequestWithPersonalData(accountId).tap { + ext.prebid.trace = null + 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: IAB_US_GENERAL, 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 trace" + assert !bidResponse.ext.debug.trace + } + private static List getActivityByName(List activityInfrastructures, ActivityType activity) { def firstIndex = activityInfrastructures.findLastIndexOf { it -> it.activity == activity } diff --git a/src/test/java/org/prebid/server/activity/infrastructure/debug/ActivityInfrastructureDebugTest.java b/src/test/java/org/prebid/server/activity/infrastructure/debug/ActivityInfrastructureDebugTest.java index 835fe189603..2a86b5962c4 100644 --- a/src/test/java/org/prebid/server/activity/infrastructure/debug/ActivityInfrastructureDebugTest.java +++ b/src/test/java/org/prebid/server/activity/infrastructure/debug/ActivityInfrastructureDebugTest.java @@ -9,6 +9,9 @@ import org.prebid.server.activity.Activity; import org.prebid.server.activity.ComponentType; import org.prebid.server.activity.infrastructure.payload.ActivityInvocationPayload; +import org.prebid.server.activity.infrastructure.privacy.PrivacyModuleQualifier; +import org.prebid.server.activity.infrastructure.privacy.SkippedPrivacyModule; +import org.prebid.server.activity.infrastructure.rule.AndRule; import org.prebid.server.activity.infrastructure.rule.Rule; import org.prebid.server.activity.infrastructure.rule.TestRule; import org.prebid.server.metric.Metrics; @@ -18,6 +21,8 @@ import org.prebid.server.proto.openrtb.ext.response.ExtTraceActivityInvocationResult; import org.prebid.server.proto.openrtb.ext.response.ExtTraceActivityRule; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -156,6 +161,69 @@ public void emitProcessedRuleShouldReturnExpectedResultIfTraceLevelIsVerbose() { verifyNoMoreInteractions(metrics); } + @Test + public void emitProcessedRuleShouldLogModuleWhenModuleIsSkipped() { + // given + final ActivityInfrastructureDebug debug = debug(TraceLevel.verbose); + + // when + debug.emitProcessedRule(new SkippedPrivacyModule(PrivacyModuleQualifier.US_NAT), Rule.Result.ABSTAIN); + + // then + assertThat(debug.skippedPrivacyModules()).containsExactly(PrivacyModuleQualifier.US_NAT); + assertThat(debug.trace()).containsExactly(ExtTraceActivityRule.of( + "Processing rule.", + mapper.createObjectNode() + .put("privacy_module", "iab.usgeneral") + .put("skipped", true) + .put("result", "ABSTAIN"), + Rule.Result.ABSTAIN)); + verify(metrics).updateRequestsActivityProcessedRulesCount(); + verify(metrics).updateAccountActivityProcessedRulesCount(eq("accountId")); + verifyNoMoreInteractions(metrics); + } + + @Test + public void emitProcessedRuleShouldLogSkippedModuleWhenAndRuleHasAbstainModule() { + // given + final ActivityInfrastructureDebug debug = debug(TraceLevel.verbose); + + // when + debug.emitProcessedRule( + new AndRule(List.of(new SkippedPrivacyModule(PrivacyModuleQualifier.US_NAT))), + Rule.Result.ABSTAIN); + + // then + assertThat(debug.skippedPrivacyModules()).containsExactly(PrivacyModuleQualifier.US_NAT); + assertThat(debug.trace()).containsExactly(ExtTraceActivityRule.of( + "Processing rule.", + mapper.createObjectNode().set("and", mapper.createArrayNode().add(mapper.createObjectNode() + .put("privacy_module", "iab.usgeneral") + .put("skipped", true) + .put("result", "ABSTAIN"))), + Rule.Result.ABSTAIN)); + verify(metrics).updateRequestsActivityProcessedRulesCount(); + verify(metrics).updateAccountActivityProcessedRulesCount(eq("accountId")); + verifyNoMoreInteractions(metrics); + } + + @Test + public void emitProcessedRuleShouldLogSkippedModuleWhenTraceLevelIsNull() { + // given + final ActivityInfrastructureDebug debug = debug(null); + + // when + debug.emitProcessedRule( + new AndRule(List.of(new SkippedPrivacyModule(PrivacyModuleQualifier.US_NAT))), + Rule.Result.ABSTAIN); + + // then + assertThat(debug.skippedPrivacyModules()).containsExactly(PrivacyModuleQualifier.US_NAT); + assertThat(debug.trace()).isEmpty(); + verify(metrics).updateRequestsActivityProcessedRulesCount(); + verifyNoMoreInteractions(metrics); + } + @Test public void emitActivityInvocationResultShouldDoNothingIfTraceLevelIsNullAndActivityAllowed() { // given