diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml
index b6dfbac6ea8..74257f0b3cd 100644
--- a/extra/bundle/pom.xml
+++ b/extra/bundle/pom.xml
@@ -50,6 +50,11 @@
greenbids-real-time-data
${project.version}
+
+ org.prebid.server.hooks.modules
+ pb-request-correction
+ ${project.version}
+
diff --git a/extra/modules/pb-request-correction/pom.xml b/extra/modules/pb-request-correction/pom.xml
new file mode 100644
index 00000000000..1686cadfaac
--- /dev/null
+++ b/extra/modules/pb-request-correction/pom.xml
@@ -0,0 +1,15 @@
+
+
+ 4.0.0
+
+
+ org.prebid.server.hooks.modules
+ all-modules
+ 3.15.0-SNAPSHOT
+
+
+ pb-request-correction
+
+ pb-request-correction
+ Request correction module
+
diff --git a/extra/modules/pb-request-correction/src/lombok.config b/extra/modules/pb-request-correction/src/lombok.config
new file mode 100644
index 00000000000..efd92714219
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/lombok.config
@@ -0,0 +1 @@
+lombok.anyConstructor.addConstructorProperties = true
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/RequestCorrectionProvider.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/RequestCorrectionProvider.java
new file mode 100644
index 00000000000..3daa937c37c
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/RequestCorrectionProvider.java
@@ -0,0 +1,25 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core;
+
+import com.iab.openrtb.request.BidRequest;
+import org.prebid.server.hooks.modules.pb.request.correction.core.config.model.Config;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.Correction;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.CorrectionProducer;
+
+import java.util.List;
+import java.util.Objects;
+
+public class RequestCorrectionProvider {
+
+ private final List correctionProducers;
+
+ public RequestCorrectionProvider(List correctionProducers) {
+ this.correctionProducers = Objects.requireNonNull(correctionProducers);
+ }
+
+ public List corrections(Config config, BidRequest bidRequest) {
+ return correctionProducers.stream()
+ .filter(correctionProducer -> correctionProducer.shouldProduce(config, bidRequest))
+ .map(correctionProducer -> correctionProducer.produce(config))
+ .toList();
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/config/model/Config.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/config/model/Config.java
new file mode 100644
index 00000000000..44cac23337e
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/config/model/Config.java
@@ -0,0 +1,21 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.config.model;
+
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+public class Config {
+
+ boolean enabled;
+
+ @JsonAlias("pbsdkAndroidInstlRemove")
+ @JsonProperty("pbsdk-android-instl-remove")
+ boolean interstitialCorrectionEnabled;
+
+ @JsonAlias("pbsdkUaCleanup")
+ @JsonProperty("pbsdk-ua-cleanup")
+ boolean userAgentCorrectionEnabled;
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/Correction.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/Correction.java
new file mode 100644
index 00000000000..2cfda5fce68
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/Correction.java
@@ -0,0 +1,9 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.correction;
+
+import com.iab.openrtb.request.BidRequest;
+import org.prebid.server.hooks.modules.pb.request.correction.core.config.model.Config;
+
+public interface Correction {
+
+ BidRequest apply(BidRequest bidRequest);
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/CorrectionProducer.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/CorrectionProducer.java
new file mode 100644
index 00000000000..a92132656d5
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/CorrectionProducer.java
@@ -0,0 +1,11 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.correction;
+
+import com.iab.openrtb.request.BidRequest;
+import org.prebid.server.hooks.modules.pb.request.correction.core.config.model.Config;
+
+public interface CorrectionProducer {
+
+ boolean shouldProduce(Config config, BidRequest bidRequest);
+
+ Correction produce(Config config);
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrection.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrection.java
new file mode 100644
index 00000000000..75d86c511fb
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrection.java
@@ -0,0 +1,24 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.correction.interstitial;
+
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Imp;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.Correction;
+
+public class InterstitialCorrection implements Correction {
+
+ @Override
+ public BidRequest apply(BidRequest bidRequest) {
+ return bidRequest.toBuilder()
+ .imp(bidRequest.getImp().stream()
+ .map(InterstitialCorrection::removeInterstitial)
+ .toList())
+ .build();
+ }
+
+ private static Imp removeInterstitial(Imp imp) {
+ final Integer interstitial = imp.getInstl();
+ return interstitial != null && interstitial == 1
+ ? imp.toBuilder().instl(null).build()
+ : imp;
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrectionProducer.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrectionProducer.java
new file mode 100644
index 00000000000..c9bd1995867
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrectionProducer.java
@@ -0,0 +1,80 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.correction.interstitial;
+
+import com.iab.openrtb.request.App;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Imp;
+import org.apache.commons.lang3.StringUtils;
+import org.prebid.server.hooks.modules.pb.request.correction.core.config.model.Config;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.Correction;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.CorrectionProducer;
+import org.prebid.server.hooks.modules.pb.request.correction.core.util.VersionUtil;
+import org.prebid.server.proto.openrtb.ext.request.ExtApp;
+import org.prebid.server.proto.openrtb.ext.request.ExtAppPrebid;
+
+import java.util.List;
+import java.util.Optional;
+
+public class InterstitialCorrectionProducer implements CorrectionProducer {
+
+ private static final InterstitialCorrection CORRECTION_INSTANCE = new InterstitialCorrection();
+
+ private static final String PREBID_MOBILE = "prebid-mobile";
+ private static final String ANDROID = "android";
+
+ private static final int MAX_VERSION_MAJOR = 2;
+ private static final int MAX_VERSION_MINOR = 2;
+ private static final int MAX_VERSION_PATCH = 3;
+
+ @Override
+ public boolean shouldProduce(Config config, BidRequest bidRequest) {
+ final App app = bidRequest.getApp();
+ return config.isInterstitialCorrectionEnabled()
+ && hasInterstitialToRemove(bidRequest.getImp())
+ && isPrebidMobile(app)
+ && isAndroid(app)
+ && isApplicableVersion(app);
+ }
+
+ private static boolean hasInterstitialToRemove(List imps) {
+ for (Imp imp : imps) {
+ final Integer interstitial = imp.getInstl();
+ if (interstitial != null && interstitial == 1) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isPrebidMobile(App app) {
+ final String source = Optional.ofNullable(app)
+ .map(App::getExt)
+ .map(ExtApp::getPrebid)
+ .map(ExtAppPrebid::getSource)
+ .orElse(null);
+
+ return StringUtils.equalsIgnoreCase(source, PREBID_MOBILE);
+ }
+
+ private static boolean isAndroid(App app) {
+ return StringUtils.containsIgnoreCase(app.getBundle(), ANDROID);
+ }
+
+ private static boolean isApplicableVersion(App app) {
+ return Optional.ofNullable(app)
+ .map(App::getExt)
+ .map(ExtApp::getPrebid)
+ .map(ExtAppPrebid::getVersion)
+ .map(InterstitialCorrectionProducer::checkVersion)
+ .orElse(false);
+ }
+
+ private static boolean checkVersion(String version) {
+ return VersionUtil.isVersionLessThan(version, MAX_VERSION_MAJOR, MAX_VERSION_MINOR, MAX_VERSION_PATCH);
+ }
+
+ @Override
+ public Correction produce(Config config) {
+ return CORRECTION_INSTANCE;
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrection.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrection.java
new file mode 100644
index 00000000000..f1b6b40eacc
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrection.java
@@ -0,0 +1,25 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.correction.useragent;
+
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Device;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.Correction;
+
+import java.util.regex.Pattern;
+
+public class UserAgentCorrection implements Correction {
+
+ private static final Pattern USER_AGENT_PATTERN = Pattern.compile("PrebidMobile/[0-9][^ ]*");
+
+ @Override
+ public BidRequest apply(BidRequest bidRequest) {
+ return bidRequest.toBuilder()
+ .device(correctDevice(bidRequest.getDevice()))
+ .build();
+ }
+
+ private static Device correctDevice(Device device) {
+ return device.toBuilder()
+ .ua(USER_AGENT_PATTERN.matcher(device.getUa()).replaceAll(""))
+ .build();
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrectionProducer.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrectionProducer.java
new file mode 100644
index 00000000000..f4c8d4f76dd
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrectionProducer.java
@@ -0,0 +1,76 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.correction.useragent;
+
+import com.iab.openrtb.request.App;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Device;
+import org.apache.commons.lang3.StringUtils;
+import org.prebid.server.hooks.modules.pb.request.correction.core.config.model.Config;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.Correction;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.CorrectionProducer;
+import org.prebid.server.hooks.modules.pb.request.correction.core.util.VersionUtil;
+import org.prebid.server.proto.openrtb.ext.request.ExtApp;
+import org.prebid.server.proto.openrtb.ext.request.ExtAppPrebid;
+
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class UserAgentCorrectionProducer implements CorrectionProducer {
+
+ private static final UserAgentCorrection CORRECTION_INSTANCE = new UserAgentCorrection();
+
+ private static final String PREBID_MOBILE = "prebid-mobile";
+ private static final Pattern USER_AGENT_PATTERN = Pattern.compile(".*PrebidMobile/[0-9]+[^ ]*.*");
+
+
+ private static final int MAX_VERSION_MAJOR = 2;
+ private static final int MAX_VERSION_MINOR = 1;
+ private static final int MAX_VERSION_PATCH = 6;
+
+ @Override
+ public boolean shouldProduce(Config config, BidRequest bidRequest) {
+ final App app = bidRequest.getApp();
+ return config.isUserAgentCorrectionEnabled()
+ && isPrebidMobile(app)
+ && isApplicableVersion(app)
+ && isApplicableDevice(bidRequest.getDevice());
+ }
+
+ private static boolean isPrebidMobile(App app) {
+ final String source = Optional.ofNullable(app)
+ .map(App::getExt)
+ .map(ExtApp::getPrebid)
+ .map(ExtAppPrebid::getSource)
+ .orElse(null);
+
+ return StringUtils.equalsIgnoreCase(source, PREBID_MOBILE);
+ }
+
+ private static boolean isApplicableVersion(App app) {
+ return Optional.ofNullable(app)
+ .map(App::getExt)
+ .map(ExtApp::getPrebid)
+ .map(ExtAppPrebid::getVersion)
+ .map(UserAgentCorrectionProducer::checkVersion)
+ .orElse(false);
+ }
+
+ private static boolean checkVersion(String version) {
+ return VersionUtil.isVersionLessThan(version, MAX_VERSION_MAJOR, MAX_VERSION_MINOR, MAX_VERSION_PATCH);
+ }
+
+ private static boolean isApplicableDevice(Device device) {
+ return Optional.ofNullable(device)
+ .map(Device::getUa)
+ .filter(StringUtils::isNotEmpty)
+ .map(USER_AGENT_PATTERN::matcher)
+ .map(Matcher::matches)
+ .orElse(false);
+ }
+
+
+ @Override
+ public Correction produce(Config config) {
+ return CORRECTION_INSTANCE;
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/util/VersionUtil.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/util/VersionUtil.java
new file mode 100644
index 00000000000..2e84f01183e
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/core/util/VersionUtil.java
@@ -0,0 +1,35 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.util;
+
+public class VersionUtil {
+
+ public static boolean isVersionLessThan(String versionAsString, int major, int minor, int patch) {
+ return compareVersion(versionAsString, major, minor, patch) < 0;
+ }
+
+ private static int compareVersion(String versionAsString, int major, int minor, int patch) {
+ final String[] version = versionAsString.split("\\.");
+
+ final int parsedMajor = getAtAsIntOrDefault(version, 0, -1);
+ final int parsedMinor = getAtAsIntOrDefault(version, 1, 0);
+ final int parsedPatch = getAtAsIntOrDefault(version, 2, 0);
+
+ int diff = parsedMajor >= 0 ? parsedMajor - major : 1;
+ diff = diff == 0 ? parsedMinor - minor : diff;
+ diff = diff == 0 ? parsedPatch - patch : diff;
+
+ return diff;
+ }
+
+ private static int getAtAsIntOrDefault(String[] array, int index, int defaultValue) {
+ return array.length > index ? intOrDefault(array[index], defaultValue) : defaultValue;
+ }
+
+ private static int intOrDefault(String intAsString, int defaultValue) {
+ try {
+ final int parsed = Integer.parseInt(intAsString);
+ return parsed >= 0 ? parsed : defaultValue;
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/spring/config/RequestCorrectionModuleConfiguration.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/spring/config/RequestCorrectionModuleConfiguration.java
new file mode 100644
index 00000000000..ecbd725e42d
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/spring/config/RequestCorrectionModuleConfiguration.java
@@ -0,0 +1,38 @@
+package org.prebid.server.hooks.modules.pb.request.correction.spring.config;
+
+import org.prebid.server.hooks.modules.pb.request.correction.core.RequestCorrectionProvider;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.CorrectionProducer;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.interstitial.InterstitialCorrectionProducer;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.useragent.UserAgentCorrectionProducer;
+import org.prebid.server.hooks.modules.pb.request.correction.v1.RequestCorrectionModule;
+import org.prebid.server.json.ObjectMapperProvider;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@Configuration
+@ConditionalOnProperty(prefix = "hooks." + RequestCorrectionModule.CODE, name = "enabled", havingValue = "true")
+public class RequestCorrectionModuleConfiguration {
+
+ @Bean
+ InterstitialCorrectionProducer interstitialCorrectionProducer() {
+ return new InterstitialCorrectionProducer();
+ }
+
+ @Bean
+ UserAgentCorrectionProducer userAgentCorrectionProducer() {
+ return new UserAgentCorrectionProducer();
+ }
+
+ @Bean
+ RequestCorrectionProvider requestCorrectionProvider(List correctionProducers) {
+ return new RequestCorrectionProvider(correctionProducers);
+ }
+
+ @Bean
+ RequestCorrectionModule requestCorrectionModule(RequestCorrectionProvider requestCorrectionProvider) {
+ return new RequestCorrectionModule(requestCorrectionProvider, ObjectMapperProvider.mapper());
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionModule.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionModule.java
new file mode 100644
index 00000000000..10d20a3b823
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionModule.java
@@ -0,0 +1,32 @@
+package org.prebid.server.hooks.modules.pb.request.correction.v1;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.prebid.server.hooks.modules.pb.request.correction.core.RequestCorrectionProvider;
+import org.prebid.server.hooks.v1.Hook;
+import org.prebid.server.hooks.v1.InvocationContext;
+import org.prebid.server.hooks.v1.Module;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class RequestCorrectionModule implements Module {
+
+ public static final String CODE = "pb-request-correction";
+
+ private final Collection extends Hook, ? extends InvocationContext>> hooks;
+
+ public RequestCorrectionModule(RequestCorrectionProvider requestCorrectionProvider, ObjectMapper mapper) {
+ this.hooks = Collections.singleton(
+ new RequestCorrectionProcessedAuctionHook(requestCorrectionProvider, mapper));
+ }
+
+ @Override
+ public String code() {
+ return CODE;
+ }
+
+ @Override
+ public Collection extends Hook, ? extends InvocationContext>> hooks() {
+ return hooks;
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionProcessedAuctionHook.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionProcessedAuctionHook.java
new file mode 100644
index 00000000000..50502e844db
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionProcessedAuctionHook.java
@@ -0,0 +1,103 @@
+package org.prebid.server.hooks.modules.pb.request.correction.v1;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.iab.openrtb.request.BidRequest;
+import io.vertx.core.Future;
+import org.prebid.server.exception.PreBidException;
+import org.prebid.server.hooks.modules.pb.request.correction.core.RequestCorrectionProvider;
+import org.prebid.server.hooks.modules.pb.request.correction.core.config.model.Config;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.Correction;
+import org.prebid.server.hooks.modules.pb.request.correction.v1.model.AuctionRequestPayloadImpl;
+import org.prebid.server.hooks.modules.pb.request.correction.v1.model.InvocationResultImpl;
+import org.prebid.server.hooks.v1.InvocationAction;
+import org.prebid.server.hooks.v1.InvocationResult;
+import org.prebid.server.hooks.v1.InvocationStatus;
+import org.prebid.server.hooks.v1.auction.AuctionInvocationContext;
+import org.prebid.server.hooks.v1.auction.AuctionRequestPayload;
+import org.prebid.server.hooks.v1.auction.ProcessedAuctionRequestHook;
+
+import java.util.List;
+import java.util.Objects;
+
+public class RequestCorrectionProcessedAuctionHook implements ProcessedAuctionRequestHook {
+
+ private static final String CODE = "pb-request-correction-processed-auction-request";
+
+ private final RequestCorrectionProvider requestCorrectionProvider;
+ private final ObjectMapper mapper;
+
+ public RequestCorrectionProcessedAuctionHook(RequestCorrectionProvider requestCorrectionProvider, ObjectMapper mapper) {
+ this.requestCorrectionProvider = Objects.requireNonNull(requestCorrectionProvider);
+ this.mapper = Objects.requireNonNull(mapper);
+ }
+
+ @Override
+ public Future> call(AuctionRequestPayload payload,
+ AuctionInvocationContext context) {
+
+ final Config config;
+ try {
+ config = moduleConfig(context.accountConfig());
+ } catch (PreBidException e) {
+ return failure(e.getMessage());
+ }
+
+ if (config == null || !config.isEnabled()) {
+ return noAction();
+ }
+
+ final BidRequest bidRequest = payload.bidRequest();
+
+ final List corrections = requestCorrectionProvider.corrections(config, bidRequest);
+ if (corrections.isEmpty()) {
+ return noAction();
+ }
+
+ final InvocationResult invocationResult = InvocationResultImpl.builder()
+ .status(InvocationStatus.success)
+ .action(InvocationAction.update)
+ .payloadUpdate(initialPayload ->
+ AuctionRequestPayloadImpl.of(applyCorrections(initialPayload.bidRequest(), corrections)))
+ .build();
+
+ return Future.succeededFuture(invocationResult);
+ }
+
+ private Config moduleConfig(ObjectNode accountConfig) {
+ try {
+ return mapper.treeToValue(accountConfig, Config.class);
+ } catch (JsonProcessingException e) {
+ throw new PreBidException(e.getMessage());
+ }
+ }
+
+ private static BidRequest applyCorrections(BidRequest bidRequest, List corrections) {
+ BidRequest result = bidRequest;
+ for (Correction correction : corrections) {
+ result = correction.apply(result);
+ }
+ return result;
+ }
+
+ private Future> failure(String message) {
+ return Future.succeededFuture(InvocationResultImpl.builder()
+ .status(InvocationStatus.failure)
+ .message(message)
+ .action(InvocationAction.no_action)
+ .build());
+ }
+
+ private static Future> noAction() {
+ return Future.succeededFuture(InvocationResultImpl.builder()
+ .status(InvocationStatus.success)
+ .action(InvocationAction.no_action)
+ .build());
+ }
+
+ @Override
+ public String code() {
+ return CODE;
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/AuctionRequestPayloadImpl.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/AuctionRequestPayloadImpl.java
new file mode 100644
index 00000000000..ca8bb6aa52d
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/AuctionRequestPayloadImpl.java
@@ -0,0 +1,13 @@
+package org.prebid.server.hooks.modules.pb.request.correction.v1.model;
+
+import com.iab.openrtb.request.BidRequest;
+import lombok.Value;
+import lombok.experimental.Accessors;
+import org.prebid.server.hooks.v1.auction.AuctionRequestPayload;
+
+@Accessors(fluent = true)
+@Value(staticConstructor = "of")
+public class AuctionRequestPayloadImpl implements AuctionRequestPayload {
+
+ BidRequest bidRequest;
+}
diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/InvocationResultImpl.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/InvocationResultImpl.java
new file mode 100644
index 00000000000..96f90d14a29
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/InvocationResultImpl.java
@@ -0,0 +1,37 @@
+package org.prebid.server.hooks.modules.pb.request.correction.v1.model;
+
+import lombok.Builder;
+import lombok.Value;
+import lombok.experimental.Accessors;
+import org.prebid.server.hooks.v1.InvocationAction;
+import org.prebid.server.hooks.v1.InvocationResult;
+import org.prebid.server.hooks.v1.InvocationStatus;
+import org.prebid.server.hooks.v1.PayloadUpdate;
+import org.prebid.server.hooks.v1.analytics.Tags;
+import org.prebid.server.hooks.v1.auction.AuctionRequestPayload;
+
+import java.util.List;
+
+@Accessors(fluent = true)
+@Builder
+@Value
+public class InvocationResultImpl implements InvocationResult {
+
+ InvocationStatus status;
+
+ String message;
+
+ InvocationAction action;
+
+ PayloadUpdate payloadUpdate;
+
+ List errors;
+
+ List warnings;
+
+ List debugMessages;
+
+ Object moduleContext;
+
+ Tags analyticsTags;
+}
diff --git a/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/RequestCorrectionProviderTest.java b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/RequestCorrectionProviderTest.java
new file mode 100644
index 00000000000..56856d10c16
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/RequestCorrectionProviderTest.java
@@ -0,0 +1,58 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.Correction;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.CorrectionProducer;
+
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+@ExtendWith(MockitoExtension.class)
+public class RequestCorrectionProviderTest {
+
+ @Mock
+ private CorrectionProducer correctionProducer;
+
+ private RequestCorrectionProvider target;
+
+ @BeforeEach
+ public void setUp() {
+ target = new RequestCorrectionProvider(singletonList(correctionProducer));
+ }
+
+ @Test
+ public void correctionsShouldReturnEmptyListIfAllCorrectionsDisabled() {
+ // given
+ given(correctionProducer.shouldProduce(any(), any())).willReturn(false);
+
+ // when
+ final List corrections = target.corrections(null, null);
+
+ // then
+ assertThat(corrections).isEmpty();
+ }
+
+ @Test
+ public void correctionsShouldReturnProducedCorrection() {
+ // given
+ given(correctionProducer.shouldProduce(any(), any())).willReturn(true);
+
+ final Correction correction = mock(Correction.class);
+ given(correctionProducer.produce(any())).willReturn(correction);
+
+ // when
+ final List corrections = target.corrections(null, null);
+
+ // then
+ assertThat(corrections).containsExactly(correction);
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrectionProducerTest.java b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrectionProducerTest.java
new file mode 100644
index 00000000000..3a44b7158e3
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrectionProducerTest.java
@@ -0,0 +1,132 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.correction.interstitial;
+
+import com.iab.openrtb.request.App;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Imp;
+import org.junit.jupiter.api.Test;
+import org.prebid.server.hooks.modules.pb.request.correction.core.config.model.Config;
+import org.prebid.server.proto.openrtb.ext.request.ExtApp;
+import org.prebid.server.proto.openrtb.ext.request.ExtAppPrebid;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InterstitialCorrectionProducerTest {
+
+ private final InterstitialCorrectionProducer target = new InterstitialCorrectionProducer();
+
+ @Test
+ public void shouldProduceReturnsFalseIfCorrectionDisabled() {
+ // given
+ final Config config = Config.builder()
+ .interstitialCorrectionEnabled(false)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder().build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void shouldProduceReturnsFalseIfThereIsNothingToDo() {
+ // given
+ final Config config = Config.builder()
+ .interstitialCorrectionEnabled(true)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(emptyList())
+ .app(App.builder().build())
+ .build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void shouldProduceReturnsFalseIfSourceIsNotPrebidMobile() {
+ // given
+ final Config config = Config.builder()
+ .interstitialCorrectionEnabled(true)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(singletonList(Imp.builder().instl(1).build()))
+ .app(App.builder().ext(ExtApp.of(ExtAppPrebid.of("source", null), null)).build())
+ .build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void shouldProduceReturnsFalseIfBundleNotAnAndroid() {
+ // given
+ final Config config = Config.builder()
+ .interstitialCorrectionEnabled(true)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(singletonList(Imp.builder().instl(1).build()))
+ .app(App.builder()
+ .bundle("bundle")
+ .ext(ExtApp.of(ExtAppPrebid.of("prebid-mobile", null), null))
+ .build())
+ .build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void shouldProduceReturnsFalseIfVersionInvalid() {
+ // given
+ final Config config = Config.builder()
+ .interstitialCorrectionEnabled(true)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(singletonList(Imp.builder().instl(1).build()))
+ .app(App.builder()
+ .bundle("bundleAndroid")
+ .ext(ExtApp.of(ExtAppPrebid.of("prebid-mobile", "1a.2.3"), null))
+ .build())
+ .build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void shouldProduceReturnsTrueWhenAllConditionsMatch() {
+ // given
+ final Config config = Config.builder()
+ .interstitialCorrectionEnabled(true)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(singletonList(Imp.builder().instl(1).build()))
+ .app(App.builder()
+ .bundle("bundleAndroid")
+ .ext(ExtApp.of(ExtAppPrebid.of("prebid-mobile", "1.2.3"), null))
+ .build())
+ .build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isTrue();
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrectionTest.java b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrectionTest.java
new file mode 100644
index 00000000000..490607a7d5e
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/interstitial/InterstitialCorrectionTest.java
@@ -0,0 +1,35 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.correction.interstitial;
+
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Imp;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.Test;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InterstitialCorrectionTest {
+
+ private final InterstitialCorrection target = new InterstitialCorrection();
+
+ @Test
+ public void applyShouldCorrectInterstitial() {
+ // given
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(asList(
+ Imp.builder().instl(0).build(),
+ Imp.builder().build(),
+ Imp.builder().instl(1).build()))
+ .build();
+
+ // when
+ final BidRequest result = target.apply(bidRequest);
+
+ // then
+ assertThat(result)
+ .extracting(BidRequest::getImp)
+ .asInstanceOf(InstanceOfAssertFactories.list(Imp.class))
+ .extracting(Imp::getInstl)
+ .containsExactly(0, null, null);
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrectionProducerTest.java b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrectionProducerTest.java
new file mode 100644
index 00000000000..cb7e3458bef
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrectionProducerTest.java
@@ -0,0 +1,125 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.correction.useragent;
+
+import com.iab.openrtb.request.App;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Device;
+import com.iab.openrtb.request.Imp;
+import org.junit.jupiter.api.Test;
+import org.prebid.server.hooks.modules.pb.request.correction.core.config.model.Config;
+import org.prebid.server.proto.openrtb.ext.request.ExtApp;
+import org.prebid.server.proto.openrtb.ext.request.ExtAppPrebid;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UserAgentCorrectionProducerTest {
+
+ private final UserAgentCorrectionProducer target = new UserAgentCorrectionProducer();
+
+ @Test
+ public void shouldProduceReturnsFalseIfCorrectionDisabled() {
+ // given
+ final Config config = Config.builder()
+ .userAgentCorrectionEnabled(false)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder().build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void shouldProduceReturnsFalseIfThereIsNothingToDo() {
+ // given
+ final Config config = Config.builder()
+ .userAgentCorrectionEnabled(true)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder().build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void shouldProduceReturnsFalseIfSourceIsNotPrebidMobile() {
+ // given
+ final Config config = Config.builder()
+ .userAgentCorrectionEnabled(true)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(singletonList(Imp.builder().instl(1).build()))
+ .app(App.builder().ext(ExtApp.of(ExtAppPrebid.of("source", null), null)).build())
+ .build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void shouldProduceReturnsFalseIfVersionInvalid() {
+ // given
+ final Config config = Config.builder()
+ .userAgentCorrectionEnabled(true)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder()
+ .app(App.builder()
+ .ext(ExtApp.of(ExtAppPrebid.of("prebid-mobile", "1a.2.3"), null))
+ .build())
+ .build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void shouldProduceReturnsFalseIfDeviceUserAgentDoesNotMatch() {
+ // given
+ final Config config = Config.builder()
+ .userAgentCorrectionEnabled(true)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder()
+ .device(Device.builder().ua("Blah blah").build())
+ .app(App.builder()
+ .ext(ExtApp.of(ExtAppPrebid.of("prebid-mobile", "1.2.3"), null))
+ .build())
+ .build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void shouldProduceReturnsTrueWhenAllConditionsMatch() {
+ // given
+ final Config config = Config.builder()
+ .userAgentCorrectionEnabled(true)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder()
+ .device(Device.builder().ua("Blah PrebidMobile/1asdf blah").build())
+ .app(App.builder()
+ .ext(ExtApp.of(ExtAppPrebid.of("prebid-mobile", "1.2.3"), null))
+ .build())
+ .build();
+
+ // when
+ final boolean result = target.shouldProduce(config, bidRequest);
+
+ // then
+ assertThat(result).isTrue();
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrectionTest.java b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrectionTest.java
new file mode 100644
index 00000000000..c8ed5f6762d
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/correction/useragent/UserAgentCorrectionTest.java
@@ -0,0 +1,29 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.correction.useragent;
+
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Device;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UserAgentCorrectionTest {
+
+ private final UserAgentCorrection target = new UserAgentCorrection();
+
+ @Test
+ public void applyShouldCorrectUserAgent() {
+ // given
+ final BidRequest bidRequest = BidRequest.builder()
+ .device(Device.builder().ua("blah PrebidMobile/1asdf blah").build())
+ .build();
+
+ // when
+ final BidRequest result = target.apply(bidRequest);
+
+ // then
+ assertThat(result)
+ .extracting(BidRequest::getDevice)
+ .extracting(Device::getUa)
+ .isEqualTo("blah blah");
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/util/VersionUtilTest.java b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/util/VersionUtilTest.java
new file mode 100644
index 00000000000..8da1ec6a3c3
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/core/util/VersionUtilTest.java
@@ -0,0 +1,52 @@
+package org.prebid.server.hooks.modules.pb.request.correction.core.util;
+
+import org.junit.jupiter.api.Test;
+
+import static java.lang.Integer.MAX_VALUE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class VersionUtilTest {
+
+ @Test
+ public void isVersionLessThanShouldReturnFalseIfVersionGreaterThanRequired() {
+ // when and then
+ assertThat(VersionUtil.isVersionLessThan("2.4.3", 2, 2, 3)).isFalse();
+ }
+
+ @Test
+ public void isVersionLessThenShouldReturnFalseIfVersionIsEqualToRequired() {
+ // when and then
+ assertThat(VersionUtil.isVersionLessThan("2.4.3", 2, 4, 3)).isFalse();
+ }
+
+ @Test
+ public void isVersionLessThenShouldReturnTrueIfVersionIsLessThanRequired() {
+ // when and then
+ assertThat(VersionUtil.isVersionLessThan("2.2.3", 2, 4, 3)).isTrue();
+ }
+
+ @Test
+ public void isVersionLessThenShouldReturnExpectedResults() {
+ // major
+ assertThat(VersionUtil.isVersionLessThan("0", 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("1", 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("2", 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("3", 2, 2, 3)).isFalse();
+
+ // minor
+ assertThat(VersionUtil.isVersionLessThan("0." + MAX_VALUE, 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("1." + MAX_VALUE, 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("2.0", 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("2.1", 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("2.2", 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("2.3", 2, 2, 3)).isFalse();
+
+ // patch
+ assertThat(VersionUtil.isVersionLessThan("0.%d.%d".formatted(MAX_VALUE, MAX_VALUE), 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("1.%d.%d".formatted(MAX_VALUE, MAX_VALUE), 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("2.1." + MAX_VALUE, 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("2.2.1", 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("2.2.2", 2, 2, 3)).isTrue();
+ assertThat(VersionUtil.isVersionLessThan("2.2.3", 2, 2, 3)).isFalse();
+ }
+}
diff --git a/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionProcessedAuctionHookTest.java b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionProcessedAuctionHookTest.java
new file mode 100644
index 00000000000..9250e188cce
--- /dev/null
+++ b/extra/modules/pb-request-correction/src/test/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionProcessedAuctionHookTest.java
@@ -0,0 +1,120 @@
+package org.prebid.server.hooks.modules.pb.request.correction.v1;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.vertx.core.Future;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.prebid.server.hooks.modules.pb.request.correction.core.RequestCorrectionProvider;
+import org.prebid.server.hooks.modules.pb.request.correction.core.config.model.Config;
+import org.prebid.server.hooks.modules.pb.request.correction.core.correction.Correction;
+import org.prebid.server.hooks.v1.InvocationAction;
+import org.prebid.server.hooks.v1.InvocationResult;
+import org.prebid.server.hooks.v1.InvocationStatus;
+import org.prebid.server.hooks.v1.auction.AuctionInvocationContext;
+import org.prebid.server.hooks.v1.auction.AuctionRequestPayload;
+import org.prebid.server.json.ObjectMapperProvider;
+
+import java.util.Map;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+@ExtendWith(MockitoExtension.class)
+public class RequestCorrectionProcessedAuctionHookTest {
+
+ private static final ObjectMapper MAPPER = ObjectMapperProvider.mapper();
+
+ @Mock
+ private RequestCorrectionProvider requestCorrectionProvider;
+
+ private RequestCorrectionProcessedAuctionHook target;
+
+ @Mock
+ private AuctionRequestPayload payload;
+
+ @Mock
+ private AuctionInvocationContext invocationContext;
+
+ @BeforeEach
+ public void setUp() {
+ given(invocationContext.accountConfig()).willReturn(MAPPER.valueToTree(Config.builder()
+ .enabled(true)
+ .interstitialCorrectionEnabled(true)
+ .build()));
+
+ target = new RequestCorrectionProcessedAuctionHook(requestCorrectionProvider, MAPPER);
+ }
+
+ @Test
+ public void callShouldReturnFailedResultOnInvalidConfiguration() {
+ // given
+ given(invocationContext.accountConfig()).willReturn(MAPPER.valueToTree(Map.of("enabled", emptyList())));
+
+ // when
+ final Future> result = target.call(payload, invocationContext);
+
+ //then
+ assertThat(result.result()).satisfies(invocationResult -> {
+ assertThat(invocationResult.status()).isEqualTo(InvocationStatus.failure);
+ assertThat(invocationResult.message()).startsWith("Cannot deserialize value of type");
+ assertThat(invocationResult.action()).isEqualTo(InvocationAction.no_action);
+ });
+ }
+
+ @Test
+ public void callShouldReturnNoActionOnDisabledConfig() {
+ // given
+ given(invocationContext.accountConfig()).willReturn(MAPPER.valueToTree(Config.builder()
+ .enabled(false)
+ .interstitialCorrectionEnabled(true)
+ .build()));
+
+ // when
+ final Future> result = target.call(payload, invocationContext);
+
+ //then
+ assertThat(result.result()).satisfies(invocationResult -> {
+ assertThat(invocationResult.status()).isEqualTo(InvocationStatus.success);
+ assertThat(invocationResult.action()).isEqualTo(InvocationAction.no_action);
+ });
+ }
+
+ @Test
+ public void callShouldReturnNoActionIfThereIsNoApplicableCorrections() {
+ // given
+ given(requestCorrectionProvider.corrections(any(), any())).willReturn(emptyList());
+
+ // when
+ final Future> result = target.call(payload, invocationContext);
+
+ //then
+ assertThat(result.result()).satisfies(invocationResult -> {
+ assertThat(invocationResult.status()).isEqualTo(InvocationStatus.success);
+ assertThat(invocationResult.action()).isEqualTo(InvocationAction.no_action);
+ });
+ }
+
+ @Test
+ public void callShouldReturnUpdate() {
+ // given
+ final Correction correction = mock(Correction.class);
+ given(requestCorrectionProvider.corrections(any(), any())).willReturn(singletonList(correction));
+
+ // when
+ final Future> result = target.call(payload, invocationContext);
+
+ //then
+ assertThat(result.result()).satisfies(invocationResult -> {
+ assertThat(invocationResult.status()).isEqualTo(InvocationStatus.success);
+ assertThat(invocationResult.action()).isEqualTo(InvocationAction.update);
+ assertThat(invocationResult.payloadUpdate()).isNotNull();
+ });
+ }
+}
diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml
index 5056c0c9914..d40b7e7829e 100644
--- a/extra/modules/pom.xml
+++ b/extra/modules/pom.xml
@@ -23,6 +23,7 @@
fiftyone-devicedetection
pb-response-correction
greenbids-real-time-data
+ pb-request-correction
diff --git a/src/test/groovy/org/prebid/server/functional/model/ModuleName.groovy b/src/test/groovy/org/prebid/server/functional/model/ModuleName.groovy
index 5efcdf40709..2bc06ab7144 100644
--- a/src/test/groovy/org/prebid/server/functional/model/ModuleName.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/ModuleName.groovy
@@ -6,7 +6,8 @@ enum ModuleName {
PB_RICHMEDIA_FILTER("pb-richmedia-filter"),
PB_RESPONSE_CORRECTION ("pb-response-correction"),
- ORTB2_BLOCKING("ortb2-blocking")
+ ORTB2_BLOCKING("ortb2-blocking"),
+ PB_REQUEST_CORRECTION('pb-request-correction'),
@JsonValue
final String code
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/ModuleHookImplementation.groovy b/src/test/groovy/org/prebid/server/functional/model/config/ModuleHookImplementation.groovy
index b5c57122a3f..247bdea4353 100644
--- a/src/test/groovy/org/prebid/server/functional/model/config/ModuleHookImplementation.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/config/ModuleHookImplementation.groovy
@@ -9,7 +9,8 @@ enum ModuleHookImplementation {
PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES("pb-richmedia-filter-all-processed-bid-responses-hook"),
RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES("pb-response-correction-all-processed-bid-responses"),
ORTB2_BLOCKING_BIDDER_REQUEST("ortb2-blocking-bidder-request"),
- ORTB2_BLOCKING_RAW_BIDDER_RESPONSE("ortb2-blocking-raw-bidder-response")
+ ORTB2_BLOCKING_RAW_BIDDER_RESPONSE("ortb2-blocking-raw-bidder-response"),
+ PB_REQUEST_CORRECTION_PROCESSED_AUCTION_REQUEST("pb-request-correction-processed-auction-request"),
@JsonValue
final String code
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/PbRequestCorrectionConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/PbRequestCorrectionConfig.groovy
new file mode 100644
index 00000000000..5d7a980115b
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/PbRequestCorrectionConfig.groovy
@@ -0,0 +1,29 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class PbRequestCorrectionConfig {
+
+ @JsonProperty("pbsdkAndroidInstlRemove")
+ Boolean interstitialCorrectionEnabled
+ @JsonProperty("pbsdkUaCleanup")
+ Boolean userAgentCorrectionEnabled
+ @JsonProperty("pbsdk-android-instl-remove")
+ Boolean interstitialCorrectionEnabledKebabCase
+ @JsonProperty("pbsdk-ua-cleanup")
+ Boolean userAgentCorrectionEnabledKebabCase
+
+ Boolean enabled
+
+ static PbRequestCorrectionConfig getDefaultConfigWithInterstitial(Boolean interstitialCorrectionEnabled = true,
+ Boolean enabled = true) {
+ new PbRequestCorrectionConfig(enabled: enabled, interstitialCorrectionEnabled: interstitialCorrectionEnabled)
+ }
+
+ static PbRequestCorrectionConfig getDefaultConfigWithUserAgentCorrection(Boolean userAgentCorrectionEnabled = true,
+ Boolean enabled = true) {
+ new PbRequestCorrectionConfig(enabled: enabled, userAgentCorrectionEnabled: userAgentCorrectionEnabled)
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/PbsModulesConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/PbsModulesConfig.groovy
index f9121ae0b3a..59f640f966c 100644
--- a/src/test/groovy/org/prebid/server/functional/model/config/PbsModulesConfig.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/config/PbsModulesConfig.groovy
@@ -12,4 +12,5 @@ class PbsModulesConfig {
RichmediaFilter pbRichmediaFilter
Ortb2BlockingConfig ortb2Blocking
PbResponseCorrection pbResponseCorrection
+ PbRequestCorrectionConfig pbRequestCorrection
}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExt.groovy
index b31926c14b5..ee3c1c9a8f0 100644
--- a/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExt.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/AppExt.groovy
@@ -6,4 +6,5 @@ import groovy.transform.ToString
class AppExt {
AppExtData data
+ AppPrebid prebid
}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/AppPrebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/AppPrebid.groovy
new file mode 100644
index 00000000000..edb365d4d6f
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/AppPrebid.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class AppPrebid {
+
+ String source
+ String version
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy
index dbea9b32624..13c97a36ba4 100644
--- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy
@@ -30,7 +30,7 @@ class Imp {
Pmp pmp
String displayManager
String displayManagerVer
- Integer instl
+ OperationState instl
String tagId
BigDecimal bidFloor
Currency bidFloorCur
diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/ModuleBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/ModuleBaseSpec.groovy
index 19cb2cd53de..7d281790dcb 100644
--- a/src/test/groovy/org/prebid/server/functional/tests/module/ModuleBaseSpec.groovy
+++ b/src/test/groovy/org/prebid/server/functional/tests/module/ModuleBaseSpec.groovy
@@ -2,13 +2,16 @@ package org.prebid.server.functional.tests.module
import org.prebid.server.functional.model.config.Endpoint
import org.prebid.server.functional.model.config.ExecutionPlan
+import org.prebid.server.functional.model.config.Stage
import org.prebid.server.functional.tests.BaseSpec
import static org.prebid.server.functional.model.ModuleName.ORTB2_BLOCKING
+import static org.prebid.server.functional.model.ModuleName.PB_REQUEST_CORRECTION
import static org.prebid.server.functional.model.ModuleName.PB_RESPONSE_CORRECTION
import static org.prebid.server.functional.model.ModuleName.PB_RICHMEDIA_FILTER
import static org.prebid.server.functional.model.config.Endpoint.OPENRTB2_AUCTION
import static org.prebid.server.functional.model.config.Stage.ALL_PROCESSED_BID_RESPONSES
+import static org.prebid.server.functional.model.config.Stage.PROCESSED_AUCTION_REQUEST
class ModuleBaseSpec extends BaseSpec {
@@ -51,4 +54,9 @@ class ModuleBaseSpec extends BaseSpec {
protected static Map getOrtb2BlockingSettings(boolean isEnabled = true) {
["hooks.${ORTB2_BLOCKING.code}.enabled": isEnabled as String]
}
+
+ protected static Map getRequestCorrectionSettings(Endpoint endpoint = OPENRTB2_AUCTION, Stage stage = PROCESSED_AUCTION_REQUEST) {
+ ["hooks.${PB_REQUEST_CORRECTION.code}.enabled": "true",
+ "hooks.host-execution-plan" : encode(ExecutionPlan.getSingleEndpointExecutionPlan(endpoint, PB_REQUEST_CORRECTION, [stage]))]
+ }
}
diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/pbrequestcorrection/PbRequestCorrectionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/pbrequestcorrection/PbRequestCorrectionSpec.groovy
new file mode 100644
index 00000000000..68b00bdd0d1
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/tests/module/pbrequestcorrection/PbRequestCorrectionSpec.groovy
@@ -0,0 +1,454 @@
+package org.prebid.server.functional.tests.module.pbrequestcorrection
+
+import org.prebid.server.functional.model.config.AccountConfig
+import org.prebid.server.functional.model.config.AccountHooksConfiguration
+import org.prebid.server.functional.model.config.PbRequestCorrectionConfig
+import org.prebid.server.functional.model.config.PbsModulesConfig
+import org.prebid.server.functional.model.db.Account
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.model.request.auction.AppExt
+import org.prebid.server.functional.model.request.auction.AppPrebid
+import org.prebid.server.functional.model.request.auction.Device
+import org.prebid.server.functional.model.request.auction.Imp
+import org.prebid.server.functional.model.request.auction.OperationState
+import org.prebid.server.functional.service.PrebidServerService
+import org.prebid.server.functional.tests.module.ModuleBaseSpec
+import org.prebid.server.functional.util.PBSUtils
+
+import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP
+import static org.prebid.server.functional.model.request.auction.OperationState.YES
+
+class PbRequestCorrectionSpec extends ModuleBaseSpec {
+
+ private static final String PREBID_MOBILE = "prebid-mobile"
+ private static final String DEVICE_PREBID_MOBILE_PATTERN = "PrebidMobile/"
+ private static final String ACCEPTABLE_DEVICE_UA_VERSION_THRESHOLD = PBSUtils.getRandomVersion("0.0", "2.1.5")
+ private static final String ACCEPTABLE_DEVICE_INSTL_VERSION_THRESHOLD = PBSUtils.getRandomVersion("0.0", "2.2.3")
+ private static final String ANDROID = "android"
+ private static final String IOS = "IOS"
+
+ private PrebidServerService pbsServiceWithRequestCorrectionModule = pbsServiceFactory.getService(requestCorrectionSettings)
+
+ def "PBS should remove positive instl from imps for android app when request correction is enabled for account"() {
+ given: "Android APP bid request with version lover then version threshold"
+ def prebid = new AppPrebid(source: PBSUtils.getRandomCase(PREBID_MOBILE), version: ACCEPTABLE_DEVICE_INSTL_VERSION_THRESHOLD)
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ imp = imps
+ app.bundle = PBSUtils.getRandomCase(bundle)
+ app.ext = new AppExt(prebid: prebid)
+ }
+
+ and: "Account in the DB"
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request shouldn't contain imp.instl"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.imp.instl.every { it == null }
+
+ where:
+ imps | bundle | requestCorrectionConfig
+ [Imp.defaultImpression.tap { instl = YES }] | "$ANDROID${PBSUtils.randomString}" | PbRequestCorrectionConfig.defaultConfigWithInterstitial
+ [Imp.defaultImpression.tap { instl = null }, Imp.defaultImpression.tap { instl = YES }] | "${PBSUtils.randomString}$ANDROID${PBSUtils.randomString}" | PbRequestCorrectionConfig.defaultConfigWithInterstitial
+ [Imp.defaultImpression.tap { instl = YES }, Imp.defaultImpression.tap { instl = null }] | "${PBSUtils.randomString}$ANDROID${PBSUtils.getRandomNumber()}" | PbRequestCorrectionConfig.defaultConfigWithInterstitial
+ [Imp.defaultImpression.tap { instl = YES }, Imp.defaultImpression.tap { instl = YES }] | "$ANDROID${PBSUtils.randomString}_$ANDROID${PBSUtils.getRandomNumber()}" | PbRequestCorrectionConfig.defaultConfigWithInterstitial
+ [Imp.defaultImpression.tap { instl = YES }] | "$ANDROID${PBSUtils.randomString}" | new PbRequestCorrectionConfig(enabled: true, interstitialCorrectionEnabledKebabCase: true)
+ [Imp.defaultImpression.tap { instl = null }, Imp.defaultImpression.tap { instl = YES }] | "${PBSUtils.randomString}$ANDROID${PBSUtils.randomString}" | new PbRequestCorrectionConfig(enabled: true, interstitialCorrectionEnabledKebabCase: true)
+ [Imp.defaultImpression.tap { instl = YES }, Imp.defaultImpression.tap { instl = null }] | "${PBSUtils.randomString}$ANDROID${PBSUtils.getRandomNumber()}" | new PbRequestCorrectionConfig(enabled: true, interstitialCorrectionEnabledKebabCase: true)
+ [Imp.defaultImpression.tap { instl = YES }, Imp.defaultImpression.tap { instl = YES }] | "$ANDROID${PBSUtils.randomString}_$ANDROID${PBSUtils.getRandomNumber()}" | new PbRequestCorrectionConfig(enabled: true, interstitialCorrectionEnabledKebabCase: true)
+ }
+
+ def "PBS shouldn't remove negative instl from imps for android app when request correction is enabled for account"() {
+ given: "Android APP bid request with version lover then version threshold"
+ def prebid = new AppPrebid(source: PBSUtils.getRandomCase(PREBID_MOBILE), version: ACCEPTABLE_DEVICE_INSTL_VERSION_THRESHOLD)
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ imp = imps
+ app.bundle = PBSUtils.getRandomCase(ANDROID)
+ app.ext = new AppExt(prebid: prebid)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.defaultConfigWithInterstitial
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain original imp.instl"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.imp.instl == bidRequest.imp.instl
+
+ where:
+ imps << [[Imp.defaultImpression.tap { instl = OperationState.NO }],
+ [Imp.defaultImpression.tap { instl = null }, Imp.defaultImpression.tap { instl = OperationState.NO }],
+ [Imp.defaultImpression.tap { instl = OperationState.NO }, Imp.defaultImpression.tap { instl = null }],
+ [Imp.defaultImpression.tap { instl = OperationState.NO }, Imp.defaultImpression.tap { instl = OperationState.NO }]]
+ }
+
+ def "PBS shouldn't remove positive instl from imps for not android or not prebid-mobile app when request correction is enabled for account"() {
+ given: "Android APP bid request with version lover then version threshold"
+ def prebid = new AppPrebid(source: PBSUtils.getRandomCase(source), version: PBSUtils.getRandomVersion(ACCEPTABLE_DEVICE_INSTL_VERSION_THRESHOLD))
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ imp.first.instl = YES
+ app.bundle = PBSUtils.getRandomCase(bundle)
+ app.ext = new AppExt(prebid: prebid)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.defaultConfigWithInterstitial
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain original imp.instl"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.imp.instl == bidRequest.imp.instl
+
+ where:
+ bundle | source
+ IOS | PREBID_MOBILE
+ PBSUtils.randomString | PREBID_MOBILE
+ ANDROID | PBSUtils.randomString
+ ANDROID | PBSUtils.randomString + PREBID_MOBILE
+ ANDROID | PREBID_MOBILE + PBSUtils.randomString
+ }
+
+ def "PBS shouldn't remove positive instl from imps for app when request correction is enabled for account but some required parameter is empty"() {
+ given: "Android APP bid request with version lover then version threshold"
+ def prebid = new AppPrebid(source: source, version: version)
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ imp.first.instl = instl
+ app.bundle = bundle
+ app.ext = new AppExt(prebid: prebid)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.defaultConfigWithInterstitial
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain original imp.instl"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.imp.instl == bidRequest.imp.instl
+
+ where:
+ bundle | source | version | instl
+ null | PREBID_MOBILE | ACCEPTABLE_DEVICE_INSTL_VERSION_THRESHOLD | YES
+ ANDROID | null | ACCEPTABLE_DEVICE_INSTL_VERSION_THRESHOLD | YES
+ ANDROID | PREBID_MOBILE | null | YES
+ ANDROID | PREBID_MOBILE | ACCEPTABLE_DEVICE_INSTL_VERSION_THRESHOLD | null
+ }
+
+ def "PBS shouldn't remove positive instl from imps for android app when request correction is enabled for account and version is threshold"() {
+ given: "Android APP bid request with version threshold"
+ def prebid = new AppPrebid(source: PBSUtils.getRandomCase(PREBID_MOBILE), version: "2.2.3")
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ imp.first.instl = YES
+ app.bundle = PBSUtils.getRandomCase(ANDROID)
+ app.ext = new AppExt(prebid: prebid)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.defaultConfigWithInterstitial
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain original imp.instl"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.imp.instl == bidRequest.imp.instl
+ }
+
+ def "PBS shouldn't remove positive instl from imps for android app when request correction is enabled for account and version is higher then threshold"() {
+ given: "Android APP bid request with version higher then version threshold"
+ def prebid = new AppPrebid(source: PBSUtils.getRandomCase(PREBID_MOBILE), version: PBSUtils.getRandomVersion("2.2.4"))
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ imp.first.instl = YES
+ app.bundle = PBSUtils.getRandomCase(ANDROID)
+ app.ext = new AppExt(prebid: prebid)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.defaultConfigWithInterstitial
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain original imp.instl"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.imp.instl == bidRequest.imp.instl
+ }
+
+ def "PBS shouldn't remove positive instl from imps for android app when request correction is disabled for account"() {
+ given: "Android APP bid request with version lover then version threshold"
+ def prebid = new AppPrebid(source: PBSUtils.getRandomCase(PREBID_MOBILE), version: ACCEPTABLE_DEVICE_INSTL_VERSION_THRESHOLD)
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ imp.first.instl = YES
+ app.bundle = PBSUtils.getRandomCase(ANDROID)
+ app.ext = new AppExt(prebid: prebid)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.getDefaultConfigWithInterstitial(interstitialCorrectionEnabled, enabled)
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain original imp.instl"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.imp.instl == bidRequest.imp.instl
+
+ where:
+ enabled | interstitialCorrectionEnabled
+ false | true
+ null | true
+ true | false
+ true | null
+ null | null
+ }
+
+ def "PBS shouldn't remove positive instl from imps for android app when request correction is not applied for account"() {
+ given: "Android APP bid request with version lover then version threshold"
+ def prebid = new AppPrebid(source: PBSUtils.getRandomCase(PREBID_MOBILE), version: ACCEPTABLE_DEVICE_INSTL_VERSION_THRESHOLD)
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ imp.first.instl = YES
+ app.bundle = PBSUtils.getRandomCase(ANDROID)
+ app.ext = new AppExt(prebid: prebid)
+ }
+
+ and: "Account in the DB"
+ def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(modules: new PbsModulesConfig()))
+ def account = new Account(uuid: bidRequest.accountId, config: accountConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain original imp.instl"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.imp.instl == bidRequest.imp.instl
+ }
+
+ def "PBS should remove pattern device.ua when request correction is enabled for account and user agent correction enabled"() {
+ given: "Android APP bid request with version lover then version threshold"
+ def prebid = new AppPrebid(source: PREBID_MOBILE, version: ACCEPTABLE_DEVICE_UA_VERSION_THRESHOLD)
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ app.ext = new AppExt(prebid: prebid)
+ device = new Device(ua: deviceUa)
+ }
+
+ and: "Account in the DB"
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request shouldn't contain device.ua"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert !bidderRequest.device.ua
+
+ where:
+ deviceUa | requestCorrectionConfig
+ "${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber}" | PbRequestCorrectionConfig.defaultConfigWithUserAgentCorrection
+ "${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber}${PBSUtils.randomString}" | PbRequestCorrectionConfig.defaultConfigWithUserAgentCorrection
+ "${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber}" | new PbRequestCorrectionConfig(enabled: true, userAgentCorrectionEnabledKebabCase: true)
+ "${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber}${PBSUtils.randomString}" | new PbRequestCorrectionConfig(enabled: true, userAgentCorrectionEnabledKebabCase: true)
+ }
+
+ def "PBS should remove only pattern device.ua when request correction is enabled for account and user agent correction enabled"() {
+ given: "Android APP bid request with version lover then version threshold"
+ def prebid = new AppPrebid(source: PREBID_MOBILE, version: ACCEPTABLE_DEVICE_UA_VERSION_THRESHOLD)
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ app.ext = new AppExt(prebid: prebid)
+ device = new Device(ua: deviceUa)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.defaultConfigWithUserAgentCorrection
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain device.ua"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.device.ua.contains(deviceUa.replaceAll("PrebidMobile/[0-9][^ ]*", '').trim())
+
+ where:
+ deviceUa << ["${PBSUtils.randomNumber} ${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber} ${PBSUtils.randomString}",
+ "${PBSUtils.randomString} ${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber}${PBSUtils.randomString} ${PBSUtils.randomString}",
+ "${DEVICE_PREBID_MOBILE_PATTERN}",
+ "${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber}",
+ "${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber} ${PBSUtils.randomString}"
+ ]
+ }
+
+ def "PBS shouldn't remove pattern device.ua when request correction is enabled for account and user agent correction disabled"() {
+ given: "Android APP bid request with version lover then version threshold"
+ def deviceUserAgent = "${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber}"
+ def prebid = new AppPrebid(source: PBSUtils.getRandomCase(PREBID_MOBILE), version: ACCEPTABLE_DEVICE_UA_VERSION_THRESHOLD)
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ app.ext = new AppExt(prebid: prebid)
+ device = new Device(ua: deviceUserAgent)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.getDefaultConfigWithUserAgentCorrection(userAgentCorrectionEnabled, enabled)
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain device.ua"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.device.ua == deviceUserAgent
+
+ where:
+ enabled | userAgentCorrectionEnabled
+ false | true
+ null | true
+ true | false
+ true | null
+ null | null
+ }
+
+ def "PBS shouldn't remove pattern device.ua when request correction is enabled for account and source not a prebid-mobile"() {
+ given: "Android APP bid request with version lover then version threshold"
+ def randomDeviceUa = "${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber}"
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ app.ext = new AppExt(prebid: new AppPrebid(source: source, version: ACCEPTABLE_DEVICE_UA_VERSION_THRESHOLD))
+ device = new Device(ua: randomDeviceUa)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.defaultConfigWithUserAgentCorrection
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain device.ua"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.device.ua == randomDeviceUa
+
+ where:
+ source << ["prebid",
+ "mobile",
+ PREBID_MOBILE + PBSUtils.randomString,
+ PBSUtils.randomString + PREBID_MOBILE,
+ "mobile-prebid",
+ PBSUtils.randomString]
+ }
+
+ def "PBS shouldn't remove pattern device.ua when request correction is enabled for account and version biggest that threshold"() {
+ given: "Android APP bid request with version higher then version threshold"
+ def randomDeviceUa = "${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber}"
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ app.ext = new AppExt(prebid: new AppPrebid(source: PBSUtils.getRandomCase(PREBID_MOBILE), version: PBSUtils.getRandomVersion("2.1.6")))
+ device = new Device(ua: randomDeviceUa)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.defaultConfigWithUserAgentCorrection
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain device.ua"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.device.ua == randomDeviceUa
+ }
+
+ def "PBS shouldn't remove pattern device.ua when request correction is enabled for account and version threshold"() {
+ given: "Android APP bid request with version threshold"
+ def randomDeviceUa = "${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber}"
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ app.ext = new AppExt(prebid: new AppPrebid(source: PBSUtils.getRandomCase(PREBID_MOBILE), version: "2.1.6"))
+ device = new Device(ua: randomDeviceUa)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.defaultConfigWithUserAgentCorrection
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain device.ua"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.device.ua == randomDeviceUa
+ }
+
+ def "PBS shouldn't remove device.ua pattern when request correction is enabled for account and version threshold"() {
+ given: "Android APP bid request with version higher then version threshold"
+ def randomDeviceUa = PBSUtils.randomString
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ app.ext = new AppExt(prebid: new AppPrebid(source: PBSUtils.getRandomCase(PREBID_MOBILE), version: PBSUtils.getRandomVersion("2.1.6")))
+ device = new Device(ua: randomDeviceUa)
+ }
+
+ and: "Account in the DB"
+ def requestCorrectionConfig = PbRequestCorrectionConfig.defaultConfigWithUserAgentCorrection
+ def account = createAccountWithRequestCorrectionConfig(bidRequest, requestCorrectionConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain device.ua"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.device.ua == randomDeviceUa
+ }
+
+ def "PBS shouldn't remove device.ua pattern from device for android app when request correction is not applied for account"() {
+ given: "Android APP bid request with version lover then version threshold"
+ def prebid = new AppPrebid(source: PREBID_MOBILE, version: ACCEPTABLE_DEVICE_UA_VERSION_THRESHOLD)
+ def deviceUa = "${DEVICE_PREBID_MOBILE_PATTERN}${PBSUtils.randomNumber}"
+ def bidRequest = BidRequest.getDefaultBidRequest(APP).tap {
+ app.ext = new AppExt(prebid: prebid)
+ device = new Device(ua: deviceUa)
+ }
+
+ and: "Account in the DB"
+ def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(modules: new PbsModulesConfig()))
+ def account = new Account(uuid: bidRequest.accountId, config: accountConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ pbsServiceWithRequestCorrectionModule.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain request device ua"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.device.ua == deviceUa
+ }
+
+ private static Account createAccountWithRequestCorrectionConfig(BidRequest bidRequest,
+ PbRequestCorrectionConfig requestCorrectionConfig) {
+ def pbsModulesConfig = new PbsModulesConfig(pbRequestCorrection: requestCorrectionConfig)
+ def accountHooksConfig = new AccountHooksConfiguration(modules: pbsModulesConfig)
+ def accountConfig = new AccountConfig(hooks: accountHooksConfig)
+ new Account(uuid: bidRequest.accountId, config: accountConfig)
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy
index 2db485448b4..9ac2c148b5b 100644
--- a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy
+++ b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy
@@ -128,4 +128,27 @@ class PBSUtils implements ObjectMapperWrapper {
throw new IllegalArgumentException("Unknown case type: $caseType")
}
}
+
+ static String getRandomVersion(String minVersion = "0.0.0", String maxVersion = "99.99.99") {
+ def minParts = minVersion.split('\\.').collect { it.toInteger() }
+ def maxParts = maxVersion.split('\\.').collect { it.toInteger() }
+ def versionParts = []
+
+ def major = getRandomNumber(minParts[0], maxParts[0])
+ versionParts << major
+
+ def minorMin = (major == minParts[0]) ? minParts[1] : 0
+ def minorMax = (major == maxParts[0]) ? maxParts[1] : 99
+ def minor = getRandomNumber(minorMin, minorMax)
+ versionParts << minor
+
+ if (minParts.size() > 2 || maxParts.size() > 2) {
+ def patchMin = (major == minParts[0] && minor == minParts[1]) ? minParts[2] : 0
+ def patchMax = (major == maxParts[0] && minor == maxParts[1]) ? maxParts[2] : 99
+ def patch = getRandomNumber(patchMin, patchMax)
+ versionParts << patch
+ }
+ def version = versionParts.join('.')
+ return (version >= minVersion && version <= maxVersion) ? version : getRandomVersion(minVersion, maxVersion)
+ }
}