diff --git a/docs/config-app.md b/docs/config-app.md index 238a6511d37..a661f5a74a2 100644 --- a/docs/config-app.md +++ b/docs/config-app.md @@ -349,7 +349,7 @@ For HTTP data source available next options: - `settings.http.rfc3986-compatible` - if equals to `true` the url will be build according to RFC 3986, `false` by default For account processing rules available next options: -- `settings.enforce-valid-account` - if equals to `true` then request without account id will be rejected with 401. +- `settings.enforce-valid-account` - if equals to `true` then request without account id will be rejection with 401. - `settings.generate-storedrequest-bidrequest-id` - overrides `bidrequest.id` in amp or app stored request with generated UUID if true. Default value is false. This flag can be overridden by setting `bidrequest.id` as `{{UUID}}` placeholder directly in stored request. It is possible to specify default account configuration values that will be assumed if account config have them diff --git a/docs/metrics.md b/docs/metrics.md index 823c41465c8..c07e0660598 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -93,7 +93,7 @@ Following metrics are collected and submitted if account is configured with `bas Following metrics are collected and submitted if account is configured with `detailed` verbosity: - `account..requests.type.(openrtb2-web,openrtb-app,amp,legacy)` - number of requests received from account with `` broken down by type of incoming request - `account..debug_requests` - number of requests received from account with `` broken down by type of incoming request (when debug mode is enabled) -- `account..requests.rejected` - number of rejected requests caused by incorrect `accountId` +- `account..requests.rejection` - number of rejection requests caused by incorrect `accountId` - `account..requests.disabled_bidder` - number of disabled bidders received within requests from account with `` - `account..requests.unknown_bidder` - number of unknown bidder names received within requests from account with `` - `account..adapter..request_time` - timer tracking how long did it take to make a request to `` when incoming request was from `` @@ -140,7 +140,7 @@ Following metrics are collected and submitted if account is configured with `det - `analytics..(auction|amp|video|cookie_sync|event|setuid).ok` - number of succeeded processed event requests - `analytics..(auction|amp|video|cookie_sync|event|setuid).timeout` - number of event requests, failed with timeout cause - `analytics..(auction|amp|video|cookie_sync|event|setuid).err` - number of event requests, failed with errors -- `analytics..(auction|amp|video|cookie_sync|event|setuid).badinput` - number of event requests, rejected with bad input cause +- `analytics..(auction|amp|video|cookie_sync|event|setuid).badinput` - number of event requests, rejection with bad input cause ## Modules metrics - `modules.module..stage..hook..call` - number of times the hook is called diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/config/GreenbidsRealTimeDataConfiguration.java b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/config/GreenbidsRealTimeDataConfiguration.java index c76d442218a..ec8cb4c10b1 100644 --- a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/config/GreenbidsRealTimeDataConfiguration.java +++ b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/config/GreenbidsRealTimeDataConfiguration.java @@ -8,7 +8,6 @@ import org.prebid.server.geolocation.CountryCodeMapper; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.FilterService; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInferenceDataService; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInvocationService; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.ModelCache; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunner; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunnerFactory; @@ -48,16 +47,14 @@ GreenbidsInferenceDataService greenbidsInferenceDataService(DatabaseReaderFactor GreenbidsRealTimeDataModule greenbidsRealTimeDataModule( FilterService filterService, OnnxModelRunnerWithThresholds onnxModelRunnerWithThresholds, - GreenbidsInferenceDataService greenbidsInferenceDataService, - GreenbidsInvocationService greenbidsInvocationService) { + GreenbidsInferenceDataService greenbidsInferenceDataService) { return new GreenbidsRealTimeDataModule(List.of( new GreenbidsRealTimeDataProcessedAuctionRequestHook( ObjectMapperProvider.mapper(), filterService, onnxModelRunnerWithThresholds, - greenbidsInferenceDataService, - greenbidsInvocationService))); + greenbidsInferenceDataService))); } @Bean @@ -123,15 +120,7 @@ ThresholdCache thresholdCache( } @Bean - OnnxModelRunnerWithThresholds onnxModelRunnerWithThresholds( - ModelCache modelCache, - ThresholdCache thresholdCache) { - + OnnxModelRunnerWithThresholds onnxModelRunnerWithThresholds(ModelCache modelCache, ThresholdCache thresholdCache) { return new OnnxModelRunnerWithThresholds(modelCache, thresholdCache); } - - @Bean - GreenbidsInvocationService greenbidsInvocationService() { - return new GreenbidsInvocationService(); - } } diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationResultCreator.java b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationResultCreator.java new file mode 100644 index 00000000000..d93f8343f22 --- /dev/null +++ b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationResultCreator.java @@ -0,0 +1,86 @@ +package org.prebid.server.hooks.modules.greenbids.real.time.data.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.analytics.reporter.greenbids.model.ExplorationResult; +import org.prebid.server.analytics.reporter.greenbids.model.Ortb2ImpExtResult; +import org.prebid.server.hooks.modules.greenbids.real.time.data.model.data.GreenbidsConfig; +import org.prebid.server.hooks.modules.greenbids.real.time.data.model.result.AnalyticsResult; +import org.prebid.server.hooks.modules.greenbids.real.time.data.model.result.GreenbidsInvocationResult; +import org.prebid.server.hooks.v1.InvocationAction; + +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +public class GreenbidsInvocationResultCreator { + + private static final int RANGE_16_BIT_INTEGER_DIVISION_BASIS = 0x10000; + private static final double DEFAULT_EXPLORATION_RATE = 1.0; + + private GreenbidsInvocationResultCreator() { + + } + + public static GreenbidsInvocationResult create(GreenbidsConfig greenbidsConfig, + BidRequest bidRequest, + Map> impsBiddersFilterMap) { + + final String greenbidsId = UUID.randomUUID().toString(); + final boolean isExploration = isExploration(greenbidsConfig, greenbidsId); + + final boolean allRejected = bidRequest.getImp().stream() + .noneMatch(imp -> impsBiddersFilterMap.get(imp.getId()).values().stream().anyMatch(isKept -> isKept)); + + final InvocationAction invocationAction = isExploration + ? InvocationAction.no_action + : allRejected + ? InvocationAction.reject + : InvocationAction.update; + + final Map ort2ImpExtResultMap = createOrtb2ImpExtForImps( + bidRequest, impsBiddersFilterMap, greenbidsId, isExploration); + final AnalyticsResult analyticsResult = AnalyticsResult.of("success", ort2ImpExtResultMap); + return GreenbidsInvocationResult.of(invocationAction, analyticsResult); + } + + private static boolean isExploration(GreenbidsConfig greenbidsConfig, String greenbidsId) { + final double explorationRate = ObjectUtils.defaultIfNull( + greenbidsConfig.getExplorationRate(), + DEFAULT_EXPLORATION_RATE); + final int hashInt = Integer.parseInt(greenbidsId.substring(greenbidsId.length() - 4), 16); + return hashInt < explorationRate * RANGE_16_BIT_INTEGER_DIVISION_BASIS; + } + + private static Map createOrtb2ImpExtForImps( + BidRequest bidRequest, + Map> impsBiddersFilterMap, + String greenbidsId, + boolean isExploration) { + + return bidRequest.getImp().stream() + .collect(Collectors.toMap( + Imp::getId, + imp -> createOrtb2ImpExt(imp, impsBiddersFilterMap, greenbidsId, isExploration))); + } + + private static Ortb2ImpExtResult createOrtb2ImpExt(Imp imp, + Map> impsBiddersFilterMap, + String greenbidsId, + boolean isExploration) { + + final String tid = Optional.ofNullable(imp) + .map(Imp::getExt) + .map(impExt -> impExt.get("tid")) + .map(JsonNode::asText) + .orElse(StringUtils.EMPTY); + final Map impBiddersFilterMap = impsBiddersFilterMap.get(imp.getId()); + final ExplorationResult explorationResult = ExplorationResult.of( + greenbidsId, impBiddersFilterMap, isExploration); + return Ortb2ImpExtResult.of(explorationResult, tid); + } +} diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationService.java b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationService.java deleted file mode 100644 index 77f45d0e6b2..00000000000 --- a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationService.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.prebid.server.hooks.modules.greenbids.real.time.data.core; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Imp; -import org.apache.commons.lang3.StringUtils; -import org.prebid.server.analytics.reporter.greenbids.model.ExplorationResult; -import org.prebid.server.analytics.reporter.greenbids.model.Ortb2ImpExtResult; -import org.prebid.server.hooks.modules.greenbids.real.time.data.model.data.GreenbidsConfig; -import org.prebid.server.hooks.modules.greenbids.real.time.data.model.result.AnalyticsResult; -import org.prebid.server.hooks.modules.greenbids.real.time.data.model.result.GreenbidsInvocationResult; -import org.prebid.server.hooks.v1.InvocationAction; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; - -public class GreenbidsInvocationService { - - private static final int RANGE_16_BIT_INTEGER_DIVISION_BASIS = 0x10000; - - private static final double DEFAULT_EXPLORATION_RATE = 1.0; - - public GreenbidsInvocationResult createGreenbidsInvocationResult( - GreenbidsConfig greenbidsConfig, - BidRequest bidRequest, - Map> impsBiddersFilterMap) { - - final String greenbidsId = UUID.randomUUID().toString(); - final boolean isExploration = isExploration(greenbidsConfig, greenbidsId); - - final List updatedImps = updateImps(bidRequest, impsBiddersFilterMap); - - final BidRequest updatedBidRequest = isExploration || updatedImps.isEmpty() - ? bidRequest - : bidRequest.toBuilder() - .imp(updatedImps) - .build(); - - final InvocationAction invocationAction = updatedImps.isEmpty() - ? InvocationAction.reject - : isExploration - ? InvocationAction.no_action - : InvocationAction.update; - - final Map ort2ImpExtResultMap = createOrtb2ImpExtForImps( - bidRequest, impsBiddersFilterMap, greenbidsId, isExploration); - final AnalyticsResult analyticsResult = AnalyticsResult.of( - "success", ort2ImpExtResultMap); - - return GreenbidsInvocationResult.of(updatedBidRequest, invocationAction, analyticsResult); - } - - private Boolean isExploration(GreenbidsConfig greenbidsConfig, String greenbidsId) { - final double explorationRate = Optional.ofNullable(greenbidsConfig.getExplorationRate()) - .orElse(DEFAULT_EXPLORATION_RATE); - final int hashInt = Integer.parseInt( - greenbidsId.substring(greenbidsId.length() - 4), 16); - return hashInt < explorationRate * RANGE_16_BIT_INTEGER_DIVISION_BASIS; - } - - private List updateImps(BidRequest bidRequest, Map> impsBiddersFilterMap) { - return bidRequest.getImp().stream() - .filter(imp -> isImpKept(imp, impsBiddersFilterMap)) - .map(imp -> updateImp(imp, impsBiddersFilterMap.get(imp.getId()))) - .toList(); - } - - private boolean isImpKept(Imp imp, Map> impsBiddersFilterMap) { - return impsBiddersFilterMap.get(imp.getId()).values().stream().anyMatch(isKept -> isKept); - } - - private Imp updateImp(Imp imp, Map bidderFilterMap) { - return imp.toBuilder() - .ext(updateImpExt(imp.getExt(), bidderFilterMap)) - .build(); - } - - private ObjectNode updateImpExt(ObjectNode impExt, Map bidderFilterMap) { - final ObjectNode updatedExt = impExt.deepCopy(); - Optional.ofNullable((ObjectNode) updatedExt.get("prebid")) - .map(prebidNode -> (ObjectNode) prebidNode.get("bidder")) - .ifPresent(bidderNode -> - bidderFilterMap.entrySet().stream() - .filter(entry -> !entry.getValue()) - .map(Map.Entry::getKey) - .forEach(bidderNode::remove)); - return updatedExt; - } - - private Map createOrtb2ImpExtForImps( - BidRequest bidRequest, - Map> impsBiddersFilterMap, - String greenbidsId, - Boolean isExploration) { - - return bidRequest.getImp().stream() - .collect(Collectors.toMap( - Imp::getId, - imp -> createOrtb2ImpExt(imp, impsBiddersFilterMap, greenbidsId, isExploration))); - } - - private Ortb2ImpExtResult createOrtb2ImpExt( - Imp imp, - Map> impsBiddersFilterMap, - String greenbidsId, - Boolean isExploration) { - - final String tid = Optional.of(imp) - .map(Imp::getExt) - .map(impExt -> impExt.get("tid")) - .map(JsonNode::asText) - .orElse(StringUtils.EMPTY); - final Map impBiddersFilterMap = impsBiddersFilterMap.get(imp.getId()); - final ExplorationResult explorationResult = ExplorationResult.of( - greenbidsId, impBiddersFilterMap, isExploration); - return Ortb2ImpExtResult.of(explorationResult, tid); - } -} diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsPayloadUpdater.java b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsPayloadUpdater.java new file mode 100644 index 00000000000..3b1c6aa57ed --- /dev/null +++ b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsPayloadUpdater.java @@ -0,0 +1,51 @@ +package org.prebid.server.hooks.modules.greenbids.real.time.data.core; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class GreenbidsPayloadUpdater { + + private GreenbidsPayloadUpdater() { + + } + + public static BidRequest update(BidRequest bidRequest, Map> impsBiddersFilterMap) { + return bidRequest.toBuilder() + .imp(updateImps(bidRequest, impsBiddersFilterMap)) + .build(); + } + + private static List updateImps(BidRequest bidRequest, Map> impsBiddersFilterMap) { + return bidRequest.getImp().stream() + .filter(imp -> isImpKept(impsBiddersFilterMap.get(imp.getId()))) + .map(imp -> updateImp(imp, impsBiddersFilterMap.get(imp.getId()))) + .toList(); + } + + private static boolean isImpKept(Map bidderFilterMap) { + return bidderFilterMap.values().stream().anyMatch(isKept -> isKept); + } + + private static Imp updateImp(Imp imp, Map bidderFilterMap) { + return imp.toBuilder() + .ext(updateImpExt(imp.getExt(), bidderFilterMap)) + .build(); + } + + private static ObjectNode updateImpExt(ObjectNode impExt, Map bidderFilterMap) { + final ObjectNode updatedExt = impExt.deepCopy(); + Optional.ofNullable((ObjectNode) updatedExt.get("prebid")) + .map(prebidNode -> (ObjectNode) prebidNode.get("bidder")) + .ifPresent(bidderNode -> + bidderFilterMap.entrySet().stream() + .filter(entry -> !entry.getValue()) + .map(Map.Entry::getKey) + .forEach(bidderNode::remove)); + return updatedExt; + } +} diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/model/result/GreenbidsInvocationResult.java b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/model/result/GreenbidsInvocationResult.java index 0aff44ceaec..39e56fe5dcd 100644 --- a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/model/result/GreenbidsInvocationResult.java +++ b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/model/result/GreenbidsInvocationResult.java @@ -1,14 +1,11 @@ package org.prebid.server.hooks.modules.greenbids.real.time.data.model.result; -import com.iab.openrtb.request.BidRequest; import lombok.Value; import org.prebid.server.hooks.v1.InvocationAction; @Value(staticConstructor = "of") public class GreenbidsInvocationResult { - BidRequest updatedBidRequest; - InvocationAction invocationAction; AnalyticsResult analyticsResult; diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHook.java b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHook.java index f9713d4f56d..f0bd5467d0e 100644 --- a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHook.java +++ b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHook.java @@ -7,9 +7,12 @@ import com.iab.openrtb.request.BidRequest; import io.vertx.core.Future; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.tuple.Pair; import org.prebid.server.analytics.reporter.greenbids.model.ExplorationResult; import org.prebid.server.analytics.reporter.greenbids.model.Ortb2ImpExtResult; -import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.Rejection; +import org.prebid.server.auction.model.ImpRejection; import org.prebid.server.exception.PreBidException; import org.prebid.server.hooks.execution.v1.InvocationResultImpl; import org.prebid.server.hooks.execution.v1.analytics.ActivityImpl; @@ -19,7 +22,8 @@ import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.FilterService; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInferenceDataService; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInvocationService; +import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInvocationResultCreator; +import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsPayloadUpdater; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunner; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunnerWithThresholds; import org.prebid.server.hooks.modules.greenbids.real.time.data.model.data.GreenbidsConfig; @@ -43,6 +47,8 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class GreenbidsRealTimeDataProcessedAuctionRequestHook implements ProcessedAuctionRequestHook { @@ -55,34 +61,29 @@ public class GreenbidsRealTimeDataProcessedAuctionRequestHook implements Process private final FilterService filterService; private final OnnxModelRunnerWithThresholds onnxModelRunnerWithThresholds; private final GreenbidsInferenceDataService greenbidsInferenceDataService; - private final GreenbidsInvocationService greenbidsInvocationService; public GreenbidsRealTimeDataProcessedAuctionRequestHook( ObjectMapper mapper, FilterService filterService, OnnxModelRunnerWithThresholds onnxModelRunnerWithThresholds, - GreenbidsInferenceDataService greenbidsInferenceDataService, - GreenbidsInvocationService greenbidsInvocationService) { + GreenbidsInferenceDataService greenbidsInferenceDataService) { + this.mapper = Objects.requireNonNull(mapper); this.filterService = Objects.requireNonNull(filterService); this.onnxModelRunnerWithThresholds = Objects.requireNonNull(onnxModelRunnerWithThresholds); this.greenbidsInferenceDataService = Objects.requireNonNull(greenbidsInferenceDataService); - this.greenbidsInvocationService = Objects.requireNonNull(greenbidsInvocationService); } @Override - public Future> call( - AuctionRequestPayload auctionRequestPayload, - AuctionInvocationContext invocationContext) { + public Future> call(AuctionRequestPayload auctionRequestPayload, + AuctionInvocationContext invocationContext) { - final AuctionContext auctionContext = invocationContext.auctionContext(); - final BidRequest bidRequest = auctionContext.getBidRequest(); - final GreenbidsConfig greenbidsConfig = Optional.ofNullable(parseBidRequestExt(auctionContext)) + final BidRequest bidRequest = auctionRequestPayload.bidRequest(); + final GreenbidsConfig greenbidsConfig = Optional.ofNullable(parseBidRequestExt(bidRequest.getExt())) .orElseGet(() -> toGreenbidsConfig(invocationContext.accountConfig())); if (greenbidsConfig == null) { - return Future.failedFuture( - new PreBidException("Greenbids config is null; cannot proceed.")); + return Future.failedFuture(new PreBidException("Greenbids config is null; cannot proceed.")); } return Future.all( @@ -93,14 +94,11 @@ public Future> call( greenbidsConfig, compositeFuture.resultAt(0), compositeFuture.resultAt(1))) - .recover(throwable -> Future.succeededFuture(toInvocationResult( - bidRequest, null, InvocationAction.no_action))); + .recover(throwable -> noActionInvocationResult()); } - private GreenbidsConfig parseBidRequestExt(AuctionContext auctionContext) { - return Optional.ofNullable(auctionContext) - .map(AuctionContext::getBidRequest) - .map(BidRequest::getExt) + private GreenbidsConfig parseBidRequestExt(ExtRequest extRequest) { + return Optional.ofNullable(extRequest) .map(ExtRequest::getPrebid) .map(ExtRequestPrebid::getAnalytics) .filter(this::isNotEmptyObjectNode) @@ -137,39 +135,44 @@ private Future> toInvocationResult( throttlingMessages, threshold); } catch (PreBidException e) { - return Future.succeededFuture(toInvocationResult( - bidRequest, null, InvocationAction.no_action)); + return noActionInvocationResult(); } - final GreenbidsInvocationResult greenbidsInvocationResult = greenbidsInvocationService - .createGreenbidsInvocationResult(greenbidsConfig, bidRequest, impsBiddersFilterMap); + final GreenbidsInvocationResult invocationResult = GreenbidsInvocationResultCreator.create( + greenbidsConfig, + bidRequest, + impsBiddersFilterMap); - return Future.succeededFuture(toInvocationResult( - greenbidsInvocationResult.getUpdatedBidRequest(), - greenbidsInvocationResult.getAnalyticsResult(), - greenbidsInvocationResult.getInvocationAction())); + return invocationResult.getInvocationAction() == InvocationAction.no_action + ? noActionInvocationResult(invocationResult.getAnalyticsResult()) + : toInvocationResult(bidRequest, impsBiddersFilterMap, invocationResult); } - private InvocationResult toInvocationResult( + private Future> toInvocationResult( BidRequest bidRequest, - AnalyticsResult analyticsResult, - InvocationAction action) { - - return switch (action) { - case InvocationAction.update -> InvocationResultImpl - .builder() - .status(InvocationStatus.success) - .action(action) - .payloadUpdate(payload -> AuctionRequestPayloadImpl.of(bidRequest)) - .analyticsTags(toAnalyticsTags(analyticsResult)) - .build(); - default -> InvocationResultImpl - .builder() - .status(InvocationStatus.success) - .action(action) - .analyticsTags(toAnalyticsTags(analyticsResult)) - .build(); - }; + Map> impsBiddersFilterMap, + GreenbidsInvocationResult invocationResult) { + + return Future.succeededFuture(InvocationResultImpl.builder() + .status(InvocationStatus.success) + .action(invocationResult.getInvocationAction()) + .payloadUpdate(payload -> AuctionRequestPayloadImpl.of( + GreenbidsPayloadUpdater.update(bidRequest, impsBiddersFilterMap))) + .analyticsTags(toAnalyticsTags(invocationResult.getAnalyticsResult())) + .rejections(toRejections(impsBiddersFilterMap)) + .build()); + } + + private Future> noActionInvocationResult(AnalyticsResult analyticsResult) { + return Future.succeededFuture(InvocationResultImpl.builder() + .status(InvocationStatus.success) + .action(InvocationAction.no_action) + .analyticsTags(toAnalyticsTags(analyticsResult)) + .build()); + } + + private Future> noActionInvocationResult() { + return noActionInvocationResult(null); } private Tags toAnalyticsTags(AnalyticsResult analyticsResult) { @@ -215,6 +218,19 @@ private ObjectNode toObjectNode(Map.Entry values) { return values != null ? mapper.valueToTree(values) : null; } + private Map> toRejections(Map> impsBiddersFilterMap) { + return impsBiddersFilterMap.entrySet().stream() + .flatMap(entry -> Stream.ofNullable(entry.getValue()) + .map(Map::entrySet) + .flatMap(Collection::stream) + .filter(e -> BooleanUtils.isFalse(e.getValue())) + .map(Map.Entry::getKey) + .map(bidder -> Pair.of( + bidder, + ImpRejection.of(entry.getKey(), BidRejectionReason.REQUEST_BLOCKED_OPTIMIZED)))) + .collect(Collectors.groupingBy(Pair::getKey, Collectors.mapping(Pair::getValue, Collectors.toList()))); + } + @Override public String code() { return CODE; diff --git a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInferenceDataServiceTest.java b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInferenceDataServiceTest.java index c56b19e14c2..1a93b22a502 100644 --- a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInferenceDataServiceTest.java +++ b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInferenceDataServiceTest.java @@ -25,9 +25,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; -import java.util.function.UnaryOperator; -import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; @@ -73,8 +71,7 @@ public void extractThrottlingMessagesFromBidRequestShouldReturnValidThrottlingMe .ext(givenImpExt()) .banner(banner) .build(); - final Device device = givenDevice(identity()); - final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp), device); + final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp)); final CountryResponse countryResponse = mock(CountryResponse.class); @@ -114,8 +111,9 @@ public void extractThrottlingMessagesFromBidRequestShouldReturnValidThrottlingMe .ext(givenImpExt()) .banner(banner) .build(); - final Device device = givenDevice(identity(), "FRA"); - final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp), device); + final BidRequest bidRequest = givenBidRequest( + request -> request.device(givenDevice("FRA")), + List.of(imp)); final ZonedDateTime timestamp = ZonedDateTime.now(ZoneId.of("UTC")); final Integer expectedHourBucket = timestamp.getHour(); @@ -151,8 +149,7 @@ public void extractThrottlingMessagesFromBidRequestShouldHandleMissingIp() { .ext(givenImpExt()) .banner(banner) .build(); - final Device device = givenDeviceWithoutIp(identity()); - final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp), device); + final BidRequest bidRequest = givenBidRequest(request -> request.device(givenDeviceWithoutIp()), List.of(imp)); final ZonedDateTime timestamp = ZonedDateTime.now(ZoneId.of("UTC")); final Integer expectedHourBucket = timestamp.getHour(); @@ -187,8 +184,7 @@ public void extractThrottlingMessagesFromBidRequestShouldThrowPreBidExceptionWhe .ext(givenImpExt()) .banner(banner) .build(); - final Device device = givenDevice(identity()); - final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp), device); + final BidRequest bidRequest = givenBidRequest(request -> request.device(givenDevice()), List.of(imp)); when(databaseReader.country(any(InetAddress.class))).thenThrow(new GeoIp2Exception("GeoIP failure")); @@ -198,9 +194,9 @@ public void extractThrottlingMessagesFromBidRequestShouldThrowPreBidExceptionWhe .hasMessageContaining("Failed to fetch country from geoLite DB"); } - private Device givenDeviceWithoutIp(UnaryOperator deviceCustomizer) { + private Device givenDeviceWithoutIp() { final String userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36" + " (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"; - return deviceCustomizer.apply(Device.builder().ua(userAgent)).build(); + return Device.builder().ua(userAgent).build(); } } diff --git a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationResultCreatorTest.java b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationResultCreatorTest.java new file mode 100644 index 00000000000..1aa9e5022b4 --- /dev/null +++ b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationResultCreatorTest.java @@ -0,0 +1,136 @@ +package org.prebid.server.hooks.modules.greenbids.real.time.data.core; + +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.analytics.reporter.greenbids.model.Ortb2ImpExtResult; +import org.prebid.server.hooks.modules.greenbids.real.time.data.model.data.GreenbidsConfig; +import org.prebid.server.hooks.modules.greenbids.real.time.data.model.result.GreenbidsInvocationResult; +import org.prebid.server.hooks.v1.InvocationAction; + +import java.util.List; +import java.util.Map; + +import static java.util.function.UnaryOperator.identity; +import static org.assertj.core.api.Assertions.assertThat; +import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenBanner; +import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenBidRequest; +import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenImpExt; + +@ExtendWith(MockitoExtension.class) +public class GreenbidsInvocationResultCreatorTest { + + @Test + public void createGreenbidsInvocationResultWhenNotExploration() { + // given + final Banner banner = givenBanner(); + final Imp imp = Imp.builder() + .id("adunitcodevalue") + .ext(givenImpExt()) + .banner(banner) + .build(); + + final BidRequest bidRequest = givenBidRequest(identity(), List.of(imp)); + final Map> impsBiddersFilterMap = givenImpsBiddersFilterMap(); + final GreenbidsConfig greenbidsConfig = givenConfig(0.0); + + // when + final GreenbidsInvocationResult result = GreenbidsInvocationResultCreator.create( + greenbidsConfig, bidRequest, impsBiddersFilterMap); + + // then + final Ortb2ImpExtResult ortb2ImpExtResult = result.getAnalyticsResult().getValues().get("adunitcodevalue"); + final Map keptInAuction = ortb2ImpExtResult.getGreenbids().getKeptInAuction(); + + assertThat(result.getInvocationAction()).isEqualTo(InvocationAction.update); + assertThat(ortb2ImpExtResult).isNotNull(); + assertThat(ortb2ImpExtResult.getGreenbids().getIsExploration()).isFalse(); + assertThat(ortb2ImpExtResult.getGreenbids().getFingerprint()).isNotNull(); + assertThat(keptInAuction.get("rubicon")).isTrue(); + assertThat(keptInAuction.get("appnexus")).isFalse(); + assertThat(keptInAuction.get("pubmatic")).isFalse(); + } + + @Test + public void createShouldReturnNoActionWhenExploration() { + // given + final Imp imp = Imp.builder() + .id("adunitcodevalue") + .ext(givenImpExt()) + .build(); + + final BidRequest bidRequest = givenBidRequest(identity(), List.of(imp)); + final Map> impsBiddersFilterMap = givenFilterMapWithAllFilteredImps(); + final GreenbidsConfig greenbidsConfig = givenConfig(1.0); + + // when + final GreenbidsInvocationResult result = GreenbidsInvocationResultCreator.create( + greenbidsConfig, bidRequest, impsBiddersFilterMap); + + // then + final Ortb2ImpExtResult ortb2ImpExtResult = result.getAnalyticsResult().getValues().get("adunitcodevalue"); + final Map keptInAuction = ortb2ImpExtResult.getGreenbids().getKeptInAuction(); + + assertThat(result.getInvocationAction()).isEqualTo(InvocationAction.no_action); + assertThat(ortb2ImpExtResult).isNotNull(); + assertThat(ortb2ImpExtResult.getGreenbids().getIsExploration()).isTrue(); + assertThat(ortb2ImpExtResult.getGreenbids().getFingerprint()).isNotNull(); + assertThat(keptInAuction.get("rubicon")).isFalse(); + assertThat(keptInAuction.get("appnexus")).isFalse(); + assertThat(keptInAuction.get("pubmatic")).isFalse(); + } + + @Test + public void createShouldReturnRejectWhenAllImpsAreFilteredOutAndNoExploration() { + // given + final Imp imp = Imp.builder() + .id("adunitcodevalue") + .ext(givenImpExt()) + .build(); + + final BidRequest bidRequest = givenBidRequest(identity(), List.of(imp)); + final Map> impsBiddersFilterMap = givenFilterMapWithAllFilteredImps(); + final GreenbidsConfig greenbidsConfig = givenConfig(0.001); + + // when + final GreenbidsInvocationResult result = GreenbidsInvocationResultCreator.create( + greenbidsConfig, bidRequest, impsBiddersFilterMap); + + // then + final Ortb2ImpExtResult ortb2ImpExtResult = result.getAnalyticsResult().getValues().get("adunitcodevalue"); + final Map keptInAuction = ortb2ImpExtResult.getGreenbids().getKeptInAuction(); + + assertThat(result.getInvocationAction()).isEqualTo(InvocationAction.reject); + assertThat(ortb2ImpExtResult).isNotNull(); + assertThat(ortb2ImpExtResult.getGreenbids().getIsExploration()).isFalse(); + assertThat(ortb2ImpExtResult.getGreenbids().getFingerprint()).isNotNull(); + assertThat(keptInAuction.get("rubicon")).isFalse(); + assertThat(keptInAuction.get("appnexus")).isFalse(); + assertThat(keptInAuction.get("pubmatic")).isFalse(); + } + + private Map> givenImpsBiddersFilterMap() { + final Map biddersFitlerMap = Map.of( + "rubicon", true, + "appnexus", false, + "pubmatic", false); + + return Map.of("adunitcodevalue", biddersFitlerMap); + } + + private Map> givenFilterMapWithAllFilteredImps() { + final Map biddersFitlerMap = Map.of( + "rubicon", false, + "appnexus", false, + "pubmatic", false); + + return Map.of("adunitcodevalue", biddersFitlerMap); + } + + private GreenbidsConfig givenConfig(Double explorationRate) { + return GreenbidsConfig.of("test-pbuid", 0.60, explorationRate); + } +} diff --git a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationServiceTest.java b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationServiceTest.java deleted file mode 100644 index 0c137ffa403..00000000000 --- a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsInvocationServiceTest.java +++ /dev/null @@ -1,233 +0,0 @@ -package org.prebid.server.hooks.modules.greenbids.real.time.data.core; - -import com.fasterxml.jackson.databind.JsonNode; -import com.iab.openrtb.request.Banner; -import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Device; -import com.iab.openrtb.request.Imp; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.prebid.server.analytics.reporter.greenbids.model.Ortb2ImpExtResult; -import org.prebid.server.hooks.modules.greenbids.real.time.data.model.data.GreenbidsConfig; -import org.prebid.server.hooks.modules.greenbids.real.time.data.model.result.GreenbidsInvocationResult; -import org.prebid.server.hooks.v1.InvocationAction; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static java.util.function.UnaryOperator.identity; -import static org.assertj.core.api.Assertions.assertThat; -import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenBanner; -import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenBidRequest; -import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenDevice; -import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenImpExt; -import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenImpExtToFilterAllBidders; - -@ExtendWith(MockitoExtension.class) -public class GreenbidsInvocationServiceTest { - - private GreenbidsInvocationService target; - - @BeforeEach - public void setUp() { - target = new GreenbidsInvocationService(); - } - - @Test - public void createGreenbidsInvocationResultShouldReturnUpdateBidRequestWhenNotExploration() { - // given - final Banner banner = givenBanner(); - final Imp imp = Imp.builder() - .id("adunitcodevalue") - .ext(givenImpExt()) - .banner(banner) - .build(); - final Device device = givenDevice(identity()); - final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp), device); - final Map> impsBiddersFilterMap = givenImpsBiddersFilterMap(); - final GreenbidsConfig greenbidsConfig = givenPartner(0.0); - - // when - final GreenbidsInvocationResult result = target.createGreenbidsInvocationResult( - greenbidsConfig, bidRequest, impsBiddersFilterMap); - - // then - final JsonNode updatedBidRequestExtPrebidBidders = result.getUpdatedBidRequest().getImp().getFirst().getExt() - .get("prebid").get("bidder"); - final Ortb2ImpExtResult ortb2ImpExtResult = result.getAnalyticsResult().getValues().get("adunitcodevalue"); - final Map keptInAuction = ortb2ImpExtResult.getGreenbids().getKeptInAuction(); - - assertThat(result.getInvocationAction()).isEqualTo(InvocationAction.update); - assertThat(updatedBidRequestExtPrebidBidders.has("rubicon")).isTrue(); - assertThat(updatedBidRequestExtPrebidBidders.has("appnexus")).isFalse(); - assertThat(updatedBidRequestExtPrebidBidders.has("pubmatic")).isFalse(); - assertThat(ortb2ImpExtResult).isNotNull(); - assertThat(ortb2ImpExtResult.getGreenbids().getIsExploration()).isFalse(); - assertThat(ortb2ImpExtResult.getGreenbids().getFingerprint()).isNotNull(); - assertThat(keptInAuction.get("rubicon")).isTrue(); - assertThat(keptInAuction.get("appnexus")).isFalse(); - assertThat(keptInAuction.get("pubmatic")).isFalse(); - } - - @Test - public void createGreenbidsInvocationResultShouldReturnNoActionWhenExploration() { - // given - final Banner banner = givenBanner(); - final Imp imp = Imp.builder() - .id("adunitcodevalue") - .ext(givenImpExt()) - .banner(banner) - .build(); - final Device device = givenDevice(identity()); - final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp), device); - final Map> impsBiddersFilterMap = givenImpsBiddersFilterMap(); - final GreenbidsConfig greenbidsConfig = givenPartner(1.0); - - // when - final GreenbidsInvocationResult result = target.createGreenbidsInvocationResult( - greenbidsConfig, bidRequest, impsBiddersFilterMap); - - // then - final JsonNode updatedBidRequestExtPrebidBidders = result.getUpdatedBidRequest().getImp().getFirst().getExt() - .get("prebid").get("bidder"); - final Ortb2ImpExtResult ortb2ImpExtResult = result.getAnalyticsResult().getValues().get("adunitcodevalue"); - final Map keptInAuction = ortb2ImpExtResult.getGreenbids().getKeptInAuction(); - - assertThat(result.getInvocationAction()).isEqualTo(InvocationAction.no_action); - assertThat(updatedBidRequestExtPrebidBidders.has("rubicon")).isTrue(); - assertThat(updatedBidRequestExtPrebidBidders.has("appnexus")).isTrue(); - assertThat(updatedBidRequestExtPrebidBidders.has("pubmatic")).isTrue(); - assertThat(ortb2ImpExtResult).isNotNull(); - assertThat(ortb2ImpExtResult.getGreenbids().getIsExploration()).isTrue(); - assertThat(ortb2ImpExtResult.getGreenbids().getFingerprint()).isNotNull(); - assertThat(keptInAuction.get("rubicon")).isTrue(); - assertThat(keptInAuction.get("appnexus")).isFalse(); - assertThat(keptInAuction.get("pubmatic")).isFalse(); - } - - @Test - public void createGreenbidsInvocationResultShouldReturnRejectWhenAllImpsFiltered() { - // given - final Banner banner = givenBanner(); - final Imp imp = Imp.builder() - .id("adunitcodevalue") - .ext(givenImpExt()) - .banner(banner) - .build(); - final Device device = givenDevice(identity()); - final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp), device); - final Map> impsBiddersFilterMap = givenFilterMapWithAllFilteredImps(); - final GreenbidsConfig greenbidsConfig = givenPartner(1.0); - - // when - final GreenbidsInvocationResult result = target.createGreenbidsInvocationResult( - greenbidsConfig, bidRequest, impsBiddersFilterMap); - - // then - final JsonNode updatedBidRequestExtPrebidBidders = result.getUpdatedBidRequest().getImp().getFirst().getExt() - .get("prebid").get("bidder"); - final Ortb2ImpExtResult ortb2ImpExtResult = result.getAnalyticsResult().getValues().get("adunitcodevalue"); - final Map keptInAuction = ortb2ImpExtResult.getGreenbids().getKeptInAuction(); - - assertThat(result.getInvocationAction()).isEqualTo(InvocationAction.reject); - assertThat(updatedBidRequestExtPrebidBidders.has("rubicon")).isTrue(); - assertThat(updatedBidRequestExtPrebidBidders.has("appnexus")).isTrue(); - assertThat(updatedBidRequestExtPrebidBidders.has("pubmatic")).isTrue(); - assertThat(ortb2ImpExtResult).isNotNull(); - assertThat(ortb2ImpExtResult.getGreenbids().getIsExploration()).isTrue(); - assertThat(ortb2ImpExtResult.getGreenbids().getFingerprint()).isNotNull(); - assertThat(keptInAuction.get("rubicon")).isFalse(); - assertThat(keptInAuction.get("appnexus")).isFalse(); - assertThat(keptInAuction.get("pubmatic")).isFalse(); - } - - @Test - public void createGreenbidsInvocationResultShouldRemoveImpFromUpdateBidRequestWhenAllBiddersFiltered() { - // given - final Banner banner = givenBanner(); - final Imp imp1 = Imp.builder() - .id("adunitcodevalue1") - .ext(givenImpExt()) - .banner(banner) - .build(); - final Imp imp2 = Imp.builder() - .id("adunitcodevalue2") - .ext(givenImpExtToFilterAllBidders()) - .banner(banner) - .build(); - final Device device = givenDevice(identity()); - final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp1, imp2), device); - final Map> impsBiddersFilterMap = givenFilterMapWithAllFilteredBiddersInImp(); - final GreenbidsConfig greenbidsConfig = givenPartner(0.0); - - // when - final GreenbidsInvocationResult result = target.createGreenbidsInvocationResult( - greenbidsConfig, bidRequest, impsBiddersFilterMap); - - // then - final JsonNode updatedBidRequestExtPrebidBidders = result.getUpdatedBidRequest().getImp().getFirst().getExt() - .get("prebid").get("bidder"); - final Ortb2ImpExtResult ortb2ImpExtResult = result.getAnalyticsResult().getValues().get("adunitcodevalue1"); - final Map keptInAuction = ortb2ImpExtResult.getGreenbids().getKeptInAuction(); - - assertThat(result.getInvocationAction()).isEqualTo(InvocationAction.update); - assertThat(result.getUpdatedBidRequest().getImp()).hasSize(1); - assertThat(updatedBidRequestExtPrebidBidders.has("rubicon")).isTrue(); - assertThat(updatedBidRequestExtPrebidBidders.has("appnexus")).isFalse(); - assertThat(updatedBidRequestExtPrebidBidders.has("pubmatic")).isFalse(); - assertThat(ortb2ImpExtResult).isNotNull(); - assertThat(ortb2ImpExtResult.getGreenbids().getIsExploration()).isFalse(); - assertThat(ortb2ImpExtResult.getGreenbids().getFingerprint()).isNotNull(); - assertThat(keptInAuction.get("rubicon")).isTrue(); - assertThat(keptInAuction.get("appnexus")).isFalse(); - assertThat(keptInAuction.get("pubmatic")).isFalse(); - - } - - private Map> givenImpsBiddersFilterMap() { - final Map biddersFitlerMap = new HashMap<>(); - biddersFitlerMap.put("rubicon", true); - biddersFitlerMap.put("appnexus", false); - biddersFitlerMap.put("pubmatic", false); - - final Map> impsBiddersFilterMap = new HashMap<>(); - impsBiddersFilterMap.put("adunitcodevalue", biddersFitlerMap); - - return impsBiddersFilterMap; - } - - private Map> givenFilterMapWithAllFilteredImps() { - final Map biddersFitlerMap = new HashMap<>(); - biddersFitlerMap.put("rubicon", false); - biddersFitlerMap.put("appnexus", false); - biddersFitlerMap.put("pubmatic", false); - - final Map> impsBiddersFilterMap = new HashMap<>(); - impsBiddersFilterMap.put("adunitcodevalue", biddersFitlerMap); - - return impsBiddersFilterMap; - } - - private Map> givenFilterMapWithAllFilteredBiddersInImp() { - final Map biddersFitlerMapForKeptImp = new HashMap<>(); - biddersFitlerMapForKeptImp.put("rubicon", true); - biddersFitlerMapForKeptImp.put("appnexus", false); - biddersFitlerMapForKeptImp.put("pubmatic", false); - - final Map biddersFitlerMapForRemovedImp = new HashMap<>(); - biddersFitlerMapForRemovedImp.put("appnexus", false); - - final Map> impsBiddersFilterMap = new HashMap<>(); - impsBiddersFilterMap.put("adunitcodevalue1", biddersFitlerMapForKeptImp); - impsBiddersFilterMap.put("adunitcodevalue2", biddersFitlerMapForRemovedImp); - - return impsBiddersFilterMap; - } - - private GreenbidsConfig givenPartner(Double explorationRate) { - return GreenbidsConfig.of("test-pbuid", 0.60, explorationRate); - } -} diff --git a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsPayloadUpdaterTest.java b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsPayloadUpdaterTest.java new file mode 100644 index 00000000000..36ed3f8e980 --- /dev/null +++ b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/core/GreenbidsPayloadUpdaterTest.java @@ -0,0 +1,60 @@ +package org.prebid.server.hooks.modules.greenbids.real.time.data.core; + +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static java.util.function.UnaryOperator.identity; +import static org.assertj.core.api.Assertions.assertThat; +import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.getAppnexusNode; +import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.getPubmaticNode; +import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.getRubiconNode; +import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenBidRequest; +import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenImpExt; + +public class GreenbidsPayloadUpdaterTest { + + @Test + public void updateShouldReturnUpdatedBidRequest() { + // given + final Imp givenImp = Imp.builder() + .id("adunitcodevalue") + .ext(givenImpExt(getRubiconNode(), getAppnexusNode(), getPubmaticNode())) + .build(); + + // when + final BidRequest result = GreenbidsPayloadUpdater.update( + givenBidRequest(identity(), List.of(givenImp)), + Map.of("adunitcodevalue", Map.of("rubicon", true, "appnexus", false, "pubmatic", false))); + + // then + final Imp expectedImp = Imp.builder() + .id("adunitcodevalue") + .ext(givenImpExt(getRubiconNode(), null, null)) + .build(); + + assertThat(result.getImp()).containsOnly(expectedImp); + } + + @Test + public void updateShouldRemoveImpFromUpdateBidRequestWhenAllBiddersFiltered() { + // given + final Imp givenImp = Imp.builder() + .id("adunitcodevalue") + .ext(givenImpExt(getRubiconNode(), null, null)) + .build(); + + // when + final BidRequest result = GreenbidsPayloadUpdater.update( + givenBidRequest(identity(), List.of(givenImp)), + Map.of("adunitcodevalue", Map.of("rubicon", false, "appnexus", false, "pubmatic", false))); + + // then + assertThat(result.getImp()).isEmpty(); + + } + +} diff --git a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/util/TestBidRequestProvider.java b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/util/TestBidRequestProvider.java index 5e07f350869..9a33620c087 100644 --- a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/util/TestBidRequestProvider.java +++ b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/util/TestBidRequestProvider.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Device; @@ -11,8 +10,6 @@ import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Site; import org.prebid.server.json.ObjectMapperProvider; -import org.prebid.server.proto.openrtb.ext.request.ExtRequest; -import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; import java.util.Collections; import java.util.List; @@ -24,114 +21,73 @@ public class TestBidRequestProvider { private TestBidRequestProvider() { } - public static BidRequest givenBidRequest( - UnaryOperator bidRequestCustomizer, - List imps, - Device device) { + public static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer, + List imps) { return bidRequestCustomizer.apply(BidRequest.builder() - .id("request") - .imp(imps) - .site(givenSite(site -> site)) - .device(device)).build(); - } - - public static BidRequest givenBidRequestWithExtension( - UnaryOperator bidRequestCustomizer, - List imps) { - final BidRequest.BidRequestBuilder bidRequestBuilder = BidRequest.builder() - .id("request") - .imp(imps) - .site(givenSite(site -> site)) - .device(givenDevice(device -> device)) - .ext(givenExtRequest()); - - return bidRequestCustomizer.apply(bidRequestBuilder).build(); + .id("request") + .imp(imps) + .site(givenSite()) + .device(givenDevice())) + .build(); } - public static ExtRequest givenExtRequest() { - final ObjectNode greenbidsNode = new ObjectMapper().createObjectNode(); - greenbidsNode.put("pbuid", "leparisien"); - greenbidsNode.put("greenbidsSampling", 1.0); - - final ObjectNode analyticsNode = new ObjectMapper().createObjectNode(); - analyticsNode.set("greenbids", greenbidsNode); - - return ExtRequest.of( - ExtRequestPrebid - .builder() - .analytics(analyticsNode) - .build()); + public static Site givenSite() { + return Site.builder().domain("www.leparisien.fr").build(); } - public static Site givenSite(UnaryOperator siteCustomizer) { - return siteCustomizer.apply(Site.builder().domain("www.leparisien.fr")).build(); + public static ObjectNode givenImpExt() { + return givenImpExt(getRubiconNode(), getAppnexusNode(), getPubmaticNode()); } - public static ObjectNode givenImpExt() { + public static ObjectNode givenImpExt(ObjectNode rubiconNode, ObjectNode appnexusNode, ObjectNode pubmaticNode) { final ObjectNode bidderNode = MAPPER.createObjectNode(); - final ObjectNode rubiconNode = MAPPER.createObjectNode(); - rubiconNode.put("accountId", 1001); - rubiconNode.put("siteId", 267318); - rubiconNode.put("zoneId", 1861698); - bidderNode.set("rubicon", rubiconNode); - - final ObjectNode appnexusNode = MAPPER.createObjectNode(); - appnexusNode.put("placementId", 123456); - bidderNode.set("appnexus", appnexusNode); - - final ObjectNode pubmaticNode = MAPPER.createObjectNode(); - pubmaticNode.put("publisherId", "156209"); - pubmaticNode.put("adSlot", "slot1@300x250"); - bidderNode.set("pubmatic", pubmaticNode); + if (rubiconNode != null) { + bidderNode.set("rubicon", rubiconNode); + } - final ObjectNode prebidNode = MAPPER.createObjectNode(); - prebidNode.set("bidder", bidderNode); + if (appnexusNode != null) { + bidderNode.set("appnexus", appnexusNode); + } - final ObjectNode extNode = MAPPER.createObjectNode(); - extNode.set("prebid", prebidNode); - extNode.set("tid", TextNode.valueOf("67eaab5f-27a6-4689-93f7-bd8f024576e3")); + if (pubmaticNode != null) { + bidderNode.set("pubmatic", pubmaticNode); + } - return extNode; + return MAPPER.createObjectNode() + .put("tid", "67eaab5f-27a6-4689-93f7-bd8f024576e3") + .set("prebid", MAPPER.createObjectNode().set("bidder", bidderNode)); } - public static ObjectNode givenImpExtToFilterAllBidders() { - final ObjectNode bidderNode = MAPPER.createObjectNode(); - - final ObjectNode appnexusNode = MAPPER.createObjectNode(); - appnexusNode.put("placementId", 789); - bidderNode.set("appnexus", appnexusNode); - - final ObjectNode prebidNode = MAPPER.createObjectNode(); - prebidNode.set("bidder", bidderNode); + public static ObjectNode getPubmaticNode() { + return MAPPER.createObjectNode() + .put("publisherId", "156209") + .put("adSlot", "slot1@300x250"); + } - final ObjectNode extNode = MAPPER.createObjectNode(); - extNode.set("prebid", prebidNode); - extNode.set("tid", TextNode.valueOf("af65045c-2774-44c2-a949-4f42d5c9e179")); + public static ObjectNode getAppnexusNode() { + return MAPPER.createObjectNode().put("placementId", 123456); + } - return extNode; + public static ObjectNode getRubiconNode() { + return MAPPER.createObjectNode() + .put("accountId", 1001) + .put("siteId", 267318) + .put("zoneId", 1861698); } - public static Device givenDevice(UnaryOperator deviceCustomizer, String countryAlpha3) { + public static Device givenDevice(String countryAlpha3) { final String userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36" + " (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"; - final Geo geo = givenGeoWithCountry(countryAlpha3); - return deviceCustomizer.apply(Device.builder().ua(userAgent).ip("151.101.194.216").geo(geo)).build(); + final Geo geo = Geo.builder().country(countryAlpha3).build(); + return Device.builder().ua(userAgent).ip("151.101.194.216").geo(geo).build(); } - public static Device givenDevice(UnaryOperator deviceCustomizer) { + public static Device givenDevice() { final String userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36" + " (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"; - return deviceCustomizer.apply(Device.builder().ua(userAgent).ip("151.101.194.216")).build(); - } - - public static Geo givenGeoWithCountry(String countryAlpha3) { - return Geo.builder().country(countryAlpha3).build(); - } - - public static Device givenDeviceWithoutUserAgent(UnaryOperator deviceCustomizer) { - return deviceCustomizer.apply(Device.builder().ip("151.101.194.216")).build(); + return Device.builder().ua(userAgent).ip("151.101.194.216").build(); } public static Banner givenBanner() { diff --git a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHookTest.java b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHookTest.java index 26b4fe37a31..42e16253445 100644 --- a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHookTest.java +++ b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHookTest.java @@ -1,363 +1,137 @@ package org.prebid.server.hooks.modules.greenbids.real.time.data.v1; -import ai.onnxruntime.OrtException; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; -import com.github.benmanes.caffeine.cache.Cache; -import com.google.cloud.storage.Storage; -import com.google.cloud.storage.StorageOptions; -import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Imp; -import com.maxmind.geoip2.DatabaseReader; -import com.maxmind.geoip2.exception.GeoIp2Exception; -import com.maxmind.geoip2.model.CountryResponse; -import com.maxmind.geoip2.record.Country; import io.vertx.core.Future; -import io.vertx.core.Vertx; 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.analytics.reporter.greenbids.model.ExplorationResult; -import org.prebid.server.analytics.reporter.greenbids.model.Ortb2ImpExtResult; -import org.prebid.server.auction.model.AuctionContext; -import org.prebid.server.geolocation.CountryCodeMapper; +import org.prebid.server.auction.model.ImpRejection; import org.prebid.server.hooks.execution.v1.analytics.ActivityImpl; -import org.prebid.server.hooks.execution.v1.analytics.AppliedToImpl; import org.prebid.server.hooks.execution.v1.analytics.ResultImpl; -import org.prebid.server.hooks.execution.v1.analytics.TagsImpl; +import org.prebid.server.hooks.execution.v1.auction.AuctionInvocationContextImpl; import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl; -import org.prebid.server.hooks.modules.greenbids.real.time.data.config.DatabaseReaderFactory; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.FilterService; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInferenceDataService; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInvocationService; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.ModelCache; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunner; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunnerFactory; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunnerWithThresholds; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.ThresholdCache; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.ThrottlingThresholdsFactory; -import org.prebid.server.hooks.modules.greenbids.real.time.data.model.filter.ThrottlingThresholds; -import org.prebid.server.hooks.modules.greenbids.real.time.data.model.result.AnalyticsResult; -import org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider; 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.analytics.Result; -import org.prebid.server.hooks.v1.analytics.Tags; +import org.prebid.server.hooks.v1.analytics.AppliedTo; import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; import org.prebid.server.hooks.v1.auction.AuctionRequestPayload; -import org.prebid.server.model.HttpRequestContext; -import java.io.IOException; -import java.net.InetAddress; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.function.UnaryOperator; import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenBanner; +import static org.prebid.server.auction.model.BidRejectionReason.REQUEST_BLOCKED_OPTIMIZED; +import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.MAPPER; +import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.getRubiconNode; import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenBidRequest; -import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenBidRequestWithExtension; -import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenDevice; -import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenDeviceWithoutUserAgent; -import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenExtRequest; import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenImpExt; -import static org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider.givenSite; @ExtendWith(MockitoExtension.class) public class GreenbidsRealTimeDataProcessedAuctionRequestHookTest { @Mock - private Cache modelCacheWithExpiration; + private FilterService filterService; @Mock - private Cache thresholdsCacheWithExpiration; - - @Mock(strictness = LENIENT) - private DatabaseReaderFactory databaseReaderFactory; - - @Mock(strictness = LENIENT) - private DatabaseReader databaseReader; - - @Mock(strictness = LENIENT) - private CountryResponse countryResponse; - - @Mock(strictness = LENIENT) - private Country country; + private OnnxModelRunnerWithThresholds onnxModelRunnerWithThresholds; @Mock - private CountryCodeMapper countryCodeMapper; + private GreenbidsInferenceDataService greenbidsInferenceDataService; private GreenbidsRealTimeDataProcessedAuctionRequestHook target; @BeforeEach - public void setUp() throws IOException, GeoIp2Exception { - final Storage storage = StorageOptions.newBuilder() - .setProjectId("test_project").build().getService(); + public void setUp() { + given(onnxModelRunnerWithThresholds.retrieveOnnxModelRunner(any())) + .willReturn(Future.succeededFuture(mock(OnnxModelRunner.class))); + given(onnxModelRunnerWithThresholds.retrieveThreshold(any())) + .willReturn(Future.succeededFuture(18.2d)); + given(greenbidsInferenceDataService.extractThrottlingMessagesFromBidRequest(any())) + .willReturn(Collections.emptyList()); - when(country.getName()).thenReturn("United States"); - when(countryResponse.getCountry()).thenReturn(country); - when(databaseReader.country(any(InetAddress.class))).thenReturn(countryResponse); - when(databaseReaderFactory.getDatabaseReader()).thenReturn(databaseReader); - - final FilterService filterService = new FilterService(); - final OnnxModelRunnerFactory onnxModelRunnerFactory = new OnnxModelRunnerFactory(); - final ThrottlingThresholdsFactory throttlingThresholdsFactory = new ThrottlingThresholdsFactory(); - final ModelCache modelCache = new ModelCache( - storage, - "test_bucket", - modelCacheWithExpiration, - "onnxModelRunner_", - Vertx.vertx(), - onnxModelRunnerFactory); - final ThresholdCache thresholdCache = new ThresholdCache( - storage, - "test_bucket", - TestBidRequestProvider.MAPPER, - thresholdsCacheWithExpiration, - "throttlingThresholds_", - Vertx.vertx(), - throttlingThresholdsFactory); - final OnnxModelRunnerWithThresholds onnxModelRunnerWithThresholds = new OnnxModelRunnerWithThresholds( - modelCache, - thresholdCache); - - final GreenbidsInferenceDataService greenbidsInferenceDataService = new GreenbidsInferenceDataService( - databaseReaderFactory, - TestBidRequestProvider.MAPPER, - countryCodeMapper); - final GreenbidsInvocationService greenbidsInvocationService = new GreenbidsInvocationService(); target = new GreenbidsRealTimeDataProcessedAuctionRequestHook( - TestBidRequestProvider.MAPPER, + MAPPER, filterService, onnxModelRunnerWithThresholds, - greenbidsInferenceDataService, - greenbidsInvocationService); + greenbidsInferenceDataService); } @Test - public void callShouldFilterBiddersWhenPartnerActivatedInBidRequest() - throws IOException, OrtException { + public void callShouldReturnAnalyticTagsWithoutFilteringOutBiddersWhenExplorationIsTrue() { // given - final Banner banner = givenBanner(); - final Imp imp = Imp.builder() .id("adunitcodevalue") .ext(givenImpExt()) - .banner(banner) - .build(); - - final Double explorationRate = 0.0001; - final Device device = givenDevice(identity()); - final BidRequest bidRequest = givenBidRequestWithExtension(identity(), List.of(imp)); - final AuctionContext auctionContext = givenAuctionContext( - bidRequest, - context -> context); - final AuctionInvocationContext invocationContext = givenAuctionInvocationContext( - auctionContext, explorationRate); - when(invocationContext.auctionContext()).thenReturn(auctionContext); - when(modelCacheWithExpiration.getIfPresent("onnxModelRunner_test-pbuid")) - .thenReturn(givenOnnxModelRunner()); - when(thresholdsCacheWithExpiration.getIfPresent("throttlingThresholds_test-pbuid")) - .thenReturn(givenThrottlingThresholds()); - - final BidRequest expectedBidRequest = expectedUpdatedBidRequest( - request -> request, device, true); - final AnalyticsResult expectedAnalyticsResult = expectedAnalyticsResult(false, true); - - // when - final Future> future = target - .call(null, invocationContext); - final InvocationResult result = future.result(); - final BidRequest resultBidRequest = result - .payloadUpdate() - .apply(AuctionRequestPayloadImpl.of(bidRequest)) - .bidRequest(); - - // then - final ActivityImpl activityImpl = (ActivityImpl) result.analyticsTags().activities().getFirst(); - final ResultImpl resultImpl = (ResultImpl) activityImpl.results().getFirst(); - final String fingerprint = resultImpl.values() - .get("adunitcodevalue") - .get("greenbids") - .get("fingerprint").asText(); - - assertThat(future).isNotNull(); - assertThat(future.succeeded()).isTrue(); - assertThat(result).isNotNull(); - assertThat(result.status()).isEqualTo(InvocationStatus.success); - assertThat(result.action()).isEqualTo(InvocationAction.update); - assertThat(result.analyticsTags()).isNotNull(); - assertThat(result.analyticsTags()).usingRecursiveComparison() - .ignoringFields( - "activities.results" - + ".values._children" - + ".adunitcodevalue._children" - + ".greenbids._children.fingerprint") - .isEqualTo(toAnalyticsTags(List.of(expectedAnalyticsResult))); - assertThat(fingerprint).isNotNull(); - assertThat(resultBidRequest).usingRecursiveComparison() - .isEqualTo(expectedBidRequest); - } - - @Test - public void callShouldFilterBiddersAndReturnAnalyticsTagWhenExploration() throws OrtException, IOException { - // given - final Banner banner = givenBanner(); - - final Imp imp = Imp.builder() - .id("adunitcodevalue") - .ext(givenImpExt()) - .banner(banner) .build(); final Double explorationRate = 1.0; - final Device device = givenDevice(identity()); - final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp), device); - final AuctionContext auctionContext = givenAuctionContext(bidRequest, context -> context); - final AuctionInvocationContext invocationContext = givenAuctionInvocationContext( - auctionContext, explorationRate); - when(invocationContext.auctionContext()).thenReturn(auctionContext); - when(modelCacheWithExpiration.getIfPresent("onnxModelRunner_test-pbuid")) - .thenReturn(givenOnnxModelRunner()); - when(thresholdsCacheWithExpiration.getIfPresent("throttlingThresholds_test-pbuid")) - .thenReturn(givenThrottlingThresholds()); + final BidRequest bidRequest = givenBidRequest(identity(), List.of(imp)); + final AuctionInvocationContext invocationContext = givenAuctionInvocationContext(explorationRate); - final AnalyticsResult expectedAnalyticsResult = expectedAnalyticsResult(true, true); + given(filterService.filterBidders(any(), any(), any())).willReturn(Map.of("adunitcodevalue", + Map.of("rubicon", false, "appnexus", false, "pubmatic", false))); // when final Future> future = target - .call(null, invocationContext); + .call(AuctionRequestPayloadImpl.of(bidRequest), invocationContext); final InvocationResult result = future.result(); // then - final ActivityImpl activity = (ActivityImpl) result.analyticsTags().activities().getFirst(); - final ResultImpl resultImpl = (ResultImpl) activity.results().getFirst(); - final String fingerprint = resultImpl.values() - .get("adunitcodevalue") - .get("greenbids") - .get("fingerprint").asText(); - - assertThat(future).isNotNull(); assertThat(future.succeeded()).isTrue(); - assertThat(result).isNotNull(); assertThat(result.status()).isEqualTo(InvocationStatus.success); assertThat(result.action()).isEqualTo(InvocationAction.no_action); - assertThat(result.analyticsTags()).isNotNull(); - assertThat(result.analyticsTags()).usingRecursiveComparison() - .ignoringFields( - "activities.results" - + ".values._children" - + ".adunitcodevalue._children" - + ".greenbids._children.fingerprint") - .isEqualTo(toAnalyticsTags(List.of(expectedAnalyticsResult))); - assertThat(fingerprint).isNotNull(); - } - @Test - public void callShouldFilterBiddersBasedOnModelWhenAnyFeatureNotAvailable() throws OrtException, IOException { - // given - final Banner banner = givenBanner(); + final ActivityImpl actualActivity = (ActivityImpl) result.analyticsTags().activities().getFirst(); + final ResultImpl actualResult = (ResultImpl) actualActivity.results().getFirst(); + final AppliedTo acctualAppliedTo = actualResult.appliedTo(); - final Imp imp = Imp.builder() - .id("adunitcodevalue") - .ext(givenImpExt()) - .banner(banner) - .build(); - - final Double explorationRate = 0.0001; - final Device device = givenDeviceWithoutUserAgent(identity()); - final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp), device); - final AuctionContext auctionContext = givenAuctionContext(bidRequest, context -> context); - final AuctionInvocationContext invocationContext = givenAuctionInvocationContext( - auctionContext, explorationRate); - when(invocationContext.auctionContext()).thenReturn(auctionContext); - when(modelCacheWithExpiration.getIfPresent("onnxModelRunner_test-pbuid")) - .thenReturn(givenOnnxModelRunner()); - when(thresholdsCacheWithExpiration.getIfPresent("throttlingThresholds_test-pbuid")) - .thenReturn(givenThrottlingThresholds()); - - final BidRequest expectedBidRequest = expectedUpdatedBidRequest( - request -> request, device, false); - final AnalyticsResult expectedAnalyticsResult = expectedAnalyticsResult(false, true); - - // when - final Future> future = target - .call(null, invocationContext); - final InvocationResult result = future.result(); - final BidRequest resultBidRequest = result - .payloadUpdate() - .apply(AuctionRequestPayloadImpl.of(bidRequest)) - .bidRequest(); - - // then - final ActivityImpl activity = (ActivityImpl) result.analyticsTags().activities().getFirst(); - final ResultImpl resultImpl = (ResultImpl) activity.results().getFirst(); - final String fingerprint = resultImpl.values() - .get("adunitcodevalue") - .get("greenbids") - .get("fingerprint").asText(); - - assertThat(future).isNotNull(); - assertThat(future.succeeded()).isTrue(); - assertThat(result).isNotNull(); - assertThat(result.status()).isEqualTo(InvocationStatus.success); - assertThat(result.action()).isEqualTo(InvocationAction.update); - assertThat(result.analyticsTags()).isNotNull(); - assertThat(result.analyticsTags()).usingRecursiveComparison() - .ignoringFields( - "activities.results" - + ".values._children" - + ".adunitcodevalue._children" - + ".greenbids._children.fingerprint") - .isEqualTo(toAnalyticsTags(List.of(expectedAnalyticsResult))); - assertThat(fingerprint).isNotNull(); - assertThat(resultBidRequest).usingRecursiveComparison().isEqualTo(expectedBidRequest); + assertThat(acctualAppliedTo.bidders()).containsOnly("appnexus", "pubmatic", "rubicon"); + assertThat(acctualAppliedTo.impIds()).containsOnly("adunitcodevalue"); + assertThat(actualResult.values().get("adunitcodevalue").get("greenbids").get("keptInAuction")) + .isEqualTo(MAPPER.createObjectNode() + .put("rubicon", false) + .put("appnexus", false) + .put("pubmatic", false)); + assertThat(actualResult.values().get("adunitcodevalue").get("greenbids").get("fingerprint").asText()) + .isNotNull(); + assertThat(actualResult.values().get("adunitcodevalue").get("tid").asText()) + .isEqualTo("67eaab5f-27a6-4689-93f7-bd8f024576e3"); + assertThat(result.rejections()).isNull(); } @Test - public void callShouldFilterBiddersBasedOnModelResults() throws OrtException, IOException { + public void callShouldFilterBiddersBasedOnModelResultsWhenExplorationIsFalse() { // given - final Banner banner = givenBanner(); - final Imp imp = Imp.builder() .id("adunitcodevalue") .ext(givenImpExt()) - .banner(banner) .build(); final Double explorationRate = 0.0001; - final Device device = givenDevice(identity()); - final BidRequest bidRequest = givenBidRequest(request -> request, List.of(imp), device); - final AuctionContext auctionContext = givenAuctionContext(bidRequest, context -> context); - final AuctionInvocationContext invocationContext = givenAuctionInvocationContext( - auctionContext, explorationRate); - when(invocationContext.auctionContext()).thenReturn(auctionContext); - when(modelCacheWithExpiration.getIfPresent("onnxModelRunner_test-pbuid")) - .thenReturn(givenOnnxModelRunner()); - when(thresholdsCacheWithExpiration.getIfPresent("throttlingThresholds_test-pbuid")) - .thenReturn(givenThrottlingThresholds()); + final BidRequest bidRequest = givenBidRequest(identity(), List.of(imp)); + final AuctionInvocationContext invocationContext = givenAuctionInvocationContext(explorationRate); - final BidRequest expectedBidRequest = expectedUpdatedBidRequest( - request -> request, device, false); - final AnalyticsResult expectedAnalyticsResult = expectedAnalyticsResult(false, true); + given(filterService.filterBidders(any(), any(), any())).willReturn(Map.of("adunitcodevalue", + Map.of("rubicon", true, "appnexus", false, "pubmatic", false))); // when final Future> future = target - .call(null, invocationContext); + .call(AuctionRequestPayloadImpl.of(bidRequest), invocationContext); final InvocationResult result = future.result(); final BidRequest resultBidRequest = result .payloadUpdate() @@ -365,165 +139,51 @@ public void callShouldFilterBiddersBasedOnModelResults() throws OrtException, IO .bidRequest(); // then - final ActivityImpl activityImpl = (ActivityImpl) result.analyticsTags().activities().getFirst(); - final ResultImpl resultImpl = (ResultImpl) activityImpl.results().getFirst(); - final String fingerprint = resultImpl.values() - .get("adunitcodevalue") - .get("greenbids") - .get("fingerprint").asText(); - - assertThat(future).isNotNull(); assertThat(future.succeeded()).isTrue(); - assertThat(result).isNotNull(); assertThat(result.status()).isEqualTo(InvocationStatus.success); assertThat(result.action()).isEqualTo(InvocationAction.update); - assertThat(result.analyticsTags()).isNotNull(); - assertThat(result.analyticsTags()).usingRecursiveComparison() - .ignoringFields( - "activities.results" - + ".values._children" - + ".adunitcodevalue._children" - + ".greenbids._children.fingerprint") - .isEqualTo(toAnalyticsTags(List.of(expectedAnalyticsResult))); - assertThat(fingerprint).isNotNull(); - assertThat(resultBidRequest).usingRecursiveComparison() - .isEqualTo(expectedBidRequest); - } - private AuctionContext givenAuctionContext( - BidRequest bidRequest, - UnaryOperator auctionContextCustomizer) { - - final AuctionContext.AuctionContextBuilder auctionContextBuilder = AuctionContext.builder() - .httpRequest(HttpRequestContext.builder().build()) - .bidRequest(bidRequest); - - return auctionContextCustomizer.apply(auctionContextBuilder).build(); - } - - private AuctionInvocationContext givenAuctionInvocationContext( - AuctionContext auctionContext, Double explorationRate) { - final AuctionInvocationContext invocationContext = mock(AuctionInvocationContext.class); - when(invocationContext.auctionContext()).thenReturn(auctionContext); - when(invocationContext.accountConfig()).thenReturn(givenAccountConfig(explorationRate)); - return invocationContext; + final Imp expectedImp = Imp.builder() + .id("adunitcodevalue") + .ext(givenImpExt(getRubiconNode(), null, null)) + .build(); + assertThat(resultBidRequest).isEqualTo(givenBidRequest(identity(), List.of(expectedImp))); + + final ActivityImpl actualActivity = (ActivityImpl) result.analyticsTags().activities().getFirst(); + final ResultImpl actualResult = (ResultImpl) actualActivity.results().getFirst(); + final AppliedTo acctualAppliedTo = actualResult.appliedTo(); + + assertThat(acctualAppliedTo.bidders()).containsOnly("appnexus", "pubmatic"); + assertThat(acctualAppliedTo.impIds()).containsOnly("adunitcodevalue"); + assertThat(actualResult.values().get("adunitcodevalue").get("greenbids").get("keptInAuction")) + .isEqualTo(MAPPER.createObjectNode() + .put("rubicon", true) + .put("appnexus", false) + .put("pubmatic", false)); + assertThat(actualResult.values().get("adunitcodevalue").get("greenbids").get("fingerprint").asText()) + .isNotNull(); + assertThat(actualResult.values().get("adunitcodevalue").get("tid").asText()) + .isEqualTo("67eaab5f-27a6-4689-93f7-bd8f024576e3"); + assertThat(result.rejections()).containsOnly( + entry("appnexus", List.of(ImpRejection.of("adunitcodevalue", REQUEST_BLOCKED_OPTIMIZED))), + entry("pubmatic", List.of(ImpRejection.of("adunitcodevalue", REQUEST_BLOCKED_OPTIMIZED)))); + } + + private AuctionInvocationContext givenAuctionInvocationContext(Double explorationRate) { + return AuctionInvocationContextImpl.of( + null, + null, + false, + givenAccountConfig(explorationRate), + null); } private ObjectNode givenAccountConfig(Double explorationRate) { - final ObjectNode greenbidsNode = TestBidRequestProvider.MAPPER.createObjectNode(); + final ObjectNode greenbidsNode = MAPPER.createObjectNode(); greenbidsNode.put("enabled", true); greenbidsNode.put("pbuid", "test-pbuid"); greenbidsNode.put("target-tpr", 0.99); greenbidsNode.put("exploration-rate", explorationRate); return greenbidsNode; } - - private OnnxModelRunner givenOnnxModelRunner() throws OrtException, IOException { - final byte[] onnxModelBytes = Files.readAllBytes(Paths.get( - "src/test/resources/models_pbuid=test-pbuid.onnx")); - return new OnnxModelRunner(onnxModelBytes); - } - - private ThrottlingThresholds givenThrottlingThresholds() throws IOException { - final JsonNode thresholdsJsonNode = TestBidRequestProvider.MAPPER.readTree( - Files.newInputStream(Paths.get( - "src/test/resources/thresholds_pbuid=test-pbuid.json"))); - return TestBidRequestProvider.MAPPER - .treeToValue(thresholdsJsonNode, ThrottlingThresholds.class); - } - - private BidRequest expectedUpdatedBidRequest( - UnaryOperator bidRequestCustomizer, - Device device, - Boolean isExtRequest) { - - final Banner banner = givenBanner(); - - final ObjectNode bidderNode = TestBidRequestProvider.MAPPER.createObjectNode(); - - final ObjectNode rubiconNode = TestBidRequestProvider.MAPPER.createObjectNode(); - rubiconNode.put("accountId", 1001); - rubiconNode.put("siteId", 267318); - rubiconNode.put("zoneId", 1861698); - bidderNode.set("rubicon", rubiconNode); - - final ObjectNode appnexusNode = TestBidRequestProvider.MAPPER.createObjectNode(); - appnexusNode.put("placementId", 123456); - bidderNode.set("appnexus", appnexusNode); - - final ObjectNode pubmaticNode = TestBidRequestProvider.MAPPER.createObjectNode(); - pubmaticNode.put("publisherId", "156209"); - pubmaticNode.put("adSlot", "slot1@300x250"); - bidderNode.set("pubmatic", pubmaticNode); - - final ObjectNode prebidNode = TestBidRequestProvider.MAPPER.createObjectNode(); - prebidNode.set("bidder", bidderNode); - - final ObjectNode extNode = TestBidRequestProvider.MAPPER.createObjectNode(); - extNode.set("prebid", prebidNode); - extNode.set("tid", TextNode.valueOf("67eaab5f-27a6-4689-93f7-bd8f024576e3")); - - final Imp imp = Imp.builder() - .id("adunitcodevalue") - .ext(extNode) - .banner(banner) - .build(); - - final BidRequest.BidRequestBuilder bidRequestBuilder = BidRequest.builder() - .id("request") - .imp(List.of(imp)) - .site(givenSite(site -> site)) - .device(device); - - if (isExtRequest) { - bidRequestBuilder.ext(givenExtRequest()); - } - - return bidRequestCustomizer.apply(bidRequestBuilder).build(); - } - - private AnalyticsResult expectedAnalyticsResult(Boolean isExploration, Boolean isKeptInAuction) { - return AnalyticsResult.of( - "success", - Map.of("adunitcodevalue", expectedOrtb2ImpExtResult(isExploration, isKeptInAuction))); - } - - private Ortb2ImpExtResult expectedOrtb2ImpExtResult(Boolean isExploration, Boolean isKeptInAuction) { - return Ortb2ImpExtResult.of( - expectedExplorationResult(isExploration, isKeptInAuction), "67eaab5f-27a6-4689-93f7-bd8f024576e3"); - } - - private ExplorationResult expectedExplorationResult(Boolean isExploration, Boolean isKeptInAuction) { - final Map keptInAuction = Map.of( - "appnexus", isKeptInAuction, - "pubmatic", isKeptInAuction, - "rubicon", isKeptInAuction); - return ExplorationResult.of("60a7c66c-c542-48c6-a319-ea7b9f97947f", keptInAuction, isExploration); - } - - private Tags toAnalyticsTags(List analyticsResults) { - return TagsImpl.of(Collections.singletonList(ActivityImpl.of( - "greenbids-filter", - "success", - toResults(analyticsResults)))); - } - - private List toResults(List analyticsResults) { - return analyticsResults.stream() - .map(this::toResult) - .toList(); - } - - private Result toResult(AnalyticsResult analyticsResult) { - return ResultImpl.of( - analyticsResult.getStatus(), - toObjectNode(analyticsResult.getValues()), - AppliedToImpl.builder() - .impIds(Collections.singletonList("adunitcodevalue")) - .build()); - } - - private ObjectNode toObjectNode(Map values) { - return values != null ? TestBidRequestProvider.MAPPER.valueToTree(values) : null; - } } diff --git a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/core/BidsBlocker.java b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/core/BidsBlocker.java index 628768f1df9..f34355dc9e4 100644 --- a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/core/BidsBlocker.java +++ b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/core/BidsBlocker.java @@ -6,7 +6,8 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.prebid.server.auction.model.BidRejectionReason; -import org.prebid.server.auction.model.BidRejectionTracker; +import org.prebid.server.auction.model.Rejection; +import org.prebid.server.auction.model.BidRejection; import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.hooks.modules.ortb2.blocking.core.exception.InvalidAccountConfigurationException; @@ -49,7 +50,6 @@ public class BidsBlocker { private final OrtbVersion ortbVersion; private final ObjectNode accountConfig; private final BlockedAttributes blockedAttributes; - private final BidRejectionTracker bidRejectionTracker; private final boolean debugEnabled; private BidsBlocker(List bids, @@ -57,7 +57,6 @@ private BidsBlocker(List bids, OrtbVersion ortbVersion, ObjectNode accountConfig, BlockedAttributes blockedAttributes, - BidRejectionTracker bidRejectionTracker, boolean debugEnabled) { this.bids = bids; @@ -65,7 +64,6 @@ private BidsBlocker(List bids, this.ortbVersion = ortbVersion; this.accountConfig = accountConfig; this.blockedAttributes = blockedAttributes; - this.bidRejectionTracker = bidRejectionTracker; this.debugEnabled = debugEnabled; } @@ -74,7 +72,6 @@ public static BidsBlocker create(List bids, OrtbVersion ortbVersion, ObjectNode accountConfig, BlockedAttributes blockedAttributes, - BidRejectionTracker bidRejectionTracker, boolean debugEnabled) { return new BidsBlocker( @@ -83,7 +80,6 @@ public static BidsBlocker create(List bids, Objects.requireNonNull(ortbVersion), accountConfig, blockedAttributes, - bidRejectionTracker, debugEnabled); } @@ -104,9 +100,10 @@ public ExecutionResult block() { final BlockedBids blockedBids = !blockedBidIndexes.isEmpty() ? BlockedBids.of(blockedBidIndexes) : null; final List warnings = MergeUtils.mergeMessages(blockedBidResults); + final List rejectedBids = new ArrayList<>(); if (blockedBids != null) { blockedBidIndexes.forEach(index -> - rejectBlockedBid(blockedBidResults.get(index).getValue(), bids.get(index))); + rejectBlockedBid(rejectedBids, blockedBidResults.get(index).getValue(), bids.get(index))); } return ExecutionResult.builder() @@ -114,6 +111,7 @@ public ExecutionResult block() { .debugMessages(blockedBids != null ? debugMessages(blockedBidIndexes, blockedBidResults) : null) .warnings(warnings) .analyticsResults(toAnalyticsResults(blockedBidResults)) + .rejections(rejectedBids) .build(); } catch (InvalidAccountConfigurationException e) { return debugEnabled ? ExecutionResult.withError(e.getMessage()) : ExecutionResult.empty(); @@ -288,20 +286,20 @@ private String debugEntryFor(int index, BlockingResult blockingResult) { blockingResult.getFailedChecks()); } - private void rejectBlockedBid(BlockingResult blockingResult, BidderBid blockedBid) { + private void rejectBlockedBid(List rejections, BlockingResult blockingResult, BidderBid blockedBid) { if (blockingResult.getBattrCheckResult().isFailed() || blockingResult.getBappCheckResult().isFailed() || blockingResult.getBcatCheckResult().isFailed()) { - bidRejectionTracker.rejectBid( + rejections.add(BidRejection.of( blockedBid, - BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); + BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE)); } if (blockingResult.getBadvCheckResult().isFailed()) { - bidRejectionTracker.rejectBid( + rejections.add(BidRejection.of( blockedBid, - BidRejectionReason.RESPONSE_REJECTED_ADVERTISER_BLOCKED); + BidRejectionReason.RESPONSE_REJECTED_ADVERTISER_BLOCKED)); } } diff --git a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/core/model/ExecutionResult.java b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/core/model/ExecutionResult.java index 5765dfb5452..9b767cdf264 100644 --- a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/core/model/ExecutionResult.java +++ b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/core/model/ExecutionResult.java @@ -2,6 +2,7 @@ import lombok.Builder; import lombok.Value; +import org.prebid.server.auction.model.Rejection; import java.util.Collections; import java.util.List; @@ -22,6 +23,8 @@ public class ExecutionResult { List analyticsResults; + List rejections; + public boolean hasValue() { return value != null; } diff --git a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHook.java b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHook.java index 720823f4513..94271ff7ca5 100644 --- a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHook.java +++ b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHook.java @@ -59,7 +59,6 @@ public Future> call(BidderResponsePayloa ObjectUtils.defaultIfNull(moduleContext.ortbVersionOf(bidder), OrtbVersion.ORTB_2_5), invocationContext.accountConfig(), moduleContext.blockedAttributesFor(bidder), - invocationContext.auctionContext().getBidRejectionTrackers().get(bidder), invocationContext.debugEnabled()) .block(); @@ -73,7 +72,10 @@ public Future> call(BidderResponsePayloa .errors(blockedBidsResult.getErrors()) .warnings(blockedBidsResult.getWarnings()) .debugMessages(blockedBidsResult.getDebugMessages()) - .analyticsTags(toAnalyticsTags(blockedBidsResult.getAnalyticsResults())); + .analyticsTags(toAnalyticsTags(blockedBidsResult.getAnalyticsResults())) + .rejections(CollectionUtils.isEmpty(blockedBidsResult.getRejections()) + ? null + : Map.of(bidder, blockedBidsResult.getRejections())); if (blockedBidsResult.hasValue()) { final ResponseUpdater responseUpdater = ResponseUpdater.create(blockedBidsResult.getValue()); diff --git a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/core/BidsBlockerTest.java b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/core/BidsBlockerTest.java index ea2cd38c879..549d48e5ae4 100644 --- a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/core/BidsBlockerTest.java +++ b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/core/BidsBlockerTest.java @@ -7,10 +7,8 @@ import com.iab.openrtb.response.Bid; 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.auction.model.BidRejectionReason; -import org.prebid.server.auction.model.BidRejectionTracker; +import org.prebid.server.auction.model.BidRejection; import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.hooks.modules.ortb2.blocking.core.config.Attribute; @@ -35,9 +33,8 @@ import static java.util.Collections.singletonMap; import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.prebid.server.auction.model.BidRejectionReason.RESPONSE_REJECTED_ADVERTISER_BLOCKED; +import static org.prebid.server.auction.model.BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE; @ExtendWith(MockitoExtension.class) public class BidsBlockerTest { @@ -48,9 +45,6 @@ public class BidsBlockerTest { private static final OrtbVersion ORTB_VERSION = OrtbVersion.ORTB_2_5; - @Mock - private BidRejectionTracker bidRejectionTracker; - @Test public void shouldReturnEmptyResultWhenNoBlockingResponseConfig() { // given @@ -59,7 +53,6 @@ public void shouldReturnEmptyResultWhenNoBlockingResponseConfig() { // when and then assertThat(blocker.block()).satisfies(BidsBlockerTest::isEmpty); - verifyNoInteractions(bidRejectionTracker); } @Test @@ -75,7 +68,6 @@ public void shouldReturnEmptyResultWithErrorWhenInvalidAccountConfig() { assertThat(blocker.block()).isEqualTo(ExecutionResult.builder() .errors(singletonList("attributes field in account configuration is not an object")) .build()); - verifyNoInteractions(bidRejectionTracker); } @Test @@ -89,7 +81,7 @@ public void shouldReturnEmptyResultWithoutErrorWhenInvalidAccountConfigAndDebugD // when and then assertThat(blocker.block()).isEqualTo(ExecutionResult.empty()); - verifyNoInteractions(bidRejectionTracker); + } @Test @@ -108,7 +100,7 @@ public void shouldReturnEmptyResultWhenBidWithoutAdomainAndBlockUnknownFalse() { // when and then assertThat(blocker.block()).satisfies(BidsBlockerTest::isEmpty); - verifyNoInteractions(bidRejectionTracker); + } @Test @@ -127,7 +119,7 @@ public void shouldReturnEmptyResultWhenBidWithoutAdomainAndEnforceBlocksFalseAnd // when and then assertThat(blocker.block()).satisfies(BidsBlockerTest::isEmpty); - verifyNoInteractions(bidRejectionTracker); + } @Test @@ -164,7 +156,7 @@ public void shouldReturnEmptyResultWhenBidWithBlockedAdomainAndEnforceBlocksFals // when and then assertThat(blocker.block()).satisfies(BidsBlockerTest::isEmpty); - verifyNoInteractions(bidRejectionTracker); + } @Test @@ -183,7 +175,7 @@ public void shouldReturnEmptyResultWhenBidWithNotBlockedAdomain() { // when and then assertThat(blocker.block()).satisfies(BidsBlockerTest::isEmpty); - verifyNoInteractions(bidRejectionTracker); + } @Test @@ -202,8 +194,10 @@ public void shouldReturnResultWithBidWhenBidWithBlockedAdomainAndEnforceBlocksTr singletonList(bid), ORTB_VERSION, accountConfig, blockedAttributes, false); // when and then - assertThat(blocker.block()).satisfies(result -> hasValue(result, 0)); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_ADVERTISER_BLOCKED); + assertThat(blocker.block()).satisfies(result -> { + hasValue(result, 0); + assertThat(result.getRejections()).containsOnly(BidRejection.of(bid, RESPONSE_REJECTED_ADVERTISER_BLOCKED)); + }); } @Test @@ -221,7 +215,7 @@ public void shouldReturnEmptyResultWhenBidWithAdomainAndNoBlockedAttributes() { // when and then assertThat(blocker.block()).satisfies(BidsBlockerTest::isEmpty); - verifyNoInteractions(bidRejectionTracker); + } @Test @@ -244,7 +238,7 @@ public void shouldReturnEmptyResultWhenBidWithAttrAndNoBlockedBannerAttrForImp() // when and then assertThat(blocker.block()).satisfies(BidsBlockerTest::isEmpty); - verifyNoInteractions(bidRejectionTracker); + } @Test @@ -267,7 +261,7 @@ public void shouldReturnEmptyResultWhenBidWithAttrAndNoBlockedVideoAttrForImp() // when and then assertThat(blocker.block()).satisfies(BidsBlockerTest::isEmpty); - verifyNoInteractions(bidRejectionTracker); + } @Test @@ -290,7 +284,7 @@ public void shouldReturnEmptyResultWhenBidWithAttrAndNoBlockedAudioAttrForImp() // when and then assertThat(blocker.block()).satisfies(BidsBlockerTest::isEmpty); - verifyNoInteractions(bidRejectionTracker); + } @Test @@ -311,7 +305,7 @@ public void shouldReturnEmptyResultWhenBidWithBlockedAdomainAndInDealsExceptions // when and then assertThat(blocker.block()).satisfies(BidsBlockerTest::isEmpty); - verifyNoInteractions(bidRejectionTracker); + } @Test @@ -331,8 +325,10 @@ public void shouldReturnResultWithBidWhenBidWithBlockedAdomainAndNotInDealsExcep singletonList(bid), ORTB_VERSION, accountConfig, blockedAttributes, false); // when and then - assertThat(blocker.block()).satisfies(result -> hasValue(result, 0)); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_ADVERTISER_BLOCKED); + assertThat(blocker.block()).satisfies(result -> { + hasValue(result, 0); + assertThat(result.getRejections()).containsOnly(BidRejection.of(bid, RESPONSE_REJECTED_ADVERTISER_BLOCKED)); + }); } @Test @@ -354,8 +350,8 @@ public void shouldReturnResultWithBidAndDebugMessageWhenBidIsBlocked() { assertThat(result.getValue()).isEqualTo(BlockedBids.of(singleton(0))); assertThat(result.getDebugMessages()).containsOnly( "Bid 0 from bidder bidder1 has been rejected, failed checks: [bcat]"); + assertThat(result.getRejections()).containsOnly(BidRejection.of(bid, RESPONSE_REJECTED_INVALID_CREATIVE)); }); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); } @Test @@ -373,8 +369,10 @@ public void shouldReturnResultWithBidWithoutDebugMessageWhenBidIsBlockedAndDebug final BidsBlocker blocker = bidsBlocker(singletonList(bid), ORTB_VERSION, accountConfig, null, false); // when and then - assertThat(blocker.block()).satisfies(result -> hasValue(result, 0)); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); + assertThat(blocker.block()).satisfies(result -> { + hasValue(result, 0); + assertThat(result.getRejections()).containsOnly(BidRejection.of(bid, RESPONSE_REJECTED_INVALID_CREATIVE)); + }); } @Test @@ -437,12 +435,12 @@ public void shouldReturnResultWithAnalyticsResults() { AnalyticsResult.of("success-blocked", analyticsResultValues1, "bidder1", "impId1"), AnalyticsResult.of("success-blocked", analyticsResultValues2, "bidder1", "impId2"), AnalyticsResult.of("success-allow", null, "bidder1", "impId1")); - }); - verify(bidRejectionTracker).rejectBid(bid1, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); - verify(bidRejectionTracker).rejectBid(bid2, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); - verify(bidRejectionTracker).rejectBid(bid1, BidRejectionReason.RESPONSE_REJECTED_ADVERTISER_BLOCKED); - verifyNoMoreInteractions(bidRejectionTracker); + assertThat(result.getRejections()).containsOnly( + BidRejection.of(bid1, RESPONSE_REJECTED_INVALID_CREATIVE), + BidRejection.of(bid2, RESPONSE_REJECTED_INVALID_CREATIVE), + BidRejection.of(bid1, RESPONSE_REJECTED_ADVERTISER_BLOCKED)); + }); } @Test @@ -511,16 +509,15 @@ public void shouldReturnResultWithoutSomeBidsWhenAllAttributesInConfig() { "Bid 3 from bidder bidder1 has been rejected, failed checks: [bapp]", "Bid 5 from bidder bidder1 has been rejected, failed checks: [battr]", "Bid 7 from bidder bidder1 has been rejected, failed checks: [badv, bcat]"); + assertThat(result.getRejections()).containsOnly( + BidRejection.of(bid1, RESPONSE_REJECTED_INVALID_CREATIVE), + BidRejection.of(bid1, RESPONSE_REJECTED_ADVERTISER_BLOCKED), + BidRejection.of(bid2, RESPONSE_REJECTED_INVALID_CREATIVE), + BidRejection.of(bid4, RESPONSE_REJECTED_INVALID_CREATIVE), + BidRejection.of(bid6, RESPONSE_REJECTED_INVALID_CREATIVE), + BidRejection.of(bid8, RESPONSE_REJECTED_INVALID_CREATIVE), + BidRejection.of(bid8, RESPONSE_REJECTED_ADVERTISER_BLOCKED)); }); - - verify(bidRejectionTracker).rejectBid(bid1, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); - verify(bidRejectionTracker).rejectBid(bid1, BidRejectionReason.RESPONSE_REJECTED_ADVERTISER_BLOCKED); - verify(bidRejectionTracker).rejectBid(bid2, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); - verify(bidRejectionTracker).rejectBid(bid4, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); - verify(bidRejectionTracker).rejectBid(bid6, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); - verify(bidRejectionTracker).rejectBid(bid8, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); - verify(bidRejectionTracker).rejectBid(bid8, BidRejectionReason.RESPONSE_REJECTED_ADVERTISER_BLOCKED); - verifyNoMoreInteractions(bidRejectionTracker); } @Test @@ -547,8 +544,6 @@ public void shouldReturnEmptyResultForCattaxIfBidderSupportsLowerThan26() { assertThat(blocker.block()) .extracting(ExecutionResult::getValue) .isNull(); - - verifyNoInteractions(bidRejectionTracker); } @Test @@ -571,8 +566,6 @@ public void shouldPassBidIfCattaxIsNull() { assertThat(blocker.block()) .extracting(ExecutionResult::getValue) .isNull(); - - verifyNoInteractions(bidRejectionTracker); } @Test @@ -599,8 +592,6 @@ public void shouldBlockBidIfCattaxNotEqualsAllowedCattax() { assertThat(result.getDebugMessages()).containsExactly( "Bid 0 from bidder bidder1 has been rejected, failed checks: [cattax]"); }); - - verifyNoInteractions(bidRejectionTracker); } @Test @@ -627,8 +618,6 @@ public void shouldBlockBidIfCattaxNotEquals1IfBlockedAttributesCattaxAbsent() { assertThat(result.getDebugMessages()).containsExactly( "Bid 1 from bidder bidder1 has been rejected, failed checks: [cattax]"); }); - - verifyNoInteractions(bidRejectionTracker); } private static BidderBid bid() { @@ -673,7 +662,6 @@ private BidsBlocker bidsBlocker(List bids, BlockedAttributes blockedAttributes, boolean debugEnabled) { - return BidsBlocker.create( - bids, "bidder1", ortbVersion, accountConfig, blockedAttributes, bidRejectionTracker, debugEnabled); + return BidsBlocker.create(bids, "bidder1", ortbVersion, accountConfig, blockedAttributes, debugEnabled); } } diff --git a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHookTest.java b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHookTest.java index 9ecb3380322..7cf65af249e 100644 --- a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHookTest.java +++ b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHookTest.java @@ -14,7 +14,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.BidderCatalog; import org.prebid.server.bidder.BidderInfo; @@ -56,9 +55,6 @@ public class Ortb2BlockingBidderRequestHookTest { @Mock(strictness = Mock.Strictness.LENIENT) private BidderCatalog bidderCatalog; - @Mock(strictness = Mock.Strictness.LENIENT) - private BidRejectionTracker bidRejectionTracker; - private Ortb2BlockingBidderRequestHook hook; @BeforeEach @@ -83,7 +79,6 @@ public void shouldReturnResultWithNoActionWhenNoBlockingAttributes() { BidderInvocationContextImpl.of( "bidder1", Map.of("bidder1", "bidder1Base"), - bidRejectionTracker, null, true)); @@ -105,7 +100,7 @@ public void shouldReturnResultWithNoActionAndErrorWhenInvalidAccountConfig() { // when final Future> result = hook.call( BidderRequestPayloadImpl.of(emptyRequest()), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, accountConfig, true)); + BidderInvocationContextImpl.of("bidder1", accountConfig, true)); // then assertThat(result.succeeded()).isTrue(); @@ -126,7 +121,7 @@ public void shouldReturnResultWithNoActionAndNoErrorWhenInvalidAccountConfigAndD // when final Future> result = hook.call( BidderRequestPayloadImpl.of(emptyRequest()), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, accountConfig, false)); + BidderInvocationContextImpl.of("bidder1", accountConfig, false)); // then assertThat(result.succeeded()).isTrue(); @@ -152,7 +147,7 @@ public void shouldReturnResultWithModuleContextAndPayloadUpdate() { // when final Future> result = hook.call( BidderRequestPayloadImpl.of(emptyRequest()), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, accountConfig, true)); + BidderInvocationContextImpl.of("bidder1", accountConfig, true)); // then assertThat(result.succeeded()).isTrue(); @@ -204,7 +199,7 @@ public void shouldReturnResultWithUpdateActionAndWarning() { // when final Future> result = hook.call( BidderRequestPayloadImpl.of(emptyRequest()), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, accountConfig, true)); + BidderInvocationContextImpl.of("bidder1", accountConfig, true)); // then assertThat(result.succeeded()).isTrue(); @@ -241,7 +236,7 @@ public void shouldReturnResultWithUpdateActionAndNoWarningWhenDebugDisabled() { // when final Future> result = hook.call( BidderRequestPayloadImpl.of(emptyRequest()), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, accountConfig, false)); + BidderInvocationContextImpl.of("bidder1", accountConfig, false)); // then assertThat(result.succeeded()).isTrue(); diff --git a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHookTest.java b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHookTest.java index a8f21e6abf1..9c4d20693a6 100644 --- a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHookTest.java +++ b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHookTest.java @@ -8,10 +8,9 @@ import io.vertx.core.Future; 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.auction.model.AuctionContext; -import org.prebid.server.auction.model.BidRejectionTracker; +import org.prebid.server.auction.model.BidRejection; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.hooks.execution.v1.InvocationResultImpl; import org.prebid.server.hooks.execution.v1.analytics.ActivityImpl; @@ -36,14 +35,15 @@ import org.prebid.server.json.ObjectMapperProvider; import org.prebid.server.proto.openrtb.ext.response.BidType; -import java.util.Map; +import java.util.List; import java.util.function.UnaryOperator; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; -import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.prebid.server.auction.model.BidRejectionReason.RESPONSE_REJECTED_ADVERTISER_BLOCKED; @ExtendWith(MockitoExtension.class) public class Ortb2BlockingRawBidderResponseHookTest { @@ -55,15 +55,12 @@ public class Ortb2BlockingRawBidderResponseHookTest { private final Ortb2BlockingRawBidderResponseHook hook = new Ortb2BlockingRawBidderResponseHook( ObjectMapperProvider.mapper()); - @Mock - private BidRejectionTracker bidRejectionTracker; - @Test public void shouldReturnResultWithNoActionWhenNoBidsBlocked() { // when final Future> result = hook.call( BidderResponsePayloadImpl.of(singletonList(bid())), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, null, true)); + BidderInvocationContextImpl.of("bidder1", null, true)); // then assertThat(result.succeeded()).isTrue(); @@ -93,7 +90,7 @@ public void shouldReturnResultWithNoActionAndErrorWhenInvalidAccountConfig() { // when final Future> result = hook.call( BidderResponsePayloadImpl.of(singletonList(bid())), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, accountConfig, true)); + BidderInvocationContextImpl.of("bidder1", accountConfig, true)); // then assertThat(result.succeeded()).isTrue(); @@ -114,7 +111,7 @@ public void shouldReturnResultWithNoActionAndNoErrorWhenInvalidAccountConfigAndD // when final Future> result = hook.call( BidderResponsePayloadImpl.of(singletonList(bid())), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, accountConfig, false)); + BidderInvocationContextImpl.of("bidder1", accountConfig, false)); // then assertThat(result.succeeded()).isTrue(); @@ -135,18 +132,17 @@ public void shouldReturnResultWithPayloadUpdateAndAnalyticsTags() { .build()) .build())); + final BidderBid bid1 = bid(bid -> bid.id("bidId1")); + final BidderBid bid2 = bid(bid -> bid.id("bidId2").adomain(singletonList("domain1.com"))); + final BidderBid bid3 = bid(bid -> bid.id("bidId2").adomain(singletonList("domain2.com"))); + // when final Future> result = hook.call( - BidderResponsePayloadImpl.of(asList( - bid(), - bid(bid -> bid.adomain(singletonList("domain1.com"))), - bid(bid -> bid.adomain(singletonList("domain2.com"))))), + BidderResponsePayloadImpl.of(asList(bid1, bid2, bid3)), BidderInvocationContextImpl.builder() .bidder("bidder1") .accountConfig(accountConfig) - .auctionContext(AuctionContext.builder() - .bidRejectionTrackers(Map.of("bidder1", bidRejectionTracker)) - .build()) + .auctionContext(AuctionContext.builder().build()) .moduleContext(ModuleContext.create().with( "bidder1", BlockedAttributes.builder().badv(singletonList("domain2.com")).build())) .debugEnabled(true) @@ -163,12 +159,9 @@ public void shouldReturnResultWithPayloadUpdateAndAnalyticsTags() { }); final PayloadUpdate payloadUpdate = invocationResult.payloadUpdate(); - final BidderResponsePayloadImpl payloadToUpdate = BidderResponsePayloadImpl.of(asList( - bid(), - bid(bid -> bid.adomain(singletonList("domain1.com"))), - bid(bid -> bid.adomain(singletonList("domain2.com"))))); + final BidderResponsePayloadImpl payloadToUpdate = BidderResponsePayloadImpl.of(asList(bid1, bid2, bid3)); assertThat(payloadUpdate.apply(payloadToUpdate)).isEqualTo(BidderResponsePayloadImpl.of( - singletonList(bid(bid -> bid.adomain(singletonList("domain1.com")))))); + singletonList(bid2))); assertThat(invocationResult.analyticsTags()).isEqualTo(TagsImpl.of(singletonList(ActivityImpl.of( "enforce-blocking", @@ -202,6 +195,12 @@ public void shouldReturnResultWithPayloadUpdateAndAnalyticsTags() { .bidders(singletonList("bidder1")) .impIds(singletonList("impId1")) .build())))))); + + assertThat(invocationResult.rejections()).containsOnly(entry("bidder1", List.of( + BidRejection.of(bid1, + RESPONSE_REJECTED_ADVERTISER_BLOCKED), + BidRejection.of(bid(bid -> bid.id("bidId2").adomain(singletonList("domain2.com"))), + RESPONSE_REJECTED_ADVERTISER_BLOCKED)))); } @Test @@ -224,7 +223,7 @@ public void shouldReturnResultWithUpdateActionAndWarning() { // when final Future> result = hook.call( BidderResponsePayloadImpl.of(singletonList(bid())), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, accountConfig, true)); + BidderInvocationContextImpl.of("bidder1", accountConfig, true)); // then assertThat(result.succeeded()).isTrue(); @@ -258,7 +257,7 @@ public void shouldReturnResultWithUpdateActionAndNoWarningWhenDebugDisabled() { // when final Future> result = hook.call( BidderResponsePayloadImpl.of(singletonList(bid())), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, accountConfig, false)); + BidderInvocationContextImpl.of("bidder1", accountConfig, false)); // then assertThat(result.succeeded()).isTrue(); @@ -284,7 +283,7 @@ public void shouldReturnResultWithUpdateActionAndDebugMessage() { // when final Future> result = hook.call( BidderResponsePayloadImpl.of(singletonList(bid())), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, accountConfig, true)); + BidderInvocationContextImpl.of("bidder1", accountConfig, true)); // then assertThat(result.succeeded()).isTrue(); @@ -310,7 +309,7 @@ public void shouldReturnResultWithUpdateActionAndNoDebugMessageWhenDebugDisabled // when final Future> result = hook.call( BidderResponsePayloadImpl.of(singletonList(bid())), - BidderInvocationContextImpl.of("bidder1", bidRejectionTracker, accountConfig, false)); + BidderInvocationContextImpl.of("bidder1", accountConfig, false)); // then assertThat(result.succeeded()).isTrue(); @@ -323,7 +322,7 @@ public void shouldReturnResultWithUpdateActionAndNoDebugMessageWhenDebugDisabled } private static BidderBid bid() { - return bid(identity()); + return bid(bid -> bid.id("bidId")); } private static BidderBid bid(UnaryOperator bidCustomizer) { diff --git a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/BidderInvocationContextImpl.java b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/BidderInvocationContextImpl.java index d39d0a4ca5f..5812885d88d 100644 --- a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/BidderInvocationContextImpl.java +++ b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/BidderInvocationContextImpl.java @@ -6,7 +6,6 @@ import lombok.Value; import lombok.experimental.Accessors; import org.prebid.server.auction.model.AuctionContext; -import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.execution.timeout.Timeout; import org.prebid.server.hooks.v1.bidder.BidderInvocationContext; import org.prebid.server.model.Endpoint; @@ -35,7 +34,6 @@ public class BidderInvocationContextImpl implements BidderInvocationContext { String bidder; public static BidderInvocationContext of(String bidder, - BidRejectionTracker bidRejectionTracker, ObjectNode accountConfig, boolean debugEnabled) { @@ -43,7 +41,6 @@ public static BidderInvocationContext of(String bidder, .bidder(bidder) .auctionContext(AuctionContext.builder() .bidRequest(BidRequest.builder().build()) - .bidRejectionTrackers(Map.of(bidder, bidRejectionTracker)) .build()) .accountConfig(accountConfig) .debugEnabled(debugEnabled) @@ -52,7 +49,6 @@ public static BidderInvocationContext of(String bidder, public static BidderInvocationContext of(String bidder, Map aliases, - BidRejectionTracker bidRejectionTracker, ObjectNode accountConfig, boolean debugEnabled) { @@ -64,7 +60,6 @@ public static BidderInvocationContext of(String bidder, .aliases(aliases) .build())) .build()) - .bidRejectionTrackers(Map.of(bidder, bidRejectionTracker)) .build()) .accountConfig(accountConfig) .debugEnabled(debugEnabled) diff --git a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/core/BidResponsesMraidFilter.java b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/core/BidResponsesMraidFilter.java index d3d721778f5..6cca35cdf7b 100644 --- a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/core/BidResponsesMraidFilter.java +++ b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/core/BidResponsesMraidFilter.java @@ -3,7 +3,6 @@ import com.iab.openrtb.response.Bid; import org.apache.commons.lang3.StringUtils; import org.prebid.server.auction.model.BidRejectionReason; -import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidderResponse; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; @@ -24,8 +23,7 @@ public class BidResponsesMraidFilter { private static final Map TAG_VALUES = Map.of("richmedia-format", "mraid"); public MraidFilterResult filterByPattern(String mraidScriptPattern, - List responses, - Map bidRejectionTrackers) { + List responses) { final List filteredResponses = new ArrayList<>(); final List analyticsResults = new ArrayList<>(); @@ -53,12 +51,11 @@ public MraidFilterResult filterByPattern(String mraidScriptPattern, TAG_STATUS, TAG_VALUES, bidder, - rejectedImps); + rejectedImps, + invalidBids, + BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); analyticsResults.add(analyticsResult); - bidRejectionTrackers.get(bidder) - .rejectBids(invalidBids, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); - final List errors = new ArrayList<>(seatBid.getErrors()); errors.add(BidderError.of("Invalid bid", BidderError.Type.invalid_bid, new HashSet<>(rejectedImps))); filteredResponses.add(bidderResponse.with(seatBid.with(validBids, errors))); diff --git a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/model/AnalyticsResult.java b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/model/AnalyticsResult.java index 79eb7760d36..7a4c390a639 100644 --- a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/model/AnalyticsResult.java +++ b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/model/AnalyticsResult.java @@ -1,6 +1,8 @@ package org.prebid.server.hooks.modules.pb.richmedia.filter.model; import lombok.Value; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.bidder.model.BidderBid; import java.util.List; import java.util.Map; @@ -15,4 +17,8 @@ public class AnalyticsResult { String bidder; List impId; + + List rejectedBids; + + BidRejectionReason rejectionReason; } diff --git a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHook.java b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHook.java index c63baeda58c..dcec0dfa3ff 100644 --- a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHook.java +++ b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHook.java @@ -6,6 +6,8 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.prebid.server.auction.model.BidderResponse; +import org.prebid.server.auction.model.Rejection; +import org.prebid.server.auction.model.BidRejection; import org.prebid.server.hooks.execution.v1.InvocationResultImpl; import org.prebid.server.hooks.execution.v1.analytics.ActivityImpl; import org.prebid.server.hooks.execution.v1.analytics.AppliedToImpl; @@ -25,11 +27,13 @@ import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; import org.prebid.server.hooks.v1.bidder.AllProcessedBidResponsesHook; import org.prebid.server.hooks.v1.bidder.AllProcessedBidResponsesPayload; +import org.prebid.server.util.ListUtil; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; public class PbRichmediaFilterAllProcessedBidResponsesHook implements AllProcessedBidResponsesHook { @@ -60,26 +64,38 @@ public Future> call( if (BooleanUtils.isTrue(properties.getFilterMraid())) { final MraidFilterResult filterResult = mraidFilter.filterByPattern( properties.getMraidScriptPattern(), - responses, - auctionInvocationContext.auctionContext().getBidRejectionTrackers()); + responses); final InvocationAction action = filterResult.hasRejectedBids() ? InvocationAction.update : InvocationAction.no_action; return Future.succeededFuture(toInvocationResult( filterResult.getFilterResult(), toAnalyticsTags(filterResult.getAnalyticsResult()), + toRejections(filterResult.getAnalyticsResult()), action)); } return Future.succeededFuture(toInvocationResult( responses, toAnalyticsTags(Collections.emptyList()), + null, InvocationAction.no_action)); } + private Map> toRejections(List analyticsResults) { + return analyticsResults.stream().collect(Collectors.toMap( + AnalyticsResult::getBidder, + result -> result.getRejectedBids().stream() + .map(bid -> BidRejection.of(bid, result.getRejectionReason())) + .map(Rejection.class::cast) + .toList(), + ListUtil::union)); + } + private static InvocationResult toInvocationResult( List bidderResponses, Tags analyticsTags, + Map> rejections, InvocationAction action) { return InvocationResultImpl.builder() @@ -87,6 +103,7 @@ private static InvocationResult toInvocationRes .action(action) .analyticsTags(analyticsTags) .payloadUpdate(payload -> AllProcessedBidResponsesPayloadImpl.of(bidderResponses)) + .rejections(rejections) .build(); } diff --git a/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/core/BidResponsesMraidFilterTest.java b/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/core/BidResponsesMraidFilterTest.java index ad7eb512e77..02add5a0aad 100644 --- a/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/core/BidResponsesMraidFilterTest.java +++ b/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/core/BidResponsesMraidFilterTest.java @@ -3,7 +3,6 @@ import com.iab.openrtb.response.Bid; import org.junit.jupiter.api.Test; import org.prebid.server.auction.model.BidRejectionReason; -import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidderResponse; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; @@ -16,10 +15,6 @@ import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; public class BidResponsesMraidFilterTest { @@ -30,22 +25,15 @@ public void filterShouldReturnOriginalBidsWhenNoBidsHaveMraidScriptInAdm() { // given final BidderResponse responseA = givenBidderResponse("bidderA", List.of(givenBid("imp_id", "adm1"))); final BidderResponse responseB = givenBidderResponse("bidderB", List.of(givenBid("imp_id", "adm2"))); - final BidRejectionTracker bidRejectionTrackerA = mock(BidRejectionTracker.class); - final BidRejectionTracker bidRejectionTrackerB = mock(BidRejectionTracker.class); - final Map givenTrackers = Map.of( - "bidderA", bidRejectionTrackerA, - "bidderB", bidRejectionTrackerB); // when final MraidFilterResult filterResult = target.filterByPattern( - "mraid.js", List.of(responseA, responseB), givenTrackers); + "mraid.js", List.of(responseA, responseB)); // then assertThat(filterResult.getFilterResult()).containsExactly(responseA, responseB); assertThat(filterResult.getAnalyticsResult()).isEmpty(); assertThat(filterResult.hasRejectedBids()).isFalse(); - - verifyNoInteractions(bidRejectionTrackerA, bidRejectionTrackerB); } @Test @@ -60,19 +48,10 @@ public void filterShouldReturnFilteredBidsWhenBidsWithMraidScriptIsFilteredOut() final BidderResponse responseB = givenBidderResponse("bidderB", List.of(givenBid1, givenInvalidBid2)); final BidderResponse responseC = givenBidderResponse("bidderC", List.of(givenInvalidBid1, givenInvalidBid2)); - final BidRejectionTracker bidRejectionTrackerA = mock(BidRejectionTracker.class); - final BidRejectionTracker bidRejectionTrackerB = mock(BidRejectionTracker.class); - final BidRejectionTracker bidRejectionTrackerC = mock(BidRejectionTracker.class); - final Map givenTrackers = Map.of( - "bidderA", bidRejectionTrackerA, - "bidderB", bidRejectionTrackerB, - "bidderC", bidRejectionTrackerC); - // when final MraidFilterResult filterResult = target.filterByPattern( "mraid.js", - List.of(responseA, responseB, responseC), - givenTrackers); + List.of(responseA, responseB, responseC)); // then final BidderResponse expectedResponseA = givenBidderResponse( @@ -91,26 +70,22 @@ public void filterShouldReturnFilteredBidsWhenBidsWithMraidScriptIsFilteredOut() "success-block", Map.of("richmedia-format", "mraid"), "bidderB", - List.of("imp_id2")); + List.of("imp_id2"), + List.of(givenInvalidBid2), + BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); final AnalyticsResult expectedAnalyticsResultC = AnalyticsResult.of( "success-block", Map.of("richmedia-format", "mraid"), "bidderC", - List.of("imp_id1", "imp_id2")); + List.of("imp_id1", "imp_id2"), + List.of(givenInvalidBid1, givenInvalidBid2), + BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); assertThat(filterResult.getFilterResult()) .containsExactly(expectedResponseA, expectedResponseB, expectedResponseC); assertThat(filterResult.getAnalyticsResult()) .containsExactlyInAnyOrder(expectedAnalyticsResultB, expectedAnalyticsResultC); assertThat(filterResult.hasRejectedBids()).isTrue(); - - verifyNoInteractions(bidRejectionTrackerA); - verify(bidRejectionTrackerB) - .rejectBids(List.of(givenInvalidBid2), BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); - verify(bidRejectionTrackerC).rejectBids( - List.of(givenInvalidBid1, givenInvalidBid2), - BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE); - verifyNoMoreInteractions(bidRejectionTrackerB, bidRejectionTrackerC); } private static BidderResponse givenBidderResponse(String bidder, List bids) { diff --git a/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHookTest.java b/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHookTest.java index 80f38ffdcc1..b5707a9fe22 100644 --- a/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHookTest.java +++ b/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHookTest.java @@ -1,15 +1,16 @@ package org.prebid.server.hooks.modules.pb.richmedia.filter.v1; import com.fasterxml.jackson.databind.ObjectMapper; +import com.iab.openrtb.response.Bid; 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.auction.model.AuctionContext; -import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidderResponse; +import org.prebid.server.auction.model.BidRejection; +import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderSeatBid; import org.prebid.server.hooks.execution.v1.analytics.ActivityImpl; import org.prebid.server.hooks.execution.v1.analytics.AppliedToImpl; @@ -32,15 +33,18 @@ import java.util.List; import java.util.Map; import java.util.stream.IntStream; +import java.util.stream.Stream; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import static org.prebid.server.auction.model.BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE; @ExtendWith(MockitoExtension.class) public class PbRichmediaFilterAllProcessedBidResponsesHookTest { @@ -61,16 +65,11 @@ public class PbRichmediaFilterAllProcessedBidResponsesHookTest { private PbRichmediaFilterAllProcessedBidResponsesHook target; - private static final Map BID_REJECTION_TRACKERS = Map.of( - "bidder", new BidRejectionTracker("bidder", Collections.emptySet(), 0.1)); - @BeforeEach public void setUp() { target = new PbRichmediaFilterAllProcessedBidResponsesHook( ObjectMapperProvider.mapper(), mraidFilter, configResolver); when(configResolver.resolve(any())).thenReturn(PbRichMediaFilterProperties.of(true, "pattern")); - when(auctionInvocationContext.auctionContext()) - .thenReturn(AuctionContext.builder().bidRejectionTrackers(BID_REJECTION_TRACKERS).build()); } @Test @@ -100,6 +99,7 @@ public void callShouldReturnResultWithNoActionWhenFilterMraidIsFalse() { assertThat(result.status()).isEqualTo(InvocationStatus.success); assertThat(result.action()).isEqualTo(InvocationAction.no_action); assertThat(result.analyticsTags()).isNull(); + assertThat(result.rejections()).isNull(); assertThat(result.payloadUpdate().apply(AllProcessedBidResponsesPayloadImpl.of(List.of())).bidResponses()) .isEqualTo(givenResponses); @@ -111,7 +111,7 @@ public void callShouldReturnResultWithUpdateActionWhenSomeResponsesWereFilteredO // given final List givenResponses = givenBidderResponses(2); doReturn(givenResponses).when(allProcessedBidResponsesPayload).bidResponses(); - given(mraidFilter.filterByPattern("pattern", givenResponses, BID_REJECTION_TRACKERS)) + given(mraidFilter.filterByPattern("pattern", givenResponses)) .willReturn(MraidFilterResult.of(givenResponses, List.of(givenAnalyticsResult("bidder", "imp_id")))); // when @@ -134,7 +134,7 @@ public void callShouldReturnResultWithNoActionWhenNothingWereFilteredOut() { // given final List givenResponses = givenBidderResponses(2); doReturn(givenResponses).when(allProcessedBidResponsesPayload).bidResponses(); - given(mraidFilter.filterByPattern("pattern", givenResponses, BID_REJECTION_TRACKERS)) + given(mraidFilter.filterByPattern("pattern", givenResponses)) .willReturn(MraidFilterResult.of(givenResponses, Collections.emptyList())); // when @@ -158,7 +158,7 @@ public void callShouldReturnResultOfFilteredResponses() { final List givenResponses = givenBidderResponses(3); doReturn(givenResponses).when(allProcessedBidResponsesPayload).bidResponses(); final List expectedResponses = givenBidderResponses(2); - given(mraidFilter.filterByPattern("pattern", givenResponses, BID_REJECTION_TRACKERS)) + given(mraidFilter.filterByPattern("pattern", givenResponses)) .willReturn(MraidFilterResult.of(expectedResponses, Collections.emptyList())); // when @@ -182,7 +182,7 @@ public void callShouldReturnAnalyticsResultsOfRejectedBids() { // given final List givenResponses = givenBidderResponses(3); doReturn(givenResponses).when(allProcessedBidResponsesPayload).bidResponses(); - given(mraidFilter.filterByPattern("pattern", givenResponses, BID_REJECTION_TRACKERS)) + given(mraidFilter.filterByPattern("pattern", givenResponses)) .willReturn(MraidFilterResult.of( givenResponses, List.of( @@ -220,6 +220,24 @@ public void callShouldReturnAnalyticsResultsOfRejectedBids() { "reject-richmedia", "success", List.of(expectedResult1, expectedResult2))))); + + assertThat(result.rejections()).containsOnly( + entry("bidderA", List.of( + BidRejection.of( + BidderBid.builder() + .bid(Bid.builder().id("bid-imp_id1").impid("imp_id1").build()) + .build(), + RESPONSE_REJECTED_INVALID_CREATIVE), + BidRejection.of( + BidderBid.builder() + .bid(Bid.builder().id("bid-imp_id2").impid("imp_id2").build()) + .build(), + RESPONSE_REJECTED_INVALID_CREATIVE))), + entry("bidderB", List.of( + BidRejection.of(BidderBid.builder() + .bid(Bid.builder().id("bid-imp_id3").impid("imp_id3").build()) + .build(), + RESPONSE_REJECTED_INVALID_CREATIVE)))); } @Test @@ -227,7 +245,7 @@ public void callShouldReturnEmptyAnalyticsResultsWhenThereAreNoRejectedBids() { // given final List givenResponses = givenBidderResponses(3); doReturn(givenResponses).when(allProcessedBidResponsesPayload).bidResponses(); - given(mraidFilter.filterByPattern("pattern", givenResponses, BID_REJECTION_TRACKERS)) + given(mraidFilter.filterByPattern("pattern", givenResponses)) .willReturn(MraidFilterResult.of(givenResponses, Collections.emptyList())); // when @@ -243,6 +261,7 @@ public void callShouldReturnEmptyAnalyticsResultsWhenThereAreNoRejectedBids() { assertThat(result).isNotNull(); assertThat(result.status()).isEqualTo(InvocationStatus.success); assertThat(result.analyticsTags()).isNull(); + assertThat(result.rejections()).isEmpty(); } private static List givenBidderResponses(int number) { @@ -252,7 +271,14 @@ private static List givenBidderResponses(int number) { } private static AnalyticsResult givenAnalyticsResult(String bidder, String... rejectedImpIds) { - return AnalyticsResult.of("status", Map.of("key", "value"), bidder, List.of(rejectedImpIds)); + return AnalyticsResult.of( + "status", + Map.of("key", "value"), + bidder, + List.of(rejectedImpIds), + Stream.of(rejectedImpIds).map(impId -> BidderBid.builder().bid( + Bid.builder().id("bid-" + impId).impid(impId).build()).build()).toList(), + RESPONSE_REJECTED_INVALID_CREATIVE); } } diff --git a/src/main/java/org/prebid/server/analytics/reporter/greenbids/GreenbidsAnalyticsReporter.java b/src/main/java/org/prebid/server/analytics/reporter/greenbids/GreenbidsAnalyticsReporter.java index 3b1f3693e55..c37ac7aeff2 100644 --- a/src/main/java/org/prebid/server/analytics/reporter/greenbids/GreenbidsAnalyticsReporter.java +++ b/src/main/java/org/prebid/server/analytics/reporter/greenbids/GreenbidsAnalyticsReporter.java @@ -382,8 +382,8 @@ private static Map getSeatsWithNonBids(AuctionContext auctionCon } private static SeatNonBid toSeatNonBid(String bidder, BidRejectionTracker bidRejectionTracker) { - final List nonBids = bidRejectionTracker.getRejectedImps().entrySet().stream() - .map(entry -> NonBid.of(entry.getKey(), entry.getValue().getRight())) + final List nonBids = bidRejectionTracker.getRejected().stream() + .map(rejectedImp -> NonBid.of(rejectedImp.impId(), rejectedImp.reason())) .toList(); return SeatNonBid.of(bidder, nonBids); diff --git a/src/main/java/org/prebid/server/auction/BidResponseCreator.java b/src/main/java/org/prebid/server/auction/BidResponseCreator.java index aec0c82831b..f983830e37f 100644 --- a/src/main/java/org/prebid/server/auction/BidResponseCreator.java +++ b/src/main/java/org/prebid/server/auction/BidResponseCreator.java @@ -36,6 +36,7 @@ import org.prebid.server.auction.model.CategoryMappingResult; import org.prebid.server.auction.model.MultiBidConfig; import org.prebid.server.auction.model.PaaFormat; +import org.prebid.server.auction.model.Rejection; import org.prebid.server.auction.model.TargetingInfo; import org.prebid.server.auction.model.debug.DebugContext; import org.prebid.server.auction.requestfactory.Ortb2ImplicitParametersResolver; @@ -1950,12 +1951,10 @@ private static BidResponse populateSeatNonBid(AuctionContext auctionContext, Bid } final List seatNonBids = auctionContext.getBidRejectionTrackers().values().stream() - .flatMap(bidRejectionTracker -> bidRejectionTracker.getRejectedImps().entrySet().stream()) + .flatMap(bidRejectionTracker -> bidRejectionTracker.getRejected().stream()) .collect(Collectors.groupingBy( - entry -> entry.getValue().getLeft(), - Collectors.mapping( - entry -> NonBid.of(entry.getKey(), entry.getValue().getRight()), - Collectors.toList()))) + Rejection::seat, + Collectors.mapping(entry -> NonBid.of(entry.impId(), entry.reason()), Collectors.toList()))) .entrySet().stream() .filter(entry -> !entry.getValue().isEmpty()) .map(entry -> SeatNonBid.of(entry.getKey(), entry.getValue())) diff --git a/src/main/java/org/prebid/server/auction/DsaEnforcer.java b/src/main/java/org/prebid/server/auction/DsaEnforcer.java index 369842b36c6..e123603b9a0 100644 --- a/src/main/java/org/prebid/server/auction/DsaEnforcer.java +++ b/src/main/java/org/prebid/server/auction/DsaEnforcer.java @@ -9,6 +9,7 @@ import org.prebid.server.auction.model.BidRejectionReason; import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidderResponse; +import org.prebid.server.auction.model.BidRejection; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; import org.prebid.server.bidder.model.BidderSeatBid; @@ -72,7 +73,7 @@ public AuctionParticipation enforce(BidRequest bidRequest, } } catch (PreBidException e) { warnings.add(BidderError.invalidBid("Bid \"%s\": %s".formatted(bid.getId(), e.getMessage()))); - rejectionTracker.rejectBid(bidderBid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY); + rejectionTracker.reject(BidRejection.of(bidderBid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY)); updatedBidderBids.remove(bidderBid); } } diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java index d9561ccc868..3d48565906c 100644 --- a/src/main/java/org/prebid/server/auction/ExchangeService.java +++ b/src/main/java/org/prebid/server/auction/ExchangeService.java @@ -247,7 +247,8 @@ private Future runAuction(AuctionContext receivedContext) { final BidderAliases aliases = aliases(bidRequest, account); final BidRequestCacheInfo cacheInfo = bidRequestCacheInfo(bidRequest, account); final Map bidderToMultiBid = bidderToMultiBids(bidRequest, debugWarnings); - receivedContext.getBidRejectionTrackers().putAll(makeBidRejectionTrackers(bidRequest, aliases)); + + populateBidRejectionTrackers(receivedContext, aliases); final boolean debugEnabled = receivedContext.getDebugContext().isDebugEnabled(); metrics.updateDebugRequestMetrics(debugEnabled); @@ -424,7 +425,29 @@ private static MultiBidConfig toMultiBid(String bidder, Integer maxBids, String return MultiBidConfig.of(bidder, bidLimit, codePrefix); } - private Map makeBidRejectionTrackers(BidRequest bidRequest, BidderAliases aliases) { + private void populateBidRejectionTrackers(AuctionContext auctionContext, BidderAliases aliases) { + final Map bidRejectionTrackers = auctionContext.getBidRejectionTrackers(); + removeInvalidBidRejectionTrackers(bidRejectionTrackers, aliases); + makeBidRejectionTrackers(bidRejectionTrackers, auctionContext.getBidRequest(), aliases); + } + + private void removeInvalidBidRejectionTrackers(Map bidRejectionTrackers, + BidderAliases aliases) { + + final Set bidderNames = new HashSet<>(bidRejectionTrackers.keySet()); + for (String bidder: bidderNames) { + if (!isValidBidder(bidder, aliases)) { + bidRejectionTrackers.remove(bidder); + logger.warn("Pre-rejected impressions of the bidder {} have been removed. " + + "Reason: the bidder is invalid"); + } + } + } + + private void makeBidRejectionTrackers(Map bidRejectionTrackers, + BidRequest bidRequest, + BidderAliases aliases) { + final Map> impIdToBidders = bidRequest.getImp().stream() .filter(Objects::nonNull) .filter(imp -> StringUtils.isNotEmpty(imp.getId())) @@ -438,9 +461,13 @@ private Map makeBidRejectionTrackers(BidRequest bid bidderToImpIds.computeIfAbsent(bidder, bidderName -> new HashSet<>()).add(impId)); } - return bidderToImpIds.entrySet().stream().collect(Collectors.toMap( - Map.Entry::getKey, - entry -> new BidRejectionTracker(entry.getKey(), entry.getValue(), logSamplingRate))); + bidderToImpIds.forEach((bidder, impIds) -> { + if (bidRejectionTrackers.containsKey(bidder)) { + bidRejectionTrackers.put(bidder, new BidRejectionTracker(bidRejectionTrackers.get(bidder), impIds)); + } else { + bidRejectionTrackers.put(bidder, new BidRejectionTracker(bidder, impIds, logSamplingRate)); + } + }); } private static StoredResponseResult populateStoredResponse(StoredResponseResult storedResponseResult, @@ -727,7 +754,7 @@ private AuctionParticipation createAuctionParticipation( if (blockedRequestByTcf) { context.getBidRejectionTrackers() .get(bidder) - .rejectAllImps(BidRejectionReason.REQUEST_BLOCKED_PRIVACY); + .rejectAll(BidRejectionReason.REQUEST_BLOCKED_PRIVACY); return AuctionParticipation.builder() .bidder(bidder) @@ -1182,7 +1209,7 @@ private static Future processReject(AuctionContext auctionContex auctionContext.getBidRejectionTrackers() .get(bidderName) - .rejectAllImps(bidRejectionReason); + .rejectAll(bidRejectionReason); final BidderSeatBid bidderSeatBid = BidderSeatBid.builder() .warnings(warnings) .build(); @@ -1221,7 +1248,7 @@ private Future requestBidsOrRejectBidder( if (hookStageResult.isShouldReject()) { auctionContext.getBidRejectionTrackers() .get(bidderRequest.getBidder()) - .rejectAllImps(BidRejectionReason.REQUEST_BLOCKED_GENERAL); + .rejectAll(BidRejectionReason.REQUEST_BLOCKED_GENERAL); return Future.succeededFuture(BidderResponse.of(bidderRequest.getBidder(), BidderSeatBid.empty(), 0)); } diff --git a/src/main/java/org/prebid/server/auction/model/BidRejection.java b/src/main/java/org/prebid/server/auction/model/BidRejection.java new file mode 100644 index 00000000000..17f8924447b --- /dev/null +++ b/src/main/java/org/prebid/server/auction/model/BidRejection.java @@ -0,0 +1,31 @@ +package org.prebid.server.auction.model; + +import lombok.Value; +import org.prebid.server.bidder.model.BidderBid; + +@Value(staticConstructor = "of") +public class BidRejection implements Rejection { + + BidderBid bid; + + BidRejectionReason reason; + + @Override + public String impId() { + return bid.getBid().getImpid(); + } + + @Override + public BidRejectionReason reason() { + return reason; + } + + @Override + public String seat() { + return bid.getSeat(); + } + + public String bidId() { + return bid.getBid().getId(); + } +} diff --git a/src/main/java/org/prebid/server/auction/model/BidRejectionReason.java b/src/main/java/org/prebid/server/auction/model/BidRejectionReason.java index fc3ee36bd2a..8aed823f6d1 100644 --- a/src/main/java/org/prebid/server/auction/model/BidRejectionReason.java +++ b/src/main/java/org/prebid/server/auction/model/BidRejectionReason.java @@ -53,6 +53,11 @@ public enum BidRejectionReason { */ REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE(202), + /** + * This impression not sent to the bid adapter because the impression or the bidder was removed from the request. + */ + REQUEST_BLOCKED_OPTIMIZED(203), + /** * If the bidder was not called due to GDPR purpose 2 */ diff --git a/src/main/java/org/prebid/server/auction/model/BidRejectionTracker.java b/src/main/java/org/prebid/server/auction/model/BidRejectionTracker.java index 3ac6e29087c..c9fd9adf00b 100644 --- a/src/main/java/org/prebid/server/auction/model/BidRejectionTracker.java +++ b/src/main/java/org/prebid/server/auction/model/BidRejectionTracker.java @@ -1,7 +1,6 @@ package org.prebid.server.auction.model; import com.iab.openrtb.response.Bid; -import org.apache.commons.lang3.tuple.Pair; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.log.ConditionalLogger; import org.prebid.server.log.Logger; @@ -16,7 +15,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; public class BidRejectionTracker { @@ -36,7 +34,7 @@ public class BidRejectionTracker { private final String bidder; private final Set involvedImpIds; private final Map> succeededBidsIds; - private final Map>> rejectedBids; + private final Map> rejections; public BidRejectionTracker(String bidder, Set involvedImpIds, double logSamplingRate) { this.bidder = bidder; @@ -44,7 +42,17 @@ public BidRejectionTracker(String bidder, Set involvedImpIds, double log this.logSamplingRate = logSamplingRate; succeededBidsIds = new HashMap<>(); - rejectedBids = new HashMap<>(); + rejections = new HashMap<>(); + } + + public BidRejectionTracker(BidRejectionTracker anotherTracker, Set additionalImpIds) { + this.bidder = anotherTracker.bidder; + this.logSamplingRate = anotherTracker.logSamplingRate; + this.involvedImpIds = new HashSet<>(anotherTracker.involvedImpIds); + this.involvedImpIds.addAll(additionalImpIds); + + this.succeededBidsIds = new HashMap<>(anotherTracker.succeededBidsIds); + this.rejections = new HashMap<>(anotherTracker.rejections); } public void succeed(Collection bids) { @@ -59,7 +67,7 @@ private void succeed(Bid bid) { final String impId = bid.getImpid(); if (involvedImpIds.contains(impId)) { succeededBidsIds.computeIfAbsent(impId, key -> new HashSet<>()).add(bidId); - if (rejectedBids.containsKey(impId)) { + if (rejections.containsKey(impId)) { bidRejectionsLogger.warn( INCONSISTENT_RESPONSES_WARNING_TEMPLATE.formatted(bidder, impId), logSamplingRate); @@ -71,28 +79,31 @@ public void restoreFromRejection(Collection bids) { succeed(bids); } - public void rejectBids(Collection bidderBids, BidRejectionReason reason) { - bidderBids.forEach(bidderBid -> rejectBid(bidderBid, reason)); + public void reject(Collection rejections) { + rejections.forEach(this::reject); } - public void rejectBid(BidderBid bidderBid, BidRejectionReason reason) { - final Bid bid = bidderBid.getBid(); - final String impId = bid.getImpid(); - - reject(impId, bidderBid, reason); - } + public void reject(Rejection rejection) { + if (rejection instanceof ImpRejection && rejection.reason().getValue() >= 300) { + logger.warn("The rejected imp {} with the code {} equal to or higher than 300 assumes " + + "that there is a rejected bid that shouldn't be lost"); + return; + } - private void reject(String impId, BidderBid bid, BidRejectionReason reason) { + final String impId = rejection.impId(); if (involvedImpIds.contains(impId)) { - if (rejectedBids.containsKey(impId)) { + if (rejections.containsKey(impId)) { bidRejectionsLogger.warn( MULTIPLE_REJECTIONS_WARNING_TEMPLATE.formatted(bidder, impId), logSamplingRate); } - rejectedBids.computeIfAbsent(impId, key -> new ArrayList<>()).add(Pair.of(bid, reason)); + rejections.computeIfAbsent(impId, key -> new ArrayList<>()) + .add(rejection instanceof ImpRejection + ? ImpRejection.of(bidder, rejection.impId(), rejection.reason()) + : rejection); if (succeededBidsIds.containsKey(impId)) { - final String bidId = Optional.ofNullable(bid).map(BidderBid::getBid).map(Bid::getId).orElse(null); + final String bidId = rejection instanceof BidRejection ? ((BidRejection) rejection).bidId() : null; final Set succeededBids = succeededBidsIds.get(impId); final boolean removed = bidId == null || succeededBids.remove(bidId); if (removed && !succeededBids.isEmpty()) { @@ -105,50 +116,39 @@ private void reject(String impId, BidderBid bid, BidRejectionReason reason) { } public void rejectImps(Collection impIds, BidRejectionReason reason) { - impIds.forEach(impId -> rejectImp(impId, reason)); - } - - public void rejectImp(String impId, BidRejectionReason reason) { - if (reason.getValue() >= 300) { - throw new IllegalArgumentException("The non-bid code 300 and higher assumes " - + "that there is a rejected bid that shouldn't be lost"); - } - reject(impId, null, reason); + impIds.forEach(impId -> reject(ImpRejection.of(impId, reason))); } - public void rejectAllImps(BidRejectionReason reason) { - involvedImpIds.forEach(impId -> rejectImp(impId, reason)); + public void rejectAll(BidRejectionReason reason) { + involvedImpIds.forEach(impId -> reject(ImpRejection.of(impId, reason))); } - public Map> getRejectedImps() { - final Map> rejectedImpIds = new HashMap<>(); + public Set getRejected() { + final Set rejectedResult = new HashSet<>(); for (String impId : involvedImpIds) { final Set succeededBids = succeededBidsIds.getOrDefault(impId, Collections.emptySet()); if (succeededBids.isEmpty()) { - if (rejectedBids.containsKey(impId)) { - final Pair rejected = rejectedBids.get(impId).getFirst(); - final String seat = Optional.ofNullable(rejected.getLeft()).map(BidderBid::getSeat).orElse(bidder); - final BidRejectionReason bidRejectionReason = rejected.getRight(); - rejectedImpIds.put(impId, Pair.of(seat, bidRejectionReason)); + if (rejections.containsKey(impId)) { + rejectedResult.add(rejections.get(impId).getFirst()); } else { - rejectedImpIds.put(impId, Pair.of(bidder, BidRejectionReason.NO_BID)); + rejectedResult.add(ImpRejection.of(bidder, impId, BidRejectionReason.NO_BID)); } } } - return rejectedImpIds; + return rejectedResult; } - public Map>> getRejectedBids() { - final Map>> missingImpIds = new HashMap<>(); + public Map> getAllRejected() { + final Map> missingImpIds = new HashMap<>(); for (String impId : involvedImpIds) { final Set succeededBids = succeededBidsIds.getOrDefault(impId, Collections.emptySet()); - if (succeededBids.isEmpty() && !rejectedBids.containsKey(impId)) { + if (succeededBids.isEmpty() && !rejections.containsKey(impId)) { missingImpIds.computeIfAbsent(impId, key -> new ArrayList<>()) - .add(Pair.of(null, BidRejectionReason.NO_BID)); + .add(ImpRejection.of(bidder, impId, BidRejectionReason.NO_BID)); } } - return MapUtil.merge(missingImpIds, rejectedBids); + return MapUtil.merge(missingImpIds, rejections); } } diff --git a/src/main/java/org/prebid/server/auction/model/ImpRejection.java b/src/main/java/org/prebid/server/auction/model/ImpRejection.java new file mode 100644 index 00000000000..578a9f0ccfc --- /dev/null +++ b/src/main/java/org/prebid/server/auction/model/ImpRejection.java @@ -0,0 +1,20 @@ +package org.prebid.server.auction.model; + +import lombok.Value; +import lombok.experimental.Accessors; + +@Value(staticConstructor = "of") +@Accessors(fluent = true) +public class ImpRejection implements Rejection { + + String seat; + + String impId; + + BidRejectionReason reason; + + public static ImpRejection of(String impId, BidRejectionReason reason) { + return ImpRejection.of(null, impId, reason); + } + +} diff --git a/src/main/java/org/prebid/server/auction/model/Rejection.java b/src/main/java/org/prebid/server/auction/model/Rejection.java new file mode 100644 index 00000000000..9604cffcea2 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/model/Rejection.java @@ -0,0 +1,11 @@ +package org.prebid.server.auction.model; + +public interface Rejection { + + String seat(); + + String impId(); + + BidRejectionReason reason(); + +} diff --git a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java index ac9619f6b83..1604783c3b8 100644 --- a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java +++ b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java @@ -378,7 +378,7 @@ public Future executeEntrypointHooks(RoutingContext routingC toCaseInsensitiveMultiMap(routingContext.queryParams()), toCaseInsensitiveMultiMap(routingContext.request().headers()), body, - auctionContext.getHookExecutionContext()) + auctionContext) .map(stageResult -> toHttpRequest(stageResult, routingContext, auctionContext)); } diff --git a/src/main/java/org/prebid/server/bidder/HttpBidderRequester.java b/src/main/java/org/prebid/server/bidder/HttpBidderRequester.java index f2aa98f0a37..61e074ecb26 100644 --- a/src/main/java/org/prebid/server/bidder/HttpBidderRequester.java +++ b/src/main/java/org/prebid/server/bidder/HttpBidderRequester.java @@ -418,11 +418,11 @@ private void handleBidderCallError(BidderCall bidderCall) { return; } - if (callErrorType == BidderError.Type.timeout) { - bidRejectionTracker.rejectImps(requestedImpIds, BidRejectionReason.ERROR_TIMED_OUT); - } else { - bidRejectionTracker.rejectImps(requestedImpIds, BidRejectionReason.ERROR_GENERAL); - } + final BidRejectionReason reason = callErrorType == BidderError.Type.timeout + ? BidRejectionReason.ERROR_TIMED_OUT + : BidRejectionReason.ERROR_GENERAL; + + bidRejectionTracker.rejectImps(requestedImpIds, reason); } private void handleFledgeAuctionConfigs(CompositeBidderResponse bidderResponse) { diff --git a/src/main/java/org/prebid/server/floors/BasicPriceFloorEnforcer.java b/src/main/java/org/prebid/server/floors/BasicPriceFloorEnforcer.java index 1b9c7425385..2252a8fad54 100644 --- a/src/main/java/org/prebid/server/floors/BasicPriceFloorEnforcer.java +++ b/src/main/java/org/prebid/server/floors/BasicPriceFloorEnforcer.java @@ -11,6 +11,7 @@ import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.auction.model.BidderResponse; +import org.prebid.server.auction.model.BidRejection; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; import org.prebid.server.bidder.model.BidderSeatBid; @@ -179,7 +180,7 @@ private AuctionParticipation applyEnforcement(BidRequest bidRequest, "Bid with id '%s' was rejected by floor enforcement: price %s is below the floor %s" .formatted(bid.getId(), price, floor), impId)); - rejectionTracker.rejectBid(bidderBid, BidRejectionReason.RESPONSE_REJECTED_BELOW_FLOOR); + rejectionTracker.reject(BidRejection.of(bidderBid, BidRejectionReason.RESPONSE_REJECTED_BELOW_FLOOR)); updatedBidderBids.remove(bidderBid); } } diff --git a/src/main/java/org/prebid/server/hooks/execution/GroupResult.java b/src/main/java/org/prebid/server/hooks/execution/GroupResult.java index 8bc7c5c0723..718548ae15b 100644 --- a/src/main/java/org/prebid/server/hooks/execution/GroupResult.java +++ b/src/main/java/org/prebid/server/hooks/execution/GroupResult.java @@ -2,6 +2,8 @@ import lombok.Getter; import lombok.experimental.Accessors; +import org.apache.commons.collections4.MapUtils; +import org.prebid.server.auction.model.Rejection; import org.prebid.server.hooks.execution.model.ExecutionAction; import org.prebid.server.hooks.execution.model.ExecutionStatus; import org.prebid.server.hooks.execution.model.GroupExecutionOutcome; @@ -15,7 +17,9 @@ import org.prebid.server.log.LoggerFactory; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeoutException; @Accessors(fluent = true) @@ -33,6 +37,8 @@ class GroupResult { private final boolean rejectAllowed; + private final Map> rejections = new HashMap<>(); + private final List hookExecutionOutcomes = new ArrayList<>(); private GroupResult(T payload, boolean rejectAllowed) { @@ -52,6 +58,8 @@ public GroupResult applyInvocationResult(InvocationResult invocationResult if (invocationResult.status() == InvocationStatus.success && invocationResult.action() != null) { try { applyAction(hookId, invocationResult.action(), invocationResult.payloadUpdate()); + MapUtils.emptyIfNull(invocationResult.rejections()).forEach((bidder, rejectionList) -> + rejections.computeIfAbsent(bidder, key -> new ArrayList<>()).addAll(rejectionList)); } catch (Exception e) { hookExecutionOutcomes.add(toExecutionOutcome(e, hookId, executionTime)); diff --git a/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java b/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java index 0f96bb31c6f..4f8a1f3f209 100644 --- a/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java +++ b/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java @@ -14,8 +14,10 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.auction.model.BidderResponse; +import org.prebid.server.auction.model.Rejection; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.execution.timeout.Timeout; import org.prebid.server.execution.timeout.TimeoutFactory; @@ -69,6 +71,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; public class HookStageExecutor { @@ -89,6 +92,7 @@ public class HookStageExecutor { private final Clock clock; private final ObjectMapper mapper; private final boolean isConfigToInvokeRequired; + private final double logSamplingRate; private HookStageExecutor(ExecutionPlan hostExecutionPlan, ExecutionPlan defaultAccountExecutionPlan, @@ -98,7 +102,8 @@ private HookStageExecutor(ExecutionPlan hostExecutionPlan, Vertx vertx, Clock clock, ObjectMapper mapper, - boolean isConfigToInvokeRequired) { + boolean isConfigToInvokeRequired, + double logSamplingRate) { this.hostExecutionPlan = hostExecutionPlan; this.defaultAccountExecutionPlan = defaultAccountExecutionPlan; @@ -109,6 +114,7 @@ private HookStageExecutor(ExecutionPlan hostExecutionPlan, this.mapper = mapper; this.isConfigToInvokeRequired = isConfigToInvokeRequired; this.hostModuleExecution = hostModuleExecution; + this.logSamplingRate = logSamplingRate; } public static HookStageExecutor create(String hostExecutionPlan, @@ -119,7 +125,8 @@ public static HookStageExecutor create(String hostExecutionPlan, Vertx vertx, Clock clock, JacksonMapper mapper, - boolean isConfigToInvokeRequired) { + boolean isConfigToInvokeRequired, + double logSamplingRate) { Objects.requireNonNull(hookCatalog); Objects.requireNonNull(mapper); @@ -133,7 +140,8 @@ public static HookStageExecutor create(String hostExecutionPlan, Objects.requireNonNull(vertx), Objects.requireNonNull(clock), mapper.mapper(), - isConfigToInvokeRequired); + isConfigToInvokeRequired, + logSamplingRate); } private static ExecutionPlan parseAndValidateExecutionPlan(String executionPlan, @@ -182,8 +190,9 @@ public Future> executeEntrypointStag CaseInsensitiveMultiMap queryParams, CaseInsensitiveMultiMap headers, String body, - HookExecutionContext context) { + AuctionContext auctionContext) { + final HookExecutionContext context = auctionContext.getHookExecutionContext(); final Endpoint endpoint = context.getEndpoint(); return stageExecutor(StageWithHookType.ENTRYPOINT, ENTITY_HTTP_REQUEST, context) @@ -193,7 +202,8 @@ public Future> executeEntrypointStag .withInvocationContextProvider(invocationContextProvider(endpoint)) .withModulesExecution(DefaultedMap.defaultedMap(hostModuleExecution, true)) .withRejectAllowed(true) - .execute(); + .execute() + .map(result -> rejectAll(auctionContext, result)); } public Future> executeRawAuctionRequestStage( @@ -211,7 +221,8 @@ public Future> executeRawAuction .withInitialPayload(AuctionRequestPayloadImpl.of(bidRequest)) .withInvocationContextProvider(auctionInvocationContextProvider(endpoint, auctionContext)) .withRejectAllowed(true) - .execute(); + .execute() + .map(result -> rejectAll(auctionContext, result)); } public Future> executeProcessedAuctionRequestStage( @@ -229,7 +240,8 @@ public Future> executeProcessedA .withInitialPayload(AuctionRequestPayloadImpl.of(bidRequest)) .withInvocationContextProvider(auctionInvocationContextProvider(endpoint, auctionContext)) .withRejectAllowed(true) - .execute(); + .execute() + .map(result -> rejectAll(auctionContext, result)); } public Future> executeBidderRequestStage( @@ -247,7 +259,8 @@ public Future> executeBidderReque .withInitialPayload(BidderRequestPayloadImpl.of(bidderRequest.getBidRequest())) .withInvocationContextProvider(bidderInvocationContextProvider(endpoint, auctionContext, bidder)) .withRejectAllowed(true) - .execute(); + .execute() + .map(result -> rejectAllIgnoringUnknowns(auctionContext, result)); } public Future> executeRawBidderResponseStage( @@ -267,7 +280,8 @@ public Future> executeRawBidderR .withInitialPayload(BidderResponsePayloadImpl.of(bids)) .withInvocationContextProvider(bidderInvocationContextProvider(endpoint, auctionContext, bidder)) .withRejectAllowed(true) - .execute(); + .execute() + .map(result -> rejectAllIgnoringUnknowns(auctionContext, result)); } public Future> executeProcessedBidderResponseStage( @@ -286,7 +300,8 @@ public Future> executeProcessedB .withInitialPayload(BidderResponsePayloadImpl.of(bids)) .withInvocationContextProvider(bidderInvocationContextProvider(endpoint, auctionContext, bidder)) .withRejectAllowed(true) - .execute(); + .execute() + .map(result -> rejectAllIgnoringUnknowns(auctionContext, result)); } public Future> executeAllProcessedBidResponsesStage( @@ -304,7 +319,8 @@ public Future> execute .withInitialPayload(AllProcessedBidResponsesPayloadImpl.of(bidderResponses)) .withInvocationContextProvider(auctionInvocationContextProvider(endpoint, auctionContext)) .withRejectAllowed(false) - .execute(); + .execute() + .map(result -> rejectAllIgnoringUnknowns(auctionContext, result)); } public Future> executeAuctionResponseStage( @@ -543,4 +559,35 @@ private static boolean isABTestApplicable(ABTest abTest, String account) { final Set accounts = abTest.getAccounts(); return CollectionUtils.isEmpty(accounts) || accounts.contains(account); } + + //todo: should it be more strict? e.g. allowing rejecting only imps/bids on the particular stages + + private HookStageExecutionResult rejectAll(AuctionContext auctionContext, + HookStageExecutionResult result) { + + result.getRejections() + .forEach((bidder, rejectedList) -> auctionContext.getBidRejectionTrackers().computeIfAbsent( + bidder, + key -> new BidRejectionTracker( + key, + rejectedList.stream().map(Rejection::impId).collect(Collectors.toSet()), + logSamplingRate)) + .reject(rejectedList)); + + return result; + } + + private HookStageExecutionResult rejectAllIgnoringUnknowns(AuctionContext auctionContext, + HookStageExecutionResult result) { + + result.getRejections() + .forEach((bidder, rejectedList) -> auctionContext.getBidRejectionTrackers().computeIfPresent( + bidder, + (key, value) -> { + value.reject(rejectedList); + return value; + })); + + return result; + } } diff --git a/src/main/java/org/prebid/server/hooks/execution/StageExecutor.java b/src/main/java/org/prebid/server/hooks/execution/StageExecutor.java index 8dfa03e9a7f..2db76e7c657 100644 --- a/src/main/java/org/prebid/server/hooks/execution/StageExecutor.java +++ b/src/main/java/org/prebid/server/hooks/execution/StageExecutor.java @@ -136,6 +136,6 @@ private HookStageExecutionResult toHookStageExecutionResult(StageResult return stageResult.shouldReject() ? HookStageExecutionResult.reject() - : HookStageExecutionResult.success(stageResult.payload()); + : HookStageExecutionResult.success(stageResult.payload(), stageResult.rejections()); } } diff --git a/src/main/java/org/prebid/server/hooks/execution/StageResult.java b/src/main/java/org/prebid/server/hooks/execution/StageResult.java index 695747e73ec..5183a1571ad 100644 --- a/src/main/java/org/prebid/server/hooks/execution/StageResult.java +++ b/src/main/java/org/prebid/server/hooks/execution/StageResult.java @@ -2,11 +2,15 @@ import lombok.Getter; import lombok.experimental.Accessors; +import org.prebid.server.auction.model.Rejection; import org.prebid.server.hooks.execution.model.GroupExecutionOutcome; import org.prebid.server.hooks.execution.model.StageExecutionOutcome; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Accessors(fluent = true) @Getter @@ -47,4 +51,20 @@ private List groupExecutionOutcomes() { .map(GroupResult::toGroupExecutionOutcome) .toList(); } + + public Map> rejections() { + return groupResults.stream() + .map(GroupResult::rejections) + .reduce(StageResult::collectionMerge) + .orElse(Collections.emptyMap()); + } + + private static Map> collectionMerge(Map> left, + Map> right) { + + final Map> merged = new HashMap<>(); + left.forEach((key, value) -> merged.put(key, new ArrayList<>(value))); + right.forEach((key, value) -> merged.computeIfAbsent(key, k -> new ArrayList<>()).addAll(value)); + return Collections.unmodifiableMap(merged); + } } diff --git a/src/main/java/org/prebid/server/hooks/execution/model/HookStageExecutionResult.java b/src/main/java/org/prebid/server/hooks/execution/model/HookStageExecutionResult.java index 5e867add4ef..d88d2599bf8 100644 --- a/src/main/java/org/prebid/server/hooks/execution/model/HookStageExecutionResult.java +++ b/src/main/java/org/prebid/server/hooks/execution/model/HookStageExecutionResult.java @@ -1,6 +1,11 @@ package org.prebid.server.hooks.execution.model; import lombok.Value; +import org.prebid.server.auction.model.Rejection; + +import java.util.Collections; +import java.util.List; +import java.util.Map; @Value(staticConstructor = "of") public class HookStageExecutionResult { @@ -9,11 +14,18 @@ public class HookStageExecutionResult { PAYLOAD payload; + Map> rejections; + + public static HookStageExecutionResult success(PAYLOAD payload, + Map> rejections) { + return of(false, payload, rejections); + } + public static HookStageExecutionResult success(PAYLOAD payload) { - return of(false, payload); + return of(false, payload, Collections.emptyMap()); } public static HookStageExecutionResult reject() { - return of(true, null); + return of(true, null, Collections.emptyMap()); } } diff --git a/src/main/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHook.java b/src/main/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHook.java index e7b82803f9a..fc7865d1211 100644 --- a/src/main/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHook.java +++ b/src/main/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHook.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.vertx.core.Future; +import org.prebid.server.auction.model.Rejection; import org.prebid.server.hooks.execution.v1.InvocationResultImpl; import org.prebid.server.hooks.execution.v1.analytics.ActivityImpl; import org.prebid.server.hooks.execution.v1.analytics.ResultImpl; @@ -19,6 +20,7 @@ import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; public class ABTestHook implements Hook { @@ -120,6 +122,11 @@ public List warnings() { return invocationResult.warnings(); } + @Override + public Map> rejections() { + return invocationResult.rejections(); + } + @Override public List debugMessages() { return invocationResult.debugMessages(); diff --git a/src/main/java/org/prebid/server/hooks/execution/v1/InvocationResultImpl.java b/src/main/java/org/prebid/server/hooks/execution/v1/InvocationResultImpl.java index 761aef951ec..694c50e8104 100644 --- a/src/main/java/org/prebid/server/hooks/execution/v1/InvocationResultImpl.java +++ b/src/main/java/org/prebid/server/hooks/execution/v1/InvocationResultImpl.java @@ -3,6 +3,7 @@ import lombok.Builder; import lombok.Value; import lombok.experimental.Accessors; +import org.prebid.server.auction.model.Rejection; import org.prebid.server.hooks.v1.InvocationAction; import org.prebid.server.hooks.v1.InvocationResult; import org.prebid.server.hooks.v1.InvocationStatus; @@ -10,6 +11,7 @@ import org.prebid.server.hooks.v1.analytics.Tags; import java.util.List; +import java.util.Map; @Accessors(fluent = true) @Builder @@ -30,6 +32,8 @@ public class InvocationResultImpl implements InvocationResult List debugMessages; + Map> rejections; + Object moduleContext; Tags analyticsTags; diff --git a/src/main/java/org/prebid/server/hooks/v1/InvocationResult.java b/src/main/java/org/prebid/server/hooks/v1/InvocationResult.java index 979ff697316..efdb308d34c 100644 --- a/src/main/java/org/prebid/server/hooks/v1/InvocationResult.java +++ b/src/main/java/org/prebid/server/hooks/v1/InvocationResult.java @@ -1,8 +1,10 @@ package org.prebid.server.hooks.v1; +import org.prebid.server.auction.model.Rejection; import org.prebid.server.hooks.v1.analytics.Tags; import java.util.List; +import java.util.Map; public interface InvocationResult { @@ -18,6 +20,8 @@ public interface InvocationResult { List warnings(); + Map> rejections(); + List debugMessages(); Object moduleContext(); diff --git a/src/main/java/org/prebid/server/spring/config/HooksConfiguration.java b/src/main/java/org/prebid/server/spring/config/HooksConfiguration.java index 5a05ccb8c8e..4cbfde1ffde 100644 --- a/src/main/java/org/prebid/server/spring/config/HooksConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/HooksConfiguration.java @@ -36,7 +36,8 @@ HookStageExecutor hookStageExecutor(HooksConfigurationProperties hooksConfigurat Clock clock, JacksonMapper mapper, @Value("${settings.modules.require-config-to-invoke:false}") - boolean isConfigToInvokeRequired) { + boolean isConfigToInvokeRequired, + @Value("${logging.sampling-rate:0.01}") double logSamplingRate) { return HookStageExecutor.create( hooksConfiguration.getHostExecutionPlan(), @@ -49,7 +50,8 @@ HookStageExecutor hookStageExecutor(HooksConfigurationProperties hooksConfigurat vertx, clock, mapper, - isConfigToInvokeRequired); + isConfigToInvokeRequired, + logSamplingRate); } @Bean diff --git a/src/main/java/org/prebid/server/validation/ResponseBidValidator.java b/src/main/java/org/prebid/server/validation/ResponseBidValidator.java index cca8f88faf7..660d5785395 100644 --- a/src/main/java/org/prebid/server/validation/ResponseBidValidator.java +++ b/src/main/java/org/prebid/server/validation/ResponseBidValidator.java @@ -13,6 +13,7 @@ import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidRejectionReason; import org.prebid.server.auction.model.BidRejectionTracker; +import org.prebid.server.auction.model.BidRejection; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.log.ConditionalLogger; import org.prebid.server.log.Logger; @@ -165,7 +166,7 @@ private void validateSeat(BidderBid bid, final String message = "invalid bidder code %s was set by the adapter %s for the account %s" .formatted(bid.getSeat(), bidder, account.getId()); - bidRejectionTracker.rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_GENERAL); + bidRejectionTracker.reject(BidRejection.of(bid, BidRejectionReason.RESPONSE_REJECTED_GENERAL)); metrics.updateSeatValidationMetrics(bidder); alternateBidderCodeLogger.warn(message, logSamplingRate); throw new ValidationException(message); @@ -317,7 +318,7 @@ private List singleWarningOrValidationException( return switch (enforcement) { case enforce -> { - bidRejectionTracker.rejectBid(bidderBid, bidRejectionReason); + bidRejectionTracker.reject(BidRejection.of(bidderBid, bidRejectionReason)); metricsRecorder.accept(MetricName.err); conditionalLogger.warn(message, logSamplingRate); throw new ValidationException(message); diff --git a/src/test/java/org/prebid/server/analytics/reporter/greenbids/GreenbidsAnalyticsReporterTest.java b/src/test/java/org/prebid/server/analytics/reporter/greenbids/GreenbidsAnalyticsReporterTest.java index 4683dc5370e..000feebbd5d 100644 --- a/src/test/java/org/prebid/server/analytics/reporter/greenbids/GreenbidsAnalyticsReporterTest.java +++ b/src/test/java/org/prebid/server/analytics/reporter/greenbids/GreenbidsAnalyticsReporterTest.java @@ -39,6 +39,7 @@ import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidRejectionReason; import org.prebid.server.auction.model.BidRejectionTracker; +import org.prebid.server.auction.model.ImpRejection; import org.prebid.server.hooks.execution.model.ExecutionStatus; import org.prebid.server.hooks.execution.model.GroupExecutionOutcome; import org.prebid.server.hooks.execution.model.HookExecutionContext; @@ -200,7 +201,7 @@ public void shouldReceiveValidResponseOnAuctionContextWithAnalyticsTagForBanner( .build(); final AuctionContext auctionContext = givenAuctionContextWithAnalyticsTag( - context -> context, List.of(imp), true); + context -> context, List.of(imp)); final AuctionEvent event = AuctionEvent.builder() .auctionContext(auctionContext) .bidResponse(auctionContext.getBidResponse()) @@ -642,8 +643,8 @@ private static AuctionContext givenAuctionContext( private static AuctionContext givenAuctionContextWithAnalyticsTag( UnaryOperator auctionContextCustomizer, - List imps, - boolean includeBidResponse) { + List imps) { + final AuctionContext.AuctionContextBuilder auctionContextBuilder = AuctionContext.builder() .httpRequest(HttpRequestContext.builder().build()) .bidRequest(givenBidRequest(request -> request, imps)) @@ -652,11 +653,7 @@ private static AuctionContext givenAuctionContextWithAnalyticsTag( final HookExecutionContext hookExecutionContext = givenHookExecutionContextWithAnalyticsTag(); auctionContextBuilder.hookExecutionContext(hookExecutionContext); - - if (includeBidResponse) { - auctionContextBuilder.bidResponse(givenBidResponse(response -> response)); - } - + auctionContextBuilder.bidResponse(givenBidResponse(response -> response)); return auctionContextCustomizer.apply(auctionContextBuilder).build(); } @@ -820,7 +817,7 @@ private static BidRejectionTracker givenBidRejectionTracker() { "seat3", Set.of("adunitcodevalue"), 1.0); - bidRejectionTracker.rejectImp("imp1", BidRejectionReason.NO_BID); + bidRejectionTracker.reject(ImpRejection.of("imp1", BidRejectionReason.NO_BID)); return bidRejectionTracker; } diff --git a/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java b/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java index d956beae62f..33c9747d70d 100644 --- a/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java +++ b/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java @@ -21,7 +21,6 @@ import com.iab.openrtb.response.SeatBid; import io.vertx.core.Future; import org.apache.commons.collections4.MapUtils; -import org.apache.commons.lang3.tuple.Pair; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -39,7 +38,6 @@ import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.AuctionParticipation; import org.prebid.server.auction.model.BidInfo; -import org.prebid.server.auction.model.BidRejectionReason; import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidRequestCacheInfo; import org.prebid.server.auction.model.BidderResponse; @@ -47,6 +45,7 @@ import org.prebid.server.auction.model.CategoryMappingResult; import org.prebid.server.auction.model.MultiBidConfig; import org.prebid.server.auction.model.PaaFormat; +import org.prebid.server.auction.model.ImpRejection; import org.prebid.server.auction.model.TargetingInfo; import org.prebid.server.auction.model.TimeoutContext; import org.prebid.server.auction.model.debug.DebugContext; @@ -142,6 +141,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; +import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static java.util.function.UnaryOperator.identity; @@ -163,6 +163,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.prebid.server.auction.model.BidRejectionReason.NO_BID; import static org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidAdservertargetingRule.Source.xStatic; import static org.prebid.server.proto.openrtb.ext.response.BidType.audio; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; @@ -244,11 +245,12 @@ public void setUp() { false, BidderResponsePayloadImpl.of(((BidderResponse) invocation.getArgument(0)) .getSeatBid() - .getBids())))); + .getBids()), + null))); given(hookStageExecutor.executeAllProcessedBidResponsesStage(any(), any())) .willAnswer(invocation -> Future.succeededFuture( HookStageExecutionResult.of( - false, AllProcessedBidResponsesPayloadImpl.of(invocation.getArgument(0))))); + false, AllProcessedBidResponsesPayloadImpl.of(invocation.getArgument(0)), null))); clock = Clock.fixed(Instant.ofEpochMilli(1000L), ZoneOffset.UTC); @@ -325,7 +327,7 @@ public void shouldPassBidWithGeneratedIdAndPreserveExtFieldsWhenIdGeneratorTypeU @Test public void shouldSkipBidderWhenRejectedByProcessedBidderResponseHooks() { // given - doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of(true, null))) + doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of(true, null, null))) .when(hookStageExecutor).executeProcessedBidderResponseStage(any(), any()); final Bid bid = Bid.builder() @@ -351,8 +353,7 @@ public void shouldSkipBidderWhenRejectedByProcessedBidderResponseHooks() { @Test public void shouldPassRequestModifiedByBidderRequestHooks() { - doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( BidderResponsePayloadImpl.of(singletonList(BidderBid.of( Bid.builder() .id("bidIdModifiedByHook") @@ -4393,8 +4394,7 @@ public void shouldDropFledgeResponsesReferencingUnknownImps() { public void shouldPopulateExtPrebidSeatNonBidWhenReturnAllBidStatusFlagIsTrue() { // given final BidRejectionTracker bidRejectionTracker = mock(BidRejectionTracker.class); - given(bidRejectionTracker.getRejectedImps()) - .willReturn(singletonMap("impId2", Pair.of("someBidder", BidRejectionReason.NO_BID))); + given(bidRejectionTracker.getRejected()).willReturn(singleton(ImpRejection.of("someBidder", "impId2", NO_BID))); final Bid bid = Bid.builder().id("bidId").price(BigDecimal.valueOf(3.67)).impid("impId").build(); final List bidderResponses = singletonList( @@ -4419,7 +4419,7 @@ public void shouldPopulateExtPrebidSeatNonBidWhenReturnAllBidStatusFlagIsTrue() // then final SeatNonBid expectedSeatNonBid = SeatNonBid.of( - "someBidder", singletonList(NonBid.of("impId2", BidRejectionReason.NO_BID))); + "someBidder", singletonList(NonBid.of("impId2", NO_BID))); assertThat(bidResponse.getExt()) .extracting(ExtBidResponse::getSeatnonbid) diff --git a/src/test/java/org/prebid/server/auction/DsaEnforcerTest.java b/src/test/java/org/prebid/server/auction/DsaEnforcerTest.java index 5254ea4edda..916cb934482 100644 --- a/src/test/java/org/prebid/server/auction/DsaEnforcerTest.java +++ b/src/test/java/org/prebid/server/auction/DsaEnforcerTest.java @@ -14,6 +14,7 @@ import org.prebid.server.auction.model.BidRejectionReason; import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidderResponse; +import org.prebid.server.auction.model.BidRejection; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; import org.prebid.server.bidder.model.BidderSeatBid; @@ -104,7 +105,7 @@ public void enforceShouldRejectBidAndAddWarningWhenDsaIsNotRequiredAndDsaRespons .bidderResponse(BidderResponse.of("bidder", expectedSeatBid, 100)) .build(); assertThat(actual).isEqualTo(expectedParticipation); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY); + verify(bidRejectionTracker).reject(BidRejection.of(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY)); } @Test @@ -137,7 +138,7 @@ public void enforceShouldRejectBidAndAddWarningWhenDsaIsNotRequiredAndDsaRespons .bidderResponse(BidderResponse.of("bidder", expectedSeatBid, 100)) .build(); assertThat(actual).isEqualTo(expectedParticipation); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY); + verify(bidRejectionTracker).reject(BidRejection.of(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY)); } @Test @@ -333,7 +334,7 @@ public void enforceShouldRejectBidAndAddWarningWhenBidExtHasEmptyDsaAndDsaIsRequ .bidderResponse(BidderResponse.of("bidder", expectedSeatBid, 100)) .build(); assertThat(actual).isEqualTo(expectedParticipation); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY); + verify(bidRejectionTracker).reject(BidRejection.of(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY)); } @Test @@ -367,7 +368,7 @@ public void enforceShouldRejectBidAndAddWarningWhenDsaIsRequiredAndDsaResponseHa .bidderResponse(BidderResponse.of("bidder", expectedSeatBid, 100)) .build(); assertThat(actual).isEqualTo(expectedParticipation); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY); + verify(bidRejectionTracker).reject(BidRejection.of(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY)); } @Test @@ -401,7 +402,7 @@ public void enforceShouldRejectBidAndAddWarningWhenDsaIsRequiredAndDsaResponseHa .bidderResponse(BidderResponse.of("bidder", expectedSeatBid, 100)) .build(); assertThat(actual).isEqualTo(expectedParticipation); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY); + verify(bidRejectionTracker).reject(BidRejection.of(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY)); } @Test @@ -436,7 +437,7 @@ public void enforceShouldRejectBidAndAddWarningWhenDsaIsRequiredAndPublisherAndA .bidderResponse(BidderResponse.of("bidder", expectedSeatBid, 100)) .build(); assertThat(actual).isEqualTo(expectedParticipation); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY); + verify(bidRejectionTracker).reject(BidRejection.of(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY)); } @Test @@ -471,7 +472,7 @@ public void enforceShouldRejectBidAndAddWarningWhenDsaIsRequiredAndPublisherAndA .bidderResponse(BidderResponse.of("bidder", expectedSeatBid, 100)) .build(); assertThat(actual).isEqualTo(expectedParticipation); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY); + verify(bidRejectionTracker).reject(BidRejection.of(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY)); } @Test @@ -505,7 +506,7 @@ public void enforceShouldRejectBidAndAddWarningWhenDsaIsRequiredAndPublisherNotR .bidderResponse(BidderResponse.of("bidder", expectedSeatBid, 100)) .build(); assertThat(actual).isEqualTo(expectedParticipation); - verify(bidRejectionTracker).rejectBid(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY); + verify(bidRejectionTracker).reject(BidRejection.of(bid, BidRejectionReason.RESPONSE_REJECTED_DSA_PRIVACY)); } private static ExtRegs givenExtRegs(DsaRequired dsaRequired, DsaPublisherRender pubRender) { diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index 92523904e2e..748ce616e5c 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -54,6 +54,7 @@ import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.auction.model.BidderResponse; import org.prebid.server.auction.model.MultiBidConfig; +import org.prebid.server.auction.model.ImpRejection; import org.prebid.server.auction.model.StoredResponseResult; import org.prebid.server.auction.model.TimeoutContext; import org.prebid.server.auction.model.debug.DebugContext; @@ -206,6 +207,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import static org.prebid.server.auction.model.BidRejectionReason.NO_BID; import static org.prebid.server.auction.model.BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.video; @@ -348,17 +350,14 @@ public void setUp() { given(supplyChainResolver.resolveForBidder(anyString(), any())).willReturn(null); given(hookStageExecutor.executeBidderRequestStage(any(), any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( BidderRequestPayloadImpl.of(invocation.getArgument(0).getBidRequest())))); given(hookStageExecutor.executeRawBidderResponseStage(any(), any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( BidderResponsePayloadImpl.of(invocation.getArgument(0).getSeatBid() .getBids())))); given(hookStageExecutor.executeAuctionResponseStage(any(), any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( AuctionResponsePayloadImpl.of(invocation.getArgument(0))))); given(bidsAdjuster.validateAndAdjustBids(any(), any(), any())) @@ -608,7 +607,7 @@ public void shouldExtractMultipleRequests() { @Test public void shouldSkipBidderWhenRejectedByBidderRequestHooks() { // given - doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of(true, null))) + doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.reject())) .when(hookStageExecutor).executeBidderRequestStage(any(), any()); final BidRequest bidRequest = givenBidRequest(givenSingleImp(singletonMap("someBidder", 1)), identity()); @@ -625,8 +624,7 @@ public void shouldPassRequestModifiedByBidderRequestHooks() { // given givenBidder(givenEmptySeatBid()); - doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( BidderRequestPayloadImpl.of(BidRequest.builder().id("bidderRequestId").build())))) .when(hookStageExecutor).executeBidderRequestStage(any(), any()); @@ -648,7 +646,7 @@ public void shouldSkipBidderWhenRejectedByRawBidderResponseHooks() { givenBidder(bidder, mock(Bidder.class), givenSeatBid(singletonList( givenBidderBid(Bid.builder().price(BigDecimal.ONE).build())))); - doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of(true, null))) + doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.reject())) .when(hookStageExecutor).executeRawBidderResponseStage(any(), any()); final BidRequest bidRequest = givenBidRequest(givenSingleImp(singletonMap(bidder, 1)), identity()); @@ -677,8 +675,7 @@ public void shouldPassRequestModifiedByRawBidderResponseHooks() { givenBidderBid(Bid.builder().build())))); final BidderBid hookChangedBid = BidderBid.of(Bid.builder().id("newId").build(), video, "USD"); - doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( BidderResponsePayloadImpl.of(singletonList(hookChangedBid))))) .when(hookStageExecutor).executeRawBidderResponseStage(any(), any()); @@ -3373,8 +3370,7 @@ public void shouldReturnBidResponseModifiedByAuctionResponseHooks() { given(httpBidderRequester.requestBids(any(), any(), any(), any(), any(), any(), anyBoolean())) .willReturn(Future.succeededFuture(givenSeatBid(emptyList()))); - doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + doAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( AuctionResponsePayloadImpl.of(BidResponse.builder().id("bidResponseId").build())))) .when(hookStageExecutor).executeAuctionResponseStage(any(), any()); @@ -4065,9 +4061,46 @@ public void shouldResponseWithEmptySeatBidIfBidderNotSupportRequestCurrency() { assertThat(result.result()) .extracting(AuctionContext::getBidRejectionTrackers) .extracting(rejectionTrackers -> rejectionTrackers.get("bidder1")) - .extracting(BidRejectionTracker::getRejectedImps) - .isEqualTo(Map.of("impId1", Pair.of("bidder1", REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY))); + .extracting(BidRejectionTracker::getRejected) + .isEqualTo(Set.of(ImpRejection.of("bidder1", "impId1", REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY))); + } + + @Test + public void shouldMakeBidRejectionTrackers() { + // given + final Bidder bidder1 = mock(Bidder.class); + final Bidder bidder2 = mock(Bidder.class); + givenBidder("bidder1", bidder1, givenEmptySeatBid()); + givenBidder("bidder2", bidder2, givenEmptySeatBid()); + + final Imp imp1 = givenImp(Map.of("bidder1", 1, "bidder2", 2), builder -> builder.id("impId1")); + final Imp imp2 = givenImp(Map.of("bidder1", 1, "bidder2", 2), builder -> builder.id("impId2")); + final BidRequest bidRequest = givenBidRequest(List.of(imp1, imp2)); + + final Map bidRejectionTrackers = new HashMap<>(); + bidRejectionTrackers.put("invalidBidder", new BidRejectionTracker("invalidBidder", Set.of("impId1"), 0.0)); + bidRejectionTrackers.put("bidder1", new BidRejectionTracker("bidder1", Set.of("impId1"), 0.0)); + + final AuctionContext auctionContext = givenRequestContext(bidRequest) + .toBuilder() + .bidRejectionTrackers(bidRejectionTrackers) + .build(); + given(bidderCatalog.isValidName("invalidBidder")).willReturn(false); + + // when + final Future result = target.holdAuction(auctionContext); + + // then + assertThat(result.succeeded()).isTrue(); + final Map actualTrackers = result.result().getBidRejectionTrackers(); + assertThat(actualTrackers.keySet()).containsOnly("bidder1", "bidder2"); + assertThat(actualTrackers.get("bidder1").getRejected()).containsOnly( + ImpRejection.of("bidder1", "impId1", NO_BID), + ImpRejection.of("bidder1", "impId2", NO_BID)); + assertThat(actualTrackers.get("bidder2").getRejected()).containsOnly( + ImpRejection.of("bidder2", "impId1", NO_BID), + ImpRejection.of("bidder2", "impId2", NO_BID)); } @Test @@ -4289,7 +4322,7 @@ private static BidRequest givenBidRequest(List imp) { return givenBidRequest(imp, identity()); } - private static Imp givenImp(T ext, Function impBuilderCustomizer) { + private static Imp givenImp(T ext, UnaryOperator impBuilderCustomizer) { return impBuilderCustomizer.apply(Imp.builder() .id(UUID.randomUUID().toString()) .ext(mapper.valueToTree(singletonMap( diff --git a/src/test/java/org/prebid/server/auction/model/BidRejectionTrackerTest.java b/src/test/java/org/prebid/server/auction/model/BidRejectionTrackerTest.java index e25207e0e16..6e3bd66af7a 100644 --- a/src/test/java/org/prebid/server/auction/model/BidRejectionTrackerTest.java +++ b/src/test/java/org/prebid/server/auction/model/BidRejectionTrackerTest.java @@ -1,19 +1,17 @@ package org.prebid.server.auction.model; import com.iab.openrtb.response.Bid; -import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.prebid.server.bidder.model.BidderBid; import java.util.List; -import java.util.Map; import java.util.Set; import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.entry; +import static org.assertj.core.api.Assertions.tuple; import static org.prebid.server.auction.model.BidRejectionReason.ERROR_GENERAL; import static org.prebid.server.auction.model.BidRejectionReason.ERROR_INVALID_BID_RESPONSE; import static org.prebid.server.auction.model.BidRejectionReason.ERROR_TIMED_OUT; @@ -34,85 +32,89 @@ public void setUp() { @Test public void succeedShouldRestoreImpFromImpRejection() { // given - target.rejectImp("impId1", ERROR_GENERAL); + target.reject(ImpRejection.of("impId1", ERROR_GENERAL)); // when final BidderBid bid = givenBid("bidId1", "impId1"); target.succeed(singleton(bid)); // then - assertThat(target.getRejectedImps()).isEmpty(); - assertThat(target.getRejectedBids()) - .containsOnly(entry("impId1", List.of(Pair.of(null, ERROR_GENERAL)))); + assertThat(target.getRejected()).isEmpty(); + assertThat(target.getAllRejected()) + .containsOnly(entry("impId1", List.of(ImpRejection.of("bidder", "impId1", ERROR_GENERAL)))); } @Test public void succeedShouldRestoreImpFromBidRejection() { // given final BidderBid bid = givenBid("bidId1", "impId1"); - target.rejectBid(bid, ERROR_GENERAL); + target.reject(BidRejection.of(bid, ERROR_GENERAL)); // when target.succeed(singleton(bid)); // then - assertThat(target.getRejectedImps()).isEmpty(); - assertThat(target.getRejectedBids()) - .containsOnly(entry("impId1", List.of(Pair.of(bid, ERROR_GENERAL)))); + assertThat(target.getRejected()).isEmpty(); + assertThat(target.getAllRejected()) + .containsOnly(entry("impId1", List.of(BidRejection.of(bid, ERROR_GENERAL)))); } @Test public void succeedShouldIgnoreUninvolvedImpIdsOnImpRejection() { // given - target.rejectImp("impId1", ERROR_GENERAL); + target.reject(ImpRejection.of("impId1", ERROR_GENERAL)); // when final BidderBid bid = givenBid("bidId2", "impId2"); target.succeed(singleton(bid)); // then - assertThat(target.getRejectedImps()).containsOnly(entry("impId1", Pair.of("bidder", ERROR_GENERAL))); - assertThat(target.getRejectedBids()) - .containsOnly(entry("impId1", List.of(Pair.of(null, ERROR_GENERAL)))); + assertThat(target.getRejected()).extracting(Rejection::seat, Rejection::impId, Rejection::reason) + .containsOnly(tuple("bidder", "impId1", ERROR_GENERAL)); + assertThat(target.getAllRejected()) + .containsOnly(entry("impId1", List.of(ImpRejection.of("bidder", "impId1", ERROR_GENERAL)))); } @Test public void succeedShouldIgnoreUninvolvedImpIdsOnBidRejection() { // given final BidderBid bid1 = givenBid("bidId1", "impId1"); - target.rejectBid(bid1, ERROR_GENERAL); + target.reject(BidRejection.of(bid1, ERROR_GENERAL)); // when final BidderBid bid2 = givenBid("bidId2", "impId2"); target.succeed(singleton(bid2)); // then - assertThat(target.getRejectedImps()).containsOnly(entry("impId1", Pair.of("seat", ERROR_GENERAL))); - assertThat(target.getRejectedBids()) - .containsOnly(entry("impId1", List.of(Pair.of(bid1, ERROR_GENERAL)))); + assertThat(target.getRejected()).extracting(Rejection::seat, Rejection::impId, Rejection::reason) + .containsOnly(tuple("seat", "impId1", ERROR_GENERAL)); + assertThat(target.getAllRejected()) + .containsOnly(entry("impId1", List.of(BidRejection.of(bid1, ERROR_GENERAL)))); } @Test public void rejectImpShouldRecordImpRejectionFirstTimeIfImpIdIsInvolved() { // when - target.rejectImp("impId1", ERROR_GENERAL); + target.reject(ImpRejection.of("impId1", ERROR_GENERAL)); // then - assertThat(target.getRejectedImps()).containsOnly(entry("impId1", Pair.of("bidder", ERROR_GENERAL))); - assertThat(target.getRejectedBids()) - .containsOnly(entry("impId1", List.of(Pair.of(null, ERROR_GENERAL)))); + assertThat(target.getRejected()).extracting(Rejection::seat, Rejection::impId, Rejection::reason) + .containsOnly(tuple("bidder", "impId1", ERROR_GENERAL)); + assertThat(target.getAllRejected()) + .containsOnly(entry("impId1", List.of(ImpRejection.of("bidder", "impId1", ERROR_GENERAL)))); } @Test public void rejectBidShouldRecordBidRejectionFirstTimeIfImpIdIsInvolved() { // when final BidderBid bid = givenBid("bidId1", "impId1"); - target.rejectBid(bid, ERROR_GENERAL); + target.reject(BidRejection.of(bid, ERROR_GENERAL)); // then - assertThat(target.getRejectedImps()).containsOnly(entry("impId1", Pair.of("seat", ERROR_GENERAL))); - assertThat(target.getRejectedBids()) - .containsOnly(entry("impId1", List.of(Pair.of(bid, ERROR_GENERAL)))); + assertThat(target.getRejected()).extracting(Rejection::seat, Rejection::impId, Rejection::reason) + .containsOnly(tuple("seat", "impId1", ERROR_GENERAL)); + assertThat(target.getAllRejected()) + .containsOnly(entry("impId1", List.of(BidRejection.of(bid, ERROR_GENERAL)))); } @Test @@ -123,72 +125,73 @@ public void rejectBidShouldRecordBidRejectionAfterPreviouslySucceededBid() { target.succeed(Set.of(bid1, bid2)); // when - target.rejectBid(bid1, ERROR_GENERAL); + target.reject(BidRejection.of(bid1, ERROR_GENERAL)); // then - assertThat(target.getRejectedImps()).isEmpty(); - assertThat(target.getRejectedBids()) - .containsOnly(entry("impId1", List.of(Pair.of(bid1, ERROR_GENERAL)))); + assertThat(target.getRejected()).isEmpty(); + assertThat(target.getAllRejected()) + .containsOnly(entry("impId1", List.of(BidRejection.of(bid1, ERROR_GENERAL)))); } @Test public void rejectImpShouldNotRecordImpRejectionIfImpIdIsAlreadyRejected() { // given - target.rejectImp("impId1", ERROR_GENERAL); + target.reject(ImpRejection.of("impId1", ERROR_GENERAL)); // when - target.rejectImp("impId1", ERROR_INVALID_BID_RESPONSE); + target.reject(ImpRejection.of("impId1", ERROR_INVALID_BID_RESPONSE)); // then - assertThat(target.getRejectedImps()).containsOnly(entry("impId1", Pair.of("bidder", ERROR_GENERAL))); - assertThat(target.getRejectedBids()) + assertThat(target.getRejected()).extracting(Rejection::seat, Rejection::impId, Rejection::reason) + .containsOnly(tuple("bidder", "impId1", ERROR_GENERAL)); + assertThat(target.getAllRejected()) .containsOnly(entry("impId1", List.of( - Pair.of(null, ERROR_GENERAL), - Pair.of(null, ERROR_INVALID_BID_RESPONSE)))); + ImpRejection.of("bidder", "impId1", ERROR_GENERAL), + ImpRejection.of("bidder", "impId1", ERROR_INVALID_BID_RESPONSE)))); } @Test public void rejectBidShouldNotRecordImpRejectionButRecordBidRejectionEvenIfImpIsAlreadyRejected() { // given final BidderBid bid1 = givenBid("bidId1", "impId1"); - target.rejectBid(bid1, RESPONSE_REJECTED_GENERAL); + target.reject(BidRejection.of(bid1, RESPONSE_REJECTED_GENERAL)); // when final BidderBid bid2 = givenBid("bidId2", "impId1"); - target.rejectBid(bid2, RESPONSE_REJECTED_BELOW_FLOOR); + target.reject(BidRejection.of(bid2, RESPONSE_REJECTED_BELOW_FLOOR)); // then - assertThat(target.getRejectedImps()) - .containsOnly(entry("impId1", Pair.of("seat", RESPONSE_REJECTED_GENERAL))); - assertThat(target.getRejectedBids()) + assertThat(target.getRejected()).extracting(Rejection::seat, Rejection::impId, Rejection::reason) + .containsOnly(tuple("seat", "impId1", RESPONSE_REJECTED_GENERAL)); + assertThat(target.getAllRejected()) .containsOnly(entry("impId1", List.of( - Pair.of(bid1, RESPONSE_REJECTED_GENERAL), - Pair.of(bid2, RESPONSE_REJECTED_BELOW_FLOOR)))); + BidRejection.of(bid1, RESPONSE_REJECTED_GENERAL), + BidRejection.of(bid2, RESPONSE_REJECTED_BELOW_FLOOR)))); } @Test - public void rejectAllImpsShouldTryRejectingEachImpId() { + public void rejectAllShouldTryRejectingEachImpId() { // given target = new BidRejectionTracker("bidder", Set.of("impId1", "impId2", "impId3"), 0); - target.rejectImp("impId1", NO_BID); + target.reject(ImpRejection.of("impId1", NO_BID)); // when - target.rejectAllImps(ERROR_TIMED_OUT); + target.rejectAll(ERROR_TIMED_OUT); // then - assertThat(target.getRejectedImps()) - .isEqualTo(Map.of( - "impId1", Pair.of("bidder", NO_BID), - "impId2", Pair.of("bidder", ERROR_TIMED_OUT), - "impId3", Pair.of("bidder", ERROR_TIMED_OUT))); + assertThat(target.getRejected()).extracting(Rejection::seat, Rejection::impId, Rejection::reason) + .containsOnly( + tuple("bidder", "impId1", NO_BID), + tuple("bidder", "impId2", ERROR_TIMED_OUT), + tuple("bidder", "impId3", ERROR_TIMED_OUT)); - assertThat(target.getRejectedBids()) + assertThat(target.getAllRejected()) .containsOnly( entry("impId1", List.of( - Pair.of(null, NO_BID), - Pair.of(null, ERROR_TIMED_OUT))), - entry("impId2", List.of(Pair.of(null, ERROR_TIMED_OUT))), - entry("impId3", List.of(Pair.of(null, ERROR_TIMED_OUT)))); + ImpRejection.of("bidder", "impId1", NO_BID), + ImpRejection.of("bidder", "impId1", ERROR_TIMED_OUT))), + entry("impId2", List.of(ImpRejection.of("bidder", "impId2", ERROR_TIMED_OUT))), + entry("impId3", List.of(ImpRejection.of("bidder", "impId3", ERROR_TIMED_OUT)))); } @Test @@ -196,28 +199,31 @@ public void rejectBidsShouldTryRejectingEachBid() { // given target = new BidRejectionTracker("bidder", Set.of("impId1", "impId2", "impId3"), 0); final BidderBid bid0 = givenBid("bidId0", "impId1"); - target.rejectBid(bid0, RESPONSE_REJECTED_GENERAL); + target.reject(BidRejection.of(bid0, RESPONSE_REJECTED_GENERAL)); // when final BidderBid bid1 = givenBid("bidId1", "impId1"); final BidderBid bid2 = givenBid("bidId2", "impId2"); final BidderBid bid3 = givenBid("bidId3", "impId3"); - target.rejectBids(Set.of(bid1, bid2, bid3), RESPONSE_REJECTED_DSA_PRIVACY); + target.reject(Set.of( + BidRejection.of(bid1, RESPONSE_REJECTED_DSA_PRIVACY), + BidRejection.of(bid2, RESPONSE_REJECTED_DSA_PRIVACY), + BidRejection.of(bid3, RESPONSE_REJECTED_DSA_PRIVACY))); // then - assertThat(target.getRejectedImps()) - .isEqualTo(Map.of( - "impId1", Pair.of("seat", RESPONSE_REJECTED_GENERAL), - "impId2", Pair.of("seat", RESPONSE_REJECTED_DSA_PRIVACY), - "impId3", Pair.of("seat", RESPONSE_REJECTED_DSA_PRIVACY))); + assertThat(target.getRejected()).extracting(Rejection::seat, Rejection::impId, Rejection::reason) + .containsOnly( + tuple("seat", "impId1", RESPONSE_REJECTED_GENERAL), + tuple("seat", "impId2", RESPONSE_REJECTED_DSA_PRIVACY), + tuple("seat", "impId3", RESPONSE_REJECTED_DSA_PRIVACY)); - assertThat(target.getRejectedBids()) + assertThat(target.getAllRejected()) .containsOnly( entry("impId1", List.of( - Pair.of(bid0, RESPONSE_REJECTED_GENERAL), - Pair.of(bid1, RESPONSE_REJECTED_DSA_PRIVACY))), - entry("impId2", List.of(Pair.of(bid2, RESPONSE_REJECTED_DSA_PRIVACY))), - entry("impId3", List.of(Pair.of(bid3, RESPONSE_REJECTED_DSA_PRIVACY)))); + BidRejection.of(bid0, RESPONSE_REJECTED_GENERAL), + BidRejection.of(bid1, RESPONSE_REJECTED_DSA_PRIVACY))), + entry("impId2", List.of(BidRejection.of(bid2, RESPONSE_REJECTED_DSA_PRIVACY))), + entry("impId3", List.of(BidRejection.of(bid3, RESPONSE_REJECTED_DSA_PRIVACY)))); } @Test @@ -228,15 +234,8 @@ public void getRejectedImpsShouldTreatUnsuccessfulImpsAsNoBidRejection() { target.succeed(singleton(bid)); // then - assertThat(target.getRejectedImps()).containsOnly(entry("impId1", Pair.of("bidder", NO_BID))); - } - - @Test - public void rejectImpShouldFailRejectingWithReasonThatImpliesExistingBidToReject() { - assertThatThrownBy(() -> target.rejectImp("impId1", RESPONSE_REJECTED_DSA_PRIVACY)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("The non-bid code 300 and higher assumes " - + "that there is a rejected bid that shouldn't be lost"); + assertThat(target.getRejected()).extracting(Rejection::seat, Rejection::impId, Rejection::reason) + .containsOnly(tuple("bidder", "impId1", NO_BID)); } private BidderBid givenBid(String bidId, String impId) { diff --git a/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java b/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java index 37c3964b713..c6b62cf9186 100644 --- a/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java +++ b/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java @@ -157,21 +157,18 @@ public void setUp() { .willAnswer(invocation -> Future.succeededFuture(invocation.getArgument(1))); given(hookStageExecutor.executeEntrypointStage(any(), any(), any(), any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( EntrypointPayloadImpl.of( invocation.getArgument(0), invocation.getArgument(1), invocation.getArgument(2))))); given(hookStageExecutor.executeRawAuctionRequestStage(any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( AuctionRequestPayloadImpl.of(invocation.getArgument(0))))); given(hookStageExecutor.executeProcessedAuctionRequestStage(any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( AuctionRequestPayloadImpl.of(invocation.getArgument(0))))); givenTarget(90); @@ -1119,8 +1116,7 @@ public void executeEntrypointHooksShouldReturnExpectedHttpRequest() { .add("DHT", "1") .build(); given(hookStageExecutor.executeEntrypointStage(any(), any(), any(), any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( EntrypointPayloadImpl.of( updatedQueryParam, headerParams, @@ -1156,7 +1152,7 @@ public void shouldReturnFailedFutureIfEntrypointHooksRejectedRequest() { given(httpServerRequest.headers()).willReturn(MultiMap.caseInsensitiveMultiMap()); given(hookStageExecutor.executeEntrypointStage(any(), any(), any(), any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of(true, null))); + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.reject())); final AuctionContext auctionContext = AuctionContext.builder().hookExecutionContext(hookExecutionContext).build(); @@ -1178,8 +1174,8 @@ public void shouldUseBidRequestModifiedByRawAuctionRequestHooks() { .app(App.builder().bundle("org.company.application").build()) .build(); given(hookStageExecutor.executeRawAuctionRequestStage(any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, AuctionRequestPayloadImpl.of(modifiedBidRequest)))); + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( + AuctionRequestPayloadImpl.of(modifiedBidRequest)))); final AuctionContext auctionContext = AuctionContext.builder() .bidRequest(BidRequest.builder().site(Site.builder().build()).build()) @@ -1197,7 +1193,7 @@ public void shouldUseBidRequestModifiedByRawAuctionRequestHooks() { public void shouldReturnFailedFutureIfRawAuctionRequestHookRejectedRequest() { // given given(hookStageExecutor.executeRawAuctionRequestStage(any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of(true, null))); + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.reject())); final AuctionContext auctionContext = AuctionContext.builder() .hookExecutionContext(hookExecutionContext) @@ -1220,8 +1216,8 @@ public void shouldUseBidRequestModifiedByProcessedAuctionRequestHooks() { .app(App.builder().bundle("org.company.application").build()) .build(); given(hookStageExecutor.executeProcessedAuctionRequestStage(any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, AuctionRequestPayloadImpl.of(modifiedBidRequest)))); + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( + AuctionRequestPayloadImpl.of(modifiedBidRequest)))); final AuctionContext auctionContext = AuctionContext.builder() .bidRequest(BidRequest.builder().site(Site.builder().build()).build()) @@ -1239,7 +1235,7 @@ public void shouldUseBidRequestModifiedByProcessedAuctionRequestHooks() { public void shouldReturnFailedFutureIfProcessedAuctionRequestHookRejectedRequest() { // given given(hookStageExecutor.executeProcessedAuctionRequestStage(any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of(true, null))); + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.reject())); final AuctionContext auctionContext = AuctionContext.builder() .hookExecutionContext(hookExecutionContext) diff --git a/src/test/java/org/prebid/server/bidder/HttpBidderRequesterTest.java b/src/test/java/org/prebid/server/bidder/HttpBidderRequesterTest.java index f12bb887ca9..dab0028c791 100644 --- a/src/test/java/org/prebid/server/bidder/HttpBidderRequesterTest.java +++ b/src/test/java/org/prebid/server/bidder/HttpBidderRequesterTest.java @@ -25,6 +25,7 @@ import org.prebid.server.auction.model.BidRejectionReason; import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.auction.model.Rejection; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; import org.prebid.server.bidder.model.BidderError; @@ -228,7 +229,7 @@ public void shouldPassStoredResponseToBidderMakeBidsMethodAndReturnSeatBids() { .isEqualTo("storedResponse"); assertThat(bidderSeatBid.getBids()).hasSameElementsAs(bids); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -267,7 +268,7 @@ public void shouldMakeRequestToBidderWhenStoredResponseDefinedButBidderCreatesMo // then verify(httpClient, times(2)).request(any(), anyString(), any(), any(byte[].class), anyLong()); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -302,7 +303,7 @@ public void shouldSendPopulatedGetRequestWithoutBody() { // then verify(httpClient).request(any(), anyString(), any(), (byte[]) isNull(), anyLong()); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -338,7 +339,7 @@ public void shouldSendMultipleRequests() throws JsonProcessingException { // then verify(httpClient, times(2)).request(any(), anyString(), any(), any(byte[].class), anyLong()); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -371,7 +372,7 @@ public void shouldReturnBidsCreatedByBidder() { // then assertThat(bidderSeatBid.getBids()).hasSameElementsAs(bids); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -404,7 +405,7 @@ public void shouldReturnBidsCreatedByMakeBids() { // then assertThat(bidderSeatBid.getBids()).hasSameElementsAs(bids); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -446,7 +447,7 @@ public void shouldReturnFledgeCreatedByBidder() { assertThat(bidderSeatBid.getBids()).hasSameElementsAs(bids); assertThat(bidderSeatBid.getFledgeAuctionConfigs()).hasSameElementsAs(fledgeAuctionConfigs); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -493,7 +494,7 @@ public void shouldReturnExtIgiCreatedByBidder() { assertThat(bidderSeatBid.getBids()).hasSameElementsAs(bids); assertThat(bidderSeatBid.getIgi()).containsExactlyElementsOf(igi); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -529,7 +530,7 @@ public void shouldCompressRequestBodyIfContentEncodingHeaderIsGzip() { verify(httpClient).request(any(), anyString(), any(), actualRequestBody.capture(), anyLong()); assertThat(actualRequestBody.getValue()).isNotSameAs(EMPTY_BYTE_BODY); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -627,7 +628,7 @@ public void processBids(List bids) { assertThat(bidderSeatBid.getBids()).containsOnly(bidderBidDeal1, bidderBidDeal2); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -676,7 +677,7 @@ public void shouldFinishWhenAllDealRequestsAreFinishedAndNoDealsProvided() { assertThat(bidderSeatBid.getBids()).contains(bidderBid, bidderBid, bidderBid, bidderBid); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -743,7 +744,7 @@ public void shouldReturnFullDebugInfoIfDebugEnabled() throws JsonProcessingExcep .status(200) .build()); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -853,7 +854,7 @@ public void shouldNotReturnSensitiveHeadersInFullDebugInfo() .extracting(ExtHttpCall::getRequestheaders) .containsExactly(singletonMap("headerKey", singletonList("headerValue"))); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } @@ -1238,7 +1239,7 @@ public void shouldNotMakeBidsIfResponseStatusIs204() { verify(bidder, never()).makeBidderResponse(any(), any()); verify(bidder, never()).makeBids(any(), any()); - verify(bidRejectionTracker, never()).rejectImp(anyString(), any()); + verify(bidRejectionTracker, never()).reject(any(Rejection.class)); verify(bidRejectionTracker, never()).rejectImps(anyList(), any()); } diff --git a/src/test/java/org/prebid/server/floors/BasicPriceFloorEnforcerTest.java b/src/test/java/org/prebid/server/floors/BasicPriceFloorEnforcerTest.java index ad4fdbe0470..9b5a08bc41b 100644 --- a/src/test/java/org/prebid/server/floors/BasicPriceFloorEnforcerTest.java +++ b/src/test/java/org/prebid/server/floors/BasicPriceFloorEnforcerTest.java @@ -13,6 +13,7 @@ import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.auction.model.BidderResponse; +import org.prebid.server.auction.model.BidRejection; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; import org.prebid.server.bidder.model.BidderSeatBid; @@ -337,7 +338,7 @@ public void shouldRejectBidsHavingPriceBelowFloor() { // then final BidderBid rejectedBid = BidderBid.of( Bid.builder().id("bidId1").impid("impId1").price(BigDecimal.ONE).build(), null, null); - verify(rejectionTracker).rejectBid(rejectedBid, BidRejectionReason.RESPONSE_REJECTED_BELOW_FLOOR); + verify(rejectionTracker).reject(BidRejection.of(rejectedBid, BidRejectionReason.RESPONSE_REJECTED_BELOW_FLOOR)); assertThat(singleton(result)) .extracting(AuctionParticipation::getBidderResponse) .extracting(BidderResponse::getSeatBid) diff --git a/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java b/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java index aee1397e4c9..72ea07c12da 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java @@ -179,8 +179,7 @@ public void setUp() { given(prebidVersionProvider.getNameVersionRecord()).willReturn("pbs-java/1.00"); given(hookStageExecutor.executeExitpointStage(any(), any(), any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( ExitpointPayloadImpl.of(invocation.getArgument(0), invocation.getArgument(1))))); given(hooksMetricsService.updateHooksMetrics(any())).willAnswer(invocation -> invocation.getArgument(0)); diff --git a/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java b/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java index 1618caea8d2..0210095affd 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java @@ -167,8 +167,7 @@ public void setUp() { given(prebidVersionProvider.getNameVersionRecord()).willReturn("pbs-java/1.00"); given(hookStageExecutor.executeExitpointStage(any(), any(), any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( ExitpointPayloadImpl.of(invocation.getArgument(0), invocation.getArgument(1))))); given(hooksMetricsService.updateHooksMetrics(any())).willAnswer(invocation -> invocation.getArgument(0)); diff --git a/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java b/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java index 64efc34c093..2e89435181b 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java @@ -121,8 +121,7 @@ public void setUp() { given(prebidVersionProvider.getNameVersionRecord()).willReturn("pbs-java/1.00"); given(hookStageExecutor.executeExitpointStage(any(), any(), any())) - .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( - false, + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.success( ExitpointPayloadImpl.of(invocation.getArgument(0), invocation.getArgument(1))))); given(hooksMetricsService.updateHooksMetrics(any())).willAnswer(invocation -> invocation.getArgument(0)); diff --git a/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java b/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java index 3ee2b504a8e..24210ae6b72 100644 --- a/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java +++ b/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java @@ -26,8 +26,11 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.VertxTest; import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.auction.model.BidderResponse; +import org.prebid.server.auction.model.BidRejection; +import org.prebid.server.auction.model.ImpRejection; import org.prebid.server.auction.model.debug.DebugContext; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderSeatBid; @@ -84,7 +87,6 @@ import org.prebid.server.hooks.v1.exitpoint.ExitpointPayload; import org.prebid.server.model.CaseInsensitiveMultiMap; import org.prebid.server.model.Endpoint; -import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.settings.model.Account; import org.prebid.server.settings.model.AccountHooksConfiguration; import org.prebid.server.settings.model.HooksAdminConfig; @@ -95,8 +97,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.UnaryOperator; import static java.util.Arrays.asList; @@ -117,7 +121,15 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.prebid.server.assertion.FutureAssertion.assertThat; +import static org.prebid.server.auction.model.BidRejectionReason.REQUEST_BLOCKED_GENERAL; +import static org.prebid.server.auction.model.BidRejectionReason.REQUEST_BLOCKED_OPTIMIZED; +import static org.prebid.server.auction.model.BidRejectionReason.REQUEST_BLOCKED_PRIVACY; +import static org.prebid.server.auction.model.BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY; +import static org.prebid.server.auction.model.BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE; import static org.prebid.server.hooks.v1.PayloadUpdate.identity; +import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; +import static org.prebid.server.proto.openrtb.ext.response.BidType.video; +import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; @ExtendWith(MockitoExtension.class) @ExtendWith(VertxExtension.class) @@ -203,9 +215,13 @@ public void shouldTolerateMissingHostAndDefaultAccountExecutionPlans() { final CaseInsensitiveMultiMap headers = CaseInsensitiveMultiMap.empty(); final String body = "body"; + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(HookExecutionContext.of(Endpoint.openrtb2_auction)) + .build(); + // when final Future> future = executor.executeEntrypointStage( - queryParams, headers, body, HookExecutionContext.of(Endpoint.openrtb2_auction)); + queryParams, headers, body, givenAuctionContext); // then assertThat(future).isSucceeded(); @@ -275,13 +291,16 @@ public void shouldExecuteEntrypointHooksHappyPath(VertxTestContext context) { EndpointExecutionPlan.of(singletonMap(Stage.entrypoint, execPlanTwoGroupsTwoHooksEach()))))); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); // when final Future> future = executor.executeEntrypointStage( CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -357,13 +376,16 @@ public void shouldBypassEntrypointHooksWhenNoPlanForEndpoint(VertxTestContext co executionPlan(emptyMap())); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_amp); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); // when final Future> future = executor.executeEntrypointStage( CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -389,13 +411,16 @@ public void shouldBypassEntrypointHooksWhenNoPlanForStage(VertxTestContext conte EndpointExecutionPlan.of(emptyMap())))); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); // when final Future> future = executor.executeEntrypointStage( CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -454,16 +479,20 @@ public void shouldBypassEntrypointHooksThatAreDisabled(VertxTestContext context) vertx, clock, jacksonMapper, - false); + false, + 0.0); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); // when final Future> future = executor.executeEntrypointStage( CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -511,13 +540,16 @@ public void shouldExecuteEntrypointHooksToleratingMisbehavingHooks(VertxTestCont EndpointExecutionPlan.of(singletonMap(Stage.entrypoint, execPlanTwoGroupsTwoHooksEach()))))); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); // when final Future> future = executor.executeEntrypointStage( CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -618,6 +650,9 @@ public void shouldExecuteEntrypointHooksToleratingTimeoutAndFailedFuture(VertxTe payload.queryParams(), payload.headers(), payload.body() + "-jkl")))); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); final HookStageExecutor executor = createExecutor( executionPlan(singletonMap( @@ -629,7 +664,7 @@ public void shouldExecuteEntrypointHooksToleratingTimeoutAndFailedFuture(VertxTe CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -722,13 +757,16 @@ public void shouldExecuteEntrypointHooksHonoringStatusAndAction(VertxTestContext EndpointExecutionPlan.of(singletonMap(Stage.entrypoint, execPlanTwoGroupsTwoHooksEach()))))); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); // when final Future> future = executor.executeEntrypointStage( CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -810,13 +848,16 @@ public void shouldExecuteEntrypointHooksWhenRequestIsRejectedByFirstGroup(VertxT HookId.of("module-beta", "hook-a")))))))))); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); // when final Future> future = executor.executeEntrypointStage( CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -887,13 +928,16 @@ public void shouldExecuteEntrypointHooksWhenRequestIsRejectedBySecondGroup(Vertx EndpointExecutionPlan.of(singletonMap(Stage.entrypoint, execPlanTwoGroupsTwoHooksEach()))))); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); // when final Future> future = executor.executeEntrypointStage( CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -987,13 +1031,16 @@ public void shouldExecuteEntrypointHooksToleratingMisbehavingInvocationResult(Ve EndpointExecutionPlan.of(singletonMap(Stage.entrypoint, execPlanTwoGroupsTwoHooksEach()))))); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); // when final Future> future = executor.executeEntrypointStage( CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -1081,13 +1128,16 @@ public void shouldExecuteEntrypointHooksAndStoreResultInExecutionContext(VertxTe Stage.entrypoint, execPlanOneGroupOneHook("module-alpha", "hook-a")))))); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); // when final Future> future = executor.executeEntrypointStage( CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -1135,13 +1185,16 @@ public void shouldExecuteEntrypointHooksAndPassInvocationContext(VertxTestContex EndpointExecutionPlan.of(singletonMap(Stage.entrypoint, execPlanTwoGroupsTwoHooksEach()))))); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext givenAuctionContext = AuctionContext.builder() + .hookExecutionContext(hookExecutionContext) + .build(); // when final Future> future = executor.executeEntrypointStage( CaseInsensitiveMultiMap.empty(), CaseInsensitiveMultiMap.empty(), "body", - hookExecutionContext); + givenAuctionContext); // then future.onComplete(context.succeeding(result -> { @@ -1428,7 +1481,8 @@ public void shouldNotExecuteRawAuctionRequestHooksWhenAccountConfigIsNotRequired vertx, clock, jacksonMapper, - false); + false, + 0.0); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); @@ -1545,7 +1599,8 @@ public void shouldExecuteRawAuctionRequestHooksWhenAccountConfigIsRequired(Vertx vertx, clock, jacksonMapper, - true); + true, + 0.0); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); @@ -1663,6 +1718,119 @@ public void shouldExecuteRawAuctionRequestHooksHappyPath(VertxTestContext contex })); } + @Test + public void shouldExecuteRawAuctionRequestHooksWithAllRejectionsPopulated(VertxTestContext context) { + // given + givenRawAuctionRequestHook( + "module-alpha", + "hook-a", + immediateHook(InvocationResultUtils.succeeded( + payload -> AuctionRequestPayloadImpl.of(payload.bidRequest().toBuilder().at(1).build()), + Map.of("bidderA", List.of( + ImpRejection.of("impId1", REQUEST_BLOCKED_OPTIMIZED), + ImpRejection.of("impId3", REQUEST_BLOCKED_GENERAL)), + "bidderC", List.of( + ImpRejection.of("impId4", REQUEST_BLOCKED_GENERAL)))))); + + givenRawAuctionRequestHook( + "module-alpha", + "hook-b", + immediateHook(InvocationResultUtils.succeeded( + payload -> AuctionRequestPayloadImpl.of(payload.bidRequest().toBuilder().id("id").build()), + Map.of("bidderB", List.of( + ImpRejection.of("impId2", REQUEST_BLOCKED_PRIVACY), + ImpRejection.of("impId3", REQUEST_BLOCKED_GENERAL)), + "bidderC", List.of( + ImpRejection.of("impId4", REQUEST_BLOCKED_GENERAL)))))); + + givenRawAuctionRequestHook( + "module-beta", + "hook-a", + immediateHook(InvocationResultUtils.succeeded( + payload -> AuctionRequestPayloadImpl.of(payload.bidRequest().toBuilder().test(1).build()), + Map.of("bidderA", List.of( + ImpRejection.of("impId1", REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE), + ImpRejection.of("impId3", REQUEST_BLOCKED_GENERAL)), + "bidderC", List.of( + ImpRejection.of("impId4", REQUEST_BLOCKED_GENERAL)))))); + + givenRawAuctionRequestHook( + "module-beta", + "hook-b", + immediateHook(InvocationResultUtils.succeeded( + payload -> AuctionRequestPayloadImpl.of(payload.bidRequest().toBuilder().tmax(1000L).build()), + Map.of("bidderB", List.of( + ImpRejection.of("impId2", REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY), + ImpRejection.of("impId3", REQUEST_BLOCKED_GENERAL)), + "bidderC", List.of( + ImpRejection.of("impId4", REQUEST_BLOCKED_GENERAL)))))); + + final HookStageExecutor executor = createExecutor( + executionPlan(singletonMap( + Endpoint.openrtb2_auction, + EndpointExecutionPlan.of(singletonMap( + Stage.raw_auction_request, + execPlanTwoGroupsTwoHooksEach()))))); + + final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + + // when + final AuctionContext givenAuctionContext = AuctionContext.builder() + .bidRequest(BidRequest.builder().build()) + .account(Account.empty("accountId")) + .hookExecutionContext(hookExecutionContext) + .debugContext(DebugContext.empty()) + .bidRejectionTrackers(new HashMap<>()) + .build(); + final Future> future = executor.executeRawAuctionRequestStage( + givenAuctionContext); + + // then + future.onComplete(context.succeeding(result -> { + assertThat(result).isNotNull(); + assertThat(result.getPayload()).isNotNull().satisfies(payload -> + assertThat(payload.bidRequest()).isEqualTo(BidRequest.builder() + .at(1) + .id("id") + .test(1) + .tmax(1000L) + .build())); + + assertThat(hookExecutionContext.getStageOutcomes()) + .hasEntrySatisfying( + Stage.raw_auction_request, + stageOutcomes -> assertThat(stageOutcomes) + .hasSize(1) + .extracting(StageExecutionOutcome::getEntity) + .containsOnly("auction-request")); + + context.completeNow(); + })); + + final Map bidRejectionTrackers = givenAuctionContext.getBidRejectionTrackers(); + assertThat(bidRejectionTrackers.keySet()).containsOnly("bidderA", "bidderB", "bidderC"); + assertThat(bidRejectionTrackers.get("bidderA").getAllRejected()).containsOnly( + entry("impId1", List.of( + ImpRejection.of("bidderA", "impId1", REQUEST_BLOCKED_OPTIMIZED), + ImpRejection.of("bidderA", "impId1", REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE))), + entry("impId3", List.of( + ImpRejection.of("bidderA", "impId3", REQUEST_BLOCKED_GENERAL), + ImpRejection.of("bidderA", "impId3", REQUEST_BLOCKED_GENERAL)))); + assertThat(bidRejectionTrackers.get("bidderB").getAllRejected()).containsOnly( + entry("impId2", List.of( + ImpRejection.of("bidderB", "impId2", REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY), + ImpRejection.of("bidderB", "impId2", REQUEST_BLOCKED_PRIVACY))), + entry("impId3", List.of( + ImpRejection.of("bidderB", "impId3", REQUEST_BLOCKED_GENERAL), + ImpRejection.of("bidderB", "impId3", REQUEST_BLOCKED_GENERAL)))); + assertThat(bidRejectionTrackers.get("bidderC").getAllRejected()).containsOnly( + entry("impId4", List.of( + ImpRejection.of("bidderC", "impId4", REQUEST_BLOCKED_GENERAL), + ImpRejection.of("bidderC", "impId4", REQUEST_BLOCKED_GENERAL), + ImpRejection.of("bidderC", "impId4", REQUEST_BLOCKED_GENERAL), + ImpRejection.of("bidderC", "impId4", REQUEST_BLOCKED_GENERAL)))); + } + @Test public void shouldExecuteRawAuctionRequestHooksAndPassAuctionInvocationContext(VertxTestContext context) { // given @@ -2347,7 +2515,7 @@ public void shouldExecuteRawBidderResponseHooksHappyPath(VertxTestContext contex final Future> future1 = executor.executeRawBidderResponseStage( BidderResponse.of( "bidder1", - BidderSeatBid.of(singletonList(BidderBid.of(Bid.builder().build(), BidType.banner, "USD"))), + BidderSeatBid.of(singletonList(BidderBid.of(Bid.builder().build(), banner, "USD"))), 0), auctionContext); final Future> future2 = executor.executeRawBidderResponseStage( @@ -2368,7 +2536,7 @@ public void shouldExecuteRawBidderResponseHooksHappyPath(VertxTestContext contex .cid("cid") .adm("adm") .build(), - BidType.banner, + banner, "USD"))); checkpoint1.flag(); @@ -2407,7 +2575,7 @@ public void shouldExecuteRawBidderResponseHooksAndPassBidderInvocationContext(Ve final Future> future = executor.executeRawBidderResponseStage( BidderResponse.of( "bidder1", - BidderSeatBid.of(singletonList(BidderBid.of(Bid.builder().build(), BidType.banner, "USD"))), + BidderSeatBid.of(singletonList(BidderBid.of(Bid.builder().build(), banner, "USD"))), 0), AuctionContext.builder() .bidRequest(BidRequest.builder().build()) @@ -2504,7 +2672,7 @@ public void shouldExecuteProcessedBidderResponseHooksHappyPath(VertxTestContext BidderResponse.of( "bidder1", BidderSeatBid.of(singletonList( - BidderBid.of(Bid.builder().build(), BidType.banner, "USD"))), + BidderBid.of(Bid.builder().build(), banner, "USD"))), 0), auctionContext); final Future> future2 = @@ -2526,7 +2694,7 @@ public void shouldExecuteProcessedBidderResponseHooksHappyPath(VertxTestContext .cid("cid") .adm("adm") .build(), - BidType.banner, + banner, "USD"))); checkpoint1.flag(); @@ -2568,7 +2736,7 @@ public void shouldExecuteProcessedBidderResponseHooksAndPassBidderInvocationCont BidderResponse.of( "bidder1", BidderSeatBid.of(singletonList( - BidderBid.of(Bid.builder().build(), BidType.banner, "USD"))), + BidderBid.of(Bid.builder().build(), banner, "USD"))), 0), AuctionContext.builder() .bidRequest(BidRequest.builder().build()) @@ -2680,10 +2848,10 @@ public void shouldExecuteAllProcessedBidResponsesHooksHappyPath() { BidderResponse.of( "bidder1", BidderSeatBid.of(singletonList( - BidderBid.of(Bid.builder().build(), BidType.banner, "USD"))), 0), + BidderBid.of(Bid.builder().build(), banner, "USD"))), 0), BidderResponse.of("bidder2", BidderSeatBid.of(singletonList( - BidderBid.of(Bid.builder().build(), BidType.video, "UAH"))), 0)), + BidderBid.of(Bid.builder().build(), video, "UAH"))), 0)), auctionContext); // then @@ -2702,12 +2870,161 @@ public void shouldExecuteAllProcessedBidResponsesHooksHappyPath() { final List expectedBidderResponses = List.of( BidderResponse.of("bidder1", BidderSeatBid.of(singletonList( - BidderBid.of(expectedBid1, BidType.banner, "USD"))), 0), + BidderBid.of(expectedBid1, banner, "USD"))), 0), BidderResponse.of("bidder2", BidderSeatBid.of(singletonList( - BidderBid.of(expectedBid2, BidType.video, "UAH"))), 0)); + BidderBid.of(expectedBid2, video, "UAH"))), 0)); assertThat(result).succeededWith( - HookStageExecutionResult.of(false, AllProcessedBidResponsesPayloadImpl.of(expectedBidderResponses))); + HookStageExecutionResult.success(AllProcessedBidResponsesPayloadImpl.of(expectedBidderResponses))); + } + + @Test + public void shouldExecuteAllProcessedBidResponsesHooksRejectionAllIgnoringUnknowns(VertxTestContext context) { + final Function, UnaryOperator> bidFilterForResponse = + (Predicate bidPredicate) -> (BidderResponse response) -> { + final BidderSeatBid seatBid = response.getSeatBid(); + final List filteredBids = seatBid.getBids().stream() + .filter(bidPredicate) + .toList(); + return response.with(seatBid.with(filteredBids)); + }; + + // given + final BidderBid bidderABid1 = BidderBid.of(Bid.builder().id("bidId1").impid("impId").build(), banner, "USD"); + final BidderBid bidderABid2 = BidderBid.of(Bid.builder().id("bidId2").impid("impId").build(), banner, "USD"); + final BidderBid bidderBBid3 = BidderBid.of(Bid.builder().id("bidId3").impid("impId").build(), video, "UAH"); + final BidderBid bidderBBid4 = BidderBid.of(Bid.builder().id("bidId4").impid("impId").build(), video, "UAH"); + final BidderBid bidderCBid5 = BidderBid.of(Bid.builder().id("bidId5").impid("impId").build(), xNative, "EUR"); + final BidderBid bidderDBid6 = BidderBid.of(Bid.builder().id("bidId6").impid("impId").build(), xNative, "EUR"); + + givenAllProcessedBidderResponsesHook( + "module-alpha", + "hook-a", + immediateHook(InvocationResultUtils.succeeded(payload -> AllProcessedBidResponsesPayloadImpl.of( + payload.bidResponses().stream() + .map(bidFilterForResponse.apply( + bidderBid -> bidderBid.equals(bidderBBid3) + || bidderBid.equals(bidderBBid4))) + .toList()), + Map.of("bidderA", List.of( + BidRejection.of(bidderABid1, REQUEST_BLOCKED_OPTIMIZED), + BidRejection.of(bidderABid2, REQUEST_BLOCKED_GENERAL)), + "bidderC", List.of( + BidRejection.of(bidderCBid5, REQUEST_BLOCKED_GENERAL)), + "bidderD", List.of( + BidRejection.of(bidderDBid6, REQUEST_BLOCKED_GENERAL)))))); + + givenAllProcessedBidderResponsesHook( + "module-alpha", + "hook-b", + immediateHook(InvocationResultUtils.succeeded(payload -> AllProcessedBidResponsesPayloadImpl.of( + payload.bidResponses().stream() + .map(bidFilterForResponse.apply( + bidderBid -> bidderBid.equals(bidderABid1) + || bidderBid.equals(bidderABid2))) + .toList()), + Map.of("bidderB", List.of( + BidRejection.of(bidderBBid3, REQUEST_BLOCKED_PRIVACY), + BidRejection.of(bidderBBid4, REQUEST_BLOCKED_GENERAL)), + "bidderC", List.of( + BidRejection.of(bidderCBid5, REQUEST_BLOCKED_GENERAL)), + "bidderD", List.of( + BidRejection.of(bidderDBid6, REQUEST_BLOCKED_GENERAL)))))); + + givenAllProcessedBidderResponsesHook( + "module-beta", + "hook-a", + immediateHook(InvocationResultUtils.succeeded(payload -> AllProcessedBidResponsesPayloadImpl.of( + payload.bidResponses().stream() + .map(bidFilterForResponse.apply( + bidderBid -> bidderBid.equals(bidderBBid3) + || bidderBid.equals(bidderBBid4))) + .toList()), + Map.of("bidderA", List.of( + BidRejection.of(bidderABid1, REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE), + BidRejection.of(bidderABid2, REQUEST_BLOCKED_GENERAL)), + "bidderC", List.of( + BidRejection.of(bidderCBid5, REQUEST_BLOCKED_GENERAL)), + "bidderD", List.of( + BidRejection.of(bidderDBid6, REQUEST_BLOCKED_GENERAL)))))); + + givenAllProcessedBidderResponsesHook( + "module-beta", + "hook-b", + immediateHook(InvocationResultUtils.succeeded(payload -> AllProcessedBidResponsesPayloadImpl.of( + payload.bidResponses().stream() + .map(bidFilterForResponse.apply( + bidderBid -> bidderBid.equals(bidderABid1) + || bidderBid.equals(bidderABid2))) + .toList()), + Map.of("bidderB", List.of( + BidRejection.of(bidderBBid3, REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY), + BidRejection.of(bidderBBid4, REQUEST_BLOCKED_GENERAL)), + "bidderC", List.of( + BidRejection.of(bidderCBid5, REQUEST_BLOCKED_GENERAL)), + "bidderD", List.of( + BidRejection.of(bidderDBid6, REQUEST_BLOCKED_GENERAL)))))); + + final HookStageExecutor executor = createExecutor( + executionPlan(singletonMap( + Endpoint.openrtb2_auction, + EndpointExecutionPlan.of(singletonMap( + Stage.all_processed_bid_responses, + execPlanTwoGroupsTwoHooksEach()))))); + + final Map bidRejectionTrackers = new HashMap<>(); + bidRejectionTrackers.put("bidderA", new BidRejectionTracker("bidderA", Set.of("impId"), 0.0)); + bidRejectionTrackers.put("bidderB", new BidRejectionTracker("bidderB", Set.of("impId"), 0.0)); + bidRejectionTrackers.put("bidderC", new BidRejectionTracker("bidderC", Set.of("impId"), 0.0)); + + final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + final AuctionContext auctionContext = AuctionContext.builder() + .bidRequest(BidRequest.builder().build()) + .account(Account.empty("accountId")) + .hookExecutionContext(hookExecutionContext) + .debugContext(DebugContext.empty()) + .bidRejectionTrackers(bidRejectionTrackers) + .build(); + + // when + final Future> future = + executor.executeAllProcessedBidResponsesStage( + List.of( + BidderResponse.of("bidderA", BidderSeatBid.of(List.of(bidderABid1, bidderABid2)), 0), + BidderResponse.of("bidderB", BidderSeatBid.of(List.of(bidderBBid3, bidderBBid4)), 0), + BidderResponse.of("bidderC", BidderSeatBid.of(List.of(bidderCBid5)), 0)), + auctionContext); + + // then + final List expectedBidderResponses = List.of( + BidderResponse.of("bidderA", BidderSeatBid.of(emptyList()), 0), + BidderResponse.of("bidderB", BidderSeatBid.of(emptyList()), 0), + BidderResponse.of("bidderC", BidderSeatBid.of(emptyList()), 0)); + + future.onComplete(context.succeeding(result -> { + assertThat(result).isNotNull(); + assertThat(result.getPayload()).isNotNull().satisfies(payload -> + assertThat(payload.bidResponses()).isEqualTo(expectedBidderResponses)); + + context.completeNow(); + })); + + assertThat(bidRejectionTrackers.keySet()).containsOnly("bidderA", "bidderB", "bidderC"); + assertThat(bidRejectionTrackers.get("bidderA").getAllRejected().get("impId")).containsOnly( + BidRejection.of(bidderABid1, REQUEST_BLOCKED_OPTIMIZED), + BidRejection.of(bidderABid1, REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE), + BidRejection.of(bidderABid2, REQUEST_BLOCKED_GENERAL), + BidRejection.of(bidderABid2, REQUEST_BLOCKED_GENERAL)); + assertThat(bidRejectionTrackers.get("bidderB").getAllRejected().get("impId")).containsOnly( + BidRejection.of(bidderBBid3, REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY), + BidRejection.of(bidderBBid3, REQUEST_BLOCKED_PRIVACY), + BidRejection.of(bidderBBid4, REQUEST_BLOCKED_GENERAL), + BidRejection.of(bidderBBid4, REQUEST_BLOCKED_GENERAL)); + assertThat(bidRejectionTrackers.get("bidderC").getAllRejected().get("impId")).containsOnly( + BidRejection.of(bidderCBid5, REQUEST_BLOCKED_GENERAL), + BidRejection.of(bidderCBid5, REQUEST_BLOCKED_GENERAL), + BidRejection.of(bidderCBid5, REQUEST_BLOCKED_GENERAL), + BidRejection.of(bidderCBid5, REQUEST_BLOCKED_GENERAL)); } @Test @@ -2733,7 +3050,7 @@ public void shouldExecuteAllProcessedBidResponsesHooksAndPassAuctionInvocationCo singletonList(BidderResponse.of( "bidder1", BidderSeatBid.of(singletonList( - BidderBid.of(Bid.builder().build(), BidType.banner, "USD"))), + BidderBid.of(Bid.builder().build(), banner, "USD"))), 0)), AuctionContext.builder() .bidRequest(BidRequest.builder().build()) @@ -3421,7 +3738,8 @@ private HookStageExecutor createExecutor(String hostExecutionPlan, String defaul vertx, clock, jacksonMapper, - false); + false, + 0.0); } @Value(staticConstructor = "of") diff --git a/src/test/java/org/prebid/server/hooks/v1/InvocationResultUtils.java b/src/test/java/org/prebid/server/hooks/v1/InvocationResultUtils.java index 71d21ce9596..6518d1dced1 100644 --- a/src/test/java/org/prebid/server/hooks/v1/InvocationResultUtils.java +++ b/src/test/java/org/prebid/server/hooks/v1/InvocationResultUtils.java @@ -1,7 +1,11 @@ package org.prebid.server.hooks.v1; +import org.prebid.server.auction.model.Rejection; import org.prebid.server.hooks.execution.v1.InvocationResultImpl; +import java.util.List; +import java.util.Map; + public class InvocationResultUtils { private InvocationResultUtils() { @@ -16,6 +20,16 @@ public static InvocationResult succeeded(PayloadUpdate InvocationResult succeeded(PayloadUpdate payloadUpdate, + Map> rejections) { + return InvocationResultImpl.builder() + .status(InvocationStatus.success) + .action(InvocationAction.update) + .payloadUpdate(payloadUpdate) + .rejections(rejections) + .build(); + } + public static InvocationResult succeeded(PayloadUpdate payloadUpdate, Object moduleContext) { diff --git a/src/test/java/org/prebid/server/validation/ResponseBidValidatorTest.java b/src/test/java/org/prebid/server/validation/ResponseBidValidatorTest.java index 91969151268..df429612cdc 100644 --- a/src/test/java/org/prebid/server/validation/ResponseBidValidatorTest.java +++ b/src/test/java/org/prebid/server/validation/ResponseBidValidatorTest.java @@ -14,8 +14,8 @@ import org.prebid.server.VertxTest; import org.prebid.server.auction.aliases.BidderAliases; import org.prebid.server.auction.model.AuctionContext; -import org.prebid.server.auction.model.BidRejectionReason; import org.prebid.server.auction.model.BidRejectionTracker; +import org.prebid.server.auction.model.BidRejection; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.metric.MetricName; import org.prebid.server.metric.Metrics; @@ -39,6 +39,9 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import static org.prebid.server.auction.model.BidRejectionReason.RESPONSE_REJECTED_GENERAL; +import static org.prebid.server.auction.model.BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE_NOT_SECURE; +import static org.prebid.server.auction.model.BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE_SIZE_NOT_ALLOWED; import static org.prebid.server.settings.model.BidValidationEnforcement.enforce; import static org.prebid.server.settings.model.BidValidationEnforcement.skip; import static org.prebid.server.settings.model.BidValidationEnforcement.warn; @@ -157,7 +160,7 @@ public void validateShouldFailIfBannerBidHasNoWidthAndHeight() { creative size validation for bid bidId1, account=account, referrer=unknown, \ max imp size='100x200', bid response size='nullxnull'"""); verify(bidRejectionTracker) - .rejectBid(givenBid, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE_SIZE_NOT_ALLOWED); + .reject(BidRejection.of(givenBid, RESPONSE_REJECTED_INVALID_CREATIVE_SIZE_NOT_ALLOWED)); } @Test @@ -174,7 +177,7 @@ public void validateShouldFailIfBannerBidWidthIsGreaterThanImposedByImp() { creative size validation for bid bidId1, account=account, referrer=unknown, \ max imp size='100x200', bid response size='150x150'"""); verify(bidRejectionTracker) - .rejectBid(givenBid, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE_SIZE_NOT_ALLOWED); + .reject(BidRejection.of(givenBid, RESPONSE_REJECTED_INVALID_CREATIVE_SIZE_NOT_ALLOWED)); } @Test @@ -191,7 +194,7 @@ public void validateShouldFailIfBannerBidHeightIsGreaterThanImposedByImp() { creative size validation for bid bidId1, account=account, referrer=unknown, \ max imp size='100x200', bid response size='50x250'"""); verify(bidRejectionTracker) - .rejectBid(givenBid, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE_SIZE_NOT_ALLOWED); + .reject(BidRejection.of(givenBid, RESPONSE_REJECTED_INVALID_CREATIVE_SIZE_NOT_ALLOWED)); } @Test @@ -275,7 +278,7 @@ public void validateShouldFailIfBidHasInsecureMarkerInCreativeInSecureContext() secure creative validation for bid bidId1, account=account, referrer=unknown, \ adm=http://site.com/creative.jpg"""); verify(bidRejectionTracker) - .rejectBid(givenBid, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE_NOT_SECURE); + .reject(BidRejection.of(givenBid, RESPONSE_REJECTED_INVALID_CREATIVE_NOT_SECURE)); } @Test @@ -295,7 +298,7 @@ public void validateShouldFailIfBidHasInsecureEncodedMarkerInCreativeInSecureCon secure creative validation for bid bidId1, account=account, referrer=unknown, \ adm=http%3A//site.com/creative.jpg"""); verify(bidRejectionTracker) - .rejectBid(givenBid, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE_NOT_SECURE); + .reject(BidRejection.of(givenBid, RESPONSE_REJECTED_INVALID_CREATIVE_NOT_SECURE)); } @Test @@ -315,7 +318,7 @@ public void validateShouldFailIfBidHasNoSecureMarkersInCreativeInSecureContext() secure creative validation for bid bidId1, account=account, referrer=unknown, \ adm=//site.com/creative.jpg"""); verify(bidRejectionTracker) - .rejectBid(givenBid, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE_NOT_SECURE); + .reject(BidRejection.of(givenBid, RESPONSE_REJECTED_INVALID_CREATIVE_NOT_SECURE)); } @Test @@ -479,7 +482,7 @@ public void validateShouldIncrementSizeValidationErrMetrics() { // then verify(metrics).updateSizeValidationMetrics(BIDDER_NAME, ACCOUNT_ID, MetricName.err); verify(bidRejectionTracker) - .rejectBid(givenBid, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE_SIZE_NOT_ALLOWED); + .reject(BidRejection.of(givenBid, RESPONSE_REJECTED_INVALID_CREATIVE_SIZE_NOT_ALLOWED)); } @Test @@ -509,7 +512,7 @@ public void validateShouldIncrementSecureValidationErrMetrics() { // then verify(metrics).updateSecureValidationMetrics(BIDDER_NAME, ACCOUNT_ID, MetricName.err); verify(bidRejectionTracker) - .rejectBid(givenBid, BidRejectionReason.RESPONSE_REJECTED_INVALID_CREATIVE_NOT_SECURE); + .reject(BidRejection.of(givenBid, RESPONSE_REJECTED_INVALID_CREATIVE_NOT_SECURE)); } @Test @@ -563,7 +566,7 @@ public void validateShouldFailOnSeatValidationWhenSeatIsNotAllowed() { assertThat(result.getErrors()) .containsOnly("invalid bidder code seat was set by the adapter bidder for the account account"); verify(metrics).updateSeatValidationMetrics(BIDDER_NAME); - verify(bidRejectionTracker).rejectBid(givenBid, BidRejectionReason.RESPONSE_REJECTED_GENERAL); + verify(bidRejectionTracker).reject(BidRejection.of(givenBid, RESPONSE_REJECTED_GENERAL)); } @Test