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> hooks; + + public RequestCorrectionModule(RequestCorrectionProvider requestCorrectionProvider, ObjectMapper mapper) { + this.hooks = Collections.singleton( + new RequestCorrectionProcessedAuctionHook(requestCorrectionProvider, mapper)); + } + + @Override + public String code() { + return CODE; + } + + @Override + public Collection> 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) + } }