From 67b77031193675a5bd6fb6e7328f596a92fcc7d2 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Thu, 21 Nov 2024 09:05:19 +0100 Subject: [PATCH 01/10] Add Exitpoint Stage --- .../payload/ActivityInvocationPayload.java | 5 + .../payload/GeoActivityInvocationPayload.java | 5 + .../payload/GpcActivityInvocationPayload.java | 5 + .../impl/BasicActivityInvocationPayload.java | 22 + .../server/auction/ExchangeService.java | 65 +- .../server/auction/HooksMetricsService.java | 81 +++ .../server/auction/model/AuctionContext.java | 8 + .../auction/model/RawAuctionResponse.java | 22 + .../requestfactory/Ortb2RequestFactory.java | 1 + .../server/handler/openrtb2/AmpHandler.java | 85 ++- .../handler/openrtb2/AuctionHandler.java | 70 ++- .../server/handler/openrtb2/VideoHandler.java | 97 ++- .../hooks/execution/HookStageExecutor.java | 23 + .../server/hooks/execution/model/Stage.java | 4 +- .../execution/model/StageWithHookType.java | 4 + .../v1/exitpoint/ExitpointPayloadImpl.java | 15 + .../server/hooks/v1/exit/ExitpointHook.java | 7 + .../hooks/v1/exit/ExitpointPayload.java | 10 + .../server/model/HttpRequestContext.java | 4 + .../ExtTraceActivityInfrastructure.java | 9 + .../config/metrics/MetricsConfiguration.java | 6 + .../ApplicationServerConfiguration.java | 14 + .../handler/openrtb2/AmpHandlerTest.java | 565 ++++++++++++++++-- .../handler/openrtb2/AuctionHandlerTest.java | 528 ++++++++++++++-- .../handler/openrtb2/VideoHandlerTest.java | 111 +++- .../it/hooks/SampleItExitpointHook.java | 55 ++ .../server/it/hooks/SampleItModule.java | 3 +- .../test-auction-sample-module-response.json | 2 +- .../prebid/server/it/test-app-settings.yaml | 18 + 29 files changed, 1597 insertions(+), 247 deletions(-) create mode 100644 src/main/java/org/prebid/server/activity/infrastructure/payload/impl/BasicActivityInvocationPayload.java create mode 100644 src/main/java/org/prebid/server/auction/HooksMetricsService.java create mode 100644 src/main/java/org/prebid/server/auction/model/RawAuctionResponse.java create mode 100644 src/main/java/org/prebid/server/hooks/execution/v1/exitpoint/ExitpointPayloadImpl.java create mode 100644 src/main/java/org/prebid/server/hooks/v1/exit/ExitpointHook.java create mode 100644 src/main/java/org/prebid/server/hooks/v1/exit/ExitpointPayload.java create mode 100644 src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java diff --git a/src/main/java/org/prebid/server/activity/infrastructure/payload/ActivityInvocationPayload.java b/src/main/java/org/prebid/server/activity/infrastructure/payload/ActivityInvocationPayload.java index 2014b7ab81f..806928d4230 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/payload/ActivityInvocationPayload.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/payload/ActivityInvocationPayload.java @@ -1,8 +1,13 @@ package org.prebid.server.activity.infrastructure.payload; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.prebid.server.activity.ComponentType; +import org.prebid.server.activity.infrastructure.payload.impl.BasicActivityInvocationPayload; +@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) +@JsonSubTypes(@JsonSubTypes.Type(BasicActivityInvocationPayload.class)) public interface ActivityInvocationPayload { @JsonProperty diff --git a/src/main/java/org/prebid/server/activity/infrastructure/payload/GeoActivityInvocationPayload.java b/src/main/java/org/prebid/server/activity/infrastructure/payload/GeoActivityInvocationPayload.java index 54d115615e7..355583ae675 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/payload/GeoActivityInvocationPayload.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/payload/GeoActivityInvocationPayload.java @@ -1,7 +1,12 @@ package org.prebid.server.activity.infrastructure.payload; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.prebid.server.activity.infrastructure.payload.impl.BasicActivityInvocationPayload; +@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) +@JsonSubTypes(@JsonSubTypes.Type(BasicActivityInvocationPayload.class)) public interface GeoActivityInvocationPayload extends ActivityInvocationPayload { @JsonProperty diff --git a/src/main/java/org/prebid/server/activity/infrastructure/payload/GpcActivityInvocationPayload.java b/src/main/java/org/prebid/server/activity/infrastructure/payload/GpcActivityInvocationPayload.java index 981324a40ea..944a8463c46 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/payload/GpcActivityInvocationPayload.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/payload/GpcActivityInvocationPayload.java @@ -1,7 +1,12 @@ package org.prebid.server.activity.infrastructure.payload; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.prebid.server.activity.infrastructure.payload.impl.BasicActivityInvocationPayload; +@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) +@JsonSubTypes(@JsonSubTypes.Type(BasicActivityInvocationPayload.class)) public interface GpcActivityInvocationPayload extends ActivityInvocationPayload { @JsonProperty diff --git a/src/main/java/org/prebid/server/activity/infrastructure/payload/impl/BasicActivityInvocationPayload.java b/src/main/java/org/prebid/server/activity/infrastructure/payload/impl/BasicActivityInvocationPayload.java new file mode 100644 index 00000000000..0229886bfce --- /dev/null +++ b/src/main/java/org/prebid/server/activity/infrastructure/payload/impl/BasicActivityInvocationPayload.java @@ -0,0 +1,22 @@ +package org.prebid.server.activity.infrastructure.payload.impl; + +import lombok.Value; +import lombok.experimental.Accessors; +import org.prebid.server.activity.ComponentType; +import org.prebid.server.activity.infrastructure.payload.GeoActivityInvocationPayload; +import org.prebid.server.activity.infrastructure.payload.GpcActivityInvocationPayload; + +@Accessors(fluent = true) +@Value(staticConstructor = "of") +public class BasicActivityInvocationPayload implements GeoActivityInvocationPayload, GpcActivityInvocationPayload { + + ComponentType componentType; + + String componentName; + + String country; + + String region; + + String gpc; +} diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java index 7e37c48f219..7e16c56b0b5 100644 --- a/src/main/java/org/prebid/server/auction/ExchangeService.java +++ b/src/main/java/org/prebid/server/auction/ExchangeService.java @@ -62,14 +62,7 @@ import org.prebid.server.floors.PriceFloorAdjuster; import org.prebid.server.floors.PriceFloorProcessor; import org.prebid.server.hooks.execution.HookStageExecutor; -import org.prebid.server.hooks.execution.model.ExecutionAction; -import org.prebid.server.hooks.execution.model.ExecutionStatus; -import org.prebid.server.hooks.execution.model.GroupExecutionOutcome; -import org.prebid.server.hooks.execution.model.HookExecutionOutcome; -import org.prebid.server.hooks.execution.model.HookId; import org.prebid.server.hooks.execution.model.HookStageExecutionResult; -import org.prebid.server.hooks.execution.model.Stage; -import org.prebid.server.hooks.execution.model.StageExecutionOutcome; import org.prebid.server.hooks.v1.bidder.BidderRequestPayload; import org.prebid.server.hooks.v1.bidder.BidderResponsePayload; import org.prebid.server.json.JacksonMapper; @@ -110,7 +103,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -221,8 +213,7 @@ public Future holdAuction(AuctionContext context) { return processAuctionRequest(context) .compose(this::invokeResponseHooks) .map(AnalyticsTagsEnricher::enrichWithAnalyticsTags) - .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo) - .map(this::updateHooksMetrics); + .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo); } private Future processAuctionRequest(AuctionContext context) { @@ -1374,58 +1365,4 @@ private static MetricName bidderErrorTypeToMetric(BidderError.Type errorType) { case rejected_ipf, generic -> MetricName.unknown_error; }; } - - private AuctionContext updateHooksMetrics(AuctionContext context) { - final EnumMap> stageOutcomes = - context.getHookExecutionContext().getStageOutcomes(); - - final Account account = context.getAccount(); - - stageOutcomes.forEach((stage, outcomes) -> updateHooksStageMetrics(account, stage, outcomes)); - - // account might be null if request is rejected by the entrypoint hook - if (account != null) { - stageOutcomes.values().stream() - .flatMap(Collection::stream) - .map(StageExecutionOutcome::getGroups) - .flatMap(Collection::stream) - .map(GroupExecutionOutcome::getHooks) - .flatMap(Collection::stream) - .filter(hookOutcome -> hookOutcome.getAction() != ExecutionAction.no_invocation) - .collect(Collectors.groupingBy( - outcome -> outcome.getHookId().getModuleCode(), - Collectors.summingLong(HookExecutionOutcome::getExecutionTime))) - .forEach((moduleCode, executionTime) -> - metrics.updateAccountModuleDurationMetric(account, moduleCode, executionTime)); - } - - return context; - } - - private void updateHooksStageMetrics(Account account, Stage stage, List stageOutcomes) { - stageOutcomes.stream() - .flatMap(stageOutcome -> stageOutcome.getGroups().stream()) - .flatMap(groupOutcome -> groupOutcome.getHooks().stream()) - .forEach(hookOutcome -> updateHookInvocationMetrics(account, stage, hookOutcome)); - } - - private void updateHookInvocationMetrics(Account account, Stage stage, HookExecutionOutcome hookOutcome) { - final HookId hookId = hookOutcome.getHookId(); - final ExecutionStatus status = hookOutcome.getStatus(); - final ExecutionAction action = hookOutcome.getAction(); - final String moduleCode = hookId.getModuleCode(); - - metrics.updateHooksMetrics( - moduleCode, - stage, - hookId.getHookImplCode(), - status, - hookOutcome.getExecutionTime(), - action); - - // account might be null if request is rejected by the entrypoint hook - if (account != null) { - metrics.updateAccountHooksMetrics(account, moduleCode, status, action); - } - } } diff --git a/src/main/java/org/prebid/server/auction/HooksMetricsService.java b/src/main/java/org/prebid/server/auction/HooksMetricsService.java new file mode 100644 index 00000000000..0b31d28444f --- /dev/null +++ b/src/main/java/org/prebid/server/auction/HooksMetricsService.java @@ -0,0 +1,81 @@ +package org.prebid.server.auction; + +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.hooks.execution.model.ExecutionAction; +import org.prebid.server.hooks.execution.model.ExecutionStatus; +import org.prebid.server.hooks.execution.model.GroupExecutionOutcome; +import org.prebid.server.hooks.execution.model.HookExecutionOutcome; +import org.prebid.server.hooks.execution.model.HookId; +import org.prebid.server.hooks.execution.model.Stage; +import org.prebid.server.hooks.execution.model.StageExecutionOutcome; +import org.prebid.server.metric.Metrics; +import org.prebid.server.settings.model.Account; + +import java.util.Collection; +import java.util.EnumMap; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class HooksMetricsService { + + private final Metrics metrics; + + public HooksMetricsService(Metrics metrics) { + this.metrics = Objects.requireNonNull(metrics); + } + + public AuctionContext updateHooksMetrics(AuctionContext context) { + final EnumMap> stageOutcomes = + context.getHookExecutionContext().getStageOutcomes(); + + final Account account = context.getAccount(); + + stageOutcomes.forEach((stage, outcomes) -> updateHooksStageMetrics(account, stage, outcomes)); + + // account might be null if request is rejected by the entrypoint hook + if (account != null) { + stageOutcomes.values().stream() + .flatMap(Collection::stream) + .map(StageExecutionOutcome::getGroups) + .flatMap(Collection::stream) + .map(GroupExecutionOutcome::getHooks) + .flatMap(Collection::stream) + .filter(hookOutcome -> hookOutcome.getAction() != ExecutionAction.no_invocation) + .collect(Collectors.groupingBy( + outcome -> outcome.getHookId().getModuleCode(), + Collectors.summingLong(HookExecutionOutcome::getExecutionTime))) + .forEach((moduleCode, executionTime) -> + metrics.updateAccountModuleDurationMetric(account, moduleCode, executionTime)); + } + + return context; + } + + private void updateHooksStageMetrics(Account account, Stage stage, List stageOutcomes) { + stageOutcomes.stream() + .flatMap(stageOutcome -> stageOutcome.getGroups().stream()) + .flatMap(groupOutcome -> groupOutcome.getHooks().stream()) + .forEach(hookOutcome -> updateHookInvocationMetrics(account, stage, hookOutcome)); + } + + private void updateHookInvocationMetrics(Account account, Stage stage, HookExecutionOutcome hookOutcome) { + final HookId hookId = hookOutcome.getHookId(); + final ExecutionStatus status = hookOutcome.getStatus(); + final ExecutionAction action = hookOutcome.getAction(); + final String moduleCode = hookId.getModuleCode(); + + metrics.updateHooksMetrics( + moduleCode, + stage, + hookId.getHookImplCode(), + status, + hookOutcome.getExecutionTime(), + action); + + // account might be null if request is rejected by the entrypoint hook + if (account != null) { + metrics.updateAccountHooksMetrics(account, moduleCode, status, action); + } + } +} diff --git a/src/main/java/org/prebid/server/auction/model/AuctionContext.java b/src/main/java/org/prebid/server/auction/model/AuctionContext.java index 5dbe83c3ff2..79de199233a 100644 --- a/src/main/java/org/prebid/server/auction/model/AuctionContext.java +++ b/src/main/java/org/prebid/server/auction/model/AuctionContext.java @@ -77,6 +77,9 @@ public class AuctionContext { @Builder.Default BidAdjustments bidAdjustments = BidAdjustments.of(Collections.emptyMap()); + @JsonIgnore + RawAuctionResponse rawAuctionResponse; + public AuctionContext with(Account account) { return this.toBuilder().account(account).build(); } @@ -136,6 +139,11 @@ public AuctionContext with(BidAdjustments bidAdjustments) { .build(); } + public AuctionContext with(RawAuctionResponse rawAuctionResponse) { + return this.toBuilder().rawAuctionResponse(rawAuctionResponse).build(); + } + + public AuctionContext withRequestRejected() { return this.toBuilder() .requestRejected(true) diff --git a/src/main/java/org/prebid/server/auction/model/RawAuctionResponse.java b/src/main/java/org/prebid/server/auction/model/RawAuctionResponse.java new file mode 100644 index 00000000000..a9c1714c4d3 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/model/RawAuctionResponse.java @@ -0,0 +1,22 @@ +package org.prebid.server.auction.model; + +import io.vertx.core.MultiMap; +import lombok.Builder; +import lombok.Value; +import org.prebid.server.hooks.v1.exit.ExitpointPayload; + +@Value(staticConstructor = "of") +@Builder(toBuilder = true) +public class RawAuctionResponse { + + String responseBody; + + MultiMap responseHeaders; + + public RawAuctionResponse of(ExitpointPayload exitpointPayload) { + return this.toBuilder() + .responseHeaders(exitpointPayload.responseHeaders()) + .responseBody(exitpointPayload.responseBody()) + .build(); + } +} 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 90b20dd4f29..01c4c8a43dc 100644 --- a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java +++ b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java @@ -385,6 +385,7 @@ private static HttpRequestContext toHttpRequest(HookStageExecutionResult biddersSupportingCustomTargeting; private final AmpResponsePostProcessor ampResponsePostProcessor; private final HttpInteractionLogger httpInteractionLogger; private final PrebidVersionProvider prebidVersionProvider; + private final HookStageExecutor hookStageExecutor; private final JacksonMapper mapper; private final double logSamplingRate; public AmpHandler(AmpRequestFactory ampRequestFactory, ExchangeService exchangeService, AnalyticsReporterDelegator analyticsDelegator, - Metrics metrics, + Metrics metrics, HooksMetricsService hooksMetricsService, Clock clock, BidderCatalog bidderCatalog, Set biddersSupportingCustomTargeting, AmpResponsePostProcessor ampResponsePostProcessor, HttpInteractionLogger httpInteractionLogger, - PrebidVersionProvider prebidVersionProvider, + PrebidVersionProvider prebidVersionProvider, HookStageExecutor hookStageExecutor, JacksonMapper mapper, double logSamplingRate) { @@ -107,12 +115,14 @@ public AmpHandler(AmpRequestFactory ampRequestFactory, this.exchangeService = Objects.requireNonNull(exchangeService); this.analyticsDelegator = Objects.requireNonNull(analyticsDelegator); this.metrics = Objects.requireNonNull(metrics); + this.hooksMetricsService = Objects.requireNonNull(hooksMetricsService); this.clock = Objects.requireNonNull(clock); this.bidderCatalog = Objects.requireNonNull(bidderCatalog); this.biddersSupportingCustomTargeting = Objects.requireNonNull(biddersSupportingCustomTargeting); this.ampResponsePostProcessor = Objects.requireNonNull(ampResponsePostProcessor); this.httpInteractionLogger = Objects.requireNonNull(httpInteractionLogger); this.prebidVersionProvider = Objects.requireNonNull(prebidVersionProvider); + this.hookStageExecutor = Objects.requireNonNull(hookStageExecutor); this.mapper = Objects.requireNonNull(mapper); this.logSamplingRate = logSamplingRate; } @@ -134,15 +144,14 @@ public void handle(RoutingContext routingContext) { .httpContext(HttpRequestContext.from(routingContext)); ampRequestFactory.fromRequest(routingContext, startTime) - .map(context -> addToEvent(context, ampEventBuilder::auctionContext, context)) .map(this::updateAppAndNoCookieAndImpsMetrics) - .compose(exchangeService::holdAuction) - .map(context -> addToEvent(context, ampEventBuilder::auctionContext, context)) .map(context -> addToEvent(context.getBidResponse(), ampEventBuilder::bidResponse, context)) - .compose(context -> prepareAmpResponse(context, routingContext)) - .map(result -> addToEvent(result.getLeft().getTargeting(), ampEventBuilder::targeting, result)) + .compose(context -> prepareSuccessfulResponse(context, routingContext, ampEventBuilder)) + .compose(this::invokeExitpointHooks) + .map(hooksMetricsService::updateHooksMetrics) + .map(context -> addToEvent(context, ampEventBuilder::auctionContext, context)) .onComplete(responseResult -> handleResult(responseResult, ampEventBuilder, routingContext, startTime)); } @@ -166,8 +175,41 @@ private AuctionContext updateAppAndNoCookieAndImpsMetrics(AuctionContext context return context; } + private Future prepareSuccessfulResponse(AuctionContext auctionContext, + RoutingContext routingContext, + AmpEvent.AmpEventBuilder ampEventBuilder) { + + final String origin = originFrom(routingContext); + MultiMap responseHeaders = getCommonResponseHeaders(routingContext, origin) + .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); + + return prepareAmpResponse(auctionContext, routingContext) + .map(result -> addToEvent(result.getLeft().getTargeting(), ampEventBuilder::targeting, result)) + .map(result -> { + final RawAuctionResponse rawAuctionResponse = RawAuctionResponse.builder() + .responseBody(mapper.encodeToString(result.getLeft())) + .responseHeaders(responseHeaders) + .build(); + return result.getRight().with(rawAuctionResponse); + }); + } + + private Future invokeExitpointHooks(AuctionContext auctionContext) { + final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); + return hookStageExecutor.executeExitpointStage( + rawAuctionResponse.getResponseHeaders(), + rawAuctionResponse.getResponseBody(), + auctionContext) + .map(HookStageExecutionResult::getPayload) + .map(rawAuctionResponse::of) + .map(auctionContext::with) + .map(AnalyticsTagsEnricher::enrichWithAnalyticsTags) + .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo); + } + private Future> prepareAmpResponse(AuctionContext context, RoutingContext routingContext) { + final BidRequest bidRequest = context.getBidRequest(); final BidResponse bidResponse = context.getBidResponse(); final AmpResponse ampResponse = toAmpResponse(bidResponse); @@ -271,12 +313,13 @@ private static ExtAmpVideoResponse extResponseFrom(BidResponse bidResponse) { : null; } - private void handleResult(AsyncResult> responseResult, + private void handleResult(AsyncResult responseResult, AmpEvent.AmpEventBuilder ampEventBuilder, RoutingContext routingContext, long startTime) { final boolean responseSucceeded = responseResult.succeeded(); + final AuctionContext auctionContext = responseSucceeded ? responseResult.result() : null; final MetricName metricRequestStatus; final List errorMessages; @@ -287,15 +330,18 @@ private void handleResult(AsyncResult> respo ampEventBuilder.origin(origin); final HttpServerResponse response = routingContext.response(); - enrichResponseWithCommonHeaders(routingContext, origin); + final MultiMap responseHeaders = response.headers(); if (responseSucceeded) { metricRequestStatus = MetricName.ok; errorMessages = Collections.emptyList(); - status = HttpResponseStatus.OK; - enrichWithSuccessfulHeaders(response); - body = mapper.encodeToString(responseResult.result().getLeft()); + + final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); + rawAuctionResponse.getResponseHeaders() + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + responseHeaders, header.getKey(), header.getValue())); + body = rawAuctionResponse.getResponseBody(); } else { final Throwable exception = responseResult.cause(); if (exception instanceof InvalidRequestException invalidRequestException) { @@ -351,13 +397,15 @@ private void handleResult(AsyncResult> respo status = HttpResponseStatus.INTERNAL_SERVER_ERROR; body = "Critical error while running the auction: " + message; } + + getCommonResponseHeaders(routingContext, origin) + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + responseHeaders, header.getKey(), header.getValue())); } final int statusCode = status.code(); final AmpEvent ampEvent = ampEventBuilder.status(statusCode).errors(errorMessages).build(); - final AuctionContext auctionContext = responseSucceeded ? responseResult.result().getRight() : null; - final PrivacyContext privacyContext = auctionContext != null ? auctionContext.getPrivacyContext() : null; final TcfContext tcfContext = privacyContext != null ? privacyContext.getTcfContext() : TcfContext.empty(); respondWith(routingContext, status, body, startTime, metricRequestStatus, ampEvent, tcfContext); @@ -406,8 +454,8 @@ private void handleResponseException(Throwable exception) { metrics.updateRequestTypeMetric(REQUEST_TYPE_METRIC, MetricName.networkerr); } - private void enrichResponseWithCommonHeaders(RoutingContext routingContext, String origin) { - final MultiMap responseHeaders = routingContext.response().headers(); + private MultiMap getCommonResponseHeaders(RoutingContext routingContext, String origin) { + MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, HttpUtil.X_PREBID_HEADER, prebidVersionProvider.getNameVersionRecord()); @@ -419,10 +467,7 @@ private void enrichResponseWithCommonHeaders(RoutingContext routingContext, Stri // Add AMP headers responseHeaders.add("AMP-Access-Control-Allow-Source-Origin", origin) .add("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"); - } - private void enrichWithSuccessfulHeaders(HttpServerResponse response) { - final MultiMap headers = response.headers(); - headers.add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); + return responseHeaders; } } diff --git a/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java index b8664bc75fd..912c3cbfe8d 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java @@ -12,9 +12,13 @@ import io.vertx.ext.web.RoutingContext; import org.prebid.server.analytics.model.AuctionEvent; import org.prebid.server.analytics.reporter.AnalyticsReporterDelegator; +import org.prebid.server.auction.AnalyticsTagsEnricher; import org.prebid.server.auction.ExchangeService; +import org.prebid.server.auction.HookDebugInfoEnricher; +import org.prebid.server.auction.HooksMetricsService; import org.prebid.server.auction.SkippedAuctionService; import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.RawAuctionResponse; import org.prebid.server.auction.requestfactory.AuctionRequestFactory; import org.prebid.server.cookie.UidsCookie; import org.prebid.server.exception.BlocklistedAccountException; @@ -22,6 +26,8 @@ import org.prebid.server.exception.InvalidAccountConfigException; import org.prebid.server.exception.InvalidRequestException; import org.prebid.server.exception.UnauthorizedAccountException; +import org.prebid.server.hooks.execution.HookStageExecutor; +import org.prebid.server.hooks.execution.model.HookStageExecutionResult; import org.prebid.server.json.JacksonMapper; import org.prebid.server.log.ConditionalLogger; import org.prebid.server.log.HttpInteractionLogger; @@ -55,9 +61,11 @@ public class AuctionHandler implements ApplicationResource { private final SkippedAuctionService skippedAuctionService; private final AnalyticsReporterDelegator analyticsDelegator; private final Metrics metrics; + private final HooksMetricsService hooksMetricsService; private final Clock clock; private final HttpInteractionLogger httpInteractionLogger; private final PrebidVersionProvider prebidVersionProvider; + private final HookStageExecutor hookStageExecutor; private final JacksonMapper mapper; public AuctionHandler(double logSamplingRate, @@ -66,9 +74,11 @@ public AuctionHandler(double logSamplingRate, SkippedAuctionService skippedAuctionService, AnalyticsReporterDelegator analyticsDelegator, Metrics metrics, + HooksMetricsService hooksMetricsService, Clock clock, HttpInteractionLogger httpInteractionLogger, PrebidVersionProvider prebidVersionProvider, + HookStageExecutor hookStageExecutor, JacksonMapper mapper) { this.logSamplingRate = logSamplingRate; @@ -77,9 +87,11 @@ public AuctionHandler(double logSamplingRate, this.skippedAuctionService = Objects.requireNonNull(skippedAuctionService); this.analyticsDelegator = Objects.requireNonNull(analyticsDelegator); this.metrics = Objects.requireNonNull(metrics); + this.hooksMetricsService = Objects.requireNonNull(hooksMetricsService); this.clock = Objects.requireNonNull(clock); this.httpInteractionLogger = Objects.requireNonNull(httpInteractionLogger); this.prebidVersionProvider = Objects.requireNonNull(prebidVersionProvider); + this.hookStageExecutor = Objects.requireNonNull(hookStageExecutor); this.mapper = Objects.requireNonNull(mapper); } @@ -102,6 +114,10 @@ public void handle(RoutingContext routingContext) { auctionRequestFactory.parseRequest(routingContext, startTime) .compose(auctionContext -> skippedAuctionService.skipAuction(auctionContext) .recover(throwable -> holdAuction(auctionEventBuilder, auctionContext))) + .map(context -> prepareSuccessfulResponse(context, routingContext)) + .compose(this::invokeExitpointHooks) + .map(hooksMetricsService::updateHooksMetrics) + .map(context -> addToEvent(context, auctionEventBuilder::auctionContext, context)) .onComplete(context -> handleResult(context, auctionEventBuilder, routingContext, startTime)); } @@ -116,7 +132,6 @@ private Future holdAuction(AuctionEvent.AuctionEventBuilder auct .compose(exchangeService::holdAuction) // populate event with updated context - .map(context -> addToEvent(context, auctionEventBuilder::auctionContext, context)) .map(context -> addToEvent(context.getBidResponse(), auctionEventBuilder::bidResponse, context)); } @@ -142,6 +157,35 @@ private AuctionContext updateAppAndNoCookieAndImpsMetrics(AuctionContext context return context; } + private AuctionContext prepareSuccessfulResponse(AuctionContext auctionContext, RoutingContext routingContext) { + MultiMap responseHeaders = getCommonResponseHeaders(routingContext) + .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); + + final RawAuctionResponse rawAuctionResponse = RawAuctionResponse.builder() + .responseBody(mapper.encodeToString(auctionContext.getBidResponse())) + .responseHeaders(responseHeaders) + .build(); + + return auctionContext.with(rawAuctionResponse); + } + + private Future invokeExitpointHooks(AuctionContext auctionContext) { + if (auctionContext.isAuctionSkipped()) { + return Future.succeededFuture(auctionContext); + } + + final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); + return hookStageExecutor.executeExitpointStage( + rawAuctionResponse.getResponseHeaders(), + rawAuctionResponse.getResponseBody(), + auctionContext) + .map(HookStageExecutionResult::getPayload) + .map(rawAuctionResponse::of) + .map(auctionContext::with) + .map(AnalyticsTagsEnricher::enrichWithAnalyticsTags) + .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo); + } + private void handleResult(AsyncResult responseResult, AuctionEvent.AuctionEventBuilder auctionEventBuilder, RoutingContext routingContext, @@ -161,15 +205,18 @@ private void handleResult(AsyncResult responseResult, final String body; final HttpServerResponse response = routingContext.response(); - enrichResponseWithCommonHeaders(routingContext); + final MultiMap responseHeaders = response.headers(); if (responseSucceeded) { metricRequestStatus = MetricName.ok; errorMessages = Collections.emptyList(); - status = HttpResponseStatus.OK; - enrichWithSuccessfulHeaders(response); - body = mapper.encodeToString(responseResult.result().getBidResponse()); + + final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); + rawAuctionResponse.getResponseHeaders() + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + responseHeaders, header.getKey(), header.getValue())); + body = rawAuctionResponse.getResponseBody(); } else { final Throwable exception = responseResult.cause(); if (exception instanceof InvalidRequestException invalidRequestException) { @@ -222,6 +269,10 @@ private void handleResult(AsyncResult responseResult, status = HttpResponseStatus.INTERNAL_SERVER_ERROR; body = "Critical error while running the auction: " + message; } + + getCommonResponseHeaders(routingContext) + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + responseHeaders, header.getKey(), header.getValue())); } final AuctionEvent auctionEvent = auctionEventBuilder.status(status.code()).errors(errorMessages).build(); @@ -263,8 +314,8 @@ private void handleResponseException(Throwable throwable, MetricName requestType metrics.updateRequestTypeMetric(requestType, MetricName.networkerr); } - private void enrichResponseWithCommonHeaders(RoutingContext routingContext) { - final MultiMap responseHeaders = routingContext.response().headers(); + private MultiMap getCommonResponseHeaders(RoutingContext routingContext) { + MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, HttpUtil.X_PREBID_HEADER, prebidVersionProvider.getNameVersionRecord()); @@ -272,10 +323,7 @@ private void enrichResponseWithCommonHeaders(RoutingContext routingContext) { if (requestHeaders.contains(HttpUtil.SEC_BROWSING_TOPICS_HEADER)) { responseHeaders.add(HttpUtil.OBSERVE_BROWSING_TOPICS_HEADER, "?1"); } - } - private void enrichWithSuccessfulHeaders(HttpServerResponse response) { - response.headers() - .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); + return responseHeaders; } } diff --git a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java index d5957c15aa7..2599dc7f910 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java @@ -3,21 +3,28 @@ import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.AsyncResult; +import io.vertx.core.Future; import io.vertx.core.MultiMap; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerResponse; import io.vertx.ext.web.RoutingContext; import org.prebid.server.analytics.model.VideoEvent; import org.prebid.server.analytics.reporter.AnalyticsReporterDelegator; +import org.prebid.server.auction.AnalyticsTagsEnricher; import org.prebid.server.auction.ExchangeService; +import org.prebid.server.auction.HookDebugInfoEnricher; +import org.prebid.server.auction.HooksMetricsService; import org.prebid.server.auction.VideoResponseFactory; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.CachedDebugLog; +import org.prebid.server.auction.model.RawAuctionResponse; import org.prebid.server.auction.model.WithPodErrors; import org.prebid.server.auction.requestfactory.VideoRequestFactory; import org.prebid.server.cache.CoreCacheService; import org.prebid.server.exception.InvalidRequestException; import org.prebid.server.exception.UnauthorizedAccountException; +import org.prebid.server.hooks.execution.HookStageExecutor; +import org.prebid.server.hooks.execution.model.HookStageExecutionResult; import org.prebid.server.json.JacksonMapper; import org.prebid.server.log.Logger; import org.prebid.server.log.LoggerFactory; @@ -56,17 +63,22 @@ public class VideoHandler implements ApplicationResource { private final CoreCacheService coreCacheService; private final AnalyticsReporterDelegator analyticsDelegator; private final Metrics metrics; + private final HooksMetricsService hooksMetricsService; private final Clock clock; private final PrebidVersionProvider prebidVersionProvider; + private final HookStageExecutor hookStageExecutor; private final JacksonMapper mapper; public VideoHandler(VideoRequestFactory videoRequestFactory, VideoResponseFactory videoResponseFactory, ExchangeService exchangeService, - CoreCacheService coreCacheService, AnalyticsReporterDelegator analyticsDelegator, + CoreCacheService coreCacheService, + AnalyticsReporterDelegator analyticsDelegator, Metrics metrics, + HooksMetricsService hooksMetricsService, Clock clock, PrebidVersionProvider prebidVersionProvider, + HookStageExecutor hookStageExecutor, JacksonMapper mapper) { this.videoRequestFactory = Objects.requireNonNull(videoRequestFactory); @@ -75,8 +87,10 @@ public VideoHandler(VideoRequestFactory videoRequestFactory, this.coreCacheService = Objects.requireNonNull(coreCacheService); this.analyticsDelegator = Objects.requireNonNull(analyticsDelegator); this.metrics = Objects.requireNonNull(metrics); + this.hooksMetricsService = Objects.requireNonNull(hooksMetricsService); this.clock = Objects.requireNonNull(clock); this.prebidVersionProvider = Objects.requireNonNull(prebidVersionProvider); + this.hookStageExecutor = Objects.requireNonNull(hookStageExecutor); this.mapper = Objects.requireNonNull(mapper); } @@ -106,13 +120,48 @@ public void handle(RoutingContext routingContext) { .map(contextToErrors -> addToEvent(contextToErrors.getData(), videoEventBuilder::auctionContext, contextToErrors)) - .map(result -> videoResponseFactory.toVideoResponse( - result.getData(), result.getData().getBidResponse(), - result.getPodErrors())) + .map(contextToErrors -> prepareSuccessfulResponse(contextToErrors, routingContext, videoEventBuilder)) + .compose(this::invokeExitpointHooks) + .map(hooksMetricsService::updateHooksMetrics) + // populate event with updated context + .map(context -> addToEvent(context, videoEventBuilder::auctionContext, context)) + .onComplete(responseResult -> handleResult(responseResult, videoEventBuilder, routingContext, startTime)); + } + + private AuctionContext prepareSuccessfulResponse(WithPodErrors context, + RoutingContext routingContext, + VideoEvent.VideoEventBuilder videoEventBuilder) { + + final AuctionContext auctionContext = context.getData(); + final VideoResponse videoResponse = videoResponseFactory.toVideoResponse( + auctionContext, + auctionContext.getBidResponse(), + context.getPodErrors()); + + addToEvent(videoResponse, videoEventBuilder::bidResponse, videoResponse); + + MultiMap responseHeaders = getCommonResponseHeaders(routingContext) + .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); - .map(videoResponse -> addToEvent(videoResponse, videoEventBuilder::bidResponse, videoResponse)) - .onComplete(responseResult -> handleResult(responseResult, videoEventBuilder, routingContext, - startTime)); + final RawAuctionResponse rawAuctionResponse = RawAuctionResponse.builder() + .responseBody(mapper.encodeToString(videoResponse)) + .responseHeaders(responseHeaders) + .build(); + + return auctionContext.with(rawAuctionResponse); + } + + private Future invokeExitpointHooks(AuctionContext auctionContext) { + final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); + return hookStageExecutor.executeExitpointStage( + rawAuctionResponse.getResponseHeaders(), + rawAuctionResponse.getResponseBody(), + auctionContext) + .map(HookStageExecutionResult::getPayload) + .map(rawAuctionResponse::of) + .map(auctionContext::with) + .map(AnalyticsTagsEnricher::enrichWithAnalyticsTags) + .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo); } private static R addToEvent(T field, Consumer consumer, R result) { @@ -120,7 +169,7 @@ private static R addToEvent(T field, Consumer consumer, R result) { return result; } - private void handleResult(AsyncResult responseResult, + private void handleResult(AsyncResult responseResult, VideoEvent.VideoEventBuilder videoEventBuilder, RoutingContext routingContext, long startTime) { @@ -130,18 +179,21 @@ private void handleResult(AsyncResult responseResult, final List errorMessages; final HttpResponseStatus status; final String body; - final VideoResponse videoResponse = responseSucceeded ? responseResult.result() : null; + final AuctionContext auctionContext = responseSucceeded ? responseResult.result() : null; final HttpServerResponse response = routingContext.response(); - enrichResponseWithCommonHeaders(routingContext); + final MultiMap responseHeaders = response.headers(); if (responseSucceeded) { metricRequestStatus = MetricName.ok; errorMessages = Collections.emptyList(); status = HttpResponseStatus.OK; - enrichWithSuccessfulHeaders(response); - body = mapper.encodeToString(videoResponse); + final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); + rawAuctionResponse.getResponseHeaders() + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + responseHeaders, header.getKey(), header.getValue())); + body = rawAuctionResponse.getResponseBody(); } else { final Throwable exception = responseResult.cause(); if (exception instanceof InvalidRequestException) { @@ -171,19 +223,23 @@ private void handleResult(AsyncResult responseResult, status = HttpResponseStatus.INTERNAL_SERVER_ERROR; body = "Critical error while running the auction: " + message; } + + getCommonResponseHeaders(routingContext) + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + responseHeaders, header.getKey(), header.getValue())); } VideoEvent videoEvent = videoEventBuilder.status(status.code()).errors(errorMessages).build(); - final AuctionContext auctionContext = videoEvent.getAuctionContext(); + final AuctionContext contextFromEvent = videoEvent.getAuctionContext(); - final CachedDebugLog cachedDebugLog = auctionContext != null ? auctionContext.getCachedDebugLog() : null; + final CachedDebugLog cachedDebugLog = contextFromEvent != null ? contextFromEvent.getCachedDebugLog() : null; final String cacheKey = shouldCacheLog(status.code(), cachedDebugLog) - ? cacheDebugLog(auctionContext, videoEvent.getErrors()) + ? cacheDebugLog(contextFromEvent, videoEvent.getErrors()) : null; if (status.code() != 200 && cacheKey != null) { videoEvent = updateEventWithDebugCacheMessage(videoEvent, cacheKey); } - final PrivacyContext privacyContext = auctionContext != null ? auctionContext.getPrivacyContext() : null; + final PrivacyContext privacyContext = contextFromEvent != null ? contextFromEvent.getPrivacyContext() : null; final TcfContext tcfContext = privacyContext != null ? privacyContext.getTcfContext() : TcfContext.empty(); respondWith(routingContext, status, body, startTime, metricRequestStatus, videoEvent, tcfContext); @@ -240,8 +296,8 @@ private void handleResponseException(Throwable throwable) { metrics.updateRequestTypeMetric(REQUEST_TYPE_METRIC, MetricName.networkerr); } - private void enrichResponseWithCommonHeaders(RoutingContext routingContext) { - final MultiMap responseHeaders = routingContext.response().headers(); + private MultiMap getCommonResponseHeaders(RoutingContext routingContext) { + MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, HttpUtil.X_PREBID_HEADER, prebidVersionProvider.getNameVersionRecord()); @@ -249,10 +305,7 @@ private void enrichResponseWithCommonHeaders(RoutingContext routingContext) { if (requestHeaders.contains(HttpUtil.SEC_BROWSING_TOPICS_HEADER)) { responseHeaders.add(HttpUtil.OBSERVE_BROWSING_TOPICS_HEADER, "?1"); } - } - private void enrichWithSuccessfulHeaders(HttpServerResponse response) { - response.headers() - .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); + return responseHeaders; } } 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 8e565d29d90..de708c51de5 100644 --- a/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java +++ b/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java @@ -3,7 +3,9 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.response.BidResponse; +import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.Future; +import io.vertx.core.MultiMap; import io.vertx.core.Vertx; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -31,6 +33,7 @@ import org.prebid.server.hooks.execution.v1.bidder.BidderRequestPayloadImpl; import org.prebid.server.hooks.execution.v1.bidder.BidderResponsePayloadImpl; import org.prebid.server.hooks.execution.v1.entrypoint.EntrypointPayloadImpl; +import org.prebid.server.hooks.execution.v1.exitpoint.ExitpointPayloadImpl; import org.prebid.server.hooks.v1.Hook; import org.prebid.server.hooks.v1.InvocationContext; import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; @@ -41,10 +44,13 @@ import org.prebid.server.hooks.v1.bidder.BidderRequestPayload; import org.prebid.server.hooks.v1.bidder.BidderResponsePayload; import org.prebid.server.hooks.v1.entrypoint.EntrypointPayload; +import org.prebid.server.hooks.v1.exit.ExitpointPayload; import org.prebid.server.json.DecodeException; import org.prebid.server.json.JacksonMapper; import org.prebid.server.model.CaseInsensitiveMultiMap; import org.prebid.server.model.Endpoint; +import org.prebid.server.proto.response.AmpResponse; +import org.prebid.server.proto.response.VideoResponse; import org.prebid.server.settings.model.Account; import org.prebid.server.settings.model.AccountHooksConfiguration; @@ -59,6 +65,7 @@ public class HookStageExecutor { private static final String ENTITY_HTTP_REQUEST = "http-request"; + private static final String ENTITY_HTTP_RESPONSE = "http-response"; private static final String ENTITY_AUCTION_REQUEST = "auction-request"; private static final String ENTITY_AUCTION_RESPONSE = "auction-response"; private static final String ENTITY_ALL_PROCESSED_BID_RESPONSES = "all-processed-bid-responses"; @@ -254,6 +261,22 @@ public Future> executeAuctionRe .execute(); } + public Future> executeExitpointStage(MultiMap responseHeaders, + String responseBody, + AuctionContext auctionContext) { + + final Account account = ObjectUtils.defaultIfNull(auctionContext.getAccount(), EMPTY_ACCOUNT); + final HookExecutionContext context = auctionContext.getHookExecutionContext(); + + final Endpoint endpoint = context.getEndpoint(); + + return stageExecutor(StageWithHookType.EXITPOINT, ENTITY_HTTP_RESPONSE, context, account, endpoint) + .withInitialPayload(ExitpointPayloadImpl.of(responseHeaders, responseBody)) + .withInvocationContextProvider(auctionInvocationContextProvider(endpoint, auctionContext)) + .withRejectAllowed(false) + .execute(); + } + private StageExecutor stageExecutor( StageWithHookType> stage, String entity, diff --git a/src/main/java/org/prebid/server/hooks/execution/model/Stage.java b/src/main/java/org/prebid/server/hooks/execution/model/Stage.java index 47896d8c9ab..bb7c151ed6f 100644 --- a/src/main/java/org/prebid/server/hooks/execution/model/Stage.java +++ b/src/main/java/org/prebid/server/hooks/execution/model/Stage.java @@ -33,5 +33,7 @@ public enum Stage { @JsonProperty("auction-response") @JsonAlias("auction_response") - auction_response + auction_response, + + exitpoint } diff --git a/src/main/java/org/prebid/server/hooks/execution/model/StageWithHookType.java b/src/main/java/org/prebid/server/hooks/execution/model/StageWithHookType.java index f8738d2c2db..214b038da64 100644 --- a/src/main/java/org/prebid/server/hooks/execution/model/StageWithHookType.java +++ b/src/main/java/org/prebid/server/hooks/execution/model/StageWithHookType.java @@ -10,6 +10,7 @@ import org.prebid.server.hooks.v1.bidder.ProcessedBidderResponseHook; import org.prebid.server.hooks.v1.bidder.RawBidderResponseHook; import org.prebid.server.hooks.v1.entrypoint.EntrypointHook; +import org.prebid.server.hooks.v1.exit.ExitpointHook; public interface StageWithHookType> { @@ -29,6 +30,8 @@ public interface StageWithHookType(Stage.all_processed_bid_responses, AllProcessedBidResponsesHook.class); StageWithHookType AUCTION_RESPONSE = new StageWithHookTypeImpl<>(Stage.auction_response, AuctionResponseHook.class); + StageWithHookType EXITPOINT = + new StageWithHookTypeImpl<>(Stage.exitpoint, ExitpointHook.class); Stage stage(); @@ -44,6 +47,7 @@ public interface StageWithHookType ALL_PROCESSED_BID_RESPONSES; case processed_bidder_response -> PROCESSED_BIDDER_RESPONSE; case auction_response -> AUCTION_RESPONSE; + case exitpoint -> EXITPOINT; }; } } diff --git a/src/main/java/org/prebid/server/hooks/execution/v1/exitpoint/ExitpointPayloadImpl.java b/src/main/java/org/prebid/server/hooks/execution/v1/exitpoint/ExitpointPayloadImpl.java new file mode 100644 index 00000000000..7c528554ec0 --- /dev/null +++ b/src/main/java/org/prebid/server/hooks/execution/v1/exitpoint/ExitpointPayloadImpl.java @@ -0,0 +1,15 @@ +package org.prebid.server.hooks.execution.v1.exitpoint; + +import io.vertx.core.MultiMap; +import lombok.Value; +import lombok.experimental.Accessors; +import org.prebid.server.hooks.v1.exit.ExitpointPayload; + +@Accessors(fluent = true) +@Value(staticConstructor = "of") +public class ExitpointPayloadImpl implements ExitpointPayload { + + MultiMap responseHeaders; + + String responseBody; +} diff --git a/src/main/java/org/prebid/server/hooks/v1/exit/ExitpointHook.java b/src/main/java/org/prebid/server/hooks/v1/exit/ExitpointHook.java new file mode 100644 index 00000000000..1e371b8764c --- /dev/null +++ b/src/main/java/org/prebid/server/hooks/v1/exit/ExitpointHook.java @@ -0,0 +1,7 @@ +package org.prebid.server.hooks.v1.exit; + +import org.prebid.server.hooks.v1.Hook; +import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; + +public interface ExitpointHook extends Hook { +} diff --git a/src/main/java/org/prebid/server/hooks/v1/exit/ExitpointPayload.java b/src/main/java/org/prebid/server/hooks/v1/exit/ExitpointPayload.java new file mode 100644 index 00000000000..dfb620349a4 --- /dev/null +++ b/src/main/java/org/prebid/server/hooks/v1/exit/ExitpointPayload.java @@ -0,0 +1,10 @@ +package org.prebid.server.hooks.v1.exit; + +import io.vertx.core.MultiMap; + +public interface ExitpointPayload { + + MultiMap responseHeaders(); + + String responseBody(); +} diff --git a/src/main/java/org/prebid/server/model/HttpRequestContext.java b/src/main/java/org/prebid/server/model/HttpRequestContext.java index efa07ed621e..9237e9b1803 100644 --- a/src/main/java/org/prebid/server/model/HttpRequestContext.java +++ b/src/main/java/org/prebid/server/model/HttpRequestContext.java @@ -2,6 +2,7 @@ import io.vertx.core.MultiMap; import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.RoutingContext; import lombok.Builder; import lombok.Value; @@ -16,6 +17,8 @@ @Value public class HttpRequestContext { + HttpMethod httpMethod; + String absoluteUri; CaseInsensitiveMultiMap queryParams; @@ -30,6 +33,7 @@ public class HttpRequestContext { public static HttpRequestContext from(RoutingContext context) { return HttpRequestContext.builder() + .httpMethod(context.request().method()) .absoluteUri(context.request().uri()) .queryParams(CaseInsensitiveMultiMap.builder().addAll(toMap(context.request().params())).build()) .headers(headers(context)) diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/response/ExtTraceActivityInfrastructure.java b/src/main/java/org/prebid/server/proto/openrtb/ext/response/ExtTraceActivityInfrastructure.java index 155397d8050..176cb598d53 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/response/ExtTraceActivityInfrastructure.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/response/ExtTraceActivityInfrastructure.java @@ -1,7 +1,16 @@ package org.prebid.server.proto.openrtb.ext.response; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) +@JsonSubTypes({ + @JsonSubTypes.Type(ExtTraceActivityInvocation.class), + @JsonSubTypes.Type(ExtTraceActivityInvocationDefaultResult.class), + @JsonSubTypes.Type(ExtTraceActivityRule.class), + @JsonSubTypes.Type(ExtTraceActivityInvocationResult.class) +}) public sealed interface ExtTraceActivityInfrastructure permits ExtTraceActivityInvocation, ExtTraceActivityInvocationDefaultResult, diff --git a/src/main/java/org/prebid/server/spring/config/metrics/MetricsConfiguration.java b/src/main/java/org/prebid/server/spring/config/metrics/MetricsConfiguration.java index 21d145bf826..daba3fda594 100644 --- a/src/main/java/org/prebid/server/spring/config/metrics/MetricsConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/metrics/MetricsConfiguration.java @@ -1,5 +1,6 @@ package org.prebid.server.spring.config.metrics; +import org.prebid.server.auction.HooksMetricsService; import org.slf4j.LoggerFactory; import com.codahale.metrics.Slf4jReporter; import com.codahale.metrics.ConsoleReporter; @@ -134,6 +135,11 @@ AccountMetricsVerbosityResolver accountMetricsVerbosity(AccountsProperties accou accountsProperties.getDetailedVerbosity()); } + @Bean + HooksMetricsService hooksMetricsService(Metrics metrics) { + return new HooksMetricsService(metrics); + } + @Component @ConfigurationProperties(prefix = "metrics.graphite") @ConditionalOnProperty(prefix = "metrics.graphite", name = "enabled", havingValue = "true") diff --git a/src/main/java/org/prebid/server/spring/config/server/application/ApplicationServerConfiguration.java b/src/main/java/org/prebid/server/spring/config/server/application/ApplicationServerConfiguration.java index 8c93941c679..b7c9eb405da 100644 --- a/src/main/java/org/prebid/server/spring/config/server/application/ApplicationServerConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/server/application/ApplicationServerConfiguration.java @@ -15,6 +15,7 @@ import org.prebid.server.analytics.reporter.AnalyticsReporterDelegator; import org.prebid.server.auction.AmpResponsePostProcessor; import org.prebid.server.auction.ExchangeService; +import org.prebid.server.auction.HooksMetricsService; import org.prebid.server.auction.SkippedAuctionService; import org.prebid.server.auction.VideoResponseFactory; import org.prebid.server.auction.gpp.CookieSyncGppService; @@ -49,6 +50,7 @@ import org.prebid.server.handler.openrtb2.VideoHandler; import org.prebid.server.health.HealthChecker; import org.prebid.server.health.PeriodicHealthChecker; +import org.prebid.server.hooks.execution.HookStageExecutor; import org.prebid.server.json.JacksonMapper; import org.prebid.server.log.HttpInteractionLogger; import org.prebid.server.metric.Metrics; @@ -210,9 +212,11 @@ org.prebid.server.handler.openrtb2.AuctionHandler openrtbAuctionHandler( AuctionRequestFactory auctionRequestFactory, AnalyticsReporterDelegator analyticsReporter, Metrics metrics, + HooksMetricsService hooksMetricsService, Clock clock, HttpInteractionLogger httpInteractionLogger, PrebidVersionProvider prebidVersionProvider, + HookStageExecutor hookStageExecutor, JacksonMapper mapper) { return new org.prebid.server.handler.openrtb2.AuctionHandler( @@ -222,9 +226,11 @@ org.prebid.server.handler.openrtb2.AuctionHandler openrtbAuctionHandler( skippedAuctionService, analyticsReporter, metrics, + hooksMetricsService, clock, httpInteractionLogger, prebidVersionProvider, + hookStageExecutor, mapper); } @@ -234,12 +240,14 @@ AmpHandler openrtbAmpHandler( ExchangeService exchangeService, AnalyticsReporterDelegator analyticsReporter, Metrics metrics, + HooksMetricsService hooksMetricsService, Clock clock, BidderCatalog bidderCatalog, AmpProperties ampProperties, AmpResponsePostProcessor ampResponsePostProcessor, HttpInteractionLogger httpInteractionLogger, PrebidVersionProvider prebidVersionProvider, + HookStageExecutor hookStageExecutor, JacksonMapper mapper) { return new AmpHandler( @@ -247,12 +255,14 @@ AmpHandler openrtbAmpHandler( exchangeService, analyticsReporter, metrics, + hooksMetricsService, clock, bidderCatalog, ampProperties.getCustomTargetingSet(), ampResponsePostProcessor, httpInteractionLogger, prebidVersionProvider, + hookStageExecutor, mapper, logSamplingRate); } @@ -265,8 +275,10 @@ VideoHandler openrtbVideoHandler( CoreCacheService coreCacheService, AnalyticsReporterDelegator analyticsReporter, Metrics metrics, + HooksMetricsService hooksMetricsService, Clock clock, PrebidVersionProvider prebidVersionProvider, + HookStageExecutor hookStageExecutor, JacksonMapper mapper) { return new VideoHandler( @@ -275,8 +287,10 @@ VideoHandler openrtbVideoHandler( exchangeService, coreCacheService, analyticsReporter, metrics, + hooksMetricsService, clock, prebidVersionProvider, + hookStageExecutor, mapper); } 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 e22a9178e84..3f5866f831b 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java @@ -27,6 +27,7 @@ import org.prebid.server.analytics.reporter.AnalyticsReporterDelegator; import org.prebid.server.auction.AmpResponsePostProcessor; import org.prebid.server.auction.ExchangeService; +import org.prebid.server.auction.HooksMetricsService; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.TimeoutContext; import org.prebid.server.auction.model.debug.DebugContext; @@ -41,34 +42,67 @@ import org.prebid.server.exception.UnauthorizedAccountException; import org.prebid.server.execution.timeout.Timeout; import org.prebid.server.execution.timeout.TimeoutFactory; +import org.prebid.server.hooks.execution.HookStageExecutor; +import org.prebid.server.hooks.execution.model.ExecutionAction; +import org.prebid.server.hooks.execution.model.ExecutionStatus; +import org.prebid.server.hooks.execution.model.GroupExecutionOutcome; +import org.prebid.server.hooks.execution.model.HookExecutionContext; +import org.prebid.server.hooks.execution.model.HookExecutionOutcome; +import org.prebid.server.hooks.execution.model.HookId; +import org.prebid.server.hooks.execution.model.HookStageExecutionResult; +import org.prebid.server.hooks.execution.model.Stage; +import org.prebid.server.hooks.execution.model.StageExecutionOutcome; +import org.prebid.server.hooks.execution.v1.exitpoint.ExitpointPayloadImpl; +import org.prebid.server.hooks.v1.analytics.ActivityImpl; +import org.prebid.server.hooks.v1.analytics.AppliedToImpl; +import org.prebid.server.hooks.v1.analytics.ResultImpl; +import org.prebid.server.hooks.v1.analytics.TagsImpl; import org.prebid.server.log.HttpInteractionLogger; import org.prebid.server.metric.MetricName; import org.prebid.server.metric.Metrics; import org.prebid.server.model.CaseInsensitiveMultiMap; +import org.prebid.server.model.Endpoint; import org.prebid.server.model.HttpRequestContext; import org.prebid.server.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.TraceLevel; +import org.prebid.server.proto.openrtb.ext.response.ExtAnalytics; +import org.prebid.server.proto.openrtb.ext.response.ExtAnalyticsTags; import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; import org.prebid.server.proto.openrtb.ext.response.ExtBidResponse; import org.prebid.server.proto.openrtb.ext.response.ExtBidResponsePrebid; import org.prebid.server.proto.openrtb.ext.response.ExtModules; import org.prebid.server.proto.openrtb.ext.response.ExtModulesTrace; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceAnalyticsActivity; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceAnalyticsAppliedTo; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceAnalyticsResult; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceAnalyticsTags; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceGroup; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceInvocationResult; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceStage; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceStageOutcome; import org.prebid.server.proto.openrtb.ext.response.ExtResponseDebug; +import org.prebid.server.settings.model.Account; +import org.prebid.server.settings.model.AccountAnalyticsConfig; import org.prebid.server.util.HttpUtil; import org.prebid.server.version.PrebidVersionProvider; import java.time.Clock; import java.time.Instant; +import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.function.UnaryOperator; +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.Function.identity; +import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.ArgumentMatchers.any; @@ -104,8 +138,15 @@ public class AmpHandlerTest extends VertxTest { private Clock clock; @Mock private HttpInteractionLogger httpInteractionLogger; + @Mock + private PrebidVersionProvider prebidVersionProvider; + @Mock(strictness = LENIENT) + private HooksMetricsService hooksMetricsService; + @Mock(strictness = LENIENT) + private HookStageExecutor hookStageExecutor; + + private AmpHandler target; - private AmpHandler ampHandler; @Mock private RoutingContext routingContext; @Mock(strictness = LENIENT) @@ -114,8 +155,6 @@ public class AmpHandlerTest extends VertxTest { private HttpServerResponse httpResponse; @Mock(strictness = LENIENT) private UidsCookie uidsCookie; - @Mock - private PrebidVersionProvider prebidVersionProvider; private Timeout timeout; @@ -139,19 +178,28 @@ public void setUp() { given(prebidVersionProvider.getNameVersionRecord()).willReturn("pbs-java/1.00"); + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( + false, + ExitpointPayloadImpl.of(invocation.getArgument(0), invocation.getArgument(1))))); + + given(hooksMetricsService.updateHooksMetrics(any())).willAnswer(invocation -> invocation.getArgument(0)); + timeout = new TimeoutFactory(clock).create(2000L); - ampHandler = new AmpHandler( + target = new AmpHandler( ampRequestFactory, exchangeService, analyticsReporterDelegator, metrics, + hooksMetricsService, clock, bidderCatalog, singleton("bidder1"), new AmpResponsePostProcessor.NoOpAmpResponsePostProcessor(), httpInteractionLogger, prebidVersionProvider, + hookStageExecutor, jacksonMapper, 0); } @@ -165,7 +213,7 @@ public void shouldSetRequestTypeMetricToAuctionContext() { givenHoldAuction(BidResponse.builder().build()); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then final AuctionContext auctionContext = captureAuctionContext(); @@ -181,7 +229,7 @@ public void shouldUseTimeoutFromAuctionContext() { givenHoldAuction(BidResponse.builder().build()); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(captureAuctionContext()) @@ -203,7 +251,7 @@ public void shouldAddPrebidVersionResponseHeader() { .build())); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(httpResponse.headers()) @@ -225,7 +273,7 @@ public void shouldAddObserveBrowsingTopicsResponseHeader() { .build())); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(httpResponse.headers()) @@ -245,7 +293,7 @@ public void shouldComputeTimeoutBasedOnRequestProcessingStartTime() { given(clock.millis()).willReturn(now.toEpochMilli()).willReturn(now.plusMillis(50L).toEpochMilli()); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(captureAuctionContext()) @@ -263,7 +311,7 @@ public void shouldRespondWithBadRequestIfRequestIsInvalid() { .willReturn(Future.failedFuture(new InvalidRequestException("Request is invalid"))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verifyNoInteractions(exchangeService); @@ -276,6 +324,7 @@ public void shouldRespondWithBadRequestIfRequestIsInvalid() { tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), tuple("x-prebid", "pbs-java/1.00")); verify(httpResponse).end(eq("Invalid request format: Request is invalid")); + verifyNoInteractions(hookStageExecutor, hooksMetricsService); } @Test @@ -285,7 +334,7 @@ public void shouldRespondWithBadRequestIfRequestHasBlocklistedAccount() { .willReturn(Future.failedFuture(new BlocklistedAccountException("Blocklisted account"))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verifyNoInteractions(exchangeService); @@ -297,6 +346,7 @@ public void shouldRespondWithBadRequestIfRequestHasBlocklistedAccount() { tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), tuple("x-prebid", "pbs-java/1.00")); verify(httpResponse).end(eq("Blocklisted: Blocklisted account")); + verifyNoInteractions(hookStageExecutor, hooksMetricsService); } @Test @@ -306,7 +356,7 @@ public void shouldRespondWithBadRequestIfRequestHasBlocklistedApp() { .willReturn(Future.failedFuture(new BlocklistedAppException("Blocklisted app"))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verifyNoInteractions(exchangeService); @@ -318,6 +368,7 @@ public void shouldRespondWithBadRequestIfRequestHasBlocklistedApp() { tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), tuple("x-prebid", "pbs-java/1.00")); verify(httpResponse).end(eq("Blocklisted: Blocklisted app")); + verifyNoInteractions(hookStageExecutor, hooksMetricsService); } @Test @@ -327,7 +378,7 @@ public void shouldRespondWithUnauthorizedIfAccountIdIsInvalid() { .willReturn(Future.failedFuture(new UnauthorizedAccountException("Account id is not provided", null))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verifyNoInteractions(exchangeService); @@ -339,6 +390,7 @@ public void shouldRespondWithUnauthorizedIfAccountIdIsInvalid() { tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), tuple("x-prebid", "pbs-java/1.00")); verify(httpResponse).end(eq("Account id is not provided")); + verifyNoInteractions(hookStageExecutor, hooksMetricsService); } @Test @@ -348,7 +400,7 @@ public void shouldRespondWithBadRequestOnInvalidAccountConfigException() { .willReturn(Future.failedFuture(new InvalidAccountConfigException("Account is invalid"))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verifyNoInteractions(exchangeService); @@ -361,6 +413,7 @@ public void shouldRespondWithBadRequestOnInvalidAccountConfigException() { tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), tuple("x-prebid", "pbs-java/1.00")); verify(httpResponse).end(eq("Invalid account configuration: Account is invalid")); + verifyNoInteractions(hookStageExecutor, hooksMetricsService); } @Test @@ -373,7 +426,7 @@ public void shouldRespondWithInternalServerErrorIfAuctionFails() { .willThrow(new RuntimeException("Unexpected exception")); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).setStatusCode(eq(500)); @@ -384,6 +437,7 @@ public void shouldRespondWithInternalServerErrorIfAuctionFails() { tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), tuple("x-prebid", "pbs-java/1.00")); verify(httpResponse).end(eq("Critical error while running the auction: Unexpected exception")); + verifyNoInteractions(hookStageExecutor, hooksMetricsService); } @Test @@ -398,7 +452,7 @@ public void shouldRespondWithInternalServerErrorIfCannotExtractBidTargeting() { givenHoldAuction(givenBidResponse(ext)); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).setStatusCode(eq(500)); @@ -410,6 +464,7 @@ public void shouldRespondWithInternalServerErrorIfCannotExtractBidTargeting() { tuple("x-prebid", "pbs-java/1.00")); verify(httpResponse).end( startsWith("Critical error while running the auction: Critical error while unpacking AMP targets:")); + verifyNoInteractions(hookStageExecutor, hooksMetricsService); } @Test @@ -421,10 +476,11 @@ public void shouldNotSendResponseIfClientClosedConnection() { given(routingContext.response().closed()).willReturn(true); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse, never()).end(anyString()); + verifyNoInteractions(hookStageExecutor, hooksMetricsService); } @Test @@ -442,7 +498,7 @@ public void shouldRespondWithExpectedResponse() { givenHoldAuction(givenBidResponse(mapper.valueToTree(extPrebid))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(httpResponse.headers()).hasSize(4) @@ -453,6 +509,68 @@ public void shouldRespondWithExpectedResponse() { tuple("Content-Type", "application/json"), tuple("x-prebid", "pbs-java/1.00")); verify(httpResponse).end(eq("{\"targeting\":{\"key1\":\"value1\",\"hb_cache_id_bidder1\":\"value2\"}}")); + + final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); + verify(hookStageExecutor).executeExitpointStage( + responseHeadersCaptor.capture(), + eq("{\"targeting\":{\"key1\":\"value1\",\"hb_cache_id_bidder1\":\"value2\"}}"), + any()); + + assertThat(responseHeadersCaptor.getValue()).hasSize(4) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("AMP-Access-Control-Allow-Source-Origin", "http://example.com"), + tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); + + verify(hooksMetricsService).updateHooksMetrics(any()); + } + + @Test + public void shouldRespondWithExpectedResponseWhenExitpointHookChangesResponseAndHeaders() { + // given + given(ampRequestFactory.fromRequest(any(), anyLong())) + .willReturn(Future.succeededFuture(givenAuctionContext(identity()))); + + final Map targeting = new HashMap<>(); + targeting.put("key1", "value1"); + targeting.put("hb_cache_id_bidder1", "value2"); + final ExtPrebid extPrebid = ExtPrebid.of( + ExtBidPrebid.builder().targeting(targeting).build(), + null); + givenHoldAuction(givenBidResponse(mapper.valueToTree(extPrebid))); + + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willReturn(Future.succeededFuture(HookStageExecutionResult.success( + ExitpointPayloadImpl.of( + MultiMap.caseInsensitiveMultiMap().add("New-Header", "New-Header-Value"), + "{\"targeting\":{\"new-key\":\"new-value\"}}")))); + + // when + target.handle(routingContext); + + // then + assertThat(httpResponse.headers()).hasSize(1) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly(tuple("New-Header", "New-Header-Value")); + verify(httpResponse).end(eq("{\"targeting\":{\"new-key\":\"new-value\"}}")); + + final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); + verify(hookStageExecutor).executeExitpointStage( + responseHeadersCaptor.capture(), + eq("{\"targeting\":{\"key1\":\"value1\",\"hb_cache_id_bidder1\":\"value2\"}}"), + any()); + + assertThat(responseHeadersCaptor.getValue()).hasSize(4) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("AMP-Access-Control-Allow-Source-Origin", "http://example.com"), + tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); + + verify(hooksMetricsService).updateHooksMetrics(any()); } @Test @@ -485,11 +603,18 @@ public void shouldRespondWithCustomTargetingIncluded() { willReturn(bidder).given(bidderCatalog).bidderByName(anyString()); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).end(eq("{\"targeting\":{\"key1\":\"value1\",\"rpfl_11078\":\"15_tier0030\"," + "\"hb_cache_id_bidder1\":\"value2\"}}")); + verify(hookStageExecutor).executeExitpointStage( + any(), + eq("{\"targeting\":{\"key1\":\"value1\",\"rpfl_11078\":\"15_tier0030\"," + + "\"hb_cache_id_bidder1\":\"value2\"}}"), + any()); + + verify(hooksMetricsService).updateHooksMetrics(any()); } @Test @@ -524,10 +649,15 @@ public void shouldRespondWithAdditionalTargetingIncludedWhenSeatBidExists() { .build()); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).end(eq("{\"targeting\":{\"key\":\"value\",\"test-key\":\"test-value\"}}")); + verify(hookStageExecutor).executeExitpointStage( + any(), + eq("{\"targeting\":{\"key\":\"value\",\"test-key\":\"test-value\"}}"), + any()); + verify(hooksMetricsService).updateHooksMetrics(any()); } @Test @@ -547,10 +677,15 @@ public void shouldRespondWithAdditionalTargetingIncludedWhenNoSeatBidExists() { givenHoldAuction(givenBidResponseWithExt(ExtBidResponse.builder().prebid(extBidResponsePrebid).build())); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).end(eq("{\"targeting\":{\"key\":\"value\",\"test-key\":\"test-value\"}}")); + verify(hookStageExecutor).executeExitpointStage( + any(), + eq("{\"targeting\":{\"key\":\"value\",\"test-key\":\"test-value\"}}"), + any()); + verify(hooksMetricsService).updateHooksMetrics(any()); } @Test @@ -569,12 +704,19 @@ public void shouldRespondWithDebugInfoIncludedIfTestFlagIsTrue() { .build())); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).end(eq( "{\"targeting\":{}," + "\"ext\":{\"debug\":{\"resolvedrequest\":{\"id\":\"reqId1\",\"imp\":[],\"tmax\":5000}}}}")); + verify(hookStageExecutor).executeExitpointStage( + any(), + eq("{\"targeting\":{}," + + "\"ext\":{\"debug\":{\"resolvedrequest\":{\"id\":\"reqId1\",\"imp\":[],\"tmax\":5000}}}}"), + any()); + verify(hooksMetricsService).updateHooksMetrics(any()); + } @Test @@ -597,7 +739,7 @@ public void shouldRespondWithHooksDebugAndTraceOutput() { .build())); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).end(eq( @@ -606,6 +748,15 @@ public void shouldRespondWithHooksDebugAndTraceOutput() { + "\"errors\":{\"module1\":{\"hook1\":[\"error1\"]}}," + "\"warnings\":{\"module1\":{\"hook1\":[\"warning1\"]}}," + "\"trace\":{\"executiontimemillis\":2,\"stages\":[]}}}}}")); + verify(hookStageExecutor).executeExitpointStage( + any(), + eq("{\"targeting\":{}," + + "\"ext\":{\"prebid\":{\"modules\":{" + + "\"errors\":{\"module1\":{\"hook1\":[\"error1\"]}}," + + "\"warnings\":{\"module1\":{\"hook1\":[\"warning1\"]}}," + + "\"trace\":{\"executiontimemillis\":2,\"stages\":[]}}}}}"), + any()); + verify(hooksMetricsService).updateHooksMetrics(any()); } @Test @@ -618,7 +769,7 @@ public void shouldIncrementOkAmpRequestMetrics() { ExtPrebid.of(ExtBidPrebid.builder().build(), null)))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.amp), eq(MetricName.ok)); @@ -634,7 +785,7 @@ public void shouldIncrementAppRequestMetrics() { ExtPrebid.of(ExtBidPrebid.builder().build(), null)))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateAppAndNoCookieAndImpsRequestedMetrics(eq(true), anyBoolean(), anyInt()); @@ -655,7 +806,7 @@ public void shouldIncrementNoCookieMetrics() { + "AppleWebKit/601.7.7 (KHTML, like Gecko) Version/9.1.2 Safari/601.7.7"); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateAppAndNoCookieAndImpsRequestedMetrics(eq(false), eq(false), anyInt()); @@ -672,7 +823,7 @@ public void shouldIncrementImpsRequestedMetrics() { ExtPrebid.of(ExtBidPrebid.builder().build(), null)))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateAppAndNoCookieAndImpsRequestedMetrics(anyBoolean(), anyBoolean(), eq(1)); @@ -690,7 +841,7 @@ public void shouldIncrementImpsTypesMetrics() { ExtPrebid.of(ExtBidPrebid.builder().build(), null)))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateImpTypesMetrics(same(imps)); @@ -703,7 +854,7 @@ public void shouldIncrementBadinputAmpRequestMetrics() { .willReturn(Future.failedFuture(new InvalidRequestException("Request is invalid"))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.amp), eq(MetricName.badinput)); @@ -716,7 +867,7 @@ public void shouldIncrementErrAmpRequestMetrics() { .willReturn(Future.failedFuture(new RuntimeException())); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.amp), eq(MetricName.err)); @@ -741,7 +892,7 @@ public void shouldUpdateRequestTimeMetric() { }); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTimeMetric(eq(MetricName.request_time), eq(500L)); @@ -754,7 +905,7 @@ public void shouldNotUpdateRequestTimeMetricIfRequestFails() { .willReturn(Future.failedFuture(new InvalidRequestException("Request is invalid"))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse, never()).endHandler(any()); @@ -777,7 +928,7 @@ public void shouldUpdateNetworkErrorMetric() { }); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.amp), eq(MetricName.networkerr)); @@ -793,7 +944,7 @@ public void shouldNotUpdateNetworkErrorMetricIfResponseSucceeded() { ExtPrebid.of(ExtBidPrebid.builder().build(), null)))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics, never()).updateRequestTypeMetric(eq(MetricName.amp), eq(MetricName.networkerr)); @@ -811,7 +962,7 @@ public void shouldUpdateNetworkErrorMetricIfClientClosedConnection() { given(routingContext.response().closed()).willReturn(true); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.amp), eq(MetricName.networkerr)); @@ -824,7 +975,7 @@ public void shouldPassBadRequestEventToAnalyticsReporterIfBidRequestIsInvalid() .willReturn(Future.failedFuture(new InvalidRequestException("Request is invalid"))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then final AmpEvent ampEvent = captureAmpEvent(); @@ -834,6 +985,8 @@ public void shouldPassBadRequestEventToAnalyticsReporterIfBidRequestIsInvalid() .status(400) .errors(singletonList("Invalid request format: Request is invalid")) .build()); + + verifyNoInteractions(hookStageExecutor, hooksMetricsService); } @Test @@ -847,7 +1000,7 @@ public void shouldPassInternalServerErrorEventToAnalyticsReporterIfAuctionFails( .willThrow(new RuntimeException("Unexpected exception")); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then final AmpEvent ampEvent = captureAmpEvent(); @@ -862,6 +1015,8 @@ public void shouldPassInternalServerErrorEventToAnalyticsReporterIfAuctionFails( .status(500) .errors(singletonList("Unexpected exception")) .build()); + + verifyNoInteractions(hookStageExecutor, hooksMetricsService); } @Test @@ -876,7 +1031,7 @@ public void shouldPassSuccessfulEventToAnalyticsReporter() { null)))); // when - ampHandler.handle(routingContext); + target.handle(routingContext); // then final AmpEvent ampEvent = captureAmpEvent(); @@ -889,33 +1044,334 @@ public void shouldPassSuccessfulEventToAnalyticsReporter() { .build())) .build())) .build(); - final AuctionContext expectedAuctionContext = auctionContext.toBuilder() - .requestTypeMetric(MetricName.amp) - .bidResponse(expectedBidResponse) + + assertThat(ampEvent.getHttpContext()).isEqualTo(givenHttpContext(singletonMap("Origin", "http://example.com"))); + assertThat(ampEvent.getBidResponse()).isEqualTo(expectedBidResponse); + assertThat(ampEvent.getTargeting()) + .isEqualTo(singletonMap("hb_cache_id_bidder1", TextNode.valueOf("value1"))); + assertThat(ampEvent.getOrigin()).isEqualTo("http://example.com"); + assertThat(ampEvent.getStatus()).isEqualTo(200); + assertThat(ampEvent.getAuctionContext().getRequestTypeMetric()).isEqualTo(MetricName.amp); + assertThat(ampEvent.getAuctionContext().getBidResponse()).isEqualTo(expectedBidResponse); + assertThat(ampEvent.getAuctionContext().getRawAuctionResponse().getResponseBody()) + .isEqualTo("{\"targeting\":{\"hb_cache_id_bidder1\":\"value1\"}}"); + assertThat(ampEvent.getAuctionContext().getRawAuctionResponse().getResponseHeaders()).hasSize(4) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("AMP-Access-Control-Allow-Source-Origin", "http://example.com"), + tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); + + final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); + verify(hookStageExecutor).executeExitpointStage( + responseHeadersCaptor.capture(), + eq("{\"targeting\":{\"hb_cache_id_bidder1\":\"value1\"}}"), + any()); + + assertThat(responseHeadersCaptor.getValue()).hasSize(4) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("AMP-Access-Control-Allow-Source-Origin", "http://example.com"), + tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); + + verify(hooksMetricsService).updateHooksMetrics(any()); + } + + @Test + public void shouldPassSuccessfulEventToAnalyticsReporterWhenExitpointHookChangesResponseAndHeaders() { + // given + final AuctionContext auctionContext = givenAuctionContext(identity()); + given(ampRequestFactory.fromRequest(any(), anyLong())) + .willReturn(Future.succeededFuture(auctionContext)); + + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willReturn(Future.succeededFuture(HookStageExecutionResult.success( + ExitpointPayloadImpl.of( + MultiMap.caseInsensitiveMultiMap().add("New-Header", "New-Header-Value"), + "{\"targeting\":{\"new-key\":\"new-value\"}}")))); + + givenHoldAuction(givenBidResponse(mapper.valueToTree( + ExtPrebid.of(ExtBidPrebid.builder().targeting(singletonMap("hb_cache_id_bidder1", "value1")).build(), + null)))); + + // when + target.handle(routingContext); + + // then + final AmpEvent ampEvent = captureAmpEvent(); + final BidResponse expectedBidResponse = BidResponse.builder().seatbid(singletonList(SeatBid.builder() + .bid(singletonList(Bid.builder() + .ext(mapper.valueToTree(ExtPrebid.of( + ExtBidPrebid.builder().targeting(singletonMap("hb_cache_id_bidder1", "value1")) + .build(), + null))) + .build())) + .build())) .build(); - assertThat(ampEvent).isEqualTo(AmpEvent.builder() - .httpContext(givenHttpContext(singletonMap("Origin", "http://example.com"))) - .auctionContext(expectedAuctionContext) - .bidResponse(expectedBidResponse) - .targeting(singletonMap("hb_cache_id_bidder1", TextNode.valueOf("value1"))) - .origin("http://example.com") - .status(200) - .errors(emptyList()) - .build()); + assertThat(ampEvent.getAuctionContext().getBidResponse()).isEqualTo(expectedBidResponse); + assertThat(ampEvent.getAuctionContext().getRawAuctionResponse().getResponseBody()) + .isEqualTo("{\"targeting\":{\"new-key\":\"new-value\"}}"); + assertThat(ampEvent.getAuctionContext().getRawAuctionResponse().getResponseHeaders()).hasSize(1) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly(tuple("New-Header", "New-Header-Value")); + + final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); + verify(hookStageExecutor).executeExitpointStage( + responseHeadersCaptor.capture(), + eq("{\"targeting\":{\"hb_cache_id_bidder1\":\"value1\"}}"), + any()); + + assertThat(responseHeadersCaptor.getValue()).hasSize(4) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("AMP-Access-Control-Allow-Source-Origin", "http://example.com"), + tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); + + verify(hooksMetricsService).updateHooksMetrics(any()); + } + + @Test + public void shouldReturnSendAmpEventWithAuctionContextBidResponseDebugInfoHoldingExitpointHookOutcome() { + // given + final AuctionContext auctionContext = givenAuctionContext(identity()).toBuilder() + .hookExecutionContext(HookExecutionContext.of( + Endpoint.openrtb2_amp, + stageOutcomes())) + .build(); + + given(ampRequestFactory.fromRequest(any(), anyLong())) + .willReturn(Future.succeededFuture(auctionContext)); + + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willAnswer(invocation -> { + final AuctionContext context = invocation.getArgument(2, AuctionContext.class); + final HookExecutionContext hookExecutionContext = context.getHookExecutionContext(); + hookExecutionContext.getStageOutcomes().put(Stage.exitpoint, singletonList(StageExecutionOutcome.of( + "http-response", + singletonList( + GroupExecutionOutcome.of(singletonList( + HookExecutionOutcome.builder() + .hookId(HookId.of("exitpoint-module", "exitpoint-hook")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("exitpoint hook has been executed") + .action(ExecutionAction.update) + .analyticsTags(TagsImpl.of(singletonList( + ActivityImpl.of( + "some-activity", + "success", + singletonList(ResultImpl.of( + "success", + mapper.createObjectNode(), + givenAppliedToImpl())))))) + .build())))))); + return Future.succeededFuture(HookStageExecutionResult.success( + ExitpointPayloadImpl.of(invocation.getArgument(0), invocation.getArgument(1)))); + }); + + givenHoldAuction(givenBidResponse(mapper.valueToTree( + ExtPrebid.of(ExtBidPrebid.builder().targeting(singletonMap("hb_cache_id_bidder1", "value1")).build(), + null)))); + + + // when + target.handle(routingContext); + + // then + final AmpEvent ampEvent = captureAmpEvent(); + final BidResponse bidResponseBeforeHook = ampEvent.getBidResponse(); + assertThat(bidResponseBeforeHook.getExt()).isNull(); + + final BidResponse bidResponseAfterHook = ampEvent.getAuctionContext().getBidResponse(); + assertThat(bidResponseAfterHook.getExt().getPrebid().getModules().getTrace()).isEqualTo(ExtModulesTrace.of( + 8L, + List.of( + ExtModulesTraceStage.of( + Stage.auction_response, + 4L, + singletonList(ExtModulesTraceStageOutcome.of( + "auction-response", + 4L, + singletonList( + ExtModulesTraceGroup.of( + 4L, + asList( + ExtModulesTraceInvocationResult.builder() + .hookId(HookId.of("module1", "hook1")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("module1 hook1") + .action(ExecutionAction.update) + .build(), + ExtModulesTraceInvocationResult.builder() + .hookId(HookId.of("module1", "hook2")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("module1 hook2") + .action(ExecutionAction.no_action) + .build())))))), + + ExtModulesTraceStage.of( + Stage.exitpoint, + 4L, + singletonList(ExtModulesTraceStageOutcome.of( + "http-response", + 4L, + singletonList( + ExtModulesTraceGroup.of( + 4L, + singletonList( + ExtModulesTraceInvocationResult.builder() + .hookId(HookId.of("exitpoint-module", "exitpoint-hook")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("exitpoint hook has been executed") + .action(ExecutionAction.update) + .analyticsTags(ExtModulesTraceAnalyticsTags.of(singletonList( + ExtModulesTraceAnalyticsActivity.of( + "some-activity", + "success", + singletonList(ExtModulesTraceAnalyticsResult.of( + "success", + mapper.createObjectNode(), + givenExtModulesTraceAnalyticsAppliedTo())))))) + .build()))))))))); + } + + @Test + public void shouldReturnSendAmpEventWithAuctionContextBidResponseAnalyticsTagsHoldingExitpointHookOutcome() { + // given + final ObjectNode analyticsNode = mapper.createObjectNode(); + final ObjectNode optionsNode = analyticsNode.putObject("options"); + optionsNode.put("enableclientdetails", true); + + final AuctionContext auctionContext = givenAuctionContext( + request -> request.ext(ExtRequest.of(ExtRequestPrebid.builder() + .analytics(analyticsNode) + .build()))).toBuilder() + .hookExecutionContext(HookExecutionContext.of( + Endpoint.openrtb2_amp, + stageOutcomes())) + .build(); + + given(ampRequestFactory.fromRequest(any(), anyLong())) + .willReturn(Future.succeededFuture(auctionContext)); + + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willAnswer(invocation -> { + final AuctionContext context = invocation.getArgument(2, AuctionContext.class); + final HookExecutionContext hookExecutionContext = context.getHookExecutionContext(); + hookExecutionContext.getStageOutcomes().put(Stage.exitpoint, singletonList(StageExecutionOutcome.of( + "http-response", + singletonList( + GroupExecutionOutcome.of(singletonList( + HookExecutionOutcome.builder() + .hookId(HookId.of("exitpoint-module", "exitpoint-hook")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("exitpoint hook has been executed") + .action(ExecutionAction.update) + .analyticsTags(TagsImpl.of(singletonList( + ActivityImpl.of( + "some-activity", + "success", + singletonList(ResultImpl.of( + "success", + mapper.createObjectNode(), + givenAppliedToImpl())))))) + .build())))))); + return Future.succeededFuture(HookStageExecutionResult.success( + ExitpointPayloadImpl.of(invocation.getArgument(0), invocation.getArgument(1)))); + }); + + givenHoldAuction(givenBidResponse(mapper.valueToTree( + ExtPrebid.of(ExtBidPrebid.builder().targeting(singletonMap("hb_cache_id_bidder1", "value1")).build(), + null)))); + + + // when + target.handle(routingContext); + + // then + final AmpEvent ampEvent = captureAmpEvent(); + final BidResponse bidResponseBeforeHook = ampEvent.getBidResponse(); + assertThat(bidResponseBeforeHook.getExt()).isNull(); + + final BidResponse bidResponseAfterHook = ampEvent.getAuctionContext().getBidResponse(); + assertThat(bidResponseAfterHook.getExt()) + .extracting(ExtBidResponse::getPrebid) + .extracting(ExtBidResponsePrebid::getAnalytics) + .extracting(ExtAnalytics::getTags) + .asInstanceOf(InstanceOfAssertFactories.list(ExtAnalyticsTags.class)) + .hasSize(1) + .allSatisfy(extAnalyticsTags -> { + assertThat(extAnalyticsTags.getStage()).isEqualTo(Stage.exitpoint); + assertThat(extAnalyticsTags.getModule()).isEqualTo("exitpoint-module"); + assertThat(extAnalyticsTags.getAnalyticsTags()).isNotNull(); + }); + } + + private static AppliedToImpl givenAppliedToImpl() { + return AppliedToImpl.builder() + .impIds(asList("impId1", "impId2")) + .request(true) + .build(); + } + + private static ExtModulesTraceAnalyticsAppliedTo givenExtModulesTraceAnalyticsAppliedTo() { + return ExtModulesTraceAnalyticsAppliedTo.builder() + .impIds(asList("impId1", "impId2")) + .request(true) + .build(); + } + + private static EnumMap> stageOutcomes() { + final Map> stageOutcomes = new HashMap<>(); + + stageOutcomes.put(Stage.auction_response, singletonList(StageExecutionOutcome.of( + "auction-response", + singletonList( + GroupExecutionOutcome.of(asList( + HookExecutionOutcome.builder() + .hookId(HookId.of("module1", "hook1")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("module1 hook1") + .action(ExecutionAction.update) + .build(), + HookExecutionOutcome.builder() + .hookId(HookId.of("module1", "hook2")) + .executionTime(4L) + .message("module1 hook2") + .status(ExecutionStatus.success) + .action(ExecutionAction.no_action) + .build())))))); + + return new EnumMap<>(stageOutcomes); } private AuctionContext givenAuctionContext( - Function bidRequestBuilderCustomizer) { + UnaryOperator bidRequestBuilderCustomizer) { + final BidRequest bidRequest = bidRequestBuilderCustomizer.apply(BidRequest.builder() .imp(emptyList()).tmax(5000L)).build(); return AuctionContext.builder() + .account(Account.builder() + .analytics(AccountAnalyticsConfig.of(true, null, null)) + .build()) .uidsCookie(uidsCookie) .bidRequest(bidRequest) .requestTypeMetric(MetricName.amp) .timeoutContext(TimeoutContext.of(0, timeout, 0)) - .debugContext(DebugContext.empty()) + .debugContext(DebugContext.of(true, false, TraceLevel.verbose)) + .hookExecutionContext(HookExecutionContext.of(Endpoint.openrtb2_amp)) .build(); } @@ -924,7 +1380,6 @@ private void givenHoldAuction(BidResponse bidResponse) { .willAnswer(inv -> Future.succeededFuture(((AuctionContext) inv.getArgument(0)).toBuilder() .bidResponse(bidResponse) .build())); - } private static BidResponse givenBidResponse(ObjectNode extBid) { 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 7ff40d09899..141883ac492 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java @@ -1,9 +1,13 @@ package org.prebid.server.handler.openrtb2; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; +import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.MultiMap; @@ -18,12 +22,15 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.VertxTest; +import org.prebid.server.analytics.model.AmpEvent; import org.prebid.server.analytics.model.AuctionEvent; import org.prebid.server.analytics.reporter.AnalyticsReporterDelegator; import org.prebid.server.auction.ExchangeService; +import org.prebid.server.auction.HooksMetricsService; import org.prebid.server.auction.SkippedAuctionService; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.TimeoutContext; +import org.prebid.server.auction.model.debug.DebugContext; import org.prebid.server.auction.requestfactory.AuctionRequestFactory; import org.prebid.server.cookie.UidsCookie; import org.prebid.server.exception.BlocklistedAccountException; @@ -33,31 +40,68 @@ import org.prebid.server.exception.UnauthorizedAccountException; import org.prebid.server.execution.timeout.Timeout; import org.prebid.server.execution.timeout.TimeoutFactory; +import org.prebid.server.hooks.execution.HookStageExecutor; +import org.prebid.server.hooks.execution.model.ExecutionAction; +import org.prebid.server.hooks.execution.model.ExecutionStatus; +import org.prebid.server.hooks.execution.model.GroupExecutionOutcome; +import org.prebid.server.hooks.execution.model.HookExecutionContext; +import org.prebid.server.hooks.execution.model.HookExecutionOutcome; +import org.prebid.server.hooks.execution.model.HookId; +import org.prebid.server.hooks.execution.model.HookStageExecutionResult; +import org.prebid.server.hooks.execution.model.Stage; +import org.prebid.server.hooks.execution.model.StageExecutionOutcome; +import org.prebid.server.hooks.execution.v1.exitpoint.ExitpointPayloadImpl; +import org.prebid.server.hooks.v1.analytics.ActivityImpl; +import org.prebid.server.hooks.v1.analytics.AppliedToImpl; +import org.prebid.server.hooks.v1.analytics.ResultImpl; +import org.prebid.server.hooks.v1.analytics.TagsImpl; import org.prebid.server.log.HttpInteractionLogger; import org.prebid.server.metric.MetricName; import org.prebid.server.metric.Metrics; import org.prebid.server.model.CaseInsensitiveMultiMap; +import org.prebid.server.model.Endpoint; import org.prebid.server.model.HttpRequestContext; +import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtGranularityRange; import org.prebid.server.proto.openrtb.ext.request.ExtMediaTypePriceGranularity; import org.prebid.server.proto.openrtb.ext.request.ExtPriceGranularity; import org.prebid.server.proto.openrtb.ext.request.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtRequestTargeting; +import org.prebid.server.proto.openrtb.ext.request.TraceLevel; +import org.prebid.server.proto.openrtb.ext.response.ExtAnalytics; +import org.prebid.server.proto.openrtb.ext.response.ExtAnalyticsTags; +import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; import org.prebid.server.proto.openrtb.ext.response.ExtBidResponse; +import org.prebid.server.proto.openrtb.ext.response.ExtBidResponsePrebid; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTrace; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceAnalyticsActivity; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceAnalyticsAppliedTo; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceAnalyticsResult; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceAnalyticsTags; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceGroup; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceInvocationResult; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceStage; +import org.prebid.server.proto.openrtb.ext.response.ExtModulesTraceStageOutcome; import org.prebid.server.proto.openrtb.ext.response.ExtResponseDebug; +import org.prebid.server.settings.model.Account; +import org.prebid.server.settings.model.AccountAnalyticsConfig; import org.prebid.server.util.HttpUtil; import org.prebid.server.version.PrebidVersionProvider; import java.math.BigDecimal; import java.time.Clock; import java.time.Instant; +import java.util.EnumMap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.UnaryOperator; +import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; @@ -93,8 +137,12 @@ public class AuctionHandlerTest extends VertxTest { private HttpInteractionLogger httpInteractionLogger; @Mock private PrebidVersionProvider prebidVersionProvider; + @Mock(strictness = LENIENT) + private HooksMetricsService hooksMetricsService; + @Mock(strictness = LENIENT) + private HookStageExecutor hookStageExecutor; - private AuctionHandler auctionHandler; + private AuctionHandler target; @Mock private RoutingContext routingContext; @Mock @@ -124,18 +172,27 @@ public void setUp() { given(prebidVersionProvider.getNameVersionRecord()).willReturn("pbs-java/1.00"); + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( + false, + ExitpointPayloadImpl.of(invocation.getArgument(0), invocation.getArgument(1))))); + + given(hooksMetricsService.updateHooksMetrics(any())).willAnswer(invocation -> invocation.getArgument(0)); + timeout = new TimeoutFactory(clock).create(2000L); - auctionHandler = new AuctionHandler( + target = new AuctionHandler( 0.01, auctionRequestFactory, exchangeService, skippedAuctionService, analyticsReporterDelegator, metrics, + hooksMetricsService, clock, httpInteractionLogger, prebidVersionProvider, + hookStageExecutor, jacksonMapper); } @@ -150,7 +207,7 @@ public void shouldSetRequestTypeMetricToAuctionContext() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then final AuctionContext auctionContext = captureAuctionContext(); @@ -168,7 +225,7 @@ public void shouldUseTimeoutFromAuctionContext() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(captureAuctionContext()) @@ -194,7 +251,7 @@ public void shouldAddPrebidVersionResponseHeader() { .build())); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(httpResponse.headers()) @@ -218,7 +275,7 @@ public void shouldAddObserveBrowsingTopicsResponseHeader() { .build())); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(httpResponse.headers()) @@ -240,7 +297,7 @@ public void shouldComputeTimeoutBasedOnRequestProcessingStartTime() { given(clock.millis()).willReturn(now.toEpochMilli()).willReturn(now.plusMillis(50L).toEpochMilli()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(captureAuctionContext()) @@ -260,13 +317,14 @@ public void shouldRespondWithServiceUnavailableIfBidRequestHasAccountBlocklisted .willReturn(Future.failedFuture(new BlocklistedAccountException("Blocklisted account"))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).setStatusCode(eq(403)); verify(httpResponse).end(eq("Blocklisted: Blocklisted account")); verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.blocklisted_account)); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -278,13 +336,14 @@ public void shouldRespondWithBadRequestIfBidRequestHasAccountWithInvalidConfig() .willReturn(Future.failedFuture(new InvalidAccountConfigException("Invalid config"))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).setStatusCode(eq(400)); verify(httpResponse).end(eq("Invalid config")); verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.bad_requests)); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -296,13 +355,14 @@ public void shouldRespondWithServiceUnavailableIfBidRequestHasAppBlocklisted() { .willReturn(Future.failedFuture(new BlocklistedAppException("Blocklisted app"))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).setStatusCode(eq(403)); verify(httpResponse).end(eq("Blocklisted: Blocklisted app")); verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.blocklisted_app)); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -314,13 +374,14 @@ public void shouldRespondWithBadRequestIfBidRequestIsInvalid() { .willReturn(Future.failedFuture(new InvalidRequestException("Request is invalid"))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).setStatusCode(eq(400)); verify(httpResponse).end(eq("Invalid request format: Request is invalid")); verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.badinput)); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -332,12 +393,13 @@ public void shouldRespondWithUnauthorizedIfAccountIdIsInvalid() { .willReturn(Future.failedFuture(new UnauthorizedAccountException("Account id is not provided", null))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verifyNoInteractions(exchangeService); verify(httpResponse).setStatusCode(eq(401)); verify(httpResponse).end(eq("Account id is not provided")); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -352,13 +414,14 @@ public void shouldRespondWithInternalServerErrorIfAuctionFails() { .willThrow(new RuntimeException("Unexpected exception")); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).setStatusCode(eq(500)); verify(httpResponse).end(eq("Critical error while running the auction: Unexpected exception")); verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.err)); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -372,28 +435,26 @@ public void shouldNotSendResponseIfClientClosedConnection() { given(routingContext.response().closed()).willReturn(true); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse, never()).end(anyString()); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test public void shouldRespondWithBidResponse() { // given + final AuctionContext auctionContext = givenAuctionContext(identity()); given(auctionRequestFactory.parseRequest(any(), anyLong())) - .willReturn(Future.succeededFuture(givenAuctionContext(identity()))); + .willReturn(Future.succeededFuture(auctionContext)); given(auctionRequestFactory.enrichAuctionContext(any())) .willAnswer(invocation -> Future.succeededFuture(invocation.getArgument(0))); - - final AuctionContext auctionContext = AuctionContext.builder() - .bidResponse(BidResponse.builder().build()) - .build(); given(exchangeService.holdAuction(any())) - .willReturn(Future.succeededFuture(auctionContext)); + .willReturn(Future.succeededFuture(auctionContext.with(BidResponse.builder().build()))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(exchangeService).holdAuction(any()); @@ -404,13 +465,70 @@ public void shouldRespondWithBidResponse() { tuple("x-prebid", "pbs-java/1.00")); verify(httpResponse).end(eq("{}")); + + final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); + verify(hookStageExecutor).executeExitpointStage( + responseHeadersCaptor.capture(), + eq("{}"), + any()); + + assertThat(responseHeadersCaptor.getValue()).hasSize(2) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); + + verify(hooksMetricsService).updateHooksMetrics(any()); + } + + @Test + public void shouldRespondWithBidResponseWhenExitpointChangesHeadersAndResponse() { + // given + final AuctionContext auctionContext = givenAuctionContext(identity()); + given(auctionRequestFactory.parseRequest(any(), anyLong())) + .willReturn(Future.succeededFuture(auctionContext)); + given(auctionRequestFactory.enrichAuctionContext(any())) + .willAnswer(invocation -> Future.succeededFuture(invocation.getArgument(0))); + given(exchangeService.holdAuction(any())) + .willReturn(Future.succeededFuture(auctionContext.with(BidResponse.builder().build()))); + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willReturn(Future.succeededFuture(HookStageExecutionResult.success( + ExitpointPayloadImpl.of( + MultiMap.caseInsensitiveMultiMap().add("New-Header", "New-Header-Value"), + "{\"response\":{}}")))); + + // when + target.handle(routingContext); + + // then + verify(exchangeService).holdAuction(any()); + assertThat(httpResponse.headers()).hasSize(1) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsExactlyInAnyOrder(tuple("New-Header", "New-Header-Value")); + + verify(httpResponse).end(eq("{\"response\":{}}")); + + final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); + verify(hookStageExecutor).executeExitpointStage( + responseHeadersCaptor.capture(), + eq("{}"), + any()); + + assertThat(responseHeadersCaptor.getValue()).hasSize(2) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); + + verify(hooksMetricsService).updateHooksMetrics(any()); } @Test public void shouldRespondWithCorrectResolvedRequestMediaTypePriceGranularity() { // given + final AuctionContext auctionContext = givenAuctionContext(identity()); given(auctionRequestFactory.parseRequest(any(), anyLong())) - .willReturn(Future.succeededFuture(givenAuctionContext(identity()))); + .willReturn(Future.succeededFuture(auctionContext)); given(auctionRequestFactory.enrichAuctionContext(any())) .willAnswer(invocation -> Future.succeededFuture(invocation.getArgument(0))); @@ -430,20 +548,26 @@ public void shouldRespondWithCorrectResolvedRequestMediaTypePriceGranularity() { .debug(ExtResponseDebug.of(null, resolvedRequest, null)) .build()) .build(); - final AuctionContext auctionContext = AuctionContext.builder() - .bidResponse(bidResponse) - .build(); given(exchangeService.holdAuction(any())) - .willReturn(Future.succeededFuture(auctionContext)); + .willReturn(Future.succeededFuture(auctionContext.with(bidResponse))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(exchangeService).holdAuction(any()); verify(httpResponse).end(eq("{\"ext\":{\"debug\":{\"resolvedrequest\":{\"ext\":{\"prebid\":" + "{\"targeting\":{\"mediatypepricegranularity\":{\"banner\":{\"precision\":1,\"ranges\":" + "[{\"max\":10,\"increment\":1}]},\"native\":{}}},\"auctiontimestamp\":0}}}}}}")); + + verify(hookStageExecutor).executeExitpointStage( + any(), + eq("{\"ext\":{\"debug\":{\"resolvedrequest\":{\"ext\":{\"prebid\":" + + "{\"targeting\":{\"mediatypepricegranularity\":{\"banner\":{\"precision\":1,\"ranges\":" + + "[{\"max\":10,\"increment\":1}]},\"native\":{}}},\"auctiontimestamp\":0}}}}}}"), + any()); + + verify(hooksMetricsService).updateHooksMetrics(any()); } @Test @@ -457,7 +581,7 @@ public void shouldIncrementOkOpenrtb2WebRequestMetrics() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.ok)); @@ -475,7 +599,7 @@ public void shouldIncrementOkOpenrtb2AppRequestMetrics() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2app), eq(MetricName.ok)); @@ -492,7 +616,7 @@ public void shouldIncrementAppRequestMetrics() { .willReturn(Future.succeededFuture(givenAuctionContext(builder -> builder.app(App.builder().build())))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateAppAndNoCookieAndImpsRequestedMetrics(eq(true), anyBoolean(), anyInt()); @@ -514,7 +638,7 @@ public void shouldIncrementNoCookieMetrics() { + "AppleWebKit/601.7.7 (KHTML, like Gecko) Version/9.1.2 Safari/601.7.7"); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateAppAndNoCookieAndImpsRequestedMetrics(eq(false), eq(false), anyInt()); @@ -532,7 +656,7 @@ public void shouldIncrementImpsRequestedMetrics() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateAppAndNoCookieAndImpsRequestedMetrics(anyBoolean(), anyBoolean(), eq(1)); @@ -551,7 +675,7 @@ public void shouldIncrementImpTypesMetrics() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateImpTypesMetrics(same(imps)); @@ -564,7 +688,7 @@ public void shouldIncrementBadinputOnParsingRequestOpenrtb2WebRequestMetrics() { .willReturn(Future.failedFuture(new InvalidRequestException("Request is invalid"))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.badinput)); @@ -577,7 +701,7 @@ public void shouldIncrementErrOpenrtb2WebRequestMetrics() { .willReturn(Future.failedFuture(new RuntimeException())); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.err)); @@ -604,7 +728,7 @@ public void shouldUpdateRequestTimeMetric() { }); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTimeMetric(eq(MetricName.request_time), eq(500L)); @@ -617,7 +741,7 @@ public void shouldNotUpdateRequestTimeMetricIfRequestFails() { .willReturn(Future.failedFuture(new InvalidRequestException("Request is invalid"))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse, never()).endHandler(any()); @@ -641,7 +765,7 @@ public void shouldUpdateNetworkErrorMetric() { }); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.networkerr)); @@ -658,7 +782,7 @@ public void shouldNotUpdateNetworkErrorMetricIfResponseSucceeded() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics, never()).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.networkerr)); @@ -677,7 +801,7 @@ public void shouldUpdateNetworkErrorMetricIfClientClosedConnection() { given(routingContext.response().closed()).willReturn(true); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.networkerr)); @@ -690,7 +814,7 @@ public void shouldPassBadRequestEventToAnalyticsReporterIfBidRequestIsInvalid() .willReturn(Future.failedFuture(new InvalidRequestException("Request is invalid"))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then final AuctionEvent auctionEvent = captureAuctionEvent(); @@ -699,6 +823,7 @@ public void shouldPassBadRequestEventToAnalyticsReporterIfBidRequestIsInvalid() .status(400) .errors(singletonList("Invalid request format: Request is invalid")) .build()); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -714,7 +839,7 @@ public void shouldPassInternalServerErrorEventToAnalyticsReporterIfAuctionFails( .willThrow(new RuntimeException("Unexpected exception")); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then final AuctionEvent auctionEvent = captureAuctionEvent(); @@ -728,6 +853,8 @@ public void shouldPassInternalServerErrorEventToAnalyticsReporterIfAuctionFails( .status(500) .errors(singletonList("Unexpected exception")) .build()); + + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -742,22 +869,82 @@ public void shouldPassSuccessfulEventToAnalyticsReporter() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then final AuctionEvent auctionEvent = captureAuctionEvent(); - final AuctionContext expectedAuctionContext = auctionContext.toBuilder() - .requestTypeMetric(MetricName.openrtb2web) - .bidResponse(BidResponse.builder().build()) - .build(); + assertThat(auctionEvent.getHttpContext()).isEqualTo(givenHttpContext()); + assertThat(auctionEvent.getBidResponse()).isEqualTo(BidResponse.builder().build()); + assertThat(auctionEvent.getStatus()).isEqualTo(200); + assertThat(auctionEvent.getAuctionContext().getRequestTypeMetric()).isEqualTo(MetricName.openrtb2web); + assertThat(auctionEvent.getAuctionContext().getBidResponse()).isEqualTo(BidResponse.builder().build()); + assertThat(auctionEvent.getAuctionContext().getRawAuctionResponse().getResponseBody()).isEqualTo("{}"); + assertThat(auctionEvent.getAuctionContext().getRawAuctionResponse().getResponseHeaders()).hasSize(2) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); - assertThat(auctionEvent).isEqualTo(AuctionEvent.builder() - .httpContext(givenHttpContext()) - .auctionContext(expectedAuctionContext) - .bidResponse(BidResponse.builder().build()) - .status(200) - .errors(emptyList()) - .build()); + final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); + verify(hookStageExecutor).executeExitpointStage( + responseHeadersCaptor.capture(), + eq("{}"), + any()); + + assertThat(responseHeadersCaptor.getValue()).hasSize(2) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); + + verify(hooksMetricsService).updateHooksMetrics(any()); + } + + @Test + public void shouldPassSuccessfulEventToAnalyticsReporterWhenExitpointHookChangesResponseAndHeaders() { + // given + final AuctionContext auctionContext = givenAuctionContext(identity()); + given(auctionRequestFactory.parseRequest(any(), anyLong())) + .willReturn(Future.succeededFuture(auctionContext)); + given(auctionRequestFactory.enrichAuctionContext(any())) + .willAnswer(invocation -> Future.succeededFuture(invocation.getArgument(0))); + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willReturn(Future.succeededFuture(HookStageExecutionResult.success( + ExitpointPayloadImpl.of( + MultiMap.caseInsensitiveMultiMap().add("New-Header", "New-Header-Value"), + "{\"response\":{}}")))); + + givenHoldAuction(BidResponse.builder().build()); + + // when + target.handle(routingContext); + + // then + final AuctionEvent auctionEvent = captureAuctionEvent(); + assertThat(auctionEvent.getHttpContext()).isEqualTo(givenHttpContext()); + assertThat(auctionEvent.getBidResponse()).isEqualTo(BidResponse.builder().build()); + assertThat(auctionEvent.getStatus()).isEqualTo(200); + assertThat(auctionEvent.getAuctionContext().getRequestTypeMetric()).isEqualTo(MetricName.openrtb2web); + assertThat(auctionEvent.getAuctionContext().getBidResponse()).isEqualTo(BidResponse.builder().build()); + assertThat(auctionEvent.getAuctionContext().getRawAuctionResponse().getResponseBody()) + .isEqualTo("{\"response\":{}}"); + assertThat(auctionEvent.getAuctionContext().getRawAuctionResponse().getResponseHeaders()).hasSize(1) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly(tuple("New-Header", "New-Header-Value")); + + final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); + verify(hookStageExecutor).executeExitpointStage( + responseHeadersCaptor.capture(), + eq("{}"), + any()); + + assertThat(responseHeadersCaptor.getValue()).hasSize(2) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); + + verify(hooksMetricsService).updateHooksMetrics(any()); } @Test @@ -774,7 +961,7 @@ public void shouldTolerateDuplicateQueryParamNames() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then final AuctionEvent auctionEvent = captureAuctionEvent(); @@ -798,7 +985,7 @@ public void shouldTolerateDuplicateHeaderNames() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then final AuctionEvent auctionEvent = captureAuctionEvent(); @@ -820,17 +1007,235 @@ public void shouldSkipAuction() { givenAuctionContext.skipAuction().with(BidResponse.builder().build()))); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(auctionRequestFactory, never()).enrichAuctionContext(any()); verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.ok)); - verifyNoInteractions(exchangeService); - verifyNoInteractions(analyticsReporterDelegator); + verifyNoInteractions(exchangeService, analyticsReporterDelegator, hookStageExecutor); + verify(hooksMetricsService).updateHooksMetrics(any()); verify(httpResponse).setStatusCode(eq(200)); verify(httpResponse).end("{}"); } + @Test + public void shouldReturnSendAuctionEventWithAuctionContextBidResponseDebugInfoHoldingExitpointHookOutcome() { + // given + final AuctionContext auctionContext = givenAuctionContext(identity()).toBuilder() + .hookExecutionContext(HookExecutionContext.of( + Endpoint.openrtb2_amp, + stageOutcomes())) + .build(); + + given(auctionRequestFactory.parseRequest(any(), anyLong())) + .willReturn(Future.succeededFuture(auctionContext)); + given(auctionRequestFactory.enrichAuctionContext(any())) + .willAnswer(invocation -> Future.succeededFuture(invocation.getArgument(0))); + + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willAnswer(invocation -> { + final AuctionContext context = invocation.getArgument(2, AuctionContext.class); + final HookExecutionContext hookExecutionContext = context.getHookExecutionContext(); + hookExecutionContext.getStageOutcomes().put(Stage.exitpoint, singletonList(StageExecutionOutcome.of( + "http-response", + singletonList( + GroupExecutionOutcome.of(singletonList( + HookExecutionOutcome.builder() + .hookId(HookId.of("exitpoint-module", "exitpoint-hook")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("exitpoint hook has been executed") + .action(ExecutionAction.update) + .analyticsTags(TagsImpl.of(singletonList( + ActivityImpl.of( + "some-activity", + "success", + singletonList(ResultImpl.of( + "success", + mapper.createObjectNode(), + givenAppliedToImpl())))))) + .build())))))); + return Future.succeededFuture(HookStageExecutionResult.success( + ExitpointPayloadImpl.of(invocation.getArgument(0), invocation.getArgument(1)))); + }); + + givenHoldAuction(BidResponse.builder().build()); + + // when + target.handle(routingContext); + + // then + final AuctionEvent auctionEvent = captureAuctionEvent(); + final BidResponse bidResponseBeforeHook = auctionEvent.getBidResponse(); + assertThat(bidResponseBeforeHook.getExt()).isNull(); + + final BidResponse bidResponseAfterHook = auctionEvent.getAuctionContext().getBidResponse(); + assertThat(bidResponseAfterHook.getExt().getPrebid().getModules().getTrace()).isEqualTo(ExtModulesTrace.of( + 8L, + List.of( + ExtModulesTraceStage.of( + Stage.auction_response, + 4L, + singletonList(ExtModulesTraceStageOutcome.of( + "auction-response", + 4L, + singletonList( + ExtModulesTraceGroup.of( + 4L, + asList( + ExtModulesTraceInvocationResult.builder() + .hookId(HookId.of("module1", "hook1")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("module1 hook1") + .action(ExecutionAction.update) + .build(), + ExtModulesTraceInvocationResult.builder() + .hookId(HookId.of("module1", "hook2")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("module1 hook2") + .action(ExecutionAction.no_action) + .build())))))), + + ExtModulesTraceStage.of( + Stage.exitpoint, + 4L, + singletonList(ExtModulesTraceStageOutcome.of( + "http-response", + 4L, + singletonList( + ExtModulesTraceGroup.of( + 4L, + singletonList( + ExtModulesTraceInvocationResult.builder() + .hookId(HookId.of("exitpoint-module", "exitpoint-hook")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("exitpoint hook has been executed") + .action(ExecutionAction.update) + .analyticsTags(ExtModulesTraceAnalyticsTags.of(singletonList( + ExtModulesTraceAnalyticsActivity.of( + "some-activity", + "success", + singletonList(ExtModulesTraceAnalyticsResult.of( + "success", + mapper.createObjectNode(), + givenExtModulesTraceAnalyticsAppliedTo())))))) + .build()))))))))); + } + + @Test + public void shouldReturnSendAuctionEventWithAuctionContextBidResponseAnalyticsTagsHoldingExitpointHookOutcome() { + // given + final ObjectNode analyticsNode = mapper.createObjectNode(); + final ObjectNode optionsNode = analyticsNode.putObject("options"); + optionsNode.put("enableclientdetails", true); + + final AuctionContext givenAuctionContext = givenAuctionContext( + request -> request.ext(ExtRequest.of(ExtRequestPrebid.builder() + .analytics(analyticsNode) + .build()))).toBuilder() + .hookExecutionContext(HookExecutionContext.of( + Endpoint.openrtb2_amp, + stageOutcomes())) + .build(); + + given(auctionRequestFactory.parseRequest(any(), anyLong())) + .willReturn(Future.succeededFuture(givenAuctionContext)); + given(auctionRequestFactory.enrichAuctionContext(any())) + .willAnswer(invocation -> Future.succeededFuture(invocation.getArgument(0))); + + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willAnswer(invocation -> { + final AuctionContext context = invocation.getArgument(2, AuctionContext.class); + final HookExecutionContext hookExecutionContext = context.getHookExecutionContext(); + hookExecutionContext.getStageOutcomes().put(Stage.exitpoint, singletonList(StageExecutionOutcome.of( + "http-response", + singletonList( + GroupExecutionOutcome.of(singletonList( + HookExecutionOutcome.builder() + .hookId(HookId.of("exitpoint-module", "exitpoint-hook")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("exitpoint hook has been executed") + .action(ExecutionAction.update) + .analyticsTags(TagsImpl.of(singletonList( + ActivityImpl.of( + "some-activity", + "success", + singletonList(ResultImpl.of( + "success", + mapper.createObjectNode(), + givenAppliedToImpl())))))) + .build())))))); + return Future.succeededFuture(HookStageExecutionResult.success( + ExitpointPayloadImpl.of(invocation.getArgument(0), invocation.getArgument(1)))); + }); + + givenHoldAuction(BidResponse.builder().build()); + + // when + target.handle(routingContext); + + // then + final AuctionEvent auctionEvent = captureAuctionEvent(); + final BidResponse bidResponseBeforeHook = auctionEvent.getBidResponse(); + assertThat(bidResponseBeforeHook.getExt()).isNull(); + + final BidResponse bidResponseAfterHook = auctionEvent.getAuctionContext().getBidResponse(); + assertThat(bidResponseAfterHook.getExt()) + .extracting(ExtBidResponse::getPrebid) + .extracting(ExtBidResponsePrebid::getAnalytics) + .extracting(ExtAnalytics::getTags) + .asInstanceOf(InstanceOfAssertFactories.list(ExtAnalyticsTags.class)) + .hasSize(1) + .allSatisfy(extAnalyticsTags -> { + assertThat(extAnalyticsTags.getStage()).isEqualTo(Stage.exitpoint); + assertThat(extAnalyticsTags.getModule()).isEqualTo("exitpoint-module"); + assertThat(extAnalyticsTags.getAnalyticsTags()).isNotNull(); + }); + } + + private static AppliedToImpl givenAppliedToImpl() { + return AppliedToImpl.builder() + .impIds(asList("impId1", "impId2")) + .request(true) + .build(); + } + + private static ExtModulesTraceAnalyticsAppliedTo givenExtModulesTraceAnalyticsAppliedTo() { + return ExtModulesTraceAnalyticsAppliedTo.builder() + .impIds(asList("impId1", "impId2")) + .request(true) + .build(); + } + + private static EnumMap> stageOutcomes() { + final Map> stageOutcomes = new HashMap<>(); + + stageOutcomes.put(Stage.auction_response, singletonList(StageExecutionOutcome.of( + "auction-response", + singletonList( + GroupExecutionOutcome.of(asList( + HookExecutionOutcome.builder() + .hookId(HookId.of("module1", "hook1")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("module1 hook1") + .action(ExecutionAction.update) + .build(), + HookExecutionOutcome.builder() + .hookId(HookId.of("module1", "hook2")) + .executionTime(4L) + .message("module1 hook2") + .status(ExecutionStatus.success) + .action(ExecutionAction.no_action) + .build())))))); + + return new EnumMap<>(stageOutcomes); + } + private AuctionContext captureAuctionContext() { final ArgumentCaptor captor = ArgumentCaptor.forClass(AuctionContext.class); verify(exchangeService).holdAuction(captor.capture()); @@ -862,9 +1267,14 @@ private AuctionContext givenAuctionContext( .imp(emptyList())).build(); final AuctionContext.AuctionContextBuilder auctionContextBuilder = AuctionContext.builder() + .account(Account.builder() + .analytics(AccountAnalyticsConfig.of(true, null, null)) + .build()) .uidsCookie(uidsCookie) .bidRequest(bidRequest) .requestTypeMetric(MetricName.openrtb2web) + .debugContext(DebugContext.of(true, false, TraceLevel.verbose)) + .hookExecutionContext(HookExecutionContext.of(Endpoint.openrtb2_auction)) .timeoutContext(TimeoutContext.of(0, timeout, 0)); return auctionContextCustomizer.apply(auctionContextBuilder) 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 5da93cd4c94..8117459e43c 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java @@ -19,11 +19,13 @@ import org.prebid.server.analytics.model.VideoEvent; import org.prebid.server.analytics.reporter.AnalyticsReporterDelegator; import org.prebid.server.auction.ExchangeService; +import org.prebid.server.auction.HooksMetricsService; import org.prebid.server.auction.VideoResponseFactory; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.CachedDebugLog; import org.prebid.server.auction.model.TimeoutContext; import org.prebid.server.auction.model.WithPodErrors; +import org.prebid.server.auction.model.debug.DebugContext; import org.prebid.server.auction.requestfactory.VideoRequestFactory; import org.prebid.server.cache.CoreCacheService; import org.prebid.server.cookie.UidsCookie; @@ -31,7 +33,13 @@ import org.prebid.server.exception.UnauthorizedAccountException; import org.prebid.server.execution.timeout.Timeout; import org.prebid.server.execution.timeout.TimeoutFactory; +import org.prebid.server.hooks.execution.HookStageExecutor; +import org.prebid.server.hooks.execution.model.HookExecutionContext; +import org.prebid.server.hooks.execution.model.HookStageExecutionResult; +import org.prebid.server.hooks.execution.v1.exitpoint.ExitpointPayloadImpl; import org.prebid.server.metric.Metrics; +import org.prebid.server.model.Endpoint; +import org.prebid.server.proto.openrtb.ext.request.TraceLevel; import org.prebid.server.proto.response.VideoResponse; import org.prebid.server.settings.model.Account; import org.prebid.server.settings.model.AccountAuctionConfig; @@ -86,8 +94,12 @@ public class VideoHandlerTest extends VertxTest { private UidsCookie uidsCookie; @Mock private PrebidVersionProvider prebidVersionProvider; + @Mock(strictness = LENIENT) + private HooksMetricsService hooksMetricsService; + @Mock(strictness = LENIENT) + private HookStageExecutor hookStageExecutor; - private VideoHandler videoHandler; + private VideoHandler target; private Timeout timeout; @@ -107,16 +119,25 @@ public void setUp() { given(prebidVersionProvider.getNameVersionRecord()).willReturn("pbs-java/1.00"); + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willAnswer(invocation -> Future.succeededFuture(HookStageExecutionResult.of( + false, + ExitpointPayloadImpl.of(invocation.getArgument(0), invocation.getArgument(1))))); + + given(hooksMetricsService.updateHooksMetrics(any())).willAnswer(invocation -> invocation.getArgument(0)); + timeout = new TimeoutFactory(clock).create(2000L); - videoHandler = new VideoHandler( + target = new VideoHandler( videoRequestFactory, videoResponseFactory, exchangeService, coreCacheService, analyticsReporterDelegator, metrics, + hooksMetricsService, clock, prebidVersionProvider, + hookStageExecutor, jacksonMapper); } @@ -130,7 +151,7 @@ public void shouldUseTimeoutFromAuctionContext() { givenHoldAuction(BidResponse.builder().build()); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(captureAuctionContext()) @@ -154,7 +175,7 @@ public void shouldAddPrebidVersionResponseHeader() { .build())); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(httpResponse.headers()) @@ -176,7 +197,7 @@ public void shouldAddObserveBrowsingTopicsResponseHeader() { .build())); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(httpResponse.headers()) @@ -196,7 +217,7 @@ public void shouldComputeTimeoutBasedOnRequestProcessingStartTime() { given(clock.millis()).willReturn(now.toEpochMilli()).willReturn(now.plusMillis(50L).toEpochMilli()); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(captureAuctionContext()) @@ -214,11 +235,12 @@ public void shouldRespondWithBadRequestIfBidRequestIsInvalid() { .willReturn(Future.failedFuture(new InvalidRequestException("Request is invalid"))); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).setStatusCode(eq(400)); verify(httpResponse).end(eq("Invalid request format: Request is invalid")); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -228,12 +250,13 @@ public void shouldRespondWithUnauthorizedIfAccountIdIsInvalid() { .willReturn(Future.failedFuture(new UnauthorizedAccountException("Account id is not provided", "1"))); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then verifyNoInteractions(exchangeService); verify(httpResponse).setStatusCode(eq(401)); verify(httpResponse).end(eq("Unauthorised: Account id is not provided")); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -246,11 +269,12 @@ public void shouldRespondWithInternalServerErrorIfAuctionFails() { .willThrow(new RuntimeException("Unexpected exception")); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse).setStatusCode(eq(500)); verify(httpResponse).end(eq("Critical error while running the auction: Unexpected exception")); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -262,10 +286,11 @@ public void shouldNotSendResponseIfClientClosedConnection() { given(routingContext.response().closed()).willReturn(true); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then verify(httpResponse, never()).end(anyString()); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -280,7 +305,7 @@ public void shouldRespondWithBidResponse() { .willReturn(VideoResponse.of(emptyList(), null)); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then verify(videoResponseFactory).toVideoResponse(any(), any(), any()); @@ -291,6 +316,63 @@ public void shouldRespondWithBidResponse() { tuple("Content-Type", "application/json"), tuple("x-prebid", "pbs-java/1.00")); verify(httpResponse).end(eq("{\"adPods\":[]}")); + + final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); + verify(hookStageExecutor).executeExitpointStage( + responseHeadersCaptor.capture(), + eq("{\"adPods\":[]}"), + any()); + + assertThat(responseHeadersCaptor.getValue()).hasSize(2) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); + + verify(hooksMetricsService).updateHooksMetrics(any()); + } + + @Test + public void shouldRespondWithBidResponseWhenExitpointHookChangesResponseAndHeaders() { + // given + given(videoRequestFactory.fromRequest(any(), anyLong())) + .willReturn(Future.succeededFuture(givenAuctionContext(identity(), emptyList()))); + + givenHoldAuction(BidResponse.builder().build()); + + given(videoResponseFactory.toVideoResponse(any(), any(), any())) + .willReturn(VideoResponse.of(emptyList(), null)); + + given(hookStageExecutor.executeExitpointStage(any(), any(), any())) + .willReturn(Future.succeededFuture(HookStageExecutionResult.success( + ExitpointPayloadImpl.of( + MultiMap.caseInsensitiveMultiMap().add("New-Header", "New-Header-Value"), + "{\"adPods\":[{\"something\":1}]}")))); + + // when + target.handle(routingContext); + + // then + verify(videoResponseFactory).toVideoResponse(any(), any(), any()); + + assertThat(httpResponse.headers()).hasSize(1) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsExactlyInAnyOrder(tuple("New-Header", "New-Header-Value")); + verify(httpResponse).end(eq("{\"adPods\":[{\"something\":1}]}")); + + final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); + verify(hookStageExecutor).executeExitpointStage( + responseHeadersCaptor.capture(), + eq("{\"adPods\":[]}"), + any()); + + assertThat(responseHeadersCaptor.getValue()).hasSize(2) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("Content-Type", "application/json"), + tuple("x-prebid", "pbs-java/1.00")); + + verify(hooksMetricsService).updateHooksMetrics(any()); } @Test @@ -309,7 +391,7 @@ public void shouldUpdateVideoEventWithCacheLogIdErrorAndCallCacheForDebugLogWhen given(coreCacheService.cacheVideoDebugLog(any(), anyInt())).willReturn("cacheKey"); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then verify(coreCacheService).cacheVideoDebugLog(any(), anyInt()); @@ -327,6 +409,7 @@ public void shouldCacheDebugLogWhenNoBidsWereReturnedAndDoesNotAddErrorToVideoEv final AuctionContext auctionContext = AuctionContext.builder() .bidRequest(BidRequest.builder().imp(emptyList()).build()) .account(Account.builder().auction(AccountAuctionConfig.builder().videoCacheTtl(100).build()).build()) + .debugContext(DebugContext.empty()) .cachedDebugLog(cachedDebugLog) .build(); @@ -343,7 +426,7 @@ public void shouldCacheDebugLogWhenNoBidsWereReturnedAndDoesNotAddErrorToVideoEv .willReturn(VideoResponse.of(emptyList(), null)); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then verify(coreCacheService).cacheVideoDebugLog(any(), anyInt()); @@ -377,6 +460,8 @@ private WithPodErrors givenAuctionContext( .uidsCookie(uidsCookie) .bidRequest(bidRequest) .timeoutContext(TimeoutContext.of(0, timeout, 0)) + .debugContext(DebugContext.of(true, false, TraceLevel.verbose)) + .hookExecutionContext(HookExecutionContext.of(Endpoint.openrtb2_video)) .build(); return WithPodErrors.of(auctionContext, errors); diff --git a/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java b/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java new file mode 100644 index 00000000000..0f13eb278dd --- /dev/null +++ b/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java @@ -0,0 +1,55 @@ +package org.prebid.server.it.hooks; + +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import io.vertx.core.Future; +import org.prebid.server.hooks.execution.v1.exitpoint.ExitpointPayloadImpl; +import org.prebid.server.hooks.v1.InvocationResult; +import org.prebid.server.hooks.v1.InvocationResultUtils; +import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; +import org.prebid.server.hooks.v1.exit.ExitpointHook; +import org.prebid.server.hooks.v1.exit.ExitpointPayload; +import org.prebid.server.json.JacksonMapper; + +import java.util.List; + +public class SampleItExitpointHook implements ExitpointHook { + + private final JacksonMapper mapper; + + public SampleItExitpointHook(JacksonMapper mapper) { + this.mapper = mapper; + } + + @Override + public Future> call(ExitpointPayload exitpointPayload, + AuctionInvocationContext invocationContext) { + + final BidResponse bidResponse = mapper.decodeValue(exitpointPayload.responseBody(), BidResponse.class); + final List seatBids = updateBids(bidResponse.getSeatbid()); + final BidResponse updatedResponse = bidResponse.toBuilder().seatbid(seatBids).build(); + + return Future.succeededFuture(InvocationResultUtils.succeeded(payload -> + ExitpointPayloadImpl.of(exitpointPayload.responseHeaders(), mapper.encodeToString(updatedResponse)))); + } + + private List updateBids(List seatBids) { + return seatBids.stream() + .map(seatBid -> seatBid.toBuilder().bid(seatBid.getBid().stream() + .map(bid -> bid.toBuilder() + .adm(bid.getAdm() + + "" + + "") + .build()) + .toList()) + .build()) + .toList(); + } + + @Override + public String code() { + return "exitpoint"; + } + + +} diff --git a/src/test/java/org/prebid/server/it/hooks/SampleItModule.java b/src/test/java/org/prebid/server/it/hooks/SampleItModule.java index e2806f8c87f..441240e7a32 100644 --- a/src/test/java/org/prebid/server/it/hooks/SampleItModule.java +++ b/src/test/java/org/prebid/server/it/hooks/SampleItModule.java @@ -31,7 +31,8 @@ public SampleItModule(JacksonMapper mapper) { new SampleItRejectingProcessedAuctionRequestHook(), new SampleItRejectingBidderRequestHook(), new SampleItRejectingRawBidderResponseHook(), - new SampleItRejectingProcessedBidderResponseHook()); + new SampleItRejectingProcessedBidderResponseHook(), + new SampleItExitpointHook(mapper)); } @Override diff --git a/src/test/resources/org/prebid/server/it/hooks/sample-module/test-auction-sample-module-response.json b/src/test/resources/org/prebid/server/it/hooks/sample-module/test-auction-sample-module-response.json index eacb079d5fe..cc893e56246 100644 --- a/src/test/resources/org/prebid/server/it/hooks/sample-module/test-auction-sample-module-response.json +++ b/src/test/resources/org/prebid/server/it/hooks/sample-module/test-auction-sample-module-response.json @@ -7,7 +7,7 @@ "id": "880290288", "impid": "impId1", "price": 8.43, - "adm": "", + "adm": "", "crid": "crid1", "w": 300, "h": 250, diff --git a/src/test/resources/org/prebid/server/it/test-app-settings.yaml b/src/test/resources/org/prebid/server/it/test-app-settings.yaml index 786b376ffed..c377f8ad3ce 100644 --- a/src/test/resources/org/prebid/server/it/test-app-settings.yaml +++ b/src/test/resources/org/prebid/server/it/test-app-settings.yaml @@ -60,6 +60,12 @@ accounts: hook-sequence: - module-code: sample-it-module hook-impl-code: auction-response + exitpoint: + groups: + - timeout: 5 + hook-sequence: + - module-code: sample-it-module + hook-impl-code: exitpoint - id: 7001 hooks: execution-plan: @@ -120,6 +126,18 @@ accounts: hook-sequence: - module-code: sample-it-module hook-impl-code: rejecting-processed-bidder-response + - id: 13001 + hooks: + execution-plan: + endpoints: + /openrtb2/auction: + stages: + exitpoint: + groups: + - timeout: 5 + hook-sequence: + - module-code: sample-it-module + hook-impl-code: exitpoint - id: 12001 auction: price-floors: From fdbb72412958b8951c5a43fe318e9d8a2c51aeee Mon Sep 17 00:00:00 2001 From: antonbabak Date: Thu, 21 Nov 2024 09:50:21 +0100 Subject: [PATCH 02/10] Add Exitpoint Stage --- .../greenbids/GreenbidsAnalyticsReporter.java | 6 +- .../server/auction/model/AuctionContext.java | 1 - .../server/handler/openrtb2/AmpHandler.java | 4 +- .../handler/openrtb2/AuctionHandler.java | 4 +- .../server/handler/openrtb2/VideoHandler.java | 6 +- .../hooks/execution/HookStageExecutor.java | 3 - .../GreenbidsAnalyticsReporterTest.java | 3 +- .../server/auction/ExchangeServiceTest.java | 120 -------- .../auction/HooksMetricsServiceTest.java | 260 ++++++++++++++++++ .../Ortb2RequestFactoryTest.java | 3 + .../handler/openrtb2/AmpHandlerTest.java | 27 +- .../handler/openrtb2/AuctionHandlerTest.java | 39 +-- .../it/hooks/SampleItExitpointHook.java | 1 - 13 files changed, 308 insertions(+), 169 deletions(-) create mode 100644 src/test/java/org/prebid/server/auction/HooksMetricsServiceTest.java 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 51196d0c2e9..64b53784ecc 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 @@ -101,19 +101,17 @@ public GreenbidsAnalyticsReporter( @Override public Future processEvent(T event) { final AuctionContext auctionContext; - final BidResponse bidResponse; if (event instanceof AmpEvent ampEvent) { auctionContext = ampEvent.getAuctionContext(); - bidResponse = ampEvent.getBidResponse(); } else if (event instanceof AuctionEvent auctionEvent) { auctionContext = auctionEvent.getAuctionContext(); - bidResponse = auctionEvent.getBidResponse(); } else { return Future.failedFuture(new PreBidException("Unprocessable event received")); } - if (bidResponse == null || auctionContext == null) { + final BidResponse bidResponse = auctionContext != null ? auctionContext.getBidResponse() : null; + if (auctionContext == null || bidResponse == null) { return Future.failedFuture(new PreBidException("Bid response or auction context cannot be null")); } diff --git a/src/main/java/org/prebid/server/auction/model/AuctionContext.java b/src/main/java/org/prebid/server/auction/model/AuctionContext.java index 79de199233a..a0c771686e9 100644 --- a/src/main/java/org/prebid/server/auction/model/AuctionContext.java +++ b/src/main/java/org/prebid/server/auction/model/AuctionContext.java @@ -143,7 +143,6 @@ public AuctionContext with(RawAuctionResponse rawAuctionResponse) { return this.toBuilder().rawAuctionResponse(rawAuctionResponse).build(); } - public AuctionContext withRequestRejected() { return this.toBuilder() .requestRejected(true) diff --git a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java index 776b77a651e..c3d4a61e11f 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java @@ -180,7 +180,7 @@ private Future prepareSuccessfulResponse(AuctionContext auctionC AmpEvent.AmpEventBuilder ampEventBuilder) { final String origin = originFrom(routingContext); - MultiMap responseHeaders = getCommonResponseHeaders(routingContext, origin) + final MultiMap responseHeaders = getCommonResponseHeaders(routingContext, origin) .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); return prepareAmpResponse(auctionContext, routingContext) @@ -455,7 +455,7 @@ private void handleResponseException(Throwable exception) { } private MultiMap getCommonResponseHeaders(RoutingContext routingContext, String origin) { - MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); + final MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, HttpUtil.X_PREBID_HEADER, prebidVersionProvider.getNameVersionRecord()); diff --git a/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java index 912c3cbfe8d..28dfc612964 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java @@ -158,7 +158,7 @@ private AuctionContext updateAppAndNoCookieAndImpsMetrics(AuctionContext context } private AuctionContext prepareSuccessfulResponse(AuctionContext auctionContext, RoutingContext routingContext) { - MultiMap responseHeaders = getCommonResponseHeaders(routingContext) + final MultiMap responseHeaders = getCommonResponseHeaders(routingContext) .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); final RawAuctionResponse rawAuctionResponse = RawAuctionResponse.builder() @@ -315,7 +315,7 @@ private void handleResponseException(Throwable throwable, MetricName requestType } private MultiMap getCommonResponseHeaders(RoutingContext routingContext) { - MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); + final MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, HttpUtil.X_PREBID_HEADER, prebidVersionProvider.getNameVersionRecord()); diff --git a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java index 2599dc7f910..50e4e817956 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java @@ -125,7 +125,7 @@ public void handle(RoutingContext routingContext) { .map(hooksMetricsService::updateHooksMetrics) // populate event with updated context .map(context -> addToEvent(context, videoEventBuilder::auctionContext, context)) - .onComplete(responseResult -> handleResult(responseResult, videoEventBuilder, routingContext, startTime)); + .onComplete(result -> handleResult(result, videoEventBuilder, routingContext, startTime)); } private AuctionContext prepareSuccessfulResponse(WithPodErrors context, @@ -140,7 +140,7 @@ private AuctionContext prepareSuccessfulResponse(WithPodErrors c addToEvent(videoResponse, videoEventBuilder::bidResponse, videoResponse); - MultiMap responseHeaders = getCommonResponseHeaders(routingContext) + final MultiMap responseHeaders = getCommonResponseHeaders(routingContext) .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); final RawAuctionResponse rawAuctionResponse = RawAuctionResponse.builder() @@ -297,7 +297,7 @@ private void handleResponseException(Throwable throwable) { } private MultiMap getCommonResponseHeaders(RoutingContext routingContext) { - MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); + final MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, HttpUtil.X_PREBID_HEADER, prebidVersionProvider.getNameVersionRecord()); 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 de708c51de5..d556d76d6cc 100644 --- a/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java +++ b/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.response.BidResponse; -import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.Future; import io.vertx.core.MultiMap; import io.vertx.core.Vertx; @@ -49,8 +48,6 @@ import org.prebid.server.json.JacksonMapper; import org.prebid.server.model.CaseInsensitiveMultiMap; import org.prebid.server.model.Endpoint; -import org.prebid.server.proto.response.AmpResponse; -import org.prebid.server.proto.response.VideoResponse; import org.prebid.server.settings.model.Account; import org.prebid.server.settings.model.AccountHooksConfiguration; 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 9167b1148ec..7b5d11fcc51 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 @@ -516,16 +516,15 @@ public void shouldFailOnUnexpectedResponseStatus() { public void shouldFailWhenAdUnitsListIsEmpty() { // given final AuctionContext auctionContext = mock(AuctionContext.class); - final BidResponse bidResponse = mock(BidResponse.class); when(auctionContext.getBidRequest()) .thenReturn(BidRequest.builder() .id("request1") .ext(givenExtRequest()) .build()); + when(auctionContext.getBidResponse()).thenReturn(BidResponse.builder().build()); final AuctionEvent event = mock(AuctionEvent.class); when(event.getAuctionContext()).thenReturn(auctionContext); - when(event.getBidResponse()).thenReturn(bidResponse); // when final Future result = target.processEvent(event); diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index e8d78c33b1a..18a2870d8d3 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -188,7 +188,6 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.same; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; @@ -197,7 +196,6 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -3565,124 +3563,6 @@ public void shouldReturnBidResponseWithWarningWhenAnalyticsTagsDisabledAndReques ExtBidderError.of(999, "analytics.options.enableclientdetails not enabled for account")); } - @Test - public void shouldIncrementHooksGlobalMetrics() { - // given - final AuctionContext auctionContext = AuctionContext.builder() - .hookExecutionContext(HookExecutionContext.of( - Endpoint.openrtb2_auction, - stageOutcomes(givenAppliedToImpl(identity())))) - .debugContext(DebugContext.empty()) - .requestRejected(true) - .build(); - - // when - target.holdAuction(auctionContext); - - // then - verify(metrics, times(6)).updateHooksMetrics(anyString(), any(), any(), any(), any(), any()); - verify(metrics).updateHooksMetrics( - eq("module1"), - eq(Stage.entrypoint), - eq("hook1"), - eq(ExecutionStatus.success), - eq(4L), - eq(ExecutionAction.update)); - verify(metrics).updateHooksMetrics( - eq("module1"), - eq(Stage.entrypoint), - eq("hook2"), - eq(ExecutionStatus.invocation_failure), - eq(6L), - isNull()); - verify(metrics).updateHooksMetrics( - eq("module1"), - eq(Stage.entrypoint), - eq("hook2"), - eq(ExecutionStatus.success), - eq(4L), - eq(ExecutionAction.no_action)); - verify(metrics).updateHooksMetrics( - eq("module2"), - eq(Stage.entrypoint), - eq("hook1"), - eq(ExecutionStatus.timeout), - eq(6L), - isNull()); - verify(metrics).updateHooksMetrics( - eq("module3"), - eq(Stage.auction_response), - eq("hook1"), - eq(ExecutionStatus.success), - eq(4L), - eq(ExecutionAction.update)); - verify(metrics).updateHooksMetrics( - eq("module3"), - eq(Stage.auction_response), - eq("hook2"), - eq(ExecutionStatus.success), - eq(4L), - eq(ExecutionAction.no_action)); - verify(metrics, never()).updateAccountHooksMetrics(any(), any(), any(), any()); - verify(metrics, never()).updateAccountModuleDurationMetric(any(), any(), any()); - } - - @Test - public void shouldIncrementHooksGlobalAndAccountMetrics() { - // given - given(httpBidderRequester.requestBids(any(), any(), any(), any(), any(), any(), anyBoolean())) - .willReturn(Future.succeededFuture(givenSeatBid(emptyList()))); - - final BidRequest bidRequest = givenBidRequest(givenSingleImp(singletonMap("bidder", 2))); - final AuctionContext auctionContext = givenRequestContext(bidRequest).toBuilder() - .hookExecutionContext(HookExecutionContext.of( - Endpoint.openrtb2_auction, - stageOutcomes(givenAppliedToImpl(identity())))) - .debugContext(DebugContext.empty()) - .build(); - - // when - target.holdAuction(auctionContext); - - // then - verify(metrics, times(6)).updateHooksMetrics(anyString(), any(), any(), any(), any(), any()); - verify(metrics, times(6)).updateAccountHooksMetrics(any(), any(), any(), any()); - verify(metrics).updateAccountHooksMetrics( - any(), - eq("module1"), - eq(ExecutionStatus.success), - eq(ExecutionAction.update)); - verify(metrics).updateAccountHooksMetrics( - any(), - eq("module1"), - eq(ExecutionStatus.invocation_failure), - isNull()); - verify(metrics).updateAccountHooksMetrics( - any(), - eq("module1"), - eq(ExecutionStatus.success), - eq(ExecutionAction.no_action)); - verify(metrics).updateAccountHooksMetrics( - any(), - eq("module2"), - eq(ExecutionStatus.timeout), - isNull()); - verify(metrics).updateAccountHooksMetrics( - any(), - eq("module3"), - eq(ExecutionStatus.success), - eq(ExecutionAction.update)); - verify(metrics).updateAccountHooksMetrics( - any(), - eq("module3"), - eq(ExecutionStatus.success), - eq(ExecutionAction.no_action)); - verify(metrics, times(3)).updateAccountModuleDurationMetric(any(), any(), any()); - verify(metrics).updateAccountModuleDurationMetric(any(), eq("module1"), eq(14L)); - verify(metrics).updateAccountModuleDurationMetric(any(), eq("module2"), eq(6L)); - verify(metrics).updateAccountModuleDurationMetric(any(), eq("module3"), eq(8L)); - } - @Test public void shouldProperPopulateImpExtPrebidEvenIfInExtImpPrebidContainNotCorrectField() { // given diff --git a/src/test/java/org/prebid/server/auction/HooksMetricsServiceTest.java b/src/test/java/org/prebid/server/auction/HooksMetricsServiceTest.java new file mode 100644 index 00000000000..608cb7a2350 --- /dev/null +++ b/src/test/java/org/prebid/server/auction/HooksMetricsServiceTest.java @@ -0,0 +1,260 @@ +package org.prebid.server.auction; + +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.VertxTest; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.debug.DebugContext; +import org.prebid.server.hooks.execution.model.ExecutionAction; +import org.prebid.server.hooks.execution.model.ExecutionStatus; +import org.prebid.server.hooks.execution.model.GroupExecutionOutcome; +import org.prebid.server.hooks.execution.model.HookExecutionContext; +import org.prebid.server.hooks.execution.model.HookExecutionOutcome; +import org.prebid.server.hooks.execution.model.HookId; +import org.prebid.server.hooks.execution.model.Stage; +import org.prebid.server.hooks.execution.model.StageExecutionOutcome; +import org.prebid.server.hooks.v1.analytics.ActivityImpl; +import org.prebid.server.hooks.v1.analytics.AppliedToImpl; +import org.prebid.server.hooks.v1.analytics.ResultImpl; +import org.prebid.server.hooks.v1.analytics.TagsImpl; +import org.prebid.server.metric.Metrics; +import org.prebid.server.model.Endpoint; +import org.prebid.server.settings.model.Account; +import org.prebid.server.settings.model.AccountAuctionConfig; +import org.prebid.server.settings.model.AccountEventsConfig; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +public class HooksMetricsServiceTest extends VertxTest { + + @Mock + private Metrics metrics; + + private HooksMetricsService target; + + @BeforeEach + public void before() { + target = new HooksMetricsService(metrics); + } + + @Test + public void shouldIncrementHooksGlobalMetrics() { + // given + final AuctionContext auctionContext = AuctionContext.builder() + .hookExecutionContext(HookExecutionContext.of( + Endpoint.openrtb2_auction, + stageOutcomes(givenAppliedToImpl()))) + .debugContext(DebugContext.empty()) + .requestRejected(true) + .build(); + + // when + target.updateHooksMetrics(auctionContext); + + // then + verify(metrics, times(6)).updateHooksMetrics(anyString(), any(), any(), any(), any(), any()); + verify(metrics).updateHooksMetrics( + eq("module1"), + eq(Stage.entrypoint), + eq("hook1"), + eq(ExecutionStatus.success), + eq(4L), + eq(ExecutionAction.update)); + verify(metrics).updateHooksMetrics( + eq("module1"), + eq(Stage.entrypoint), + eq("hook2"), + eq(ExecutionStatus.invocation_failure), + eq(6L), + isNull()); + verify(metrics).updateHooksMetrics( + eq("module1"), + eq(Stage.entrypoint), + eq("hook2"), + eq(ExecutionStatus.success), + eq(4L), + eq(ExecutionAction.no_action)); + verify(metrics).updateHooksMetrics( + eq("module2"), + eq(Stage.entrypoint), + eq("hook1"), + eq(ExecutionStatus.timeout), + eq(6L), + isNull()); + verify(metrics).updateHooksMetrics( + eq("module3"), + eq(Stage.auction_response), + eq("hook1"), + eq(ExecutionStatus.success), + eq(4L), + eq(ExecutionAction.update)); + verify(metrics).updateHooksMetrics( + eq("module3"), + eq(Stage.auction_response), + eq("hook2"), + eq(ExecutionStatus.success), + eq(4L), + eq(ExecutionAction.no_action)); + verify(metrics, never()).updateAccountHooksMetrics(any(), any(), any(), any()); + verify(metrics, never()).updateAccountModuleDurationMetric(any(), any(), any()); + } + + @Test + public void shouldIncrementHooksGlobalAndAccountMetrics() { + // given + final AuctionContext auctionContext = AuctionContext.builder() + .hookExecutionContext(HookExecutionContext.of( + Endpoint.openrtb2_auction, + stageOutcomes(givenAppliedToImpl()))) + .debugContext(DebugContext.empty()) + .requestRejected(true) + .account(Account.builder() + .id("accountId") + .auction(AccountAuctionConfig.builder() + .events(AccountEventsConfig.of(true)) + .build()) + .build()) + .build(); + + // when + target.updateHooksMetrics(auctionContext); + + // then + verify(metrics, times(6)).updateHooksMetrics(anyString(), any(), any(), any(), any(), any()); + verify(metrics, times(6)).updateAccountHooksMetrics(any(), any(), any(), any()); + verify(metrics).updateAccountHooksMetrics( + any(), + eq("module1"), + eq(ExecutionStatus.success), + eq(ExecutionAction.update)); + verify(metrics).updateAccountHooksMetrics( + any(), + eq("module1"), + eq(ExecutionStatus.invocation_failure), + isNull()); + verify(metrics).updateAccountHooksMetrics( + any(), + eq("module1"), + eq(ExecutionStatus.success), + eq(ExecutionAction.no_action)); + verify(metrics).updateAccountHooksMetrics( + any(), + eq("module2"), + eq(ExecutionStatus.timeout), + isNull()); + verify(metrics).updateAccountHooksMetrics( + any(), + eq("module3"), + eq(ExecutionStatus.success), + eq(ExecutionAction.update)); + verify(metrics).updateAccountHooksMetrics( + any(), + eq("module3"), + eq(ExecutionStatus.success), + eq(ExecutionAction.no_action)); + verify(metrics, times(3)).updateAccountModuleDurationMetric(any(), any(), any()); + verify(metrics).updateAccountModuleDurationMetric(any(), eq("module1"), eq(14L)); + verify(metrics).updateAccountModuleDurationMetric(any(), eq("module2"), eq(6L)); + verify(metrics).updateAccountModuleDurationMetric(any(), eq("module3"), eq(8L)); + } + + private static AppliedToImpl givenAppliedToImpl() { + return AppliedToImpl.builder() + .impIds(asList("impId1", "impId2")) + .request(true) + .build(); + } + + private static EnumMap> stageOutcomes(AppliedToImpl appliedToImp) { + final Map> stageOutcomes = new HashMap<>(); + + stageOutcomes.put(Stage.entrypoint, singletonList(StageExecutionOutcome.of( + "http-request", + asList( + GroupExecutionOutcome.of(asList( + HookExecutionOutcome.builder() + .hookId(HookId.of("module1", "hook1")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("Message 1-1") + .action(ExecutionAction.update) + .errors(asList("error message 1-1 1", "error message 1-1 2")) + .warnings(asList("warning message 1-1 1", "warning message 1-1 2")) + .debugMessages(asList("debug message 1-1 1", "debug message 1-1 2")) + .analyticsTags(TagsImpl.of(singletonList( + ActivityImpl.of( + "some-activity", + "success", + singletonList(ResultImpl.of( + "success", + mapper.createObjectNode(), + appliedToImp)))))) + .build(), + HookExecutionOutcome.builder() + .hookId(HookId.of("module1", "hook2")) + .executionTime(6L) + .status(ExecutionStatus.invocation_failure) + .message("Message 1-2") + .errors(asList("error message 1-2 1", "error message 1-2 2")) + .warnings(asList("warning message 1-2 1", "warning message 1-2 2")) + .build())), + GroupExecutionOutcome.of(asList( + HookExecutionOutcome.builder() + .hookId(HookId.of("module1", "hook2")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("Message 1-2") + .action(ExecutionAction.no_action) + .errors(asList("error message 1-2 3", "error message 1-2 4")) + .warnings(asList("warning message 1-2 3", "warning message 1-2 4")) + .build(), + HookExecutionOutcome.builder() + .hookId(HookId.of("module2", "hook1")) + .executionTime(6L) + .status(ExecutionStatus.timeout) + .message("Message 2-1") + .errors(asList("error message 2-1 1", "error message 2-1 2")) + .warnings(asList("warning message 2-1 1", "warning message 2-1 2")) + .build())))))); + + stageOutcomes.put(Stage.auction_response, singletonList(StageExecutionOutcome.of( + "auction-response", + singletonList( + GroupExecutionOutcome.of(asList( + HookExecutionOutcome.builder() + .hookId(HookId.of("module3", "hook1")) + .executionTime(4L) + .status(ExecutionStatus.success) + .message("Message 3-1") + .action(ExecutionAction.update) + .errors(asList("error message 3-1 1", "error message 3-1 2")) + .warnings(asList("warning message 3-1 1", "warning message 3-1 2")) + .build(), + HookExecutionOutcome.builder() + .hookId(HookId.of("module3", "hook2")) + .executionTime(4L) + .status(ExecutionStatus.success) + .action(ExecutionAction.no_action) + .build())))))); + + return new EnumMap<>(stageOutcomes); + } + +} 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 5b4c0bd72e5..23d485e99a0 100644 --- a/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java +++ b/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java @@ -14,6 +14,7 @@ import com.iab.openrtb.request.User; import io.vertx.core.Future; import io.vertx.core.MultiMap; +import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.net.impl.SocketAddressImpl; import io.vertx.ext.web.RoutingContext; @@ -1079,6 +1080,7 @@ public void executeEntrypointHooksShouldReturnExpectedHttpRequest() { given(httpServerRequest.headers()).willReturn(MultiMap.caseInsensitiveMultiMap()); given(httpServerRequest.absoluteURI()).willReturn("absoluteUri"); + given(httpServerRequest.method()).willReturn(HttpMethod.POST); given(httpServerRequest.scheme()).willReturn("https"); given(httpServerRequest.remoteAddress()).willReturn(new SocketAddressImpl(1234, "host")); @@ -1107,6 +1109,7 @@ public void executeEntrypointHooksShouldReturnExpectedHttpRequest() { // then final HttpRequestContext httpRequest = result.result(); assertThat(httpRequest.getAbsoluteUri()).isEqualTo("absoluteUri"); + assertThat(httpRequest.getHttpMethod()).isEqualTo(HttpMethod.POST); assertThat(httpRequest.getQueryParams()).isSameAs(updatedQueryParam); assertThat(httpRequest.getHeaders()).isSameAs(headerParams); assertThat(httpRequest.getBody()).isEqualTo("{\"app\":{\"bundle\":\"org.company.application\"}}"); 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 3f5866f831b..5a05f7d3534 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java @@ -1179,7 +1179,6 @@ public void shouldReturnSendAmpEventWithAuctionContextBidResponseDebugInfoHoldin ExtPrebid.of(ExtBidPrebid.builder().targeting(singletonMap("hb_cache_id_bidder1", "value1")).build(), null)))); - // when target.handle(routingContext); @@ -1189,6 +1188,14 @@ public void shouldReturnSendAmpEventWithAuctionContextBidResponseDebugInfoHoldin assertThat(bidResponseBeforeHook.getExt()).isNull(); final BidResponse bidResponseAfterHook = ampEvent.getAuctionContext().getBidResponse(); + final ExtModulesTraceAnalyticsTags expectedAnalyticsTags = ExtModulesTraceAnalyticsTags.of(singletonList( + ExtModulesTraceAnalyticsActivity.of( + "some-activity", + "success", + singletonList(ExtModulesTraceAnalyticsResult.of( + "success", + mapper.createObjectNode(), + givenExtModulesTraceAnalyticsAppliedTo()))))); assertThat(bidResponseAfterHook.getExt().getPrebid().getModules().getTrace()).isEqualTo(ExtModulesTrace.of( 8L, List.of( @@ -1228,19 +1235,14 @@ public void shouldReturnSendAmpEventWithAuctionContextBidResponseDebugInfoHoldin 4L, singletonList( ExtModulesTraceInvocationResult.builder() - .hookId(HookId.of("exitpoint-module", "exitpoint-hook")) + .hookId(HookId.of( + "exitpoint-module", + "exitpoint-hook")) .executionTime(4L) .status(ExecutionStatus.success) .message("exitpoint hook has been executed") .action(ExecutionAction.update) - .analyticsTags(ExtModulesTraceAnalyticsTags.of(singletonList( - ExtModulesTraceAnalyticsActivity.of( - "some-activity", - "success", - singletonList(ExtModulesTraceAnalyticsResult.of( - "success", - mapper.createObjectNode(), - givenExtModulesTraceAnalyticsAppliedTo())))))) + .analyticsTags(expectedAnalyticsTags) .build()))))))))); } @@ -1272,7 +1274,9 @@ public void shouldReturnSendAmpEventWithAuctionContextBidResponseAnalyticsTagsHo singletonList( GroupExecutionOutcome.of(singletonList( HookExecutionOutcome.builder() - .hookId(HookId.of("exitpoint-module", "exitpoint-hook")) + .hookId(HookId.of( + "exitpoint-module", + "exitpoint-hook")) .executionTime(4L) .status(ExecutionStatus.success) .message("exitpoint hook has been executed") @@ -1294,7 +1298,6 @@ public void shouldReturnSendAmpEventWithAuctionContextBidResponseAnalyticsTagsHo ExtPrebid.of(ExtBidPrebid.builder().targeting(singletonMap("hb_cache_id_bidder1", "value1")).build(), null)))); - // when target.handle(routingContext); 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 141883ac492..05f7ecdc3c6 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java @@ -1,13 +1,10 @@ package org.prebid.server.handler.openrtb2; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; -import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; -import com.iab.openrtb.response.SeatBid; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.MultiMap; @@ -22,7 +19,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.VertxTest; -import org.prebid.server.analytics.model.AmpEvent; import org.prebid.server.analytics.model.AuctionEvent; import org.prebid.server.analytics.reporter.AnalyticsReporterDelegator; import org.prebid.server.auction.ExchangeService; @@ -61,7 +57,6 @@ import org.prebid.server.model.CaseInsensitiveMultiMap; import org.prebid.server.model.Endpoint; import org.prebid.server.model.HttpRequestContext; -import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtGranularityRange; import org.prebid.server.proto.openrtb.ext.request.ExtMediaTypePriceGranularity; import org.prebid.server.proto.openrtb.ext.request.ExtPriceGranularity; @@ -71,7 +66,6 @@ import org.prebid.server.proto.openrtb.ext.request.TraceLevel; import org.prebid.server.proto.openrtb.ext.response.ExtAnalytics; import org.prebid.server.proto.openrtb.ext.response.ExtAnalyticsTags; -import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; import org.prebid.server.proto.openrtb.ext.response.ExtBidResponse; import org.prebid.server.proto.openrtb.ext.response.ExtBidResponsePrebid; import org.prebid.server.proto.openrtb.ext.response.ExtModulesTrace; @@ -101,7 +95,6 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; @@ -166,7 +159,8 @@ public void setUp() { given(httpResponse.setStatusCode(anyInt())).willReturn(httpResponse); given(httpResponse.headers()).willReturn(MultiMap.caseInsensitiveMultiMap()); - given(skippedAuctionService.skipAuction(any())).willReturn(Future.failedFuture("Auction cannot be skipped")); + given(skippedAuctionService.skipAuction(any())) + .willReturn(Future.failedFuture("Auction cannot be skipped")); given(clock.millis()).willReturn(Instant.now().toEpochMilli()); @@ -1041,7 +1035,9 @@ public void shouldReturnSendAuctionEventWithAuctionContextBidResponseDebugInfoHo singletonList( GroupExecutionOutcome.of(singletonList( HookExecutionOutcome.builder() - .hookId(HookId.of("exitpoint-module", "exitpoint-hook")) + .hookId(HookId.of( + "exitpoint-module", + "exitpoint-hook")) .executionTime(4L) .status(ExecutionStatus.success) .message("exitpoint hook has been executed") @@ -1070,6 +1066,14 @@ public void shouldReturnSendAuctionEventWithAuctionContextBidResponseDebugInfoHo assertThat(bidResponseBeforeHook.getExt()).isNull(); final BidResponse bidResponseAfterHook = auctionEvent.getAuctionContext().getBidResponse(); + final ExtModulesTraceAnalyticsTags expectedAnalyticsTags = ExtModulesTraceAnalyticsTags.of(singletonList( + ExtModulesTraceAnalyticsActivity.of( + "some-activity", + "success", + singletonList(ExtModulesTraceAnalyticsResult.of( + "success", + mapper.createObjectNode(), + givenExtModulesTraceAnalyticsAppliedTo()))))); assertThat(bidResponseAfterHook.getExt().getPrebid().getModules().getTrace()).isEqualTo(ExtModulesTrace.of( 8L, List.of( @@ -1109,19 +1113,14 @@ public void shouldReturnSendAuctionEventWithAuctionContextBidResponseDebugInfoHo 4L, singletonList( ExtModulesTraceInvocationResult.builder() - .hookId(HookId.of("exitpoint-module", "exitpoint-hook")) + .hookId(HookId.of( + "exitpoint-module", + "exitpoint-hook")) .executionTime(4L) .status(ExecutionStatus.success) .message("exitpoint hook has been executed") .action(ExecutionAction.update) - .analyticsTags(ExtModulesTraceAnalyticsTags.of(singletonList( - ExtModulesTraceAnalyticsActivity.of( - "some-activity", - "success", - singletonList(ExtModulesTraceAnalyticsResult.of( - "success", - mapper.createObjectNode(), - givenExtModulesTraceAnalyticsAppliedTo())))))) + .analyticsTags(expectedAnalyticsTags) .build()))))))))); } @@ -1155,7 +1154,9 @@ public void shouldReturnSendAuctionEventWithAuctionContextBidResponseAnalyticsTa singletonList( GroupExecutionOutcome.of(singletonList( HookExecutionOutcome.builder() - .hookId(HookId.of("exitpoint-module", "exitpoint-hook")) + .hookId(HookId.of( + "exitpoint-module", + "exitpoint-hook")) .executionTime(4L) .status(ExecutionStatus.success) .message("exitpoint hook has been executed") diff --git a/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java b/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java index 0f13eb278dd..cfff176f8af 100644 --- a/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java +++ b/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java @@ -51,5 +51,4 @@ public String code() { return "exitpoint"; } - } From 47314ebd13f128ad333163c2bdb406fe4070b1a6 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Thu, 21 Nov 2024 10:47:11 +0100 Subject: [PATCH 03/10] Add tests --- .../execution/HookStageExecutorTest.java | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) 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 22d8e49f6a2..49acfb9954a 100644 --- a/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java +++ b/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java @@ -6,6 +6,7 @@ import com.iab.openrtb.response.BidResponse; import io.vertx.core.CompositeFuture; import io.vertx.core.Future; +import io.vertx.core.MultiMap; import io.vertx.core.Promise; import io.vertx.core.Vertx; import io.vertx.junit5.Checkpoint; @@ -49,6 +50,7 @@ import org.prebid.server.hooks.execution.v1.bidder.BidderRequestPayloadImpl; import org.prebid.server.hooks.execution.v1.bidder.BidderResponsePayloadImpl; import org.prebid.server.hooks.execution.v1.entrypoint.EntrypointPayloadImpl; +import org.prebid.server.hooks.execution.v1.exitpoint.ExitpointPayloadImpl; import org.prebid.server.hooks.v1.InvocationAction; import org.prebid.server.hooks.v1.InvocationContext; import org.prebid.server.hooks.v1.InvocationResult; @@ -75,6 +77,8 @@ import org.prebid.server.hooks.v1.bidder.RawBidderResponseHook; import org.prebid.server.hooks.v1.entrypoint.EntrypointHook; import org.prebid.server.hooks.v1.entrypoint.EntrypointPayload; +import org.prebid.server.hooks.v1.exit.ExitpointHook; +import org.prebid.server.hooks.v1.exit.ExitpointPayload; import org.prebid.server.model.CaseInsensitiveMultiMap; import org.prebid.server.model.Endpoint; import org.prebid.server.proto.openrtb.ext.response.BidType; @@ -98,6 +102,7 @@ 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.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -2726,6 +2731,183 @@ public void shouldExecuteAuctionResponseHooksAndIgnoreRejection(VertxTestContext })); } + @Test + public void shouldExecuteExitpointHooksHappyPath(VertxTestContext context) { + // given + givenExitpointHook( + "module-alpha", + "hook-a", + immediateHook(InvocationResultUtils.succeeded(payload -> ExitpointPayloadImpl.of( + payload.responseHeaders().add("Header-alpha-a", "alpha-a"), + "{\"execution1\":\"alpha-a\"")))); + + givenExitpointHook( + "module-alpha", + "hook-b", + immediateHook(InvocationResultUtils.succeeded(payload -> ExitpointPayloadImpl.of( + payload.responseHeaders().add("Header-alpha-b", "alpha-b"), + payload.responseBody() + ",\"execution4\":\"alpha-b\"}")))); + + givenExitpointHook( + "module-beta", + "hook-a", + immediateHook(InvocationResultUtils.succeeded(payload -> ExitpointPayloadImpl.of( + payload.responseHeaders().add("Header-beta-a", "beta-a"), + payload.responseBody() + ",\"execution2\":\"beta-a\"")))); + + givenExitpointHook( + "module-beta", + "hook-b", + immediateHook(InvocationResultUtils.succeeded(payload -> ExitpointPayloadImpl.of( + payload.responseHeaders().add("Header-beta-b", "beta-b"), + payload.responseBody() + ",\"execution3\":\"beta-b\"")))); + + final HookStageExecutor executor = createExecutor( + executionPlan(singletonMap( + Endpoint.openrtb2_auction, + EndpointExecutionPlan.of(singletonMap( + Stage.exitpoint, + execPlanTwoGroupsTwoHooksEach()))))); + + final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + + // when + final Future> future = executor.executeExitpointStage( + MultiMap.caseInsensitiveMultiMap().add("Header-Name", "Header-Value"), + "{}", + AuctionContext.builder() + .bidRequest(BidRequest.builder().build()) + .account(Account.empty("accountId")) + .hookExecutionContext(hookExecutionContext) + .debugContext(DebugContext.empty()) + .build()); + + // then + future.onComplete(context.succeeding(result -> { + assertThat(result).isNotNull(); + assertThat(result.getPayload()).isNotNull().satisfies(payload -> { + assertThat(payload.responseBody()) + .isEqualTo("{\"execution1\":\"alpha-a\",\"execution2\":\"beta-a\"," + + "\"execution3\":\"beta-b\",\"execution4\":\"alpha-b\"}"); + assertThat(payload.responseHeaders()).hasSize(5) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly( + tuple("Header-Name", "Header-Value"), + tuple("Header-alpha-a", "alpha-a"), + tuple("Header-alpha-b", "alpha-b"), + tuple("Header-beta-a", "beta-a"), + tuple("Header-beta-b", "beta-b")); + }); + + context.completeNow(); + })); + } + + @Test + public void shouldExecuteExitpointHooksAndPassAuctionInvocationContext(VertxTestContext context) { + // given + final ExitpointHookImpl hookImpl = spy( + ExitpointHookImpl.of(immediateHook(InvocationResultUtils.succeeded(identity())))); + given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.EXITPOINT))) + .willReturn(hookImpl); + + final HookStageExecutor executor = createExecutor( + executionPlan(singletonMap( + Endpoint.openrtb2_auction, + EndpointExecutionPlan.of(singletonMap( + Stage.exitpoint, execPlanOneGroupOneHook("module-alpha", "hook-a")))))); + + final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + + // when + final Future> future = executor.executeExitpointStage( + MultiMap.caseInsensitiveMultiMap().add("Header-Name", "Header-Value"), + "{}", + AuctionContext.builder() + .bidRequest(BidRequest.builder().build()) + .account(Account.builder() + .hooks(AccountHooksConfiguration.of( + null, singletonMap("module-alpha", mapper.createObjectNode()))) + .build()) + .hookExecutionContext(hookExecutionContext) + .debugContext(DebugContext.empty()) + .build()); + + // then + future.onComplete(context.succeeding(result -> { + final ArgumentCaptor invocationContextCaptor = + ArgumentCaptor.forClass(AuctionInvocationContext.class); + verify(hookImpl).call(any(), invocationContextCaptor.capture()); + + assertThat(invocationContextCaptor.getValue()).satisfies(invocationContext -> { + assertThat(invocationContext.endpoint()).isNotNull(); + assertThat(invocationContext.timeout()).isNotNull(); + assertThat(invocationContext.accountConfig()).isNotNull(); + }); + + context.completeNow(); + })); + } + + @Test + public void shouldExecuteExitpointHooksAndIgnoreRejection(VertxTestContext context) { + // given + givenExitpointHook( + "module-alpha", + "hook-a", + immediateHook(InvocationResultUtils.rejected("Will not apply"))); + + final HookStageExecutor executor = createExecutor( + executionPlan(singletonMap( + Endpoint.openrtb2_auction, + EndpointExecutionPlan.of(singletonMap( + Stage.exitpoint, execPlanOneGroupOneHook("module-alpha", "hook-a")))))); + + final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + + // when + final Future> future = executor.executeExitpointStage( + MultiMap.caseInsensitiveMultiMap().add("Header-Name", "Header-Value"), + "{}", + AuctionContext.builder() + .account(Account.empty("accountId")) + .hookExecutionContext(hookExecutionContext) + .debugContext(DebugContext.empty()) + .build()); + + // then + future.onComplete(context.succeeding(result -> { + assertThat(result.isShouldReject()).isFalse(); + assertThat(result.getPayload()).isNotNull().satisfies(payload -> { + assertThat(payload.responseBody()).isNotNull(); + assertThat(payload.responseBody()).isNotEmpty(); + }); + + assertThat(hookExecutionContext.getStageOutcomes()) + .hasEntrySatisfying( + Stage.exitpoint, + stageOutcomes -> assertThat(stageOutcomes) + .hasSize(1) + .allSatisfy(stageOutcome -> { + assertThat(stageOutcome.getEntity()).isEqualTo("http-response"); + + final List groups = stageOutcome.getGroups(); + + final List group0Hooks = groups.getFirst().getHooks(); + assertThat(group0Hooks.getFirst()).satisfies(hookOutcome -> { + assertThat(hookOutcome.getHookId()) + .isEqualTo(HookId.of("module-alpha", "hook-a")); + assertThat(hookOutcome.getStatus()) + .isEqualTo(ExecutionStatus.execution_failure); + assertThat(hookOutcome.getMessage()) + .isEqualTo("Rejection is not supported during this stage"); + }); + })); + + context.completeNow(); + })); + } + private String executionPlan(Map endpoints) { return jacksonMapper.encodeToString(ExecutionPlan.of(endpoints)); } @@ -2844,6 +3026,30 @@ private void givenAuctionResponseHook( .willReturn(AuctionResponseHookImpl.of(delegate)); } + @Value(staticConstructor = "of") + @NonFinal + private static class ExitpointHookImpl implements ExitpointHook { + + String code = "hook-code"; + + BiFunction< + ExitpointPayload, + AuctionInvocationContext, + Future>> delegate; + + @Override + public Future> call(ExitpointPayload payload, + AuctionInvocationContext invocationContext) { + + return delegate.apply(payload, invocationContext); + } + + @Override + public String code() { + return code; + } + } + private BiFunction>> delayedHook( InvocationResult result, int delay) { @@ -3066,4 +3272,16 @@ public String code() { return code; } } + + private void givenExitpointHook( + String moduleCode, + String hookImplCode, + BiFunction< + ExitpointPayload, + AuctionInvocationContext, + Future>> delegate) { + + given(hookCatalog.hookById(eq(moduleCode), eq(hookImplCode), eq(StageWithHookType.EXITPOINT))) + .willReturn(ExitpointHookImpl.of(delegate)); + } } From 3cb034253722269dd2c333fa0c2a56dfd770ceba Mon Sep 17 00:00:00 2001 From: antonbabak Date: Thu, 21 Nov 2024 11:36:58 +0100 Subject: [PATCH 04/10] Some fixes --- .../greenbids/GreenbidsAnalyticsReporter.java | 6 ++-- .../server/handler/openrtb2/AmpHandler.java | 13 +++++--- .../handler/openrtb2/AuctionHandler.java | 7 ++-- .../server/handler/openrtb2/VideoHandler.java | 33 ++++++++++++------- .../GreenbidsAnalyticsReporterTest.java | 3 +- .../handler/openrtb2/AmpHandlerTest.java | 14 +++----- .../handler/openrtb2/AuctionHandlerTest.java | 14 +++----- .../handler/openrtb2/VideoHandlerTest.java | 5 +-- 8 files changed, 48 insertions(+), 47 deletions(-) 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 64b53784ecc..51196d0c2e9 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 @@ -101,17 +101,19 @@ public GreenbidsAnalyticsReporter( @Override public Future processEvent(T event) { final AuctionContext auctionContext; + final BidResponse bidResponse; if (event instanceof AmpEvent ampEvent) { auctionContext = ampEvent.getAuctionContext(); + bidResponse = ampEvent.getBidResponse(); } else if (event instanceof AuctionEvent auctionEvent) { auctionContext = auctionEvent.getAuctionContext(); + bidResponse = auctionEvent.getBidResponse(); } else { return Future.failedFuture(new PreBidException("Unprocessable event received")); } - final BidResponse bidResponse = auctionContext != null ? auctionContext.getBidResponse() : null; - if (auctionContext == null || bidResponse == null) { + if (bidResponse == null || auctionContext == null) { return Future.failedFuture(new PreBidException("Bid response or auction context cannot be null")); } diff --git a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java index c3d4a61e11f..7b57510813a 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java @@ -147,11 +147,16 @@ public void handle(RoutingContext routingContext) { .map(context -> addToEvent(context, ampEventBuilder::auctionContext, context)) .map(this::updateAppAndNoCookieAndImpsMetrics) .compose(exchangeService::holdAuction) - .map(context -> addToEvent(context.getBidResponse(), ampEventBuilder::bidResponse, context)) - .compose(context -> prepareSuccessfulResponse(context, routingContext, ampEventBuilder)) + .compose(context -> prepareSuccessfulResponse(context, routingContext)) .compose(this::invokeExitpointHooks) .map(hooksMetricsService::updateHooksMetrics) .map(context -> addToEvent(context, ampEventBuilder::auctionContext, context)) + .map(context -> addToEvent(context.getBidResponse(), ampEventBuilder::bidResponse, context)) + .compose(context -> prepareAmpResponse(context, routingContext)) + .map(result -> { + addToEvent(result.getLeft().getTargeting(), ampEventBuilder::targeting, result); + return result.getRight(); + }) .onComplete(responseResult -> handleResult(responseResult, ampEventBuilder, routingContext, startTime)); } @@ -176,15 +181,13 @@ private AuctionContext updateAppAndNoCookieAndImpsMetrics(AuctionContext context } private Future prepareSuccessfulResponse(AuctionContext auctionContext, - RoutingContext routingContext, - AmpEvent.AmpEventBuilder ampEventBuilder) { + RoutingContext routingContext) { final String origin = originFrom(routingContext); final MultiMap responseHeaders = getCommonResponseHeaders(routingContext, origin) .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); return prepareAmpResponse(auctionContext, routingContext) - .map(result -> addToEvent(result.getLeft().getTargeting(), ampEventBuilder::targeting, result)) .map(result -> { final RawAuctionResponse rawAuctionResponse = RawAuctionResponse.builder() .responseBody(mapper.encodeToString(result.getLeft())) diff --git a/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java index 28dfc612964..2f90c788b64 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java @@ -118,6 +118,7 @@ public void handle(RoutingContext routingContext) { .compose(this::invokeExitpointHooks) .map(hooksMetricsService::updateHooksMetrics) .map(context -> addToEvent(context, auctionEventBuilder::auctionContext, context)) + .map(context -> addToEvent(context.getBidResponse(), auctionEventBuilder::bidResponse, context)) .onComplete(context -> handleResult(context, auctionEventBuilder, routingContext, startTime)); } @@ -126,13 +127,9 @@ private Future holdAuction(AuctionEvent.AuctionEventBuilder auct return auctionRequestFactory.enrichAuctionContext(auctionContext) .map(this::updateAppAndNoCookieAndImpsMetrics) - // In case of holdAuction Exception and auctionContext is not present below .map(context -> addToEvent(context, auctionEventBuilder::auctionContext, context)) - - .compose(exchangeService::holdAuction) - // populate event with updated context - .map(context -> addToEvent(context.getBidResponse(), auctionEventBuilder::bidResponse, context)); + .compose(exchangeService::holdAuction); } private static R addToEvent(T field, Consumer consumer, R result) { diff --git a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java index 50e4e817956..646b5b72b0d 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java @@ -120,17 +120,18 @@ public void handle(RoutingContext routingContext) { .map(contextToErrors -> addToEvent(contextToErrors.getData(), videoEventBuilder::auctionContext, contextToErrors)) - .map(contextToErrors -> prepareSuccessfulResponse(contextToErrors, routingContext, videoEventBuilder)) - .compose(this::invokeExitpointHooks) - .map(hooksMetricsService::updateHooksMetrics) - // populate event with updated context - .map(context -> addToEvent(context, videoEventBuilder::auctionContext, context)) - .onComplete(result -> handleResult(result, videoEventBuilder, routingContext, startTime)); + .compose(contextToErrors -> prepareSuccessfulResponse(contextToErrors, routingContext) + .compose(this::invokeExitpointHooks) + .map(hooksMetricsService::updateHooksMetrics) + .map(context -> addToEvent(context, videoEventBuilder::auctionContext, context)) + .map(context -> WithPodErrors.of(context, contextToErrors.getPodErrors()))) + + .map(contextToErrors -> prepareVideoResponse(contextToErrors, videoEventBuilder)) + .onComplete(context -> handleResult(context, videoEventBuilder, routingContext, startTime)); } - private AuctionContext prepareSuccessfulResponse(WithPodErrors context, - RoutingContext routingContext, - VideoEvent.VideoEventBuilder videoEventBuilder) { + private Future prepareSuccessfulResponse(WithPodErrors context, + RoutingContext routingContext) { final AuctionContext auctionContext = context.getData(); final VideoResponse videoResponse = videoResponseFactory.toVideoResponse( @@ -138,8 +139,6 @@ private AuctionContext prepareSuccessfulResponse(WithPodErrors c auctionContext.getBidResponse(), context.getPodErrors()); - addToEvent(videoResponse, videoEventBuilder::bidResponse, videoResponse); - final MultiMap responseHeaders = getCommonResponseHeaders(routingContext) .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); @@ -148,7 +147,7 @@ private AuctionContext prepareSuccessfulResponse(WithPodErrors c .responseHeaders(responseHeaders) .build(); - return auctionContext.with(rawAuctionResponse); + return Future.succeededFuture(auctionContext.with(rawAuctionResponse)); } private Future invokeExitpointHooks(AuctionContext auctionContext) { @@ -164,6 +163,16 @@ private Future invokeExitpointHooks(AuctionContext auctionContex .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo); } + private AuctionContext prepareVideoResponse(WithPodErrors contextToErrors, + VideoEvent.VideoEventBuilder videoEventBuilder) { + + final VideoResponse videoResponse = videoResponseFactory.toVideoResponse( + contextToErrors.getData(), contextToErrors.getData().getBidResponse(), + contextToErrors.getPodErrors()); + addToEvent(videoResponse, videoEventBuilder::bidResponse, videoResponse); + return contextToErrors.getData(); + } + private static R addToEvent(T field, Consumer consumer, R result) { consumer.accept(field); return result; 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 7b5d11fcc51..9167b1148ec 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 @@ -516,15 +516,16 @@ public void shouldFailOnUnexpectedResponseStatus() { public void shouldFailWhenAdUnitsListIsEmpty() { // given final AuctionContext auctionContext = mock(AuctionContext.class); + final BidResponse bidResponse = mock(BidResponse.class); when(auctionContext.getBidRequest()) .thenReturn(BidRequest.builder() .id("request1") .ext(givenExtRequest()) .build()); - when(auctionContext.getBidResponse()).thenReturn(BidResponse.builder().build()); final AuctionEvent event = mock(AuctionEvent.class); when(event.getAuctionContext()).thenReturn(auctionContext); + when(event.getBidResponse()).thenReturn(bidResponse); // when final Future result = target.processEvent(event); 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 5a05f7d3534..a77b6d1993f 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java @@ -1184,10 +1184,7 @@ public void shouldReturnSendAmpEventWithAuctionContextBidResponseDebugInfoHoldin // then final AmpEvent ampEvent = captureAmpEvent(); - final BidResponse bidResponseBeforeHook = ampEvent.getBidResponse(); - assertThat(bidResponseBeforeHook.getExt()).isNull(); - - final BidResponse bidResponseAfterHook = ampEvent.getAuctionContext().getBidResponse(); + final BidResponse bidResponse = ampEvent.getBidResponse(); final ExtModulesTraceAnalyticsTags expectedAnalyticsTags = ExtModulesTraceAnalyticsTags.of(singletonList( ExtModulesTraceAnalyticsActivity.of( "some-activity", @@ -1196,7 +1193,7 @@ public void shouldReturnSendAmpEventWithAuctionContextBidResponseDebugInfoHoldin "success", mapper.createObjectNode(), givenExtModulesTraceAnalyticsAppliedTo()))))); - assertThat(bidResponseAfterHook.getExt().getPrebid().getModules().getTrace()).isEqualTo(ExtModulesTrace.of( + assertThat(bidResponse.getExt().getPrebid().getModules().getTrace()).isEqualTo(ExtModulesTrace.of( 8L, List.of( ExtModulesTraceStage.of( @@ -1303,11 +1300,8 @@ public void shouldReturnSendAmpEventWithAuctionContextBidResponseAnalyticsTagsHo // then final AmpEvent ampEvent = captureAmpEvent(); - final BidResponse bidResponseBeforeHook = ampEvent.getBidResponse(); - assertThat(bidResponseBeforeHook.getExt()).isNull(); - - final BidResponse bidResponseAfterHook = ampEvent.getAuctionContext().getBidResponse(); - assertThat(bidResponseAfterHook.getExt()) + final BidResponse bidResponse = ampEvent.getBidResponse(); + assertThat(bidResponse.getExt()) .extracting(ExtBidResponse::getPrebid) .extracting(ExtBidResponsePrebid::getAnalytics) .extracting(ExtAnalytics::getTags) 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 05f7ecdc3c6..2f0e2f78d91 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java @@ -1062,10 +1062,7 @@ public void shouldReturnSendAuctionEventWithAuctionContextBidResponseDebugInfoHo // then final AuctionEvent auctionEvent = captureAuctionEvent(); - final BidResponse bidResponseBeforeHook = auctionEvent.getBidResponse(); - assertThat(bidResponseBeforeHook.getExt()).isNull(); - - final BidResponse bidResponseAfterHook = auctionEvent.getAuctionContext().getBidResponse(); + final BidResponse bidResponse = auctionEvent.getBidResponse(); final ExtModulesTraceAnalyticsTags expectedAnalyticsTags = ExtModulesTraceAnalyticsTags.of(singletonList( ExtModulesTraceAnalyticsActivity.of( "some-activity", @@ -1074,7 +1071,7 @@ public void shouldReturnSendAuctionEventWithAuctionContextBidResponseDebugInfoHo "success", mapper.createObjectNode(), givenExtModulesTraceAnalyticsAppliedTo()))))); - assertThat(bidResponseAfterHook.getExt().getPrebid().getModules().getTrace()).isEqualTo(ExtModulesTrace.of( + assertThat(bidResponse.getExt().getPrebid().getModules().getTrace()).isEqualTo(ExtModulesTrace.of( 8L, List.of( ExtModulesTraceStage.of( @@ -1181,11 +1178,8 @@ public void shouldReturnSendAuctionEventWithAuctionContextBidResponseAnalyticsTa // then final AuctionEvent auctionEvent = captureAuctionEvent(); - final BidResponse bidResponseBeforeHook = auctionEvent.getBidResponse(); - assertThat(bidResponseBeforeHook.getExt()).isNull(); - - final BidResponse bidResponseAfterHook = auctionEvent.getAuctionContext().getBidResponse(); - assertThat(bidResponseAfterHook.getExt()) + final BidResponse bidResponse = auctionEvent.getBidResponse(); + assertThat(bidResponse.getExt()) .extracting(ExtBidResponse::getPrebid) .extracting(ExtBidResponsePrebid::getAnalytics) .extracting(ExtAnalytics::getTags) 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 8117459e43c..64efc34c093 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java @@ -64,6 +64,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -308,7 +309,7 @@ public void shouldRespondWithBidResponse() { target.handle(routingContext); // then - verify(videoResponseFactory).toVideoResponse(any(), any(), any()); + verify(videoResponseFactory, times(2)).toVideoResponse(any(), any(), any()); assertThat(httpResponse.headers()).hasSize(2) .extracting(Map.Entry::getKey, Map.Entry::getValue) @@ -353,7 +354,7 @@ public void shouldRespondWithBidResponseWhenExitpointHookChangesResponseAndHeade target.handle(routingContext); // then - verify(videoResponseFactory).toVideoResponse(any(), any(), any()); + verify(videoResponseFactory, times(2)).toVideoResponse(any(), any(), any()); assertThat(httpResponse.headers()).hasSize(1) .extracting(Map.Entry::getKey, Map.Entry::getValue) From 8346d4fca0bcb139baac2cae654dad8cd67dafaf Mon Sep 17 00:00:00 2001 From: antonbabak Date: Thu, 21 Nov 2024 16:34:39 +0100 Subject: [PATCH 05/10] Some fixes --- .../server/handler/openrtb2/AmpHandler.java | 11 +++---- .../server/handler/openrtb2/VideoHandler.java | 33 +++++++------------ .../handler/openrtb2/VideoHandlerTest.java | 5 ++- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java index 7b57510813a..d0c80efbb75 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java @@ -147,16 +147,11 @@ public void handle(RoutingContext routingContext) { .map(context -> addToEvent(context, ampEventBuilder::auctionContext, context)) .map(this::updateAppAndNoCookieAndImpsMetrics) .compose(exchangeService::holdAuction) - .compose(context -> prepareSuccessfulResponse(context, routingContext)) + .compose(context -> prepareSuccessfulResponse(context, routingContext, ampEventBuilder)) .compose(this::invokeExitpointHooks) .map(hooksMetricsService::updateHooksMetrics) .map(context -> addToEvent(context, ampEventBuilder::auctionContext, context)) .map(context -> addToEvent(context.getBidResponse(), ampEventBuilder::bidResponse, context)) - .compose(context -> prepareAmpResponse(context, routingContext)) - .map(result -> { - addToEvent(result.getLeft().getTargeting(), ampEventBuilder::targeting, result); - return result.getRight(); - }) .onComplete(responseResult -> handleResult(responseResult, ampEventBuilder, routingContext, startTime)); } @@ -181,13 +176,15 @@ private AuctionContext updateAppAndNoCookieAndImpsMetrics(AuctionContext context } private Future prepareSuccessfulResponse(AuctionContext auctionContext, - RoutingContext routingContext) { + RoutingContext routingContext, + AmpEvent.AmpEventBuilder ampEventBuilder) { final String origin = originFrom(routingContext); final MultiMap responseHeaders = getCommonResponseHeaders(routingContext, origin) .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); return prepareAmpResponse(auctionContext, routingContext) + .map(result -> addToEvent(result.getLeft().getTargeting(), ampEventBuilder::targeting, result)) .map(result -> { final RawAuctionResponse rawAuctionResponse = RawAuctionResponse.builder() .responseBody(mapper.encodeToString(result.getLeft())) diff --git a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java index 646b5b72b0d..50e4e817956 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java @@ -120,18 +120,17 @@ public void handle(RoutingContext routingContext) { .map(contextToErrors -> addToEvent(contextToErrors.getData(), videoEventBuilder::auctionContext, contextToErrors)) - .compose(contextToErrors -> prepareSuccessfulResponse(contextToErrors, routingContext) - .compose(this::invokeExitpointHooks) - .map(hooksMetricsService::updateHooksMetrics) - .map(context -> addToEvent(context, videoEventBuilder::auctionContext, context)) - .map(context -> WithPodErrors.of(context, contextToErrors.getPodErrors()))) - - .map(contextToErrors -> prepareVideoResponse(contextToErrors, videoEventBuilder)) - .onComplete(context -> handleResult(context, videoEventBuilder, routingContext, startTime)); + .map(contextToErrors -> prepareSuccessfulResponse(contextToErrors, routingContext, videoEventBuilder)) + .compose(this::invokeExitpointHooks) + .map(hooksMetricsService::updateHooksMetrics) + // populate event with updated context + .map(context -> addToEvent(context, videoEventBuilder::auctionContext, context)) + .onComplete(result -> handleResult(result, videoEventBuilder, routingContext, startTime)); } - private Future prepareSuccessfulResponse(WithPodErrors context, - RoutingContext routingContext) { + private AuctionContext prepareSuccessfulResponse(WithPodErrors context, + RoutingContext routingContext, + VideoEvent.VideoEventBuilder videoEventBuilder) { final AuctionContext auctionContext = context.getData(); final VideoResponse videoResponse = videoResponseFactory.toVideoResponse( @@ -139,6 +138,8 @@ private Future prepareSuccessfulResponse(WithPodErrors prepareSuccessfulResponse(WithPodErrors invokeExitpointHooks(AuctionContext auctionContext) { @@ -163,16 +164,6 @@ private Future invokeExitpointHooks(AuctionContext auctionContex .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo); } - private AuctionContext prepareVideoResponse(WithPodErrors contextToErrors, - VideoEvent.VideoEventBuilder videoEventBuilder) { - - final VideoResponse videoResponse = videoResponseFactory.toVideoResponse( - contextToErrors.getData(), contextToErrors.getData().getBidResponse(), - contextToErrors.getPodErrors()); - addToEvent(videoResponse, videoEventBuilder::bidResponse, videoResponse); - return contextToErrors.getData(); - } - private static R addToEvent(T field, Consumer consumer, R result) { consumer.accept(field); return result; 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..8117459e43c 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java @@ -64,7 +64,6 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -309,7 +308,7 @@ public void shouldRespondWithBidResponse() { target.handle(routingContext); // then - verify(videoResponseFactory, times(2)).toVideoResponse(any(), any(), any()); + verify(videoResponseFactory).toVideoResponse(any(), any(), any()); assertThat(httpResponse.headers()).hasSize(2) .extracting(Map.Entry::getKey, Map.Entry::getValue) @@ -354,7 +353,7 @@ public void shouldRespondWithBidResponseWhenExitpointHookChangesResponseAndHeade target.handle(routingContext); // then - verify(videoResponseFactory, times(2)).toVideoResponse(any(), any(), any()); + verify(videoResponseFactory).toVideoResponse(any(), any(), any()); assertThat(httpResponse.headers()).hasSize(1) .extracting(Map.Entry::getKey, Map.Entry::getValue) From 3de7a14f3255a8b4df498949397d028fa601d006 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Mon, 25 Nov 2024 15:25:10 +0100 Subject: [PATCH 06/10] Refactoring and Tests --- .../payload/ActivityInvocationPayload.java | 5 -- .../payload/GeoActivityInvocationPayload.java | 5 -- .../payload/GpcActivityInvocationPayload.java | 5 -- .../impl/BasicActivityInvocationPayload.java | 22 ----- .../server/auction/model/AuctionContext.java | 7 -- .../auction/model/RawAuctionResponse.java | 22 ----- .../server/handler/openrtb2/AmpHandler.java | 60 ++++++++------ .../handler/openrtb2/AuctionHandler.java | 57 +++++++------ .../handler/openrtb2/RawResponseContext.java | 18 ++++ .../server/handler/openrtb2/VideoHandler.java | 82 ++++++++++--------- .../prebid/server/metric/StageMetrics.java | 1 + .../handler/openrtb2/AmpHandlerTest.java | 14 ---- .../handler/openrtb2/AuctionHandlerTest.java | 11 --- .../handler/openrtb2/VideoHandlerTest.java | 5 +- .../org/prebid/server/it/hooks/HooksTest.java | 54 ++++++++++++ .../it/hooks/SampleItExitpointHook.java | 34 +++++++- .../it/hooks/TestHooksConfiguration.java | 9 ++ .../org/prebid/server/metric/MetricsTest.java | 12 +++ 18 files changed, 237 insertions(+), 186 deletions(-) delete mode 100644 src/main/java/org/prebid/server/activity/infrastructure/payload/impl/BasicActivityInvocationPayload.java delete mode 100644 src/main/java/org/prebid/server/auction/model/RawAuctionResponse.java create mode 100644 src/main/java/org/prebid/server/handler/openrtb2/RawResponseContext.java diff --git a/src/main/java/org/prebid/server/activity/infrastructure/payload/ActivityInvocationPayload.java b/src/main/java/org/prebid/server/activity/infrastructure/payload/ActivityInvocationPayload.java index 806928d4230..2014b7ab81f 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/payload/ActivityInvocationPayload.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/payload/ActivityInvocationPayload.java @@ -1,13 +1,8 @@ package org.prebid.server.activity.infrastructure.payload; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.prebid.server.activity.ComponentType; -import org.prebid.server.activity.infrastructure.payload.impl.BasicActivityInvocationPayload; -@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) -@JsonSubTypes(@JsonSubTypes.Type(BasicActivityInvocationPayload.class)) public interface ActivityInvocationPayload { @JsonProperty diff --git a/src/main/java/org/prebid/server/activity/infrastructure/payload/GeoActivityInvocationPayload.java b/src/main/java/org/prebid/server/activity/infrastructure/payload/GeoActivityInvocationPayload.java index 355583ae675..54d115615e7 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/payload/GeoActivityInvocationPayload.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/payload/GeoActivityInvocationPayload.java @@ -1,12 +1,7 @@ package org.prebid.server.activity.infrastructure.payload; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import org.prebid.server.activity.infrastructure.payload.impl.BasicActivityInvocationPayload; -@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) -@JsonSubTypes(@JsonSubTypes.Type(BasicActivityInvocationPayload.class)) public interface GeoActivityInvocationPayload extends ActivityInvocationPayload { @JsonProperty diff --git a/src/main/java/org/prebid/server/activity/infrastructure/payload/GpcActivityInvocationPayload.java b/src/main/java/org/prebid/server/activity/infrastructure/payload/GpcActivityInvocationPayload.java index 944a8463c46..981324a40ea 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/payload/GpcActivityInvocationPayload.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/payload/GpcActivityInvocationPayload.java @@ -1,12 +1,7 @@ package org.prebid.server.activity.infrastructure.payload; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import org.prebid.server.activity.infrastructure.payload.impl.BasicActivityInvocationPayload; -@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) -@JsonSubTypes(@JsonSubTypes.Type(BasicActivityInvocationPayload.class)) public interface GpcActivityInvocationPayload extends ActivityInvocationPayload { @JsonProperty diff --git a/src/main/java/org/prebid/server/activity/infrastructure/payload/impl/BasicActivityInvocationPayload.java b/src/main/java/org/prebid/server/activity/infrastructure/payload/impl/BasicActivityInvocationPayload.java deleted file mode 100644 index 0229886bfce..00000000000 --- a/src/main/java/org/prebid/server/activity/infrastructure/payload/impl/BasicActivityInvocationPayload.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.prebid.server.activity.infrastructure.payload.impl; - -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.activity.ComponentType; -import org.prebid.server.activity.infrastructure.payload.GeoActivityInvocationPayload; -import org.prebid.server.activity.infrastructure.payload.GpcActivityInvocationPayload; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class BasicActivityInvocationPayload implements GeoActivityInvocationPayload, GpcActivityInvocationPayload { - - ComponentType componentType; - - String componentName; - - String country; - - String region; - - String gpc; -} diff --git a/src/main/java/org/prebid/server/auction/model/AuctionContext.java b/src/main/java/org/prebid/server/auction/model/AuctionContext.java index a0c771686e9..5dbe83c3ff2 100644 --- a/src/main/java/org/prebid/server/auction/model/AuctionContext.java +++ b/src/main/java/org/prebid/server/auction/model/AuctionContext.java @@ -77,9 +77,6 @@ public class AuctionContext { @Builder.Default BidAdjustments bidAdjustments = BidAdjustments.of(Collections.emptyMap()); - @JsonIgnore - RawAuctionResponse rawAuctionResponse; - public AuctionContext with(Account account) { return this.toBuilder().account(account).build(); } @@ -139,10 +136,6 @@ public AuctionContext with(BidAdjustments bidAdjustments) { .build(); } - public AuctionContext with(RawAuctionResponse rawAuctionResponse) { - return this.toBuilder().rawAuctionResponse(rawAuctionResponse).build(); - } - public AuctionContext withRequestRejected() { return this.toBuilder() .requestRejected(true) diff --git a/src/main/java/org/prebid/server/auction/model/RawAuctionResponse.java b/src/main/java/org/prebid/server/auction/model/RawAuctionResponse.java deleted file mode 100644 index a9c1714c4d3..00000000000 --- a/src/main/java/org/prebid/server/auction/model/RawAuctionResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.prebid.server.auction.model; - -import io.vertx.core.MultiMap; -import lombok.Builder; -import lombok.Value; -import org.prebid.server.hooks.v1.exit.ExitpointPayload; - -@Value(staticConstructor = "of") -@Builder(toBuilder = true) -public class RawAuctionResponse { - - String responseBody; - - MultiMap responseHeaders; - - public RawAuctionResponse of(ExitpointPayload exitpointPayload) { - return this.toBuilder() - .responseHeaders(exitpointPayload.responseHeaders()) - .responseBody(exitpointPayload.responseBody()) - .build(); - } -} diff --git a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java index d0c80efbb75..f90ecc106b7 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java @@ -27,7 +27,6 @@ import org.prebid.server.auction.HookDebugInfoEnricher; import org.prebid.server.auction.HooksMetricsService; import org.prebid.server.auction.model.AuctionContext; -import org.prebid.server.auction.model.RawAuctionResponse; import org.prebid.server.auction.model.Tuple2; import org.prebid.server.auction.requestfactory.AmpRequestFactory; import org.prebid.server.bidder.BidderCatalog; @@ -147,11 +146,15 @@ public void handle(RoutingContext routingContext) { .map(context -> addToEvent(context, ampEventBuilder::auctionContext, context)) .map(this::updateAppAndNoCookieAndImpsMetrics) .compose(exchangeService::holdAuction) - .compose(context -> prepareSuccessfulResponse(context, routingContext, ampEventBuilder)) - .compose(this::invokeExitpointHooks) - .map(hooksMetricsService::updateHooksMetrics) .map(context -> addToEvent(context, ampEventBuilder::auctionContext, context)) .map(context -> addToEvent(context.getBidResponse(), ampEventBuilder::bidResponse, context)) + .compose(context -> prepareSuccessfulResponse(context, routingContext, ampEventBuilder)) + .compose(this::invokeExitpointHooks) + .map(context -> addToEvent(context.getAuctionContext(), ampEventBuilder::auctionContext, context)) + .map(context -> addToEvent( + context.getAuctionContext().getBidResponse(), + ampEventBuilder::bidResponse, + context)) .onComplete(responseResult -> handleResult(responseResult, ampEventBuilder, routingContext, startTime)); } @@ -175,9 +178,9 @@ private AuctionContext updateAppAndNoCookieAndImpsMetrics(AuctionContext context return context; } - private Future prepareSuccessfulResponse(AuctionContext auctionContext, - RoutingContext routingContext, - AmpEvent.AmpEventBuilder ampEventBuilder) { + private Future prepareSuccessfulResponse(AuctionContext auctionContext, + RoutingContext routingContext, + AmpEvent.AmpEventBuilder ampEventBuilder) { final String origin = originFrom(routingContext); final MultiMap responseHeaders = getCommonResponseHeaders(routingContext, origin) @@ -185,26 +188,29 @@ private Future prepareSuccessfulResponse(AuctionContext auctionC return prepareAmpResponse(auctionContext, routingContext) .map(result -> addToEvent(result.getLeft().getTargeting(), ampEventBuilder::targeting, result)) - .map(result -> { - final RawAuctionResponse rawAuctionResponse = RawAuctionResponse.builder() - .responseBody(mapper.encodeToString(result.getLeft())) - .responseHeaders(responseHeaders) - .build(); - return result.getRight().with(rawAuctionResponse); - }); + .map(result -> RawResponseContext.builder() + .responseBody(mapper.encodeToString(result.getLeft())) + .responseHeaders(responseHeaders) + .auctionContext(auctionContext) + .build()); } - private Future invokeExitpointHooks(AuctionContext auctionContext) { - final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); + private Future invokeExitpointHooks(RawResponseContext rawResponseContext) { + final AuctionContext auctionContext = rawResponseContext.getAuctionContext(); return hookStageExecutor.executeExitpointStage( - rawAuctionResponse.getResponseHeaders(), - rawAuctionResponse.getResponseBody(), + rawResponseContext.getResponseHeaders(), + rawResponseContext.getResponseBody(), auctionContext) .map(HookStageExecutionResult::getPayload) - .map(rawAuctionResponse::of) - .map(auctionContext::with) - .map(AnalyticsTagsEnricher::enrichWithAnalyticsTags) - .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo); + .compose(payload -> Future.succeededFuture(auctionContext) + .map(AnalyticsTagsEnricher::enrichWithAnalyticsTags) + .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo) + .map(hooksMetricsService::updateHooksMetrics) + .map(context -> RawResponseContext.builder() + .auctionContext(context) + .responseHeaders(payload.responseHeaders()) + .responseBody(payload.responseBody()) + .build())); } private Future> prepareAmpResponse(AuctionContext context, @@ -313,13 +319,13 @@ private static ExtAmpVideoResponse extResponseFrom(BidResponse bidResponse) { : null; } - private void handleResult(AsyncResult responseResult, + private void handleResult(AsyncResult responseResult, AmpEvent.AmpEventBuilder ampEventBuilder, RoutingContext routingContext, long startTime) { final boolean responseSucceeded = responseResult.succeeded(); - final AuctionContext auctionContext = responseSucceeded ? responseResult.result() : null; + final RawResponseContext rawResponseContext = responseSucceeded ? responseResult.result() : null; final MetricName metricRequestStatus; final List errorMessages; @@ -337,11 +343,10 @@ private void handleResult(AsyncResult responseResult, errorMessages = Collections.emptyList(); status = HttpResponseStatus.OK; - final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); - rawAuctionResponse.getResponseHeaders() + rawResponseContext.getResponseHeaders() .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, header.getKey(), header.getValue())); - body = rawAuctionResponse.getResponseBody(); + body = rawResponseContext.getResponseBody(); } else { final Throwable exception = responseResult.cause(); if (exception instanceof InvalidRequestException invalidRequestException) { @@ -405,6 +410,7 @@ private void handleResult(AsyncResult responseResult, final int statusCode = status.code(); final AmpEvent ampEvent = ampEventBuilder.status(statusCode).errors(errorMessages).build(); + final AuctionContext auctionContext = ampEvent.getAuctionContext(); final PrivacyContext privacyContext = auctionContext != null ? auctionContext.getPrivacyContext() : null; final TcfContext tcfContext = privacyContext != null ? privacyContext.getTcfContext() : TcfContext.empty(); diff --git a/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java index 2f90c788b64..1de5e717339 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java @@ -18,7 +18,6 @@ import org.prebid.server.auction.HooksMetricsService; import org.prebid.server.auction.SkippedAuctionService; import org.prebid.server.auction.model.AuctionContext; -import org.prebid.server.auction.model.RawAuctionResponse; import org.prebid.server.auction.requestfactory.AuctionRequestFactory; import org.prebid.server.cookie.UidsCookie; import org.prebid.server.exception.BlocklistedAccountException; @@ -116,10 +115,10 @@ public void handle(RoutingContext routingContext) { .recover(throwable -> holdAuction(auctionEventBuilder, auctionContext))) .map(context -> prepareSuccessfulResponse(context, routingContext)) .compose(this::invokeExitpointHooks) - .map(hooksMetricsService::updateHooksMetrics) - .map(context -> addToEvent(context, auctionEventBuilder::auctionContext, context)) - .map(context -> addToEvent(context.getBidResponse(), auctionEventBuilder::bidResponse, context)) - .onComplete(context -> handleResult(context, auctionEventBuilder, routingContext, startTime)); + .map(context -> addToEvent(context.getAuctionContext(), auctionEventBuilder::auctionContext, context)) + .map(context -> addToEvent( + context.getAuctionContext().getBidResponse(), auctionEventBuilder::bidResponse, context)) + .onComplete(result -> handleResult(result, auctionEventBuilder, routingContext, startTime)); } private Future holdAuction(AuctionEvent.AuctionEventBuilder auctionEventBuilder, @@ -129,7 +128,8 @@ private Future holdAuction(AuctionEvent.AuctionEventBuilder auct .map(this::updateAppAndNoCookieAndImpsMetrics) // In case of holdAuction Exception and auctionContext is not present below .map(context -> addToEvent(context, auctionEventBuilder::auctionContext, context)) - .compose(exchangeService::holdAuction); + .compose(exchangeService::holdAuction) + .map(context -> addToEvent(context.getBidResponse(), auctionEventBuilder::bidResponse, context)); } private static R addToEvent(T field, Consumer consumer, R result) { @@ -154,43 +154,53 @@ private AuctionContext updateAppAndNoCookieAndImpsMetrics(AuctionContext context return context; } - private AuctionContext prepareSuccessfulResponse(AuctionContext auctionContext, RoutingContext routingContext) { + private RawResponseContext prepareSuccessfulResponse(AuctionContext auctionContext, RoutingContext routingContext) { final MultiMap responseHeaders = getCommonResponseHeaders(routingContext) .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); - final RawAuctionResponse rawAuctionResponse = RawAuctionResponse.builder() + return RawResponseContext.builder() .responseBody(mapper.encodeToString(auctionContext.getBidResponse())) .responseHeaders(responseHeaders) + .auctionContext(auctionContext) .build(); - - return auctionContext.with(rawAuctionResponse); } - private Future invokeExitpointHooks(AuctionContext auctionContext) { + private Future invokeExitpointHooks(RawResponseContext rawResponseContext) { + final AuctionContext auctionContext = rawResponseContext.getAuctionContext(); + if (auctionContext.isAuctionSkipped()) { - return Future.succeededFuture(auctionContext); + return Future.succeededFuture(auctionContext) + .map(hooksMetricsService::updateHooksMetrics) + .map(context -> rawResponseContext); } - final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); return hookStageExecutor.executeExitpointStage( - rawAuctionResponse.getResponseHeaders(), - rawAuctionResponse.getResponseBody(), + rawResponseContext.getResponseHeaders(), + rawResponseContext.getResponseBody(), auctionContext) .map(HookStageExecutionResult::getPayload) - .map(rawAuctionResponse::of) - .map(auctionContext::with) - .map(AnalyticsTagsEnricher::enrichWithAnalyticsTags) - .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo); + .compose(payload -> Future.succeededFuture(auctionContext) + .map(AnalyticsTagsEnricher::enrichWithAnalyticsTags) + .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo) + .map(hooksMetricsService::updateHooksMetrics) + .map(context -> RawResponseContext.builder() + .auctionContext(context) + .responseHeaders(payload.responseHeaders()) + .responseBody(payload.responseBody()) + .build())); } - private void handleResult(AsyncResult responseResult, + private void handleResult(AsyncResult responseResult, AuctionEvent.AuctionEventBuilder auctionEventBuilder, RoutingContext routingContext, long startTime) { final boolean responseSucceeded = responseResult.succeeded(); - final AuctionContext auctionContext = responseSucceeded ? responseResult.result() : null; + final RawResponseContext rawResponseContext = responseSucceeded ? responseResult.result() : null; + final AuctionContext auctionContext = rawResponseContext != null + ? rawResponseContext.getAuctionContext() + : null; final boolean isAuctionSkipped = responseSucceeded && auctionContext.isAuctionSkipped(); final MetricName requestType = responseSucceeded ? auctionContext.getRequestTypeMetric() @@ -209,11 +219,10 @@ private void handleResult(AsyncResult responseResult, errorMessages = Collections.emptyList(); status = HttpResponseStatus.OK; - final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); - rawAuctionResponse.getResponseHeaders() + rawResponseContext.getResponseHeaders() .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, header.getKey(), header.getValue())); - body = rawAuctionResponse.getResponseBody(); + body = rawResponseContext.getResponseBody(); } else { final Throwable exception = responseResult.cause(); if (exception instanceof InvalidRequestException invalidRequestException) { diff --git a/src/main/java/org/prebid/server/handler/openrtb2/RawResponseContext.java b/src/main/java/org/prebid/server/handler/openrtb2/RawResponseContext.java new file mode 100644 index 00000000000..5fe80a55c1d --- /dev/null +++ b/src/main/java/org/prebid/server/handler/openrtb2/RawResponseContext.java @@ -0,0 +1,18 @@ +package org.prebid.server.handler.openrtb2; + +import io.vertx.core.MultiMap; +import lombok.Builder; +import lombok.Value; +import org.prebid.server.auction.model.AuctionContext; + +@Value(staticConstructor = "of") +@Builder(toBuilder = true) +public class RawResponseContext { + + AuctionContext auctionContext; + + String responseBody; + + MultiMap responseHeaders; + +} diff --git a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java index 50e4e817956..c72e237e476 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java @@ -1,5 +1,6 @@ package org.prebid.server.handler.openrtb2; +import com.iab.openrtb.request.video.PodError; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.AsyncResult; @@ -17,7 +18,6 @@ import org.prebid.server.auction.VideoResponseFactory; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.CachedDebugLog; -import org.prebid.server.auction.model.RawAuctionResponse; import org.prebid.server.auction.model.WithPodErrors; import org.prebid.server.auction.requestfactory.VideoRequestFactory; import org.prebid.server.cache.CoreCacheService; @@ -120,48 +120,55 @@ public void handle(RoutingContext routingContext) { .map(contextToErrors -> addToEvent(contextToErrors.getData(), videoEventBuilder::auctionContext, contextToErrors)) - .map(contextToErrors -> prepareSuccessfulResponse(contextToErrors, routingContext, videoEventBuilder)) - .compose(this::invokeExitpointHooks) - .map(hooksMetricsService::updateHooksMetrics) - // populate event with updated context - .map(context -> addToEvent(context, videoEventBuilder::auctionContext, context)) + .compose(contextToErrors -> + prepareSuccessfulResponse(contextToErrors, routingContext, videoEventBuilder) + .compose(this::invokeExitpointHooks) + .compose(context -> toVideoResponse(context.getAuctionContext(), contextToErrors.getPodErrors()) + .map(videoResponse -> + addToEvent(videoResponse, videoEventBuilder::bidResponse, context))) + .map(context -> + addToEvent(context.getAuctionContext(), videoEventBuilder::auctionContext, context))) .onComplete(result -> handleResult(result, videoEventBuilder, routingContext, startTime)); } - private AuctionContext prepareSuccessfulResponse(WithPodErrors context, - RoutingContext routingContext, - VideoEvent.VideoEventBuilder videoEventBuilder) { + private Future prepareSuccessfulResponse(WithPodErrors context, + RoutingContext routingContext, + VideoEvent.VideoEventBuilder videoEventBuilder) { final AuctionContext auctionContext = context.getData(); - final VideoResponse videoResponse = videoResponseFactory.toVideoResponse( - auctionContext, - auctionContext.getBidResponse(), - context.getPodErrors()); - - addToEvent(videoResponse, videoEventBuilder::bidResponse, videoResponse); - final MultiMap responseHeaders = getCommonResponseHeaders(routingContext) .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); - final RawAuctionResponse rawAuctionResponse = RawAuctionResponse.builder() - .responseBody(mapper.encodeToString(videoResponse)) - .responseHeaders(responseHeaders) - .build(); + return toVideoResponse(auctionContext, context.getPodErrors()) + .map(videoResponse -> addToEvent(videoResponse, videoEventBuilder::bidResponse, videoResponse)) + .map(videoResponse -> RawResponseContext.builder() + .responseBody(mapper.encodeToString(videoResponse)) + .responseHeaders(responseHeaders) + .auctionContext(auctionContext) + .build()); + } - return auctionContext.with(rawAuctionResponse); + private Future toVideoResponse(AuctionContext auctionContext, List podErrors) { + return Future.succeededFuture( + videoResponseFactory.toVideoResponse(auctionContext, auctionContext.getBidResponse(), podErrors)); } - private Future invokeExitpointHooks(AuctionContext auctionContext) { - final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); + private Future invokeExitpointHooks(RawResponseContext rawResponseContext) { + final AuctionContext auctionContext = rawResponseContext.getAuctionContext(); return hookStageExecutor.executeExitpointStage( - rawAuctionResponse.getResponseHeaders(), - rawAuctionResponse.getResponseBody(), + rawResponseContext.getResponseHeaders(), + rawResponseContext.getResponseBody(), auctionContext) .map(HookStageExecutionResult::getPayload) - .map(rawAuctionResponse::of) - .map(auctionContext::with) - .map(AnalyticsTagsEnricher::enrichWithAnalyticsTags) - .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo); + .compose(payload -> Future.succeededFuture(auctionContext) + .map(AnalyticsTagsEnricher::enrichWithAnalyticsTags) + .map(HookDebugInfoEnricher::enrichWithHooksDebugInfo) + .map(hooksMetricsService::updateHooksMetrics) + .map(context -> RawResponseContext.builder() + .auctionContext(context) + .responseHeaders(payload.responseHeaders()) + .responseBody(payload.responseBody()) + .build())); } private static R addToEvent(T field, Consumer consumer, R result) { @@ -169,7 +176,7 @@ private static R addToEvent(T field, Consumer consumer, R result) { return result; } - private void handleResult(AsyncResult responseResult, + private void handleResult(AsyncResult responseResult, VideoEvent.VideoEventBuilder videoEventBuilder, RoutingContext routingContext, long startTime) { @@ -179,7 +186,7 @@ private void handleResult(AsyncResult responseResult, final List errorMessages; final HttpResponseStatus status; final String body; - final AuctionContext auctionContext = responseSucceeded ? responseResult.result() : null; + final RawResponseContext rawResponseContext = responseSucceeded ? responseResult.result() : null; final HttpServerResponse response = routingContext.response(); final MultiMap responseHeaders = response.headers(); @@ -189,11 +196,10 @@ private void handleResult(AsyncResult responseResult, errorMessages = Collections.emptyList(); status = HttpResponseStatus.OK; - final RawAuctionResponse rawAuctionResponse = auctionContext.getRawAuctionResponse(); - rawAuctionResponse.getResponseHeaders() + rawResponseContext.getResponseHeaders() .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, header.getKey(), header.getValue())); - body = rawAuctionResponse.getResponseBody(); + body = rawResponseContext.getResponseBody(); } else { final Throwable exception = responseResult.cause(); if (exception instanceof InvalidRequestException) { @@ -230,16 +236,16 @@ private void handleResult(AsyncResult responseResult, } VideoEvent videoEvent = videoEventBuilder.status(status.code()).errors(errorMessages).build(); - final AuctionContext contextFromEvent = videoEvent.getAuctionContext(); + final AuctionContext auctionContext = videoEvent.getAuctionContext(); - final CachedDebugLog cachedDebugLog = contextFromEvent != null ? contextFromEvent.getCachedDebugLog() : null; + final CachedDebugLog cachedDebugLog = auctionContext != null ? auctionContext.getCachedDebugLog() : null; final String cacheKey = shouldCacheLog(status.code(), cachedDebugLog) - ? cacheDebugLog(contextFromEvent, videoEvent.getErrors()) + ? cacheDebugLog(auctionContext, videoEvent.getErrors()) : null; if (status.code() != 200 && cacheKey != null) { videoEvent = updateEventWithDebugCacheMessage(videoEvent, cacheKey); } - final PrivacyContext privacyContext = contextFromEvent != null ? contextFromEvent.getPrivacyContext() : null; + final PrivacyContext privacyContext = auctionContext != null ? auctionContext.getPrivacyContext() : null; final TcfContext tcfContext = privacyContext != null ? privacyContext.getTcfContext() : TcfContext.empty(); respondWith(routingContext, status, body, startTime, metricRequestStatus, videoEvent, tcfContext); diff --git a/src/main/java/org/prebid/server/metric/StageMetrics.java b/src/main/java/org/prebid/server/metric/StageMetrics.java index 1348266b0f7..025a47368bd 100644 --- a/src/main/java/org/prebid/server/metric/StageMetrics.java +++ b/src/main/java/org/prebid/server/metric/StageMetrics.java @@ -22,6 +22,7 @@ class StageMetrics extends UpdatableMetrics { STAGE_TO_METRIC.put(Stage.processed_bidder_response, "procbidresponse"); STAGE_TO_METRIC.put(Stage.auction_response, "auctionresponse"); STAGE_TO_METRIC.put(Stage.all_processed_bid_responses, "allprocbidresponses"); + STAGE_TO_METRIC.put(Stage.exitpoint, "exitpoint"); } private static final String UNKNOWN_STAGE = "unknown"; 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 a77b6d1993f..1c0afa05611 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java @@ -1053,15 +1053,6 @@ public void shouldPassSuccessfulEventToAnalyticsReporter() { assertThat(ampEvent.getStatus()).isEqualTo(200); assertThat(ampEvent.getAuctionContext().getRequestTypeMetric()).isEqualTo(MetricName.amp); assertThat(ampEvent.getAuctionContext().getBidResponse()).isEqualTo(expectedBidResponse); - assertThat(ampEvent.getAuctionContext().getRawAuctionResponse().getResponseBody()) - .isEqualTo("{\"targeting\":{\"hb_cache_id_bidder1\":\"value1\"}}"); - assertThat(ampEvent.getAuctionContext().getRawAuctionResponse().getResponseHeaders()).hasSize(4) - .extracting(Map.Entry::getKey, Map.Entry::getValue) - .containsOnly( - tuple("AMP-Access-Control-Allow-Source-Origin", "http://example.com"), - tuple("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin"), - tuple("Content-Type", "application/json"), - tuple("x-prebid", "pbs-java/1.00")); final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); verify(hookStageExecutor).executeExitpointStage( @@ -1113,11 +1104,6 @@ public void shouldPassSuccessfulEventToAnalyticsReporterWhenExitpointHookChanges .build(); assertThat(ampEvent.getAuctionContext().getBidResponse()).isEqualTo(expectedBidResponse); - assertThat(ampEvent.getAuctionContext().getRawAuctionResponse().getResponseBody()) - .isEqualTo("{\"targeting\":{\"new-key\":\"new-value\"}}"); - assertThat(ampEvent.getAuctionContext().getRawAuctionResponse().getResponseHeaders()).hasSize(1) - .extracting(Map.Entry::getKey, Map.Entry::getValue) - .containsOnly(tuple("New-Header", "New-Header-Value")); final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); verify(hookStageExecutor).executeExitpointStage( 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 2f0e2f78d91..23a50d17fdb 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java @@ -872,12 +872,6 @@ public void shouldPassSuccessfulEventToAnalyticsReporter() { assertThat(auctionEvent.getStatus()).isEqualTo(200); assertThat(auctionEvent.getAuctionContext().getRequestTypeMetric()).isEqualTo(MetricName.openrtb2web); assertThat(auctionEvent.getAuctionContext().getBidResponse()).isEqualTo(BidResponse.builder().build()); - assertThat(auctionEvent.getAuctionContext().getRawAuctionResponse().getResponseBody()).isEqualTo("{}"); - assertThat(auctionEvent.getAuctionContext().getRawAuctionResponse().getResponseHeaders()).hasSize(2) - .extracting(Map.Entry::getKey, Map.Entry::getValue) - .containsOnly( - tuple("Content-Type", "application/json"), - tuple("x-prebid", "pbs-java/1.00")); final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); verify(hookStageExecutor).executeExitpointStage( @@ -920,11 +914,6 @@ public void shouldPassSuccessfulEventToAnalyticsReporterWhenExitpointHookChanges assertThat(auctionEvent.getStatus()).isEqualTo(200); assertThat(auctionEvent.getAuctionContext().getRequestTypeMetric()).isEqualTo(MetricName.openrtb2web); assertThat(auctionEvent.getAuctionContext().getBidResponse()).isEqualTo(BidResponse.builder().build()); - assertThat(auctionEvent.getAuctionContext().getRawAuctionResponse().getResponseBody()) - .isEqualTo("{\"response\":{}}"); - assertThat(auctionEvent.getAuctionContext().getRawAuctionResponse().getResponseHeaders()).hasSize(1) - .extracting(Map.Entry::getKey, Map.Entry::getValue) - .containsOnly(tuple("New-Header", "New-Header-Value")); final ArgumentCaptor responseHeadersCaptor = ArgumentCaptor.forClass(MultiMap.class); verify(hookStageExecutor).executeExitpointStage( 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 8117459e43c..64efc34c093 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/VideoHandlerTest.java @@ -64,6 +64,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -308,7 +309,7 @@ public void shouldRespondWithBidResponse() { target.handle(routingContext); // then - verify(videoResponseFactory).toVideoResponse(any(), any(), any()); + verify(videoResponseFactory, times(2)).toVideoResponse(any(), any(), any()); assertThat(httpResponse.headers()).hasSize(2) .extracting(Map.Entry::getKey, Map.Entry::getValue) @@ -353,7 +354,7 @@ public void shouldRespondWithBidResponseWhenExitpointHookChangesResponseAndHeade target.handle(routingContext); // then - verify(videoResponseFactory).toVideoResponse(any(), any(), any()); + verify(videoResponseFactory, times(2)).toVideoResponse(any(), any(), any()); assertThat(httpResponse.headers()).hasSize(1) .extracting(Map.Entry::getKey, Map.Entry::getValue) diff --git a/src/test/java/org/prebid/server/it/hooks/HooksTest.java b/src/test/java/org/prebid/server/it/hooks/HooksTest.java index 3943338630c..d41adb6ba80 100644 --- a/src/test/java/org/prebid/server/it/hooks/HooksTest.java +++ b/src/test/java/org/prebid/server/it/hooks/HooksTest.java @@ -1,8 +1,21 @@ package org.prebid.server.it.hooks; +import io.restassured.http.Header; import io.restassured.response.Response; import org.json.JSONException; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.prebid.server.analytics.model.AuctionEvent; +import org.prebid.server.analytics.reporter.AnalyticsReporterDelegator; +import org.prebid.server.hooks.execution.model.GroupExecutionOutcome; +import org.prebid.server.hooks.execution.model.HookExecutionOutcome; +import org.prebid.server.hooks.execution.model.Stage; +import org.prebid.server.hooks.execution.model.StageExecutionOutcome; +import org.prebid.server.hooks.v1.analytics.ActivityImpl; +import org.prebid.server.hooks.v1.analytics.AppliedToImpl; +import org.prebid.server.hooks.v1.analytics.ResultImpl; +import org.prebid.server.hooks.v1.analytics.TagsImpl; import org.prebid.server.it.IntegrationTest; import org.prebid.server.version.PrebidVersionProvider; import org.skyscreamer.jsonassert.JSONAssert; @@ -10,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; +import java.util.List; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; @@ -18,6 +32,8 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static io.restassured.RestAssured.given; import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; import static org.hamcrest.Matchers.empty; public class HooksTest extends IntegrationTest { @@ -27,8 +43,13 @@ public class HooksTest extends IntegrationTest { @Autowired private PrebidVersionProvider versionProvider; + @Autowired + private AnalyticsReporterDelegator analyticsReporterDelegator; + @Test public void openrtb2AuctionShouldRunHooksAtEachStage() throws IOException, JSONException { + Mockito.reset(analyticsReporterDelegator); + // given WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/rubicon-exchange")) .withRequestBody(equalToJson( @@ -47,6 +68,39 @@ public void openrtb2AuctionShouldRunHooksAtEachStage() throws IOException, JSONE "hooks/sample-module/test-auction-sample-module-response.json", response, singletonList(RUBICON)); JSONAssert.assertEquals(expectedAuctionResponse, response.asString(), JSONCompareMode.LENIENT); + + //todo: remove everything below after at least one exitpoint module is added and tested by functional tests + assertThat(response.getHeaders()) + .extracting(Header::getName, Header::getValue) + .contains(tuple("Exitpoint-Hook-Header", "Exitpoint-Hook-Value")); + + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(AuctionEvent.class); + Mockito.verify(analyticsReporterDelegator).processEvent(eventCaptor.capture(), Mockito.any()); + + final AuctionEvent actualEvent = eventCaptor.getValue(); + final List exitpointHookOutcomes = actualEvent.getAuctionContext() + .getHookExecutionContext().getStageOutcomes().get(Stage.exitpoint); + + final TagsImpl expectedTags = TagsImpl.of(singletonList(ActivityImpl.of( + "exitpoint-device-id", + "success", + singletonList(ResultImpl.of( + "success", + mapper.createObjectNode().put("exitpoint-some-field", "exitpoint-some-value"), + AppliedToImpl.builder() + .impIds(singletonList("impId1")) + .request(true) + .build()))))); + + assertThat(exitpointHookOutcomes).isNotEmpty().hasSize(1).first() + .extracting(StageExecutionOutcome::getGroups) + .extracting(List::getFirst) + .extracting(GroupExecutionOutcome::getHooks) + .extracting(List::getFirst) + .extracting(HookExecutionOutcome::getAnalyticsTags) + .isEqualTo(expectedTags); + + Mockito.reset(analyticsReporterDelegator); } @Test diff --git a/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java b/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java index cfff176f8af..cfc816ddc8c 100644 --- a/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java +++ b/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java @@ -4,13 +4,21 @@ import com.iab.openrtb.response.SeatBid; import io.vertx.core.Future; import org.prebid.server.hooks.execution.v1.exitpoint.ExitpointPayloadImpl; +import org.prebid.server.hooks.v1.InvocationAction; import org.prebid.server.hooks.v1.InvocationResult; -import org.prebid.server.hooks.v1.InvocationResultUtils; +import org.prebid.server.hooks.v1.InvocationResultImpl; +import org.prebid.server.hooks.v1.InvocationStatus; +import org.prebid.server.hooks.v1.analytics.ActivityImpl; +import org.prebid.server.hooks.v1.analytics.AppliedToImpl; +import org.prebid.server.hooks.v1.analytics.ResultImpl; +import org.prebid.server.hooks.v1.analytics.TagsImpl; import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; import org.prebid.server.hooks.v1.exit.ExitpointHook; import org.prebid.server.hooks.v1.exit.ExitpointPayload; import org.prebid.server.json.JacksonMapper; +import java.util.Arrays; +import java.util.Collections; import java.util.List; public class SampleItExitpointHook implements ExitpointHook { @@ -25,12 +33,30 @@ public SampleItExitpointHook(JacksonMapper mapper) { public Future> call(ExitpointPayload exitpointPayload, AuctionInvocationContext invocationContext) { - final BidResponse bidResponse = mapper.decodeValue(exitpointPayload.responseBody(), BidResponse.class); + final BidResponse bidResponse = invocationContext.auctionContext().getBidResponse(); final List seatBids = updateBids(bidResponse.getSeatbid()); final BidResponse updatedResponse = bidResponse.toBuilder().seatbid(seatBids).build(); - return Future.succeededFuture(InvocationResultUtils.succeeded(payload -> - ExitpointPayloadImpl.of(exitpointPayload.responseHeaders(), mapper.encodeToString(updatedResponse)))); + return Future.succeededFuture(InvocationResultImpl.builder() + .status(InvocationStatus.success) + .action(InvocationAction.update) + .payloadUpdate(payload -> ExitpointPayloadImpl.of( + exitpointPayload.responseHeaders().add("Exitpoint-Hook-Header", "Exitpoint-Hook-Value"), + mapper.encodeToString(updatedResponse))) + .debugMessages(Arrays.asList( + "exitpoint debug message 1", + "exitpoint debug message 2")) + .analyticsTags(TagsImpl.of(Collections.singletonList(ActivityImpl.of( + "exitpoint-device-id", + "success", + Collections.singletonList(ResultImpl.of( + "success", + mapper.mapper().createObjectNode().put("exitpoint-some-field", "exitpoint-some-value"), + AppliedToImpl.builder() + .impIds(Collections.singletonList("impId1")) + .request(true) + .build())))))) + .build()); } private List updateBids(List seatBids) { diff --git a/src/test/java/org/prebid/server/it/hooks/TestHooksConfiguration.java b/src/test/java/org/prebid/server/it/hooks/TestHooksConfiguration.java index a08845f9c19..5fdf0724015 100644 --- a/src/test/java/org/prebid/server/it/hooks/TestHooksConfiguration.java +++ b/src/test/java/org/prebid/server/it/hooks/TestHooksConfiguration.java @@ -1,9 +1,12 @@ package org.prebid.server.it.hooks; +import org.mockito.Mockito; +import org.prebid.server.analytics.reporter.AnalyticsReporterDelegator; import org.prebid.server.hooks.v1.Module; import org.prebid.server.json.JacksonMapper; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; @TestConfiguration public class TestHooksConfiguration { @@ -12,4 +15,10 @@ public class TestHooksConfiguration { Module sampleItModule(JacksonMapper mapper) { return new SampleItModule(mapper); } + + @Bean + @Primary + AnalyticsReporterDelegator spyAnalyticsReporterDelegator(AnalyticsReporterDelegator analyticsReporterDelegator) { + return Mockito.spy(analyticsReporterDelegator); + } } diff --git a/src/test/java/org/prebid/server/metric/MetricsTest.java b/src/test/java/org/prebid/server/metric/MetricsTest.java index 104df273c3e..c351430e454 100644 --- a/src/test/java/org/prebid/server/metric/MetricsTest.java +++ b/src/test/java/org/prebid/server/metric/MetricsTest.java @@ -1187,6 +1187,9 @@ public void updateHooksMetricsShouldIncrementMetrics() { metrics.updateHooksMetrics( "module2", Stage.auction_response, "hook4", ExecutionStatus.invocation_failure, 5L, null); + metrics.updateHooksMetrics( + "module1", Stage.exitpoint, "hook5", ExecutionStatus.success, 5L, ExecutionAction.update); + // then assertThat(metricRegistry.counter("modules.module.module1.stage.entrypoint.hook.hook1.call") .getCount()) @@ -1246,6 +1249,15 @@ public void updateHooksMetricsShouldIncrementMetrics() { .isEqualTo(1); assertThat(metricRegistry.timer("modules.module.module2.stage.auctionresponse.hook.hook4.duration").getCount()) .isEqualTo(1); + + assertThat(metricRegistry.counter("modules.module.module1.stage.exitpoint.hook.hook5.call") + .getCount()) + .isEqualTo(1); + assertThat(metricRegistry.counter("modules.module.module1.stage.exitpoint.hook.hook5.success.update") + .getCount()) + .isEqualTo(1); + assertThat(metricRegistry.timer("modules.module.module1.stage.exitpoint.hook.hook5.duration").getCount()) + .isEqualTo(1); } @Test From f251be04281075e0605a5c32d8f8376e7eb007ab Mon Sep 17 00:00:00 2001 From: antonbabak Date: Tue, 26 Nov 2024 14:19:04 +0100 Subject: [PATCH 07/10] Code Style --- .../java/org/prebid/server/handler/openrtb2/AmpHandler.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java index f90ecc106b7..6af06525430 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java @@ -100,13 +100,15 @@ public class AmpHandler implements ApplicationResource { public AmpHandler(AmpRequestFactory ampRequestFactory, ExchangeService exchangeService, AnalyticsReporterDelegator analyticsDelegator, - Metrics metrics, HooksMetricsService hooksMetricsService, + Metrics metrics, + HooksMetricsService hooksMetricsService, Clock clock, BidderCatalog bidderCatalog, Set biddersSupportingCustomTargeting, AmpResponsePostProcessor ampResponsePostProcessor, HttpInteractionLogger httpInteractionLogger, - PrebidVersionProvider prebidVersionProvider, HookStageExecutor hookStageExecutor, + PrebidVersionProvider prebidVersionProvider, + HookStageExecutor hookStageExecutor, JacksonMapper mapper, double logSamplingRate) { From 79031758f47d3011d069559df2fd873bbfa51003 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Tue, 26 Nov 2024 14:22:56 +0100 Subject: [PATCH 08/10] Package Renaming --- .../org/prebid/server/hooks/execution/HookStageExecutor.java | 2 +- .../server/hooks/execution/model/StageWithHookType.java | 2 +- .../hooks/execution/v1/exitpoint/ExitpointPayloadImpl.java | 2 +- .../server/hooks/v1/{exit => exitpoint}/ExitpointHook.java | 2 +- .../server/hooks/v1/{exit => exitpoint}/ExitpointPayload.java | 2 +- .../prebid/server/hooks/execution/HookStageExecutorTest.java | 4 ++-- .../org/prebid/server/it/hooks/SampleItExitpointHook.java | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) rename src/main/java/org/prebid/server/hooks/v1/{exit => exitpoint}/ExitpointHook.java (81%) rename src/main/java/org/prebid/server/hooks/v1/{exit => exitpoint}/ExitpointPayload.java (74%) 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 d556d76d6cc..e89d9ea4173 100644 --- a/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java +++ b/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java @@ -43,7 +43,7 @@ import org.prebid.server.hooks.v1.bidder.BidderRequestPayload; import org.prebid.server.hooks.v1.bidder.BidderResponsePayload; import org.prebid.server.hooks.v1.entrypoint.EntrypointPayload; -import org.prebid.server.hooks.v1.exit.ExitpointPayload; +import org.prebid.server.hooks.v1.exitpoint.ExitpointPayload; import org.prebid.server.json.DecodeException; import org.prebid.server.json.JacksonMapper; import org.prebid.server.model.CaseInsensitiveMultiMap; diff --git a/src/main/java/org/prebid/server/hooks/execution/model/StageWithHookType.java b/src/main/java/org/prebid/server/hooks/execution/model/StageWithHookType.java index 214b038da64..961450a3c3f 100644 --- a/src/main/java/org/prebid/server/hooks/execution/model/StageWithHookType.java +++ b/src/main/java/org/prebid/server/hooks/execution/model/StageWithHookType.java @@ -10,7 +10,7 @@ import org.prebid.server.hooks.v1.bidder.ProcessedBidderResponseHook; import org.prebid.server.hooks.v1.bidder.RawBidderResponseHook; import org.prebid.server.hooks.v1.entrypoint.EntrypointHook; -import org.prebid.server.hooks.v1.exit.ExitpointHook; +import org.prebid.server.hooks.v1.exitpoint.ExitpointHook; public interface StageWithHookType> { diff --git a/src/main/java/org/prebid/server/hooks/execution/v1/exitpoint/ExitpointPayloadImpl.java b/src/main/java/org/prebid/server/hooks/execution/v1/exitpoint/ExitpointPayloadImpl.java index 7c528554ec0..d57080f6b90 100644 --- a/src/main/java/org/prebid/server/hooks/execution/v1/exitpoint/ExitpointPayloadImpl.java +++ b/src/main/java/org/prebid/server/hooks/execution/v1/exitpoint/ExitpointPayloadImpl.java @@ -3,7 +3,7 @@ import io.vertx.core.MultiMap; import lombok.Value; import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.exit.ExitpointPayload; +import org.prebid.server.hooks.v1.exitpoint.ExitpointPayload; @Accessors(fluent = true) @Value(staticConstructor = "of") diff --git a/src/main/java/org/prebid/server/hooks/v1/exit/ExitpointHook.java b/src/main/java/org/prebid/server/hooks/v1/exitpoint/ExitpointHook.java similarity index 81% rename from src/main/java/org/prebid/server/hooks/v1/exit/ExitpointHook.java rename to src/main/java/org/prebid/server/hooks/v1/exitpoint/ExitpointHook.java index 1e371b8764c..02e36af17a5 100644 --- a/src/main/java/org/prebid/server/hooks/v1/exit/ExitpointHook.java +++ b/src/main/java/org/prebid/server/hooks/v1/exitpoint/ExitpointHook.java @@ -1,4 +1,4 @@ -package org.prebid.server.hooks.v1.exit; +package org.prebid.server.hooks.v1.exitpoint; import org.prebid.server.hooks.v1.Hook; import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; diff --git a/src/main/java/org/prebid/server/hooks/v1/exit/ExitpointPayload.java b/src/main/java/org/prebid/server/hooks/v1/exitpoint/ExitpointPayload.java similarity index 74% rename from src/main/java/org/prebid/server/hooks/v1/exit/ExitpointPayload.java rename to src/main/java/org/prebid/server/hooks/v1/exitpoint/ExitpointPayload.java index dfb620349a4..ae596949fa0 100644 --- a/src/main/java/org/prebid/server/hooks/v1/exit/ExitpointPayload.java +++ b/src/main/java/org/prebid/server/hooks/v1/exitpoint/ExitpointPayload.java @@ -1,4 +1,4 @@ -package org.prebid.server.hooks.v1.exit; +package org.prebid.server.hooks.v1.exitpoint; import io.vertx.core.MultiMap; 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 49acfb9954a..e7ff6c5429d 100644 --- a/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java +++ b/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java @@ -77,8 +77,8 @@ import org.prebid.server.hooks.v1.bidder.RawBidderResponseHook; import org.prebid.server.hooks.v1.entrypoint.EntrypointHook; import org.prebid.server.hooks.v1.entrypoint.EntrypointPayload; -import org.prebid.server.hooks.v1.exit.ExitpointHook; -import org.prebid.server.hooks.v1.exit.ExitpointPayload; +import org.prebid.server.hooks.v1.exitpoint.ExitpointHook; +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; diff --git a/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java b/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java index cfc816ddc8c..e12b4a6c132 100644 --- a/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java +++ b/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java @@ -13,8 +13,8 @@ import org.prebid.server.hooks.v1.analytics.ResultImpl; import org.prebid.server.hooks.v1.analytics.TagsImpl; import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; -import org.prebid.server.hooks.v1.exit.ExitpointHook; -import org.prebid.server.hooks.v1.exit.ExitpointPayload; +import org.prebid.server.hooks.v1.exitpoint.ExitpointHook; +import org.prebid.server.hooks.v1.exitpoint.ExitpointPayload; import org.prebid.server.json.JacksonMapper; import java.util.Arrays; From dc80e05f1c16c783c969269826bf84cca84523ad Mon Sep 17 00:00:00 2001 From: antonbabak Date: Mon, 2 Dec 2024 14:40:27 +0100 Subject: [PATCH 09/10] Fix comments --- .../server/handler/openrtb2/AmpHandler.java | 26 +++++++++-------- .../handler/openrtb2/AuctionHandler.java | 28 ++++++++++++------- .../server/handler/openrtb2/VideoHandler.java | 8 +++--- .../ExtTraceActivityInfrastructure.java | 9 ------ 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java index 6af06525430..a7b39dce659 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java @@ -148,18 +148,22 @@ public void handle(RoutingContext routingContext) { .map(context -> addToEvent(context, ampEventBuilder::auctionContext, context)) .map(this::updateAppAndNoCookieAndImpsMetrics) .compose(exchangeService::holdAuction) - .map(context -> addToEvent(context, ampEventBuilder::auctionContext, context)) - .map(context -> addToEvent(context.getBidResponse(), ampEventBuilder::bidResponse, context)) + .map(context -> addContextAndBidResponseToEvent(context, ampEventBuilder, context)) .compose(context -> prepareSuccessfulResponse(context, routingContext, ampEventBuilder)) .compose(this::invokeExitpointHooks) - .map(context -> addToEvent(context.getAuctionContext(), ampEventBuilder::auctionContext, context)) - .map(context -> addToEvent( - context.getAuctionContext().getBidResponse(), - ampEventBuilder::bidResponse, - context)) + .map(context -> addContextAndBidResponseToEvent(context.getAuctionContext(), ampEventBuilder, context)) .onComplete(responseResult -> handleResult(responseResult, ampEventBuilder, routingContext, startTime)); } + private static R addContextAndBidResponseToEvent(AuctionContext context, + AmpEvent.AmpEventBuilder ampEventBuilder, + R result) { + + ampEventBuilder.auctionContext(context); + ampEventBuilder.bidResponse(context.getBidResponse()); + return result; + } + private static R addToEvent(T field, Consumer consumer, R result) { consumer.accept(field); return result; @@ -350,6 +354,10 @@ private void handleResult(AsyncResult responseResult, responseHeaders, header.getKey(), header.getValue())); body = rawResponseContext.getResponseBody(); } else { + getCommonResponseHeaders(routingContext, origin) + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + responseHeaders, header.getKey(), header.getValue())); + final Throwable exception = responseResult.cause(); if (exception instanceof InvalidRequestException invalidRequestException) { metricRequestStatus = MetricName.badinput; @@ -404,10 +412,6 @@ private void handleResult(AsyncResult responseResult, status = HttpResponseStatus.INTERNAL_SERVER_ERROR; body = "Critical error while running the auction: " + message; } - - getCommonResponseHeaders(routingContext, origin) - .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( - responseHeaders, header.getKey(), header.getValue())); } final int statusCode = status.code(); diff --git a/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java index 1de5e717339..e0dbe2ea4e1 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java @@ -113,14 +113,23 @@ public void handle(RoutingContext routingContext) { auctionRequestFactory.parseRequest(routingContext, startTime) .compose(auctionContext -> skippedAuctionService.skipAuction(auctionContext) .recover(throwable -> holdAuction(auctionEventBuilder, auctionContext))) + .map(context -> addContextAndBidResponseToEvent(context, auctionEventBuilder, context)) .map(context -> prepareSuccessfulResponse(context, routingContext)) .compose(this::invokeExitpointHooks) - .map(context -> addToEvent(context.getAuctionContext(), auctionEventBuilder::auctionContext, context)) - .map(context -> addToEvent( - context.getAuctionContext().getBidResponse(), auctionEventBuilder::bidResponse, context)) + .map(context -> addContextAndBidResponseToEvent( + context.getAuctionContext(), auctionEventBuilder, context)) .onComplete(result -> handleResult(result, auctionEventBuilder, routingContext, startTime)); } + private static R addContextAndBidResponseToEvent(AuctionContext context, + AuctionEvent.AuctionEventBuilder auctionEventBuilder, + R result) { + + auctionEventBuilder.auctionContext(context); + auctionEventBuilder.bidResponse(context.getBidResponse()); + return result; + } + private Future holdAuction(AuctionEvent.AuctionEventBuilder auctionEventBuilder, AuctionContext auctionContext) { @@ -128,8 +137,7 @@ private Future holdAuction(AuctionEvent.AuctionEventBuilder auct .map(this::updateAppAndNoCookieAndImpsMetrics) // In case of holdAuction Exception and auctionContext is not present below .map(context -> addToEvent(context, auctionEventBuilder::auctionContext, context)) - .compose(exchangeService::holdAuction) - .map(context -> addToEvent(context.getBidResponse(), auctionEventBuilder::bidResponse, context)); + .compose(exchangeService::holdAuction); } private static R addToEvent(T field, Consumer consumer, R result) { @@ -171,7 +179,7 @@ private Future invokeExitpointHooks(RawResponseContext rawRe if (auctionContext.isAuctionSkipped()) { return Future.succeededFuture(auctionContext) .map(hooksMetricsService::updateHooksMetrics) - .map(context -> rawResponseContext); + .map(rawResponseContext); } return hookStageExecutor.executeExitpointStage( @@ -224,6 +232,10 @@ private void handleResult(AsyncResult responseResult, responseHeaders, header.getKey(), header.getValue())); body = rawResponseContext.getResponseBody(); } else { + getCommonResponseHeaders(routingContext) + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + responseHeaders, header.getKey(), header.getValue())); + final Throwable exception = responseResult.cause(); if (exception instanceof InvalidRequestException invalidRequestException) { metricRequestStatus = MetricName.badinput; @@ -275,10 +287,6 @@ private void handleResult(AsyncResult responseResult, status = HttpResponseStatus.INTERNAL_SERVER_ERROR; body = "Critical error while running the auction: " + message; } - - getCommonResponseHeaders(routingContext) - .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( - responseHeaders, header.getKey(), header.getValue())); } final AuctionEvent auctionEvent = auctionEventBuilder.status(status.code()).errors(errorMessages).build(); diff --git a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java index c72e237e476..0bb31bab72b 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java @@ -201,6 +201,10 @@ private void handleResult(AsyncResult responseResult, responseHeaders, header.getKey(), header.getValue())); body = rawResponseContext.getResponseBody(); } else { + getCommonResponseHeaders(routingContext) + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + responseHeaders, header.getKey(), header.getValue())); + final Throwable exception = responseResult.cause(); if (exception instanceof InvalidRequestException) { metricRequestStatus = MetricName.badinput; @@ -229,10 +233,6 @@ private void handleResult(AsyncResult responseResult, status = HttpResponseStatus.INTERNAL_SERVER_ERROR; body = "Critical error while running the auction: " + message; } - - getCommonResponseHeaders(routingContext) - .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( - responseHeaders, header.getKey(), header.getValue())); } VideoEvent videoEvent = videoEventBuilder.status(status.code()).errors(errorMessages).build(); diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/response/ExtTraceActivityInfrastructure.java b/src/main/java/org/prebid/server/proto/openrtb/ext/response/ExtTraceActivityInfrastructure.java index 176cb598d53..155397d8050 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/response/ExtTraceActivityInfrastructure.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/response/ExtTraceActivityInfrastructure.java @@ -1,16 +1,7 @@ package org.prebid.server.proto.openrtb.ext.response; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) -@JsonSubTypes({ - @JsonSubTypes.Type(ExtTraceActivityInvocation.class), - @JsonSubTypes.Type(ExtTraceActivityInvocationDefaultResult.class), - @JsonSubTypes.Type(ExtTraceActivityRule.class), - @JsonSubTypes.Type(ExtTraceActivityInvocationResult.class) -}) public sealed interface ExtTraceActivityInfrastructure permits ExtTraceActivityInvocation, ExtTraceActivityInvocationDefaultResult, From ca64df693fdc863b783a60b392d50535c2219407 Mon Sep 17 00:00:00 2001 From: Danylo Date: Wed, 4 Dec 2024 17:35:08 +0100 Subject: [PATCH 10/10] Fix merge issues. --- .../auction/HooksMetricsServiceTest.java | 8 +-- .../handler/openrtb2/AmpHandlerTest.java | 8 +-- .../handler/openrtb2/AuctionHandlerTest.java | 8 +-- .../execution/HookStageExecutorTest.java | 64 +++++++++---------- .../org/prebid/server/it/hooks/HooksTest.java | 8 +-- .../it/hooks/SampleItExitpointHook.java | 10 +-- 6 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/test/java/org/prebid/server/auction/HooksMetricsServiceTest.java b/src/test/java/org/prebid/server/auction/HooksMetricsServiceTest.java index 608cb7a2350..4d90cc637d7 100644 --- a/src/test/java/org/prebid/server/auction/HooksMetricsServiceTest.java +++ b/src/test/java/org/prebid/server/auction/HooksMetricsServiceTest.java @@ -16,10 +16,10 @@ import org.prebid.server.hooks.execution.model.HookId; import org.prebid.server.hooks.execution.model.Stage; import org.prebid.server.hooks.execution.model.StageExecutionOutcome; -import org.prebid.server.hooks.v1.analytics.ActivityImpl; -import org.prebid.server.hooks.v1.analytics.AppliedToImpl; -import org.prebid.server.hooks.v1.analytics.ResultImpl; -import org.prebid.server.hooks.v1.analytics.TagsImpl; +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.metric.Metrics; import org.prebid.server.model.Endpoint; import org.prebid.server.settings.model.Account; 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 1c0afa05611..aee1397e4c9 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AmpHandlerTest.java @@ -52,11 +52,11 @@ import org.prebid.server.hooks.execution.model.HookStageExecutionResult; import org.prebid.server.hooks.execution.model.Stage; import org.prebid.server.hooks.execution.model.StageExecutionOutcome; +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.exitpoint.ExitpointPayloadImpl; -import org.prebid.server.hooks.v1.analytics.ActivityImpl; -import org.prebid.server.hooks.v1.analytics.AppliedToImpl; -import org.prebid.server.hooks.v1.analytics.ResultImpl; -import org.prebid.server.hooks.v1.analytics.TagsImpl; import org.prebid.server.log.HttpInteractionLogger; import org.prebid.server.metric.MetricName; import org.prebid.server.metric.Metrics; 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 23a50d17fdb..1618caea8d2 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java @@ -46,11 +46,11 @@ import org.prebid.server.hooks.execution.model.HookStageExecutionResult; import org.prebid.server.hooks.execution.model.Stage; import org.prebid.server.hooks.execution.model.StageExecutionOutcome; +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.exitpoint.ExitpointPayloadImpl; -import org.prebid.server.hooks.v1.analytics.ActivityImpl; -import org.prebid.server.hooks.v1.analytics.AppliedToImpl; -import org.prebid.server.hooks.v1.analytics.ResultImpl; -import org.prebid.server.hooks.v1.analytics.TagsImpl; import org.prebid.server.log.HttpInteractionLogger; import org.prebid.server.metric.MetricName; import org.prebid.server.metric.Metrics; 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 c437be84086..ea9ad000891 100644 --- a/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java +++ b/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java @@ -2961,7 +2961,7 @@ public void shouldExecuteExitpointHooksAndPassAuctionInvocationContext(VertxTest // given final ExitpointHookImpl hookImpl = spy( ExitpointHookImpl.of(immediateHook(InvocationResultUtils.succeeded(identity())))); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.EXITPOINT))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.EXITPOINT))) .willReturn(hookImpl); final HookStageExecutor executor = createExecutor( @@ -2980,7 +2980,7 @@ public void shouldExecuteExitpointHooksAndPassAuctionInvocationContext(VertxTest .bidRequest(BidRequest.builder().build()) .account(Account.builder() .hooks(AccountHooksConfiguration.of( - null, singletonMap("module-alpha", mapper.createObjectNode()))) + null, singletonMap("module-alpha", mapper.createObjectNode()), null)) .build()) .hookExecutionContext(hookExecutionContext) .debugContext(DebugContext.empty()) @@ -3257,28 +3257,16 @@ private void givenAuctionResponseHook( .willReturn(AuctionResponseHookImpl.of(delegate)); } - @Value(staticConstructor = "of") - @NonFinal - private static class ExitpointHookImpl implements ExitpointHook { - - String code = "hook-code"; - - BiFunction< - ExitpointPayload, - AuctionInvocationContext, - Future>> delegate; - - @Override - public Future> call(ExitpointPayload payload, - AuctionInvocationContext invocationContext) { - - return delegate.apply(payload, invocationContext); - } + private void givenExitpointHook( + String moduleCode, + String hookImplCode, + BiFunction< + ExitpointPayload, + AuctionInvocationContext, + Future>> delegate) { - @Override - public String code() { - return code; - } + given(hookCatalog.hookById(eqHook(moduleCode, hookImplCode), eq(StageWithHookType.EXITPOINT))) + .willReturn(ExitpointHookImpl.of(delegate)); } private BiFunction>> delayedHook( @@ -3508,15 +3496,27 @@ public String code() { } } - private void givenExitpointHook( - String moduleCode, - String hookImplCode, - BiFunction< - ExitpointPayload, - AuctionInvocationContext, - Future>> delegate) { + @Value(staticConstructor = "of") + @NonFinal + private static class ExitpointHookImpl implements ExitpointHook { - given(hookCatalog.hookById(eq(moduleCode), eq(hookImplCode), eq(StageWithHookType.EXITPOINT))) - .willReturn(ExitpointHookImpl.of(delegate)); + String code = "hook-code"; + + BiFunction< + ExitpointPayload, + AuctionInvocationContext, + Future>> delegate; + + @Override + public Future> call(ExitpointPayload payload, + AuctionInvocationContext invocationContext) { + + return delegate.apply(payload, invocationContext); + } + + @Override + public String code() { + return code; + } } } diff --git a/src/test/java/org/prebid/server/it/hooks/HooksTest.java b/src/test/java/org/prebid/server/it/hooks/HooksTest.java index d41adb6ba80..902f12c6589 100644 --- a/src/test/java/org/prebid/server/it/hooks/HooksTest.java +++ b/src/test/java/org/prebid/server/it/hooks/HooksTest.java @@ -12,10 +12,10 @@ import org.prebid.server.hooks.execution.model.HookExecutionOutcome; import org.prebid.server.hooks.execution.model.Stage; import org.prebid.server.hooks.execution.model.StageExecutionOutcome; -import org.prebid.server.hooks.v1.analytics.ActivityImpl; -import org.prebid.server.hooks.v1.analytics.AppliedToImpl; -import org.prebid.server.hooks.v1.analytics.ResultImpl; -import org.prebid.server.hooks.v1.analytics.TagsImpl; +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.it.IntegrationTest; import org.prebid.server.version.PrebidVersionProvider; import org.skyscreamer.jsonassert.JSONAssert; diff --git a/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java b/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java index e12b4a6c132..82a494e7158 100644 --- a/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java +++ b/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java @@ -3,15 +3,15 @@ import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; import io.vertx.core.Future; +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; +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.exitpoint.ExitpointPayloadImpl; import org.prebid.server.hooks.v1.InvocationAction; import org.prebid.server.hooks.v1.InvocationResult; -import org.prebid.server.hooks.v1.InvocationResultImpl; import org.prebid.server.hooks.v1.InvocationStatus; -import org.prebid.server.hooks.v1.analytics.ActivityImpl; -import org.prebid.server.hooks.v1.analytics.AppliedToImpl; -import org.prebid.server.hooks.v1.analytics.ResultImpl; -import org.prebid.server.hooks.v1.analytics.TagsImpl; import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; import org.prebid.server.hooks.v1.exitpoint.ExitpointHook; import org.prebid.server.hooks.v1.exitpoint.ExitpointPayload;