diff --git a/docs/application-settings.md b/docs/application-settings.md index 7f164caa0dc..2a20e0d8342 100644 --- a/docs/application-settings.md +++ b/docs/application-settings.md @@ -101,6 +101,7 @@ Keep in mind following restrictions: - `cookie-sync.pri` - a list of prioritized bidder codes - `cookie-sync.coop-sync.default` - if the "coopSync" value isn't specified in the `/cookie_sync` request, use this - `hooks` - configuration for Prebid Server Modules. For further details, see: https://docs.prebid.org/prebid-server/pbs-modules/index.html#2-define-an-execution-plan +- `hooks.admin.module-execution` - a key-value map, where a key is a module name and a value is a boolean, that defines whether modules hooks should/should not be always executed; if the module is not specified it is executed by default when it's present in the execution plan - `settings.geo-lookup` - enables geo lookup for account if true. Defaults to false. Here are the definitions of the "purposes" that can be defined in the GDPR setting configurations: diff --git a/docs/config-app.md b/docs/config-app.md index a9a4dd59d3e..ea275c2f151 100644 --- a/docs/config-app.md +++ b/docs/config-app.md @@ -291,6 +291,7 @@ For `JVM` metrics for particular publisher account. Overrides `cache.banner-ttl-seconds` property. - `cache.account..video-ttl-seconds` - how long (in seconds) video creative will be available in Cache Service for particular publisher account. Overrides `cache.video-ttl-seconds` property. +- `cache.default-ttl-seconds.{banner, video, audio, native}` - a default value how long (in seconds) a creative of the specific type will be available in Cache Service ## Application settings (account configuration, stored ad unit configurations, stored requests) Preconfigured application settings can be obtained from multiple data sources consequently: @@ -507,6 +508,10 @@ For the `agma` analytics adapter - `analytics.agma.accounts[].publisher-id` - a publisher id to match an event to send - `analytics.agma.accounts[].site-app-id` - a site or app id to match an event to send +## Modules +- `hooks.admin.module-execution` - a key-value map, where a key is a module name and a value is a boolean, that defines whether modules hooks should/should not be always executed; if the module is not specified it is executed by default when it's present in the execution plan +- `settings.modules.require-config-to-invoke` - when enabled it requires a runtime config to exist for a module. + ## Debugging - `debug.override-token` - special string token for overriding Prebid Server account and/or adapter debug information presence in the auction response. diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index 12149265f3e..5e8e83b47e4 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.16.0-SNAPSHOT + 3.17.0-SNAPSHOT ../../extra/pom.xml diff --git a/extra/modules/confiant-ad-quality/pom.xml b/extra/modules/confiant-ad-quality/pom.xml index f4f0a90ff01..2256c4a3ed7 100644 --- a/extra/modules/confiant-ad-quality/pom.xml +++ b/extra/modules/confiant-ad-quality/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.16.0-SNAPSHOT + 3.17.0-SNAPSHOT confiant-ad-quality diff --git a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/core/AnalyticsMapper.java b/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/core/AnalyticsMapper.java index 57eac3d3620..0a7e9c7c2ea 100644 --- a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/core/AnalyticsMapper.java +++ b/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/core/AnalyticsMapper.java @@ -3,10 +3,10 @@ import com.iab.openrtb.response.Bid; import org.prebid.server.auction.model.BidderResponse; import org.prebid.server.bidder.model.BidderBid; -import org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics.ActivityImpl; -import org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics.AppliedToImpl; -import org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics.ResultImpl; -import org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.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.hooks.v1.analytics.AppliedTo; import org.prebid.server.hooks.v1.analytics.Result; import org.prebid.server.hooks.v1.analytics.Tags; diff --git a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/ConfiantAdQualityBidResponsesScanHook.java b/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/ConfiantAdQualityBidResponsesScanHook.java index 2db501f1852..8a65e74db63 100644 --- a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/ConfiantAdQualityBidResponsesScanHook.java +++ b/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/ConfiantAdQualityBidResponsesScanHook.java @@ -20,7 +20,7 @@ import org.prebid.server.hooks.modules.com.confiant.adquality.model.GroupByIssues; 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.execution.v1.InvocationResultImpl; import org.prebid.server.hooks.v1.InvocationStatus; import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; import org.prebid.server.hooks.v1.bidder.AllProcessedBidResponsesHook; diff --git a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/ActivityImpl.java b/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/ActivityImpl.java deleted file mode 100644 index 4453cb34e12..00000000000 --- a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/ActivityImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics; - -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.Activity; -import org.prebid.server.hooks.v1.analytics.Result; - -import java.util.List; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class ActivityImpl implements Activity { - - String name; - - String status; - - List results; -} diff --git a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/AppliedToImpl.java b/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/AppliedToImpl.java deleted file mode 100644 index 34beae0b73b..00000000000 --- a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/AppliedToImpl.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics; - -import lombok.Builder; -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.AppliedTo; - -import java.util.List; - -@Accessors(fluent = true) -@Value -@Builder -public class AppliedToImpl implements AppliedTo { - - List impIds; - - List bidders; - - boolean request; - - boolean response; - - List bidIds; -} diff --git a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/ResultImpl.java b/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/ResultImpl.java deleted file mode 100644 index 439552f562f..00000000000 --- a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/ResultImpl.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.AppliedTo; -import org.prebid.server.hooks.v1.analytics.Result; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class ResultImpl implements Result { - - String status; - - ObjectNode values; - - AppliedTo appliedTo; -} diff --git a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/TagsImpl.java b/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/TagsImpl.java deleted file mode 100644 index 1c01790d6b8..00000000000 --- a/extra/modules/confiant-ad-quality/src/main/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/model/analytics/TagsImpl.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics; - -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.Activity; -import org.prebid.server.hooks.v1.analytics.Tags; - -import java.util.List; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class TagsImpl implements Tags { - - List activities; -} diff --git a/extra/modules/confiant-ad-quality/src/test/java/org/prebid/server/hooks/modules/com/confiant/adquality/core/AnalyticsMapperTest.java b/extra/modules/confiant-ad-quality/src/test/java/org/prebid/server/hooks/modules/com/confiant/adquality/core/AnalyticsMapperTest.java index 9ec01a7cfed..f3ea0d4764e 100644 --- a/extra/modules/confiant-ad-quality/src/test/java/org/prebid/server/hooks/modules/com/confiant/adquality/core/AnalyticsMapperTest.java +++ b/extra/modules/confiant-ad-quality/src/test/java/org/prebid/server/hooks/modules/com/confiant/adquality/core/AnalyticsMapperTest.java @@ -2,10 +2,10 @@ import org.junit.jupiter.api.Test; import org.prebid.server.auction.model.BidderResponse; +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.modules.com.confiant.adquality.util.AdQualityModuleTestUtils; -import org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics.ActivityImpl; -import org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics.AppliedToImpl; -import org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics.ResultImpl; import org.prebid.server.hooks.v1.analytics.Tags; import java.util.List; diff --git a/extra/modules/confiant-ad-quality/src/test/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/ConfiantAdQualityBidResponsesScanHookTest.java b/extra/modules/confiant-ad-quality/src/test/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/ConfiantAdQualityBidResponsesScanHookTest.java index d4b39a8214e..926865781d3 100644 --- a/extra/modules/confiant-ad-quality/src/test/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/ConfiantAdQualityBidResponsesScanHookTest.java +++ b/extra/modules/confiant-ad-quality/src/test/java/org/prebid/server/hooks/modules/com/confiant/adquality/v1/ConfiantAdQualityBidResponsesScanHookTest.java @@ -16,15 +16,15 @@ import org.prebid.server.auction.model.BidderResponse; import org.prebid.server.auction.privacy.enforcement.mask.UserFpdActivityMask; import org.prebid.server.bidder.model.BidderSeatBid; +import org.prebid.server.hooks.execution.v1.analytics.ActivityImpl; +import org.prebid.server.hooks.execution.v1.analytics.AppliedToImpl; +import org.prebid.server.hooks.execution.v1.analytics.ResultImpl; import org.prebid.server.hooks.execution.v1.bidder.AllProcessedBidResponsesPayloadImpl; import org.prebid.server.hooks.modules.com.confiant.adquality.core.BidsMapper; import org.prebid.server.hooks.modules.com.confiant.adquality.core.BidsScanResult; import org.prebid.server.hooks.modules.com.confiant.adquality.core.BidsScanner; import org.prebid.server.hooks.modules.com.confiant.adquality.core.RedisParser; import org.prebid.server.hooks.modules.com.confiant.adquality.util.AdQualityModuleTestUtils; -import org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics.ActivityImpl; -import org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics.AppliedToImpl; -import org.prebid.server.hooks.modules.com.confiant.adquality.v1.model.analytics.ResultImpl; import org.prebid.server.hooks.v1.InvocationAction; import org.prebid.server.hooks.v1.InvocationResult; import org.prebid.server.hooks.v1.InvocationStatus; diff --git a/extra/modules/fiftyone-devicedetection/pom.xml b/extra/modules/fiftyone-devicedetection/pom.xml index 16a60f7bed8..1f6ec946878 100644 --- a/extra/modules/fiftyone-devicedetection/pom.xml +++ b/extra/modules/fiftyone-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.16.0-SNAPSHOT + 3.17.0-SNAPSHOT fiftyone-devicedetection diff --git a/extra/modules/fiftyone-devicedetection/src/main/java/org/prebid/server/hooks/modules/fiftyone/devicedetection/v1/hooks/FiftyOneDeviceDetectionEntrypointHook.java b/extra/modules/fiftyone-devicedetection/src/main/java/org/prebid/server/hooks/modules/fiftyone/devicedetection/v1/hooks/FiftyOneDeviceDetectionEntrypointHook.java index 506cf66078e..6a652ccf109 100644 --- a/extra/modules/fiftyone-devicedetection/src/main/java/org/prebid/server/hooks/modules/fiftyone/devicedetection/v1/hooks/FiftyOneDeviceDetectionEntrypointHook.java +++ b/extra/modules/fiftyone-devicedetection/src/main/java/org/prebid/server/hooks/modules/fiftyone/devicedetection/v1/hooks/FiftyOneDeviceDetectionEntrypointHook.java @@ -5,7 +5,7 @@ import org.prebid.server.hooks.v1.InvocationAction; import org.prebid.server.hooks.v1.InvocationContext; import org.prebid.server.hooks.v1.InvocationResult; -import org.prebid.server.hooks.v1.InvocationResultImpl; +import org.prebid.server.hooks.execution.v1.InvocationResultImpl; import org.prebid.server.hooks.v1.InvocationStatus; import org.prebid.server.hooks.v1.entrypoint.EntrypointHook; import org.prebid.server.hooks.v1.entrypoint.EntrypointPayload; diff --git a/extra/modules/fiftyone-devicedetection/src/main/java/org/prebid/server/hooks/modules/fiftyone/devicedetection/v1/hooks/FiftyOneDeviceDetectionRawAuctionRequestHook.java b/extra/modules/fiftyone-devicedetection/src/main/java/org/prebid/server/hooks/modules/fiftyone/devicedetection/v1/hooks/FiftyOneDeviceDetectionRawAuctionRequestHook.java index 8b0d5cdc8d4..5c4b268cf68 100644 --- a/extra/modules/fiftyone-devicedetection/src/main/java/org/prebid/server/hooks/modules/fiftyone/devicedetection/v1/hooks/FiftyOneDeviceDetectionRawAuctionRequestHook.java +++ b/extra/modules/fiftyone-devicedetection/src/main/java/org/prebid/server/hooks/modules/fiftyone/devicedetection/v1/hooks/FiftyOneDeviceDetectionRawAuctionRequestHook.java @@ -15,7 +15,7 @@ import org.prebid.server.hooks.modules.fiftyone.devicedetection.v1.model.ModuleContext; 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.execution.v1.InvocationResultImpl; import org.prebid.server.hooks.v1.InvocationStatus; import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; import org.prebid.server.hooks.v1.auction.AuctionRequestPayload; diff --git a/extra/modules/greenbids-real-time-data/pom.xml b/extra/modules/greenbids-real-time-data/pom.xml index 75253e93147..8384e2a6679 100644 --- a/extra/modules/greenbids-real-time-data/pom.xml +++ b/extra/modules/greenbids-real-time-data/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.16.0-SNAPSHOT + 3.17.0-SNAPSHOT greenbids-real-time-data diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHook.java b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHook.java index 0f20fde7041..3b677a78a18 100644 --- a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHook.java +++ b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHook.java @@ -10,21 +10,21 @@ import org.prebid.server.analytics.reporter.greenbids.model.Ortb2ImpExtResult; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.exception.PreBidException; +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.auction.AuctionRequestPayloadImpl; -import org.prebid.server.hooks.modules.greenbids.real.time.data.model.data.Partner; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInferenceDataService; -import org.prebid.server.hooks.modules.greenbids.real.time.data.model.data.ThrottlingMessage; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.FilterService; +import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInferenceDataService; +import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInvocationService; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunner; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunnerWithThresholds; +import org.prebid.server.hooks.modules.greenbids.real.time.data.model.data.Partner; +import org.prebid.server.hooks.modules.greenbids.real.time.data.model.data.ThrottlingMessage; import org.prebid.server.hooks.modules.greenbids.real.time.data.model.result.AnalyticsResult; import org.prebid.server.hooks.modules.greenbids.real.time.data.model.result.GreenbidsInvocationResult; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInvocationService; -import org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.InvocationResultImpl; -import org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics.ActivityImpl; -import org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics.AppliedToImpl; -import org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics.ResultImpl; -import org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics.TagsImpl; import org.prebid.server.hooks.v1.InvocationAction; import org.prebid.server.hooks.v1.InvocationResult; import org.prebid.server.hooks.v1.InvocationStatus; diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/ActivityImpl.java b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/ActivityImpl.java deleted file mode 100644 index 421541e59da..00000000000 --- a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/ActivityImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics; - -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.Activity; -import org.prebid.server.hooks.v1.analytics.Result; - -import java.util.List; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class ActivityImpl implements Activity { - - String name; - - String status; - - List results; -} diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/AppliedToImpl.java b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/AppliedToImpl.java deleted file mode 100644 index 68ad76ccdf3..00000000000 --- a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/AppliedToImpl.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics; - -import lombok.Builder; -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.AppliedTo; - -import java.util.List; - -@Accessors(fluent = true) -@Value -@Builder -public class AppliedToImpl implements AppliedTo { - - List impIds; - - List bidders; - - boolean request; - - boolean response; - - List bidIds; -} diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/ResultImpl.java b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/ResultImpl.java deleted file mode 100644 index d234a59eb31..00000000000 --- a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/ResultImpl.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.AppliedTo; -import org.prebid.server.hooks.v1.analytics.Result; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class ResultImpl implements Result { - - String status; - - ObjectNode values; - - AppliedTo appliedTo; -} diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/TagsImpl.java b/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/TagsImpl.java deleted file mode 100644 index 899e797dab2..00000000000 --- a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/analytics/TagsImpl.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics; - -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.Activity; -import org.prebid.server.hooks.v1.analytics.Tags; - -import java.util.List; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class TagsImpl implements Tags { - - List activities; -} diff --git a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHookTest.java b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHookTest.java index fed56cf009e..e1bde277c06 100644 --- a/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHookTest.java +++ b/extra/modules/greenbids-real-time-data/src/test/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/GreenbidsRealTimeDataProcessedAuctionRequestHookTest.java @@ -22,24 +22,24 @@ import org.prebid.server.analytics.reporter.greenbids.model.ExplorationResult; import org.prebid.server.analytics.reporter.greenbids.model.Ortb2ImpExtResult; import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.hooks.execution.v1.analytics.ActivityImpl; +import org.prebid.server.hooks.execution.v1.analytics.AppliedToImpl; +import org.prebid.server.hooks.execution.v1.analytics.ResultImpl; +import org.prebid.server.hooks.execution.v1.analytics.TagsImpl; import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl; import org.prebid.server.hooks.modules.greenbids.real.time.data.config.DatabaseReaderFactory; -import org.prebid.server.hooks.modules.greenbids.real.time.data.model.filter.ThrottlingThresholds; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.ThrottlingThresholdsFactory; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInferenceDataService; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.FilterService; +import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInferenceDataService; +import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInvocationService; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.ModelCache; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunner; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunnerFactory; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.OnnxModelRunnerWithThresholds; import org.prebid.server.hooks.modules.greenbids.real.time.data.core.ThresholdCache; +import org.prebid.server.hooks.modules.greenbids.real.time.data.core.ThrottlingThresholdsFactory; +import org.prebid.server.hooks.modules.greenbids.real.time.data.model.filter.ThrottlingThresholds; import org.prebid.server.hooks.modules.greenbids.real.time.data.model.result.AnalyticsResult; -import org.prebid.server.hooks.modules.greenbids.real.time.data.core.GreenbidsInvocationService; import org.prebid.server.hooks.modules.greenbids.real.time.data.util.TestBidRequestProvider; -import org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics.ActivityImpl; -import org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics.AppliedToImpl; -import org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics.ResultImpl; -import org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model.analytics.TagsImpl; import org.prebid.server.hooks.v1.InvocationAction; import org.prebid.server.hooks.v1.InvocationResult; import org.prebid.server.hooks.v1.InvocationStatus; diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml index fa56eb6a663..334ceb4c9ad 100644 --- a/extra/modules/ortb2-blocking/pom.xml +++ b/extra/modules/ortb2-blocking/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.16.0-SNAPSHOT + 3.17.0-SNAPSHOT ortb2-blocking diff --git a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHook.java b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHook.java index bb5e05d4fb2..6ef69c93140 100644 --- a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHook.java +++ b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHook.java @@ -5,15 +5,15 @@ import org.prebid.server.auction.BidderAliases; import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.BidderCatalog; +import org.prebid.server.hooks.execution.v1.InvocationResultImpl; +import org.prebid.server.hooks.execution.v1.bidder.BidderRequestPayloadImpl; import org.prebid.server.hooks.modules.ortb2.blocking.core.BlockedAttributesResolver; import org.prebid.server.hooks.modules.ortb2.blocking.core.RequestUpdater; import org.prebid.server.hooks.modules.ortb2.blocking.core.model.BlockedAttributes; import org.prebid.server.hooks.modules.ortb2.blocking.core.model.ExecutionResult; import org.prebid.server.hooks.modules.ortb2.blocking.model.ModuleContext; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.BidderRequestPayloadImpl; 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.bidder.BidderInvocationContext; import org.prebid.server.hooks.v1.bidder.BidderRequestHook; diff --git a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHook.java b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHook.java index c90ac94840a..720823f4513 100644 --- a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHook.java +++ b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHook.java @@ -6,20 +6,20 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.prebid.server.auction.versionconverter.OrtbVersion; +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.bidder.BidderResponsePayloadImpl; import org.prebid.server.hooks.modules.ortb2.blocking.core.BidsBlocker; import org.prebid.server.hooks.modules.ortb2.blocking.core.ResponseUpdater; import org.prebid.server.hooks.modules.ortb2.blocking.core.model.AnalyticsResult; import org.prebid.server.hooks.modules.ortb2.blocking.core.model.BlockedBids; import org.prebid.server.hooks.modules.ortb2.blocking.core.model.ExecutionResult; import org.prebid.server.hooks.modules.ortb2.blocking.model.ModuleContext; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.BidderResponsePayloadImpl; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics.ActivityImpl; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics.AppliedToImpl; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics.ResultImpl; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics.TagsImpl; 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.Result; import org.prebid.server.hooks.v1.analytics.Tags; diff --git a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/BidderRequestPayloadImpl.java b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/BidderRequestPayloadImpl.java deleted file mode 100644 index bd394217b21..00000000000 --- a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/BidderRequestPayloadImpl.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.prebid.server.hooks.modules.ortb2.blocking.v1.model; - -import com.iab.openrtb.request.BidRequest; -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.bidder.BidderRequestPayload; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class BidderRequestPayloadImpl implements BidderRequestPayload { - - BidRequest bidRequest; -} diff --git a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/BidderResponsePayloadImpl.java b/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/BidderResponsePayloadImpl.java deleted file mode 100644 index 72d678c89a5..00000000000 --- a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/BidderResponsePayloadImpl.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.prebid.server.hooks.modules.ortb2.blocking.v1.model; - -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.bidder.model.BidderBid; -import org.prebid.server.hooks.v1.bidder.BidderResponsePayload; - -import java.util.List; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class BidderResponsePayloadImpl implements BidderResponsePayload { - - List bids; -} diff --git a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHookTest.java b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHookTest.java index 4c3852bc630..5c1b7303831 100644 --- a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHookTest.java +++ b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingBidderRequestHookTest.java @@ -18,6 +18,8 @@ import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.BidderCatalog; import org.prebid.server.bidder.BidderInfo; +import org.prebid.server.hooks.execution.v1.InvocationResultImpl; +import org.prebid.server.hooks.execution.v1.bidder.BidderRequestPayloadImpl; import org.prebid.server.hooks.modules.ortb2.blocking.core.config.ArrayOverride; import org.prebid.server.hooks.modules.ortb2.blocking.core.config.Attribute; import org.prebid.server.hooks.modules.ortb2.blocking.core.config.AttributeActionOverrides; @@ -27,10 +29,8 @@ import org.prebid.server.hooks.modules.ortb2.blocking.core.model.BlockedAttributes; import org.prebid.server.hooks.modules.ortb2.blocking.model.ModuleContext; import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.BidderInvocationContextImpl; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.BidderRequestPayloadImpl; 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.PayloadUpdate; import org.prebid.server.hooks.v1.bidder.BidderRequestPayload; diff --git a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHookTest.java b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHookTest.java index 356bd7b2125..351bfbb9d33 100644 --- a/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHookTest.java +++ b/extra/modules/ortb2-blocking/src/test/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/Ortb2BlockingRawBidderResponseHookTest.java @@ -13,6 +13,12 @@ import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.bidder.model.BidderBid; +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.bidder.BidderResponsePayloadImpl; import org.prebid.server.hooks.modules.ortb2.blocking.core.config.Attribute; import org.prebid.server.hooks.modules.ortb2.blocking.core.config.AttributeActionOverrides; import org.prebid.server.hooks.modules.ortb2.blocking.core.config.Attributes; @@ -22,14 +28,8 @@ import org.prebid.server.hooks.modules.ortb2.blocking.core.model.BlockedAttributes; import org.prebid.server.hooks.modules.ortb2.blocking.model.ModuleContext; import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.BidderInvocationContextImpl; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.BidderResponsePayloadImpl; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics.ActivityImpl; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics.AppliedToImpl; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics.ResultImpl; -import org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics.TagsImpl; 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.PayloadUpdate; import org.prebid.server.hooks.v1.bidder.BidderResponsePayload; diff --git a/extra/modules/pb-request-correction/pom.xml b/extra/modules/pb-request-correction/pom.xml index e48517b0eda..a27438f28de 100644 --- a/extra/modules/pb-request-correction/pom.xml +++ b/extra/modules/pb-request-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.16.0-SNAPSHOT + 3.17.0-SNAPSHOT pb-request-correction diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionProcessedAuctionHook.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionProcessedAuctionHook.java index 50502e844db..b9142c93d26 100644 --- a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionProcessedAuctionHook.java +++ b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/RequestCorrectionProcessedAuctionHook.java @@ -6,11 +6,11 @@ import com.iab.openrtb.request.BidRequest; import io.vertx.core.Future; import org.prebid.server.exception.PreBidException; +import org.prebid.server.hooks.execution.v1.InvocationResultImpl; +import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl; import org.prebid.server.hooks.modules.pb.request.correction.core.RequestCorrectionProvider; import org.prebid.server.hooks.modules.pb.request.correction.core.config.model.Config; import org.prebid.server.hooks.modules.pb.request.correction.core.correction.Correction; -import org.prebid.server.hooks.modules.pb.request.correction.v1.model.AuctionRequestPayloadImpl; -import org.prebid.server.hooks.modules.pb.request.correction.v1.model.InvocationResultImpl; import org.prebid.server.hooks.v1.InvocationAction; import org.prebid.server.hooks.v1.InvocationResult; import org.prebid.server.hooks.v1.InvocationStatus; @@ -55,12 +55,13 @@ public Future> call(AuctionRequestPayloa return noAction(); } - final InvocationResult invocationResult = InvocationResultImpl.builder() - .status(InvocationStatus.success) - .action(InvocationAction.update) - .payloadUpdate(initialPayload -> - AuctionRequestPayloadImpl.of(applyCorrections(initialPayload.bidRequest(), corrections))) - .build(); + final InvocationResult invocationResult = + InvocationResultImpl.builder() + .status(InvocationStatus.success) + .action(InvocationAction.update) + .payloadUpdate(initialPayload -> AuctionRequestPayloadImpl.of( + applyCorrections(initialPayload.bidRequest(), corrections))) + .build(); return Future.succeededFuture(invocationResult); } @@ -82,7 +83,7 @@ private static BidRequest applyCorrections(BidRequest bidRequest, List> failure(String message) { - return Future.succeededFuture(InvocationResultImpl.builder() + return Future.succeededFuture(InvocationResultImpl.builder() .status(InvocationStatus.failure) .message(message) .action(InvocationAction.no_action) @@ -90,7 +91,7 @@ private Future> failure(String message) } private static Future> noAction() { - return Future.succeededFuture(InvocationResultImpl.builder() + return Future.succeededFuture(InvocationResultImpl.builder() .status(InvocationStatus.success) .action(InvocationAction.no_action) .build()); diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/AuctionRequestPayloadImpl.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/AuctionRequestPayloadImpl.java deleted file mode 100644 index ca8bb6aa52d..00000000000 --- a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/AuctionRequestPayloadImpl.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.prebid.server.hooks.modules.pb.request.correction.v1.model; - -import com.iab.openrtb.request.BidRequest; -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.auction.AuctionRequestPayload; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class AuctionRequestPayloadImpl implements AuctionRequestPayload { - - BidRequest bidRequest; -} diff --git a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/InvocationResultImpl.java b/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/InvocationResultImpl.java deleted file mode 100644 index 96f90d14a29..00000000000 --- a/extra/modules/pb-request-correction/src/main/java/org/prebid/server/hooks/modules/pb/request/correction/v1/model/InvocationResultImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.prebid.server.hooks.modules.pb.request.correction.v1.model; - -import lombok.Builder; -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.InvocationAction; -import org.prebid.server.hooks.v1.InvocationResult; -import org.prebid.server.hooks.v1.InvocationStatus; -import org.prebid.server.hooks.v1.PayloadUpdate; -import org.prebid.server.hooks.v1.analytics.Tags; -import org.prebid.server.hooks.v1.auction.AuctionRequestPayload; - -import java.util.List; - -@Accessors(fluent = true) -@Builder -@Value -public class InvocationResultImpl implements InvocationResult { - - InvocationStatus status; - - String message; - - InvocationAction action; - - PayloadUpdate payloadUpdate; - - List errors; - - List warnings; - - List debugMessages; - - Object moduleContext; - - Tags analyticsTags; -} diff --git a/extra/modules/pb-response-correction/pom.xml b/extra/modules/pb-response-correction/pom.xml index 43af330f26b..6004016336a 100644 --- a/extra/modules/pb-response-correction/pom.xml +++ b/extra/modules/pb-response-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.16.0-SNAPSHOT + 3.17.0-SNAPSHOT pb-response-correction diff --git a/extra/modules/pb-response-correction/src/main/java/org/prebid/server/hooks/modules/pb/response/correction/v1/ResponseCorrectionAllProcessedBidResponsesHook.java b/extra/modules/pb-response-correction/src/main/java/org/prebid/server/hooks/modules/pb/response/correction/v1/ResponseCorrectionAllProcessedBidResponsesHook.java index 4d7fb81c366..9f9e8e75659 100644 --- a/extra/modules/pb-response-correction/src/main/java/org/prebid/server/hooks/modules/pb/response/correction/v1/ResponseCorrectionAllProcessedBidResponsesHook.java +++ b/extra/modules/pb-response-correction/src/main/java/org/prebid/server/hooks/modules/pb/response/correction/v1/ResponseCorrectionAllProcessedBidResponsesHook.java @@ -13,7 +13,7 @@ import org.prebid.server.hooks.modules.pb.response.correction.core.correction.Correction; 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.execution.v1.InvocationResultImpl; import org.prebid.server.hooks.v1.InvocationStatus; import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; import org.prebid.server.hooks.v1.bidder.AllProcessedBidResponsesHook; diff --git a/extra/modules/pb-richmedia-filter/pom.xml b/extra/modules/pb-richmedia-filter/pom.xml index df4c41dece4..b5167bcbfc8 100644 --- a/extra/modules/pb-richmedia-filter/pom.xml +++ b/extra/modules/pb-richmedia-filter/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.16.0-SNAPSHOT + 3.17.0-SNAPSHOT pb-richmedia-filter diff --git a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHook.java b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHook.java index e0416c6d30d..c63baeda58c 100644 --- a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHook.java +++ b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHook.java @@ -6,19 +6,19 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.prebid.server.auction.model.BidderResponse; +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.bidder.AllProcessedBidResponsesPayloadImpl; import org.prebid.server.hooks.modules.pb.richmedia.filter.core.BidResponsesMraidFilter; import org.prebid.server.hooks.modules.pb.richmedia.filter.core.ModuleConfigResolver; import org.prebid.server.hooks.modules.pb.richmedia.filter.model.AnalyticsResult; import org.prebid.server.hooks.modules.pb.richmedia.filter.model.MraidFilterResult; import org.prebid.server.hooks.modules.pb.richmedia.filter.model.PbRichMediaFilterProperties; -import org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics.ActivityImpl; -import org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics.AppliedToImpl; -import org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics.ResultImpl; -import org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics.TagsImpl; 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.Result; import org.prebid.server.hooks.v1.analytics.Tags; diff --git a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/ActivityImpl.java b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/ActivityImpl.java deleted file mode 100644 index bb9e887ca02..00000000000 --- a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/ActivityImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics; - -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.Activity; -import org.prebid.server.hooks.v1.analytics.Result; - -import java.util.List; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class ActivityImpl implements Activity { - - String name; - - String status; - - List results; -} diff --git a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/AppliedToImpl.java b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/AppliedToImpl.java deleted file mode 100644 index 24f793287b5..00000000000 --- a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/AppliedToImpl.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics; - -import lombok.Builder; -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.AppliedTo; - -import java.util.List; - -@Accessors(fluent = true) -@Value -@Builder -public class AppliedToImpl implements AppliedTo { - - List impIds; - - List bidders; - - boolean request; - - boolean response; - - List bidIds; -} diff --git a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/ResultImpl.java b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/ResultImpl.java deleted file mode 100644 index e15359f5c14..00000000000 --- a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/ResultImpl.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.AppliedTo; -import org.prebid.server.hooks.v1.analytics.Result; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class ResultImpl implements Result { - - String status; - - ObjectNode values; - - AppliedTo appliedTo; -} diff --git a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/TagsImpl.java b/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/TagsImpl.java deleted file mode 100644 index b996bcb4355..00000000000 --- a/extra/modules/pb-richmedia-filter/src/main/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/model/analytics/TagsImpl.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics; - -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.Activity; -import org.prebid.server.hooks.v1.analytics.Tags; - -import java.util.List; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class TagsImpl implements Tags { - - List activities; -} diff --git a/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHookTest.java b/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHookTest.java index 47d5ab27253..2a87faec776 100644 --- a/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHookTest.java +++ b/extra/modules/pb-richmedia-filter/src/test/java/org/prebid/server/hooks/modules/pb/richmedia/filter/v1/PbRichmediaFilterAllProcessedBidResponsesHookTest.java @@ -11,16 +11,16 @@ import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidderResponse; import org.prebid.server.bidder.model.BidderSeatBid; +import org.prebid.server.hooks.execution.v1.analytics.ActivityImpl; +import org.prebid.server.hooks.execution.v1.analytics.AppliedToImpl; +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.bidder.AllProcessedBidResponsesPayloadImpl; import org.prebid.server.hooks.modules.pb.richmedia.filter.core.BidResponsesMraidFilter; import org.prebid.server.hooks.modules.pb.richmedia.filter.core.ModuleConfigResolver; import org.prebid.server.hooks.modules.pb.richmedia.filter.model.AnalyticsResult; import org.prebid.server.hooks.modules.pb.richmedia.filter.model.MraidFilterResult; import org.prebid.server.hooks.modules.pb.richmedia.filter.model.PbRichMediaFilterProperties; -import org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics.ActivityImpl; -import org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics.AppliedToImpl; -import org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics.ResultImpl; -import org.prebid.server.hooks.modules.pb.richmedia.filter.v1.model.analytics.TagsImpl; import org.prebid.server.hooks.v1.InvocationAction; import org.prebid.server.hooks.v1.InvocationResult; import org.prebid.server.hooks.v1.InvocationStatus; diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index ad0987866f5..e1341e77492 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.16.0-SNAPSHOT + 3.17.0-SNAPSHOT ../../extra/pom.xml diff --git a/extra/pom.xml b/extra/pom.xml index 0e279f95f98..f66d463a8b3 100644 --- a/extra/pom.xml +++ b/extra/pom.xml @@ -4,7 +4,7 @@ org.prebid prebid-server-aggregator - 3.16.0-SNAPSHOT + 3.17.0-SNAPSHOT pom diff --git a/pom.xml b/pom.xml index b203bcab93e..bc24a742946 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.16.0-SNAPSHOT + 3.17.0-SNAPSHOT extra/pom.xml diff --git a/src/main/java/org/prebid/server/auction/BidResponseCreator.java b/src/main/java/org/prebid/server/auction/BidResponseCreator.java index 31ed4ee1403..b54f8d69a73 100644 --- a/src/main/java/org/prebid/server/auction/BidResponseCreator.java +++ b/src/main/java/org/prebid/server/auction/BidResponseCreator.java @@ -95,6 +95,7 @@ import org.prebid.server.settings.model.AccountEventsConfig; import org.prebid.server.settings.model.AccountTargetingConfig; import org.prebid.server.settings.model.VideoStoredDataResult; +import org.prebid.server.spring.config.model.CacheDefaultTtlProperties; import org.prebid.server.util.StreamUtil; import org.prebid.server.vast.VastModifier; @@ -139,6 +140,7 @@ public class BidResponseCreator { private final Clock clock; private final JacksonMapper mapper; private final CacheTtl mediaTypeCacheTtl; + private final CacheDefaultTtlProperties cacheDefaultProperties; private final String cacheHost; private final String cachePath; @@ -156,7 +158,8 @@ public BidResponseCreator(CoreCacheService coreCacheService, int truncateAttrChars, Clock clock, JacksonMapper mapper, - CacheTtl mediaTypeCacheTtl) { + CacheTtl mediaTypeCacheTtl, + CacheDefaultTtlProperties cacheDefaultProperties) { this.coreCacheService = Objects.requireNonNull(coreCacheService); this.bidderCatalog = Objects.requireNonNull(bidderCatalog); @@ -171,6 +174,7 @@ public BidResponseCreator(CoreCacheService coreCacheService, this.clock = Objects.requireNonNull(clock); this.mapper = Objects.requireNonNull(mapper); this.mediaTypeCacheTtl = Objects.requireNonNull(mediaTypeCacheTtl); + this.cacheDefaultProperties = Objects.requireNonNull(cacheDefaultProperties); cacheHost = Objects.requireNonNull(coreCacheService.getEndpointHost()); cachePath = Objects.requireNonNull(coreCacheService.getEndpointPath()); @@ -436,8 +440,8 @@ private BidInfo toBidInfo(Bid bid, .bidType(type) .bidder(bidder) .correspondingImp(correspondingImp) - .ttl(resolveBannerTtl(bid, correspondingImp, cacheInfo, account)) - .videoTtl(type == BidType.video ? resolveVideoTtl(bid, correspondingImp, cacheInfo, account) : null) + .ttl(resolveTtl(bid, type, correspondingImp, cacheInfo, account)) + .vastTtl(type == BidType.video ? resolveVastTtl(bid, correspondingImp, cacheInfo, account) : null) .category(categoryMappingResult.getCategory(bid)) .satisfiedPriority(categoryMappingResult.isBidSatisfiesPriority(bid)) .build(); @@ -457,31 +461,43 @@ private static Optional correspondingImp(String impId, List imps) { .findFirst(); } - private Integer resolveBannerTtl(Bid bid, Imp imp, BidRequestCacheInfo cacheInfo, Account account) { - final AccountAuctionConfig accountAuctionConfig = account.getAuction(); + private Integer resolveTtl(Bid bid, BidType type, Imp imp, BidRequestCacheInfo cacheInfo, Account account) { final Integer bidTtl = bid.getExp(); final Integer impTtl = imp != null ? imp.getExp() : null; + final Integer requestTtl = cacheInfo.getCacheBidsTtl(); - return ObjectUtils.firstNonNull( - bidTtl, - impTtl, - cacheInfo.getCacheBidsTtl(), - accountAuctionConfig != null ? accountAuctionConfig.getBannerCacheTtl() : null, - mediaTypeCacheTtl.getBannerCacheTtl()); + final AccountAuctionConfig accountAuctionConfig = account.getAuction(); + final Integer accountTtl = accountAuctionConfig != null ? switch (type) { + case banner -> accountAuctionConfig.getBannerCacheTtl(); + case video -> accountAuctionConfig.getVideoCacheTtl(); + case audio, xNative -> null; + } : null; + + final Integer mediaTypeTtl = switch (type) { + case banner -> mediaTypeCacheTtl.getBannerCacheTtl(); + case video -> mediaTypeCacheTtl.getVideoCacheTtl(); + case audio, xNative -> null; + }; + final Integer defaultTtl = switch (type) { + case banner -> cacheDefaultProperties.getBannerTtl(); + case video -> cacheDefaultProperties.getVideoTtl(); + case audio -> cacheDefaultProperties.getAudioTtl(); + case xNative -> cacheDefaultProperties.getNativeTtl(); + }; + + return ObjectUtils.firstNonNull(bidTtl, impTtl, requestTtl, accountTtl, mediaTypeTtl, defaultTtl); } - private Integer resolveVideoTtl(Bid bid, Imp imp, BidRequestCacheInfo cacheInfo, Account account) { + private Integer resolveVastTtl(Bid bid, Imp imp, BidRequestCacheInfo cacheInfo, Account account) { final AccountAuctionConfig accountAuctionConfig = account.getAuction(); - final Integer bidTtl = bid.getExp(); - final Integer impTtl = imp != null ? imp.getExp() : null; - return ObjectUtils.firstNonNull( - bidTtl, - impTtl, + bid.getExp(), + imp != null ? imp.getExp() : null, cacheInfo.getCacheVideoBidsTtl(), accountAuctionConfig != null ? accountAuctionConfig.getVideoCacheTtl() : null, - mediaTypeCacheTtl.getVideoCacheTtl()); + mediaTypeCacheTtl.getVideoCacheTtl(), + cacheDefaultProperties.getVideoTtl()); } private Future> invokeProcessedBidderResponseHooks(List bidderResponses, @@ -1369,7 +1385,7 @@ private Bid toBid(BidInfo bidInfo, final Integer ttl = Optional.ofNullable(cacheInfo) .map(info -> ObjectUtils.max(cacheInfo.getTtl(), cacheInfo.getVideoTtl())) - .orElseGet(() -> ObjectUtils.max(bidInfo.getTtl(), bidInfo.getVideoTtl())); + .orElseGet(() -> ObjectUtils.max(bidInfo.getTtl(), bidInfo.getVastTtl())); return bid.toBuilder() .ext(updatedBidExt) diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java index 6fff31c6459..e837b90d767 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) { @@ -1378,58 +1369,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/ImpMediaTypeResolver.java b/src/main/java/org/prebid/server/auction/ImpMediaTypeResolver.java index 3256ed360e1..964b89b8b3e 100644 --- a/src/main/java/org/prebid/server/auction/ImpMediaTypeResolver.java +++ b/src/main/java/org/prebid/server/auction/ImpMediaTypeResolver.java @@ -31,12 +31,14 @@ private static ImpMediaType resolveBidAdjustmentVideoMediaType(String bidImpId, .orElse(null); if (bidImpVideo == null) { - return null; + return ImpMediaType.video_outstream; } final Integer placement = bidImpVideo.getPlacement(); - return placement == null || Objects.equals(placement, 1) - ? ImpMediaType.video + final Integer plcmt = bidImpVideo.getPlcmt(); + + return Objects.equals(placement, 1) || Objects.equals(plcmt, 1) + ? ImpMediaType.video_instream : ImpMediaType.video_outstream; } } diff --git a/src/main/java/org/prebid/server/auction/model/BidInfo.java b/src/main/java/org/prebid/server/auction/model/BidInfo.java index 1cb95bcf681..aa3be49fd48 100644 --- a/src/main/java/org/prebid/server/auction/model/BidInfo.java +++ b/src/main/java/org/prebid/server/auction/model/BidInfo.java @@ -33,7 +33,7 @@ public class BidInfo { Integer ttl; - Integer videoTtl; + Integer vastTtl; public String getBidId() { final ObjectNode extNode = bid != null ? bid.getExt() : null; diff --git a/src/main/java/org/prebid/server/auction/privacy/enforcement/ActivityEnforcement.java b/src/main/java/org/prebid/server/auction/privacy/enforcement/ActivityEnforcement.java index df7603048ee..395dff73802 100644 --- a/src/main/java/org/prebid/server/auction/privacy/enforcement/ActivityEnforcement.java +++ b/src/main/java/org/prebid/server/auction/privacy/enforcement/ActivityEnforcement.java @@ -12,6 +12,7 @@ import org.prebid.server.activity.infrastructure.payload.ActivityInvocationPayload; import org.prebid.server.activity.infrastructure.payload.impl.ActivityInvocationPayloadImpl; import org.prebid.server.activity.infrastructure.payload.impl.PrivacyEnforcementServiceActivityInvocationPayload; +import org.prebid.server.auction.BidderAliases; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidderPrivacyResult; import org.prebid.server.auction.privacy.enforcement.mask.UserFpdActivityMask; @@ -21,7 +22,7 @@ import java.util.Objects; import java.util.Optional; -public class ActivityEnforcement { +public class ActivityEnforcement implements PrivacyEnforcement { private final UserFpdActivityMask userFpdActivityMask; @@ -29,17 +30,19 @@ public ActivityEnforcement(UserFpdActivityMask userFpdActivityMask) { this.userFpdActivityMask = Objects.requireNonNull(userFpdActivityMask); } - public Future> enforce(List bidderPrivacyResults, - AuctionContext auctionContext) { + @Override + public Future> enforce(AuctionContext auctionContext, + BidderAliases aliases, + List results) { - final List results = bidderPrivacyResults.stream() + final List enforcedResults = results.stream() .map(bidderPrivacyResult -> applyActivityRestrictions( bidderPrivacyResult, auctionContext.getActivityInfrastructure(), auctionContext.getBidRequest())) .toList(); - return Future.succeededFuture(results); + return Future.succeededFuture(enforcedResults); } private BidderPrivacyResult applyActivityRestrictions(BidderPrivacyResult bidderPrivacyResult, diff --git a/src/main/java/org/prebid/server/auction/privacy/enforcement/CcpaEnforcement.java b/src/main/java/org/prebid/server/auction/privacy/enforcement/CcpaEnforcement.java index 10fe183801d..51a8d8f4622 100644 --- a/src/main/java/org/prebid/server/auction/privacy/enforcement/CcpaEnforcement.java +++ b/src/main/java/org/prebid/server/auction/privacy/enforcement/CcpaEnforcement.java @@ -1,10 +1,7 @@ package org.prebid.server.auction.privacy.enforcement; import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Device; -import com.iab.openrtb.request.User; import io.vertx.core.Future; -import org.apache.commons.lang3.ObjectUtils; import org.prebid.server.auction.BidderAliases; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidderPrivacyResult; @@ -22,12 +19,12 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; -public class CcpaEnforcement { +public class CcpaEnforcement implements PrivacyEnforcement { private static final String CATCH_ALL_BIDDERS = "*"; @@ -47,9 +44,10 @@ public CcpaEnforcement(UserFpdCcpaMask userFpdCcpaMask, this.ccpaEnforce = ccpaEnforce; } + @Override public Future> enforce(AuctionContext auctionContext, - Map bidderToUser, - BidderAliases aliases) { + BidderAliases aliases, + List results) { final Ccpa ccpa = auctionContext.getPrivacyContext().getPrivacy().getCcpa(); final BidRequest bidRequest = auctionContext.getBidRequest(); @@ -58,7 +56,7 @@ public Future> enforce(AuctionContext auctionContext, final boolean isCcpaEnabled = isCcpaEnabled(auctionContext.getAccount(), auctionContext.getRequestTypeMetric()); final Set enforcedBidders = isCcpaEnabled && isCcpaEnforced - ? extractCcpaEnforcedBidders(bidderToUser.keySet(), bidRequest, aliases) + ? extractCcpaEnforcedBidders(results, bidRequest, aliases) : Collections.emptySet(); metrics.updatePrivacyCcpaMetrics( @@ -68,7 +66,11 @@ public Future> enforce(AuctionContext auctionContext, isCcpaEnabled, enforcedBidders); - return Future.succeededFuture(maskCcpa(bidderToUser, enforcedBidders, bidRequest.getDevice())); + final List enforcedResults = results.stream() + .map(result -> enforcedBidders.contains(result.getRequestBidder()) ? maskCcpa(result) : result) + .toList(); + + return Future.succeededFuture(enforcedResults); } public boolean isCcpaEnforced(Ccpa ccpa, Account account) { @@ -79,19 +81,21 @@ private boolean isCcpaEnabled(Account account, MetricName requestType) { final Optional accountCcpaConfig = Optional.ofNullable(account.getPrivacy()) .map(AccountPrivacyConfig::getCcpa); - return ObjectUtils.firstNonNull( - accountCcpaConfig - .map(AccountCcpaConfig::getEnabledForRequestType) - .map(enabledForRequestType -> enabledForRequestType.isEnabledFor(requestType)) - .orElse(null), - accountCcpaConfig - .map(AccountCcpaConfig::getEnabled) - .orElse(null), - ccpaEnforce); + return accountCcpaConfig + .map(AccountCcpaConfig::getEnabledForRequestType) + .map(enabledForRequestType -> enabledForRequestType.isEnabledFor(requestType)) + .or(() -> accountCcpaConfig.map(AccountCcpaConfig::getEnabled)) + .orElse(ccpaEnforce); } - private Set extractCcpaEnforcedBidders(Set bidders, BidRequest bidRequest, BidderAliases aliases) { - final Set ccpaEnforcedBidders = new HashSet<>(bidders); + private Set extractCcpaEnforcedBidders(List results, + BidRequest bidRequest, + BidderAliases aliases) { + + final Set ccpaEnforcedBidders = results.stream() + .map(BidderPrivacyResult::getRequestBidder) + .collect(Collectors.toCollection(HashSet::new)); + final List nosaleBidders = Optional.ofNullable(bidRequest.getExt()) .map(ExtRequest::getPrebid) .map(ExtRequestPrebid::getNosale) @@ -109,14 +113,11 @@ private Set extractCcpaEnforcedBidders(Set bidders, BidRequest b return ccpaEnforcedBidders; } - private List maskCcpa(Map bidderToUser, Set bidders, Device device) { - final Device maskedDevice = userFpdCcpaMask.maskDevice(device); - return bidders.stream() - .map(bidder -> BidderPrivacyResult.builder() - .requestBidder(bidder) - .user(userFpdCcpaMask.maskUser(bidderToUser.get(bidder))) - .device(maskedDevice) - .build()) - .toList(); + private BidderPrivacyResult maskCcpa(BidderPrivacyResult result) { + return BidderPrivacyResult.builder() + .requestBidder(result.getRequestBidder()) + .user(userFpdCcpaMask.maskUser(result.getUser())) + .device(userFpdCcpaMask.maskDevice(result.getDevice())) + .build(); } } diff --git a/src/main/java/org/prebid/server/auction/privacy/enforcement/CoppaEnforcement.java b/src/main/java/org/prebid/server/auction/privacy/enforcement/CoppaEnforcement.java index 92471e85ec2..9ebe0c8d044 100644 --- a/src/main/java/org/prebid/server/auction/privacy/enforcement/CoppaEnforcement.java +++ b/src/main/java/org/prebid/server/auction/privacy/enforcement/CoppaEnforcement.java @@ -1,18 +1,18 @@ package org.prebid.server.auction.privacy.enforcement; -import com.iab.openrtb.request.Device; -import com.iab.openrtb.request.User; import io.vertx.core.Future; +import org.prebid.server.auction.BidderAliases; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidderPrivacyResult; import org.prebid.server.auction.privacy.enforcement.mask.UserFpdCoppaMask; import org.prebid.server.metric.Metrics; import java.util.List; -import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; -public class CoppaEnforcement { +public class CoppaEnforcement implements PrivacyEnforcement { private final UserFpdCoppaMask userFpdCoppaMask; private final Metrics metrics; @@ -22,23 +22,34 @@ public CoppaEnforcement(UserFpdCoppaMask userFpdCoppaMask, Metrics metrics) { this.metrics = Objects.requireNonNull(metrics); } - public boolean isApplicable(AuctionContext auctionContext) { - return auctionContext.getPrivacyContext().getPrivacy().getCoppa() == 1; - } + @Override + public Future> enforce(AuctionContext auctionContext, + BidderAliases aliases, + List results) { + + if (!isApplicable(auctionContext)) { + return Future.succeededFuture(results); + } + + final Set bidders = results.stream() + .map(BidderPrivacyResult::getRequestBidder) + .collect(Collectors.toSet()); - public Future> enforce(AuctionContext auctionContext, Map bidderToUser) { - metrics.updatePrivacyCoppaMetric(auctionContext.getActivityInfrastructure(), bidderToUser.keySet()); - return Future.succeededFuture(results(bidderToUser, auctionContext.getBidRequest().getDevice())); + metrics.updatePrivacyCoppaMetric(auctionContext.getActivityInfrastructure(), bidders); + return Future.succeededFuture(enforce(results)); } - private List results(Map bidderToUser, Device device) { - final Device maskedDevice = userFpdCoppaMask.maskDevice(device); - return bidderToUser.entrySet().stream() - .map(bidderAndUser -> BidderPrivacyResult.builder() - .requestBidder(bidderAndUser.getKey()) - .user(userFpdCoppaMask.maskUser(bidderAndUser.getValue())) - .device(maskedDevice) + private List enforce(List results) { + return results.stream() + .map(result -> BidderPrivacyResult.builder() + .requestBidder(result.getRequestBidder()) + .user(userFpdCoppaMask.maskUser(result.getUser())) + .device(userFpdCoppaMask.maskDevice(result.getDevice())) .build()) .toList(); } + + private static boolean isApplicable(AuctionContext auctionContext) { + return auctionContext.getPrivacyContext().getPrivacy().getCoppa() == 1; + } } diff --git a/src/main/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcement.java b/src/main/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcement.java new file mode 100644 index 00000000000..d12e290fb2e --- /dev/null +++ b/src/main/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcement.java @@ -0,0 +1,15 @@ +package org.prebid.server.auction.privacy.enforcement; + +import io.vertx.core.Future; +import org.prebid.server.auction.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidderPrivacyResult; + +import java.util.List; + +public interface PrivacyEnforcement { + + Future> enforce(AuctionContext auctionContext, + BidderAliases aliases, + List results); +} diff --git a/src/main/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcementService.java b/src/main/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcementService.java index 3f4e4055dca..f50a7c637f6 100644 --- a/src/main/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcementService.java +++ b/src/main/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcementService.java @@ -5,58 +5,41 @@ import org.prebid.server.auction.BidderAliases; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidderPrivacyResult; -import org.prebid.server.util.ListUtil; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; /** * Service provides masking for OpenRTB client sensitive information. */ public class PrivacyEnforcementService { - private final CoppaEnforcement coppaEnforcement; - private final CcpaEnforcement ccpaEnforcement; - private final TcfEnforcement tcfEnforcement; - private final ActivityEnforcement activityEnforcement; + private final List enforcements; - public PrivacyEnforcementService(CoppaEnforcement coppaEnforcement, - CcpaEnforcement ccpaEnforcement, - TcfEnforcement tcfEnforcement, - ActivityEnforcement activityEnforcement) { - - this.coppaEnforcement = Objects.requireNonNull(coppaEnforcement); - this.ccpaEnforcement = Objects.requireNonNull(ccpaEnforcement); - this.tcfEnforcement = Objects.requireNonNull(tcfEnforcement); - this.activityEnforcement = Objects.requireNonNull(activityEnforcement); + public PrivacyEnforcementService(final List enforcements) { + this.enforcements = Objects.requireNonNull(enforcements); } public Future> mask(AuctionContext auctionContext, Map bidderToUser, BidderAliases aliases) { - // For now, COPPA masking all values, so we can omit TCF masking. - return coppaEnforcement.isApplicable(auctionContext) - ? coppaEnforcement.enforce(auctionContext, bidderToUser) - : ccpaEnforcement.enforce(auctionContext, bidderToUser, aliases) - .compose(ccpaResult -> tcfEnforcement.enforce( - auctionContext, - bidderToUser, - biddersToApplyTcf(bidderToUser.keySet(), ccpaResult), - aliases) - .map(tcfResult -> ListUtil.union(ccpaResult, tcfResult))) - .compose(bidderPrivacyResults -> activityEnforcement.enforce(bidderPrivacyResults, auctionContext)); - } + final List initialResults = bidderToUser.entrySet().stream() + .map(entry -> BidderPrivacyResult.builder() + .requestBidder(entry.getKey()) + .user(entry.getValue()) + .device(auctionContext.getBidRequest().getDevice()) + .build()) + .toList(); + + Future> composedResult = Future.succeededFuture(initialResults); - private static Set biddersToApplyTcf(Set bidders, List ccpaResult) { - final Set biddersToApplyTcf = new HashSet<>(bidders); - ccpaResult.stream() - .map(BidderPrivacyResult::getRequestBidder) - .forEach(biddersToApplyTcf::remove); + for (PrivacyEnforcement enforcement : enforcements) { + composedResult = composedResult.compose( + results -> enforcement.enforce(auctionContext, aliases, results)); + } - return biddersToApplyTcf; + return composedResult; } } diff --git a/src/main/java/org/prebid/server/auction/privacy/enforcement/TcfEnforcement.java b/src/main/java/org/prebid/server/auction/privacy/enforcement/TcfEnforcement.java index 48e098f63a6..86602099401 100644 --- a/src/main/java/org/prebid/server/auction/privacy/enforcement/TcfEnforcement.java +++ b/src/main/java/org/prebid/server/auction/privacy/enforcement/TcfEnforcement.java @@ -30,8 +30,9 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; -public class TcfEnforcement { +public class TcfEnforcement implements PrivacyEnforcement { private static final Logger logger = LoggerFactory.getLogger(TcfEnforcement.class); @@ -59,30 +60,25 @@ public Future> enforce(Set vendo .map(TcfResponse::getActions); } + @Override public Future> enforce(AuctionContext auctionContext, - Map bidderToUser, - Set bidders, - BidderAliases aliases) { + BidderAliases aliases, + List results) { - final Device device = auctionContext.getBidRequest().getDevice(); - final AccountGdprConfig accountGdprConfig = accountGdprConfig(auctionContext.getAccount()); final MetricName requestType = auctionContext.getRequestTypeMetric(); final ActivityInfrastructure activityInfrastructure = auctionContext.getActivityInfrastructure(); + final Set bidders = results.stream() + .map(BidderPrivacyResult::getRequestBidder) + .collect(Collectors.toSet()); return tcfDefinerService.resultForBidderNames( bidders, VendorIdResolver.of(aliases, bidderCatalog), auctionContext.getPrivacyContext().getTcfContext(), - accountGdprConfig) + accountGdprConfig(auctionContext.getAccount())) .map(TcfResponse::getActions) - .map(enforcements -> updateMetrics( - activityInfrastructure, - enforcements, - aliases, - requestType, - bidderToUser, - device)) - .map(enforcements -> bidderToPrivacyResult(enforcements, bidders, bidderToUser, device)); + .map(enforcements -> updateMetrics(activityInfrastructure, enforcements, aliases, requestType, results)) + .map(enforcements -> applyEnforcements(enforcements, results)); } private static AccountGdprConfig accountGdprConfig(Account account) { @@ -94,22 +90,21 @@ private Map updateMetrics(ActivityInfrastructu Map enforcements, BidderAliases aliases, MetricName requestType, - Map bidderToUser, - Device device) { - - final boolean isLmtEnforcedAndEnabled = isLmtEnforcedAndEnabled(device); + List results) { // Metrics should represent real picture of the bidding process, so if bidder request is blocked // by privacy then no reason to increment another metrics, like geo masked, etc. - for (final Map.Entry bidderEnforcement : enforcements.entrySet()) { - final String bidder = bidderEnforcement.getKey(); - final PrivacyEnforcementAction enforcement = bidderEnforcement.getValue(); - final User user = bidderToUser.get(bidder); + for (BidderPrivacyResult result : results) { + final String bidder = result.getRequestBidder(); + final User user = result.getUser(); + final Device device = result.getDevice(); + final PrivacyEnforcementAction enforcement = enforcements.get(bidder); final boolean requestBlocked = enforcement.isBlockBidderRequest(); final boolean ufpdRemoved = !requestBlocked && ((enforcement.isRemoveUserFpd() && shouldRemoveUserData(user)) || (enforcement.isMaskDeviceInfo() && shouldRemoveDeviceData(device))); + final boolean isLmtEnforcedAndEnabled = isLmtEnforcedAndEnabled(device); final boolean uidsRemoved = !requestBlocked && enforcement.isRemoveUserIds() && shouldRemoveUids(user); final boolean geoMasked = !requestBlocked && enforcement.isMaskGeo() && shouldMaskGeo(user, device); final boolean analyticsBlocked = !requestBlocked && enforcement.isBlockAnalyticsReport(); @@ -165,32 +160,19 @@ private boolean isLmtEnforcedAndEnabled(Device device) { return lmtEnforce && device != null && Objects.equals(device.getLmt(), 1); } - private List bidderToPrivacyResult(Map bidderToEnforcement, - Set bidders, - Map bidderToUser, - Device device) { - - final boolean isLmtEnabled = isLmtEnforcedAndEnabled(device); + private List applyEnforcements(Map enforcements, + List results) { - return bidders.stream() - .map(bidder -> createBidderPrivacyResult( - bidder, - bidderToUser.get(bidder), - device, - bidderToEnforcement, - isLmtEnabled)) + return results.stream() + .map(result -> applyEnforcement(enforcements.get(result.getRequestBidder()), result)) .toList(); } - private BidderPrivacyResult createBidderPrivacyResult(String bidder, - User user, - Device device, - Map bidderToEnforcement, - boolean isLmtEnabled) { + private BidderPrivacyResult applyEnforcement(PrivacyEnforcementAction enforcement, BidderPrivacyResult result) { + final String bidder = result.getRequestBidder(); - final PrivacyEnforcementAction privacyEnforcementAction = bidderToEnforcement.get(bidder); - final boolean blockBidderRequest = privacyEnforcementAction.isBlockBidderRequest(); - final boolean blockAnalyticsReport = privacyEnforcementAction.isBlockAnalyticsReport(); + final boolean blockBidderRequest = enforcement.isBlockBidderRequest(); + final boolean blockAnalyticsReport = enforcement.isBlockAnalyticsReport(); if (blockBidderRequest) { return BidderPrivacyResult.builder() @@ -200,14 +182,18 @@ private BidderPrivacyResult createBidderPrivacyResult(String bidder, .build(); } - final boolean maskUserFpd = privacyEnforcementAction.isRemoveUserFpd() || isLmtEnabled; - final boolean maskUserIds = privacyEnforcementAction.isRemoveUserIds() || isLmtEnabled; - final boolean maskGeo = privacyEnforcementAction.isMaskGeo() || isLmtEnabled; - final Set eidExceptions = privacyEnforcementAction.getEidExceptions(); + final User user = result.getUser(); + final Device device = result.getDevice(); + + final boolean isLmtEnabled = isLmtEnforcedAndEnabled(device); + final boolean maskUserFpd = enforcement.isRemoveUserFpd() || isLmtEnabled; + final boolean maskUserIds = enforcement.isRemoveUserIds() || isLmtEnabled; + final boolean maskGeo = enforcement.isMaskGeo() || isLmtEnabled; + final Set eidExceptions = enforcement.getEidExceptions(); final User maskedUser = userFpdTcfMask.maskUser(user, maskUserFpd, maskUserIds, eidExceptions); - final boolean maskIp = privacyEnforcementAction.isMaskDeviceIp() || isLmtEnabled; - final boolean maskDeviceInfo = privacyEnforcementAction.isMaskDeviceInfo() || isLmtEnabled; + final boolean maskIp = enforcement.isMaskDeviceIp() || isLmtEnabled; + final boolean maskDeviceInfo = enforcement.isMaskDeviceInfo() || isLmtEnabled; final Device maskedDevice = userFpdTcfMask.maskDevice(device, maskIp, maskGeo, maskDeviceInfo); return BidderPrivacyResult.builder() 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 errors) { try { final Price originalPrice = getOriginalPrice(bidderBid); + + final ImpMediaType mediaType = ImpMediaTypeResolver.resolve( + bidderBid.getBid().getImpid(), + bidRequest.getImp(), + bidderBid.getType()); + final Price priceWithFactorsApplied = applyBidAdjustmentFactors( originalPrice, - bidderBid, bidder, - bidRequest); + bidRequest, + mediaType); + final Price priceWithAdjustmentsApplied = applyBidAdjustmentRules( priceWithFactorsApplied, - bidderBid, bidder, bidRequest, - bidAdjustments); + bidAdjustments, + mediaType, + bidderBid.getBid().getDealid()); + return updateBid(originalPrice, priceWithAdjustmentsApplied, bidderBid, bidRequest); } catch (PreBidException e) { errors.add(BidderError.generic(e.getMessage())); @@ -154,51 +152,54 @@ private Price getOriginalPrice(BidderBid bidderBid) { return Price.of(StringUtils.stripToNull(bidCurrency), price); } - private Price applyBidAdjustmentFactors(Price bidPrice, BidderBid bidderBid, String bidder, BidRequest bidRequest) { + private Price applyBidAdjustmentFactors(Price bidPrice, + String bidder, + BidRequest bidRequest, + ImpMediaType mediaType) { + final String bidCurrency = bidPrice.getCurrency(); final BigDecimal price = bidPrice.getValue(); - final BigDecimal priceAdjustmentFactor = bidAdjustmentForBidder(bidder, bidRequest, bidderBid); + final BigDecimal priceAdjustmentFactor = bidAdjustmentForBidder(bidder, bidRequest, mediaType); final BigDecimal adjustedPrice = adjustPrice(priceAdjustmentFactor, price); return Price.of(bidCurrency, adjustedPrice.compareTo(price) != 0 ? adjustedPrice : price); } - private BigDecimal bidAdjustmentForBidder(String bidder, BidRequest bidRequest, BidderBid bidderBid) { + private BigDecimal bidAdjustmentForBidder(String bidder, BidRequest bidRequest, ImpMediaType mediaType) { final ExtRequestBidAdjustmentFactors adjustmentFactors = extBidAdjustmentFactors(bidRequest); if (adjustmentFactors == null) { return null; } - final ImpMediaType mediaType = ImpMediaTypeResolver.resolve( - bidderBid.getBid().getImpid(), - bidRequest.getImp(), - bidderBid.getType()); + final ImpMediaType targetMediaType = mediaType == ImpMediaType.video_instream ? ImpMediaType.video : mediaType; + return bidAdjustmentFactorResolver.resolve(targetMediaType, adjustmentFactors, bidder); + } + + private static ExtRequestBidAdjustmentFactors extBidAdjustmentFactors(BidRequest bidRequest) { + final ExtRequestPrebid prebid = PbsUtil.extRequestPrebid(bidRequest); + return prebid != null ? prebid.getBidadjustmentfactors() : null; + } - return bidAdjustmentFactorResolver.resolve(mediaType, adjustmentFactors, bidder); + private static BigDecimal adjustPrice(BigDecimal priceAdjustmentFactor, BigDecimal price) { + return priceAdjustmentFactor != null && priceAdjustmentFactor.compareTo(BigDecimal.ONE) != 0 + ? price.multiply(priceAdjustmentFactor) + : price; } private Price applyBidAdjustmentRules(Price bidPrice, - BidderBid bidderBid, String bidder, BidRequest bidRequest, - BidAdjustments bidAdjustments) { - - final Bid bid = bidderBid.getBid(); - final String bidCurrency = bidPrice.getCurrency(); - final BigDecimal price = bidPrice.getValue(); - - final ImpMediaType mediaType = ImpMediaTypeResolver.resolve( - bid.getImpid(), - bidRequest.getImp(), - bidderBid.getType()); + BidAdjustments bidAdjustments, + ImpMediaType mediaType, + String dealId) { return bidAdjustmentsResolver.resolve( - Price.of(bidCurrency, price), + bidPrice, bidRequest, bidAdjustments, - mediaType == null || mediaType == ImpMediaType.video ? ImpMediaType.video_instream : mediaType, + mediaType, bidder, - bid.getDealid()); + dealId); } } diff --git a/src/main/java/org/prebid/server/cache/CoreCacheService.java b/src/main/java/org/prebid/server/cache/CoreCacheService.java index e60ed70f949..e86ac4f5d9b 100644 --- a/src/main/java/org/prebid/server/cache/CoreCacheService.java +++ b/src/main/java/org/prebid/server/cache/CoreCacheService.java @@ -250,7 +250,7 @@ private List getCacheBids(List bidInfos) { private List getVideoCacheBids(List bidInfos) { return bidInfos.stream() .filter(bidInfo -> Objects.equals(bidInfo.getBidType(), BidType.video)) - .map(bidInfo -> CacheBid.of(bidInfo, bidInfo.getVideoTtl())) + .map(bidInfo -> CacheBid.of(bidInfo, bidInfo.getVastTtl())) .toList(); } 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 c1d9c58dca6..a7b39dce659 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AmpHandler.java @@ -22,7 +22,10 @@ import org.prebid.server.analytics.model.AmpEvent; import org.prebid.server.analytics.reporter.AnalyticsReporterDelegator; import org.prebid.server.auction.AmpResponsePostProcessor; +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.model.AuctionContext; import org.prebid.server.auction.model.Tuple2; import org.prebid.server.auction.requestfactory.AmpRequestFactory; @@ -34,6 +37,8 @@ import org.prebid.server.exception.InvalidRequestException; import org.prebid.server.exception.PreBidException; 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; @@ -81,12 +86,14 @@ public class AmpHandler implements ApplicationResource { private final ExchangeService exchangeService; private final AnalyticsReporterDelegator analyticsDelegator; private final Metrics metrics; + private final HooksMetricsService hooksMetricsService; private final Clock clock; private final BidderCatalog bidderCatalog; private final Set 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; @@ -94,12 +101,14 @@ public AmpHandler(AmpRequestFactory ampRequestFactory, ExchangeService exchangeService, AnalyticsReporterDelegator analyticsDelegator, Metrics metrics, + HooksMetricsService hooksMetricsService, Clock clock, BidderCatalog bidderCatalog, Set biddersSupportingCustomTargeting, AmpResponsePostProcessor ampResponsePostProcessor, HttpInteractionLogger httpInteractionLogger, PrebidVersionProvider prebidVersionProvider, + HookStageExecutor hookStageExecutor, JacksonMapper mapper, double logSamplingRate) { @@ -107,12 +116,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,18 +145,25 @@ 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)) + .map(context -> addContextAndBidResponseToEvent(context, ampEventBuilder, context)) + .compose(context -> prepareSuccessfulResponse(context, routingContext, ampEventBuilder)) + .compose(this::invokeExitpointHooks) + .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; @@ -166,8 +184,44 @@ private AuctionContext updateAppAndNoCookieAndImpsMetrics(AuctionContext context return context; } + private Future prepareSuccessfulResponse(AuctionContext auctionContext, + 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 -> RawResponseContext.builder() + .responseBody(mapper.encodeToString(result.getLeft())) + .responseHeaders(responseHeaders) + .auctionContext(auctionContext) + .build()); + } + + private Future invokeExitpointHooks(RawResponseContext rawResponseContext) { + final AuctionContext auctionContext = rawResponseContext.getAuctionContext(); + return hookStageExecutor.executeExitpointStage( + rawResponseContext.getResponseHeaders(), + rawResponseContext.getResponseBody(), + auctionContext) + .map(HookStageExecutionResult::getPayload) + .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, RoutingContext routingContext) { + final BidRequest bidRequest = context.getBidRequest(); final BidResponse bidResponse = context.getBidResponse(); final AmpResponse ampResponse = toAmpResponse(bidResponse); @@ -271,12 +325,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 RawResponseContext rawResponseContext = responseSucceeded ? responseResult.result() : null; final MetricName metricRequestStatus; final List errorMessages; @@ -287,16 +342,22 @@ 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()); + + rawResponseContext.getResponseHeaders() + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + 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; @@ -355,8 +416,7 @@ private void handleResult(AsyncResult> respo final int statusCode = status.code(); final AmpEvent ampEvent = ampEventBuilder.status(statusCode).errors(errorMessages).build(); - - final AuctionContext auctionContext = responseSucceeded ? responseResult.result().getRight() : null; + final AuctionContext auctionContext = ampEvent.getAuctionContext(); final PrivacyContext privacyContext = auctionContext != null ? auctionContext.getPrivacyContext() : null; final TcfContext tcfContext = privacyContext != null ? privacyContext.getTcfContext() : TcfContext.empty(); @@ -406,8 +466,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) { + final MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, HttpUtil.X_PREBID_HEADER, prebidVersionProvider.getNameVersionRecord()); @@ -419,10 +479,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..e0dbe2ea4e1 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/AuctionHandler.java @@ -12,7 +12,10 @@ 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.requestfactory.AuctionRequestFactory; @@ -22,6 +25,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 +60,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 +73,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 +86,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,7 +113,21 @@ public void handle(RoutingContext routingContext) { auctionRequestFactory.parseRequest(routingContext, startTime) .compose(auctionContext -> skippedAuctionService.skipAuction(auctionContext) .recover(throwable -> holdAuction(auctionEventBuilder, auctionContext))) - .onComplete(context -> handleResult(context, auctionEventBuilder, routingContext, startTime)); + .map(context -> addContextAndBidResponseToEvent(context, auctionEventBuilder, context)) + .map(context -> prepareSuccessfulResponse(context, routingContext)) + .compose(this::invokeExitpointHooks) + .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, @@ -110,14 +135,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, auctionEventBuilder::auctionContext, context)) - .map(context -> addToEvent(context.getBidResponse(), auctionEventBuilder::bidResponse, context)); + .compose(exchangeService::holdAuction); } private static R addToEvent(T field, Consumer consumer, R result) { @@ -142,14 +162,53 @@ private AuctionContext updateAppAndNoCookieAndImpsMetrics(AuctionContext context return context; } - private void handleResult(AsyncResult responseResult, + private RawResponseContext prepareSuccessfulResponse(AuctionContext auctionContext, RoutingContext routingContext) { + final MultiMap responseHeaders = getCommonResponseHeaders(routingContext) + .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); + + return RawResponseContext.builder() + .responseBody(mapper.encodeToString(auctionContext.getBidResponse())) + .responseHeaders(responseHeaders) + .auctionContext(auctionContext) + .build(); + } + + private Future invokeExitpointHooks(RawResponseContext rawResponseContext) { + final AuctionContext auctionContext = rawResponseContext.getAuctionContext(); + + if (auctionContext.isAuctionSkipped()) { + return Future.succeededFuture(auctionContext) + .map(hooksMetricsService::updateHooksMetrics) + .map(rawResponseContext); + } + + return hookStageExecutor.executeExitpointStage( + rawResponseContext.getResponseHeaders(), + rawResponseContext.getResponseBody(), + auctionContext) + .map(HookStageExecutionResult::getPayload) + .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, 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() @@ -161,16 +220,22 @@ 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()); + + rawResponseContext.getResponseHeaders() + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + 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; @@ -263,8 +328,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) { + final MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, HttpUtil.X_PREBID_HEADER, prebidVersionProvider.getNameVersionRecord()); @@ -272,10 +337,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/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 d5957c15aa7..0bb31bab72b 100644 --- a/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java +++ b/src/main/java/org/prebid/server/handler/openrtb2/VideoHandler.java @@ -1,15 +1,20 @@ 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; +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; @@ -18,6 +23,8 @@ 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,55 @@ public void handle(RoutingContext routingContext) { .map(contextToErrors -> addToEvent(contextToErrors.getData(), videoEventBuilder::auctionContext, contextToErrors)) - .map(result -> videoResponseFactory.toVideoResponse( - result.getData(), result.getData().getBidResponse(), - result.getPodErrors())) + .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 Future prepareSuccessfulResponse(WithPodErrors context, + RoutingContext routingContext, + VideoEvent.VideoEventBuilder videoEventBuilder) { + + final AuctionContext auctionContext = context.getData(); + final MultiMap responseHeaders = getCommonResponseHeaders(routingContext) + .add(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON); + return toVideoResponse(auctionContext, context.getPodErrors()) .map(videoResponse -> addToEvent(videoResponse, videoEventBuilder::bidResponse, videoResponse)) - .onComplete(responseResult -> handleResult(responseResult, videoEventBuilder, routingContext, - startTime)); + .map(videoResponse -> RawResponseContext.builder() + .responseBody(mapper.encodeToString(videoResponse)) + .responseHeaders(responseHeaders) + .auctionContext(auctionContext) + .build()); + } + + private Future toVideoResponse(AuctionContext auctionContext, List podErrors) { + return Future.succeededFuture( + videoResponseFactory.toVideoResponse(auctionContext, auctionContext.getBidResponse(), podErrors)); + } + + private Future invokeExitpointHooks(RawResponseContext rawResponseContext) { + final AuctionContext auctionContext = rawResponseContext.getAuctionContext(); + return hookStageExecutor.executeExitpointStage( + rawResponseContext.getResponseHeaders(), + rawResponseContext.getResponseBody(), + auctionContext) + .map(HookStageExecutionResult::getPayload) + .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) { @@ -120,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) { @@ -130,19 +186,25 @@ private void handleResult(AsyncResult responseResult, final List errorMessages; final HttpResponseStatus status; final String body; - final VideoResponse videoResponse = responseSucceeded ? responseResult.result() : null; + final RawResponseContext rawResponseContext = 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); + rawResponseContext.getResponseHeaders() + .forEach(header -> HttpUtil.addHeaderIfValueIsNotEmpty( + 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; @@ -240,8 +302,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) { + final MultiMap responseHeaders = MultiMap.caseInsensitiveMultiMap(); HttpUtil.addHeaderIfValueIsNotEmpty( responseHeaders, HttpUtil.X_PREBID_HEADER, prebidVersionProvider.getNameVersionRecord()); @@ -249,10 +311,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/GroupExecutor.java b/src/main/java/org/prebid/server/hooks/execution/GroupExecutor.java index 5fac3d8376b..18d52b64c99 100644 --- a/src/main/java/org/prebid/server/hooks/execution/GroupExecutor.java +++ b/src/main/java/org/prebid/server/hooks/execution/GroupExecutor.java @@ -1,6 +1,5 @@ package org.prebid.server.hooks.execution; -import com.fasterxml.jackson.databind.node.ObjectNode; import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Promise; @@ -8,49 +7,41 @@ import org.prebid.server.hooks.execution.model.ExecutionGroup; import org.prebid.server.hooks.execution.model.HookExecutionContext; import org.prebid.server.hooks.execution.model.HookId; +import org.prebid.server.hooks.execution.provider.HookProvider; import org.prebid.server.hooks.v1.Hook; -import org.prebid.server.hooks.v1.InvocationAction; import org.prebid.server.hooks.v1.InvocationContext; 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.auction.AuctionInvocationContext; -import org.prebid.server.log.ConditionalLogger; -import org.prebid.server.log.LoggerFactory; import java.time.Clock; +import java.util.Map; import java.util.concurrent.TimeoutException; -import java.util.function.Function; import java.util.function.Supplier; class GroupExecutor { - private static final ConditionalLogger conditionalLogger = - new ConditionalLogger(LoggerFactory.getLogger(GroupExecutor.class)); - private final Vertx vertx; private final Clock clock; - private final boolean isConfigToInvokeRequired; + private final Map modulesExecution; private ExecutionGroup group; private PAYLOAD initialPayload; - private Function> hookProvider; + private HookProvider hookProvider; private InvocationContextProvider invocationContextProvider; private HookExecutionContext hookExecutionContext; private boolean rejectAllowed; - private GroupExecutor(Vertx vertx, Clock clock, boolean isConfigToInvokeRequired) { + private GroupExecutor(Vertx vertx, Clock clock, Map modulesExecution) { this.vertx = vertx; this.clock = clock; - this.isConfigToInvokeRequired = isConfigToInvokeRequired; + this.modulesExecution = modulesExecution; } public static GroupExecutor create( Vertx vertx, Clock clock, - boolean isConfigToInvokeRequired) { + Map modulesExecution) { - return new GroupExecutor<>(vertx, clock, isConfigToInvokeRequired); + return new GroupExecutor<>(vertx, clock, modulesExecution); } public GroupExecutor withGroup(ExecutionGroup group) { @@ -63,7 +54,7 @@ public GroupExecutor withInitialPayload(PAYLOAD initialPayload return this; } - public GroupExecutor withHookProvider(Function> hookProvider) { + public GroupExecutor withHookProvider(HookProvider hookProvider) { this.hookProvider = hookProvider; return this; } @@ -90,11 +81,15 @@ public Future> execute() { Future> groupFuture = Future.succeededFuture(initialGroupResult); for (final HookId hookId : group.getHookSequence()) { - final Hook hook = hookProvider.apply(hookId); + if (!modulesExecution.get(hookId.getModuleCode())) { + continue; + } + + final Future> hookFuture = hook(hookId); final long startTime = clock.millis(); - final Future> invocationResult = - executeHook(hook, group.getTimeout(), initialGroupResult, hookId); + final Future> invocationResult = hookFuture + .compose(hook -> executeHook(hook, group.getTimeout(), initialGroupResult, hookId)); groupFuture = groupFuture.compose(groupResult -> applyInvocationResult(invocationResult, hookId, startTime, groupResult)); @@ -103,30 +98,20 @@ public Future> execute() { return groupFuture.recover(GroupExecutor::restoreResultFromRejection); } - private Future> executeHook( - Hook hook, - Long timeout, - GroupResult groupResult, - HookId hookId) { - - if (hook == null) { - conditionalLogger.error("Hook implementation %s does not exist or disabled".formatted(hookId), 0.01d); - - return Future.failedFuture(new FailedException("Hook implementation does not exist or disabled")); + private Future> hook(HookId hookId) { + try { + return Future.succeededFuture(hookProvider.apply(hookId)); + } catch (Exception e) { + return Future.failedFuture(new FailedException(e.getMessage())); } + } - final CONTEXT invocationContext = invocationContextProvider.apply(timeout, hookId, moduleContextFor(hookId)); - - if (isConfigToInvokeRequired && invocationContext instanceof AuctionInvocationContext) { - final ObjectNode accountConfig = ((AuctionInvocationContext) invocationContext).accountConfig(); - if (accountConfig == null || accountConfig.isNull()) { - return Future.succeededFuture(InvocationResultImpl.builder() - .status(InvocationStatus.success) - .action(InvocationAction.no_invocation) - .build()); - } - } + private Future> executeHook(Hook hook, + Long timeout, + GroupResult groupResult, + HookId hookId) { + final CONTEXT invocationContext = invocationContextProvider.apply(timeout, hookId, moduleContextFor(hookId)); return executeWithTimeout(() -> hook.call(groupResult.payload(), invocationContext), timeout); } diff --git a/src/main/java/org/prebid/server/hooks/execution/HookCatalog.java b/src/main/java/org/prebid/server/hooks/execution/HookCatalog.java index 754e3925b11..f58b7f136c7 100644 --- a/src/main/java/org/prebid/server/hooks/execution/HookCatalog.java +++ b/src/main/java/org/prebid/server/hooks/execution/HookCatalog.java @@ -1,38 +1,46 @@ package org.prebid.server.hooks.execution; +import org.prebid.server.hooks.execution.model.HookId; import org.prebid.server.hooks.execution.model.StageWithHookType; import org.prebid.server.hooks.v1.Hook; import org.prebid.server.hooks.v1.InvocationContext; import org.prebid.server.hooks.v1.Module; +import org.prebid.server.log.ConditionalLogger; +import org.prebid.server.log.LoggerFactory; import java.util.Collection; import java.util.Objects; -/** - * Provides simple access to all {@link Hook}s registered in application. - */ public class HookCatalog { + private static final ConditionalLogger conditionalLogger = + new ConditionalLogger(LoggerFactory.getLogger(HookCatalog.class)); + private final Collection modules; public HookCatalog(Collection modules) { this.modules = Objects.requireNonNull(modules); } - public > HOOK hookById( - String moduleCode, - String hookImplCode, - StageWithHookType stage) { + public > HOOK hookById(HookId hookId, + StageWithHookType stage) { final Class clazz = stage.hookType(); return modules.stream() - .filter(module -> Objects.equals(module.code(), moduleCode)) + .filter(module -> Objects.equals(module.code(), hookId.getModuleCode())) .map(Module::hooks) .flatMap(Collection::stream) - .filter(hook -> Objects.equals(hook.code(), hookImplCode)) + .filter(hook -> Objects.equals(hook.code(), hookId.getHookImplCode())) .filter(clazz::isInstance) .map(clazz::cast) .findFirst() - .orElse(null); + .orElseThrow(() -> { + logAbsentHook(hookId); + return new IllegalArgumentException("Hook implementation does not exist or disabled"); + }); + } + + private static void logAbsentHook(HookId hookId) { + conditionalLogger.error("Hook implementation %s does not exist or disabled".formatted(hookId), 0.01d); } } 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..cdb946f8d37 100644 --- a/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java +++ b/src/main/java/org/prebid/server/hooks/execution/HookStageExecutor.java @@ -1,10 +1,15 @@ package org.prebid.server.hooks.execution; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.response.BidResponse; import io.vertx.core.Future; +import io.vertx.core.MultiMap; import io.vertx.core.Vertx; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.collections4.map.DefaultedMap; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.prebid.server.auction.model.AuctionContext; @@ -13,6 +18,7 @@ import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.execution.timeout.Timeout; import org.prebid.server.execution.timeout.TimeoutFactory; +import org.prebid.server.hooks.execution.model.ABTest; import org.prebid.server.hooks.execution.model.EndpointExecutionPlan; import org.prebid.server.hooks.execution.model.ExecutionGroup; import org.prebid.server.hooks.execution.model.ExecutionPlan; @@ -22,6 +28,8 @@ import org.prebid.server.hooks.execution.model.Stage; import org.prebid.server.hooks.execution.model.StageExecutionPlan; import org.prebid.server.hooks.execution.model.StageWithHookType; +import org.prebid.server.hooks.execution.provider.HookProvider; +import org.prebid.server.hooks.execution.provider.abtest.ABTestHookProvider; import org.prebid.server.hooks.execution.v1.InvocationContextImpl; import org.prebid.server.hooks.execution.v1.auction.AuctionInvocationContextImpl; import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl; @@ -31,6 +39,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,24 +50,30 @@ 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.exitpoint.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.settings.model.Account; import org.prebid.server.settings.model.AccountHooksConfiguration; +import org.prebid.server.settings.model.HooksAdminConfig; import java.time.Clock; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.stream.Stream; 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"; @@ -66,18 +81,22 @@ public class HookStageExecutor { private final ExecutionPlan hostExecutionPlan; private final ExecutionPlan defaultAccountExecutionPlan; + private final Map hostModuleExecution; private final HookCatalog hookCatalog; private final TimeoutFactory timeoutFactory; private final Vertx vertx; private final Clock clock; + private final ObjectMapper mapper; private final boolean isConfigToInvokeRequired; private HookStageExecutor(ExecutionPlan hostExecutionPlan, ExecutionPlan defaultAccountExecutionPlan, + Map hostModuleExecution, HookCatalog hookCatalog, TimeoutFactory timeoutFactory, Vertx vertx, Clock clock, + ObjectMapper mapper, boolean isConfigToInvokeRequired) { this.hostExecutionPlan = hostExecutionPlan; @@ -86,11 +105,14 @@ private HookStageExecutor(ExecutionPlan hostExecutionPlan, this.timeoutFactory = timeoutFactory; this.vertx = vertx; this.clock = clock; + this.mapper = mapper; this.isConfigToInvokeRequired = isConfigToInvokeRequired; + this.hostModuleExecution = hostModuleExecution; } public static HookStageExecutor create(String hostExecutionPlan, String defaultAccountExecutionPlan, + Map hostModuleExecution, HookCatalog hookCatalog, TimeoutFactory timeoutFactory, Vertx vertx, @@ -98,19 +120,63 @@ public static HookStageExecutor create(String hostExecutionPlan, JacksonMapper mapper, boolean isConfigToInvokeRequired) { + Objects.requireNonNull(hookCatalog); + Objects.requireNonNull(mapper); + return new HookStageExecutor( - parseAndValidateExecutionPlan( - hostExecutionPlan, - Objects.requireNonNull(mapper), - Objects.requireNonNull(hookCatalog)), + parseAndValidateExecutionPlan(hostExecutionPlan, mapper, hookCatalog), parseAndValidateExecutionPlan(defaultAccountExecutionPlan, mapper, hookCatalog), + hostModuleExecution, hookCatalog, Objects.requireNonNull(timeoutFactory), Objects.requireNonNull(vertx), Objects.requireNonNull(clock), + mapper.mapper(), isConfigToInvokeRequired); } + private static ExecutionPlan parseAndValidateExecutionPlan(String executionPlan, + JacksonMapper mapper, + HookCatalog hookCatalog) { + + return validateExecutionPlan(parseExecutionPlan(executionPlan, mapper), hookCatalog); + } + + private static ExecutionPlan parseExecutionPlan(String executionPlan, JacksonMapper mapper) { + if (StringUtils.isBlank(executionPlan)) { + return ExecutionPlan.empty(); + } + + try { + return mapper.decodeValue(executionPlan, ExecutionPlan.class); + } catch (DecodeException e) { + throw new IllegalArgumentException("Hooks execution plan could not be parsed", e); + } + } + + private static ExecutionPlan validateExecutionPlan(ExecutionPlan plan, HookCatalog hookCatalog) { + plan.getEndpoints().values().stream() + .map(EndpointExecutionPlan::getStages) + .map(Map::entrySet) + .flatMap(Collection::stream) + .forEach(stageToPlan -> stageToPlan.getValue().getGroups().stream() + .map(ExecutionGroup::getHookSequence) + .flatMap(Collection::stream) + .forEach(hookId -> validateHookId(stageToPlan.getKey(), hookId, hookCatalog))); + + return plan; + } + + private static void validateHookId(Stage stage, HookId hookId, HookCatalog hookCatalog) { + try { + hookCatalog.hookById(hookId, StageWithHookType.forStage(stage)); + } catch (Throwable e) { + throw new IllegalArgumentException( + "Hooks execution plan contains unknown or disabled hook: stage=%s, hookId=%s" + .formatted(stage, hookId)); + } + } + public Future> executeEntrypointStage( CaseInsensitiveMultiMap queryParams, CaseInsensitiveMultiMap headers, @@ -121,8 +187,10 @@ public Future> executeEntrypointStag return stageExecutor(StageWithHookType.ENTRYPOINT, ENTITY_HTTP_REQUEST, context) .withExecutionPlan(planForEntrypointStage(endpoint)) + .withHookProvider(hookProviderForEntrypointStage(context)) .withInitialPayload(EntrypointPayloadImpl.of(queryParams, headers, body)) .withInvocationContextProvider(invocationContextProvider(endpoint)) + .withModulesExecution(DefaultedMap.defaultedMap(hostModuleExecution, true)) .withRejectAllowed(true) .execute(); } @@ -254,12 +322,28 @@ 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, HookExecutionContext context) { - return StageExecutor.create(hookCatalog, vertx, clock, isConfigToInvokeRequired) + return StageExecutor.create(vertx, clock) .withStage(stage) .withEntity(entity) .withHookExecutionContext(context); @@ -273,53 +357,30 @@ private StageExecutor stageToPlan.getValue().getGroups().stream() - .map(ExecutionGroup::getHookSequence) - .flatMap(Collection::stream) - .forEach(hookId -> validateHookId(stageToPlan.getKey(), hookId, hookCatalog))); - - return plan; + .withModulesExecution(modulesExecutionForAccount(account)) + .withExecutionPlan(planForStage(account, endpoint, stage.stage())) + .withHookProvider(hookProvider(stage, account, context)); } - private static void validateHookId(Stage stage, HookId hookId, HookCatalog hookCatalog) { - final Hook hook = hookCatalog.hookById( - hookId.getModuleCode(), - hookId.getHookImplCode(), - StageWithHookType.forStage(stage)); - - if (hook == null) { - throw new IllegalArgumentException( - "Hooks execution plan contains unknown or disabled hook: stage=%s, hookId=%s" - .formatted(stage, hookId)); + private Map modulesExecutionForAccount(Account account) { + final Map accountModulesExecution = Optional.ofNullable(account.getHooks()) + .map(AccountHooksConfiguration::getAdmin) + .map(HooksAdminConfig::getModuleExecution) + .orElse(Collections.emptyMap()); + + final Map resultModulesExecution = new HashMap<>(accountModulesExecution); + + if (isConfigToInvokeRequired) { + Optional.ofNullable(account.getHooks()) + .map(AccountHooksConfiguration::getModules) + .map(Map::keySet) + .stream() + .flatMap(Collection::stream) + .forEach(module -> resultModulesExecution.computeIfAbsent(module, key -> true)); } - } - private static ExecutionPlan parseExecutionPlan(String executionPlan, JacksonMapper mapper) { - if (StringUtils.isBlank(executionPlan)) { - return ExecutionPlan.empty(); - } - - try { - return mapper.decodeValue(executionPlan, ExecutionPlan.class); - } catch (DecodeException e) { - throw new IllegalArgumentException("Hooks execution plan could not be parsed", e); - } + resultModulesExecution.putAll(hostModuleExecution); + return DefaultedMap.defaultedMap(resultModulesExecution, !isConfigToInvokeRequired); } private StageExecutionPlan planForEntrypointStage(Endpoint endpoint) { @@ -366,6 +427,34 @@ private ExecutionPlan effectiveExecutionPlanFor(Account account) { return accountExecutionPlan != null ? accountExecutionPlan : defaultAccountExecutionPlan; } + private HookProvider hookProviderForEntrypointStage( + HookExecutionContext context) { + + return new ABTestHookProvider<>( + defaultHookProvider(StageWithHookType.ENTRYPOINT), + abTestsForEntrypointStage(), + context, + mapper); + } + + private HookProvider hookProvider( + StageWithHookType> stage, + Account account, + HookExecutionContext context) { + + return new ABTestHookProvider<>( + defaultHookProvider(stage), + abTests(account), + context, + mapper); + } + + private HookProvider defaultHookProvider( + StageWithHookType> stage) { + + return hookId -> hookCatalog.hookById(hookId, stage); + } + private InvocationContextProvider invocationContextProvider(Endpoint endpoint) { return (timeout, hookId, moduleContext) -> invocationContext(endpoint, timeout); } @@ -411,10 +500,47 @@ private Timeout createTimeout(Long timeout) { } private static ObjectNode accountConfigFor(Account account, HookId hookId) { - final AccountHooksConfiguration accountHooksConfiguration = account.getHooks(); + final AccountHooksConfiguration accountHooksConfiguration = account != null ? account.getHooks() : null; final Map modulesConfiguration = accountHooksConfiguration != null ? accountHooksConfiguration.getModules() : Collections.emptyMap(); return modulesConfiguration != null ? modulesConfiguration.get(hookId.getModuleCode()) : null; } + + protected List abTestsForEntrypointStage() { + return ListUtils.emptyIfNull(hostExecutionPlan.getAbTests()).stream() + .filter(HookStageExecutor::isABTestEnabled) + .toList(); + } + + private static boolean isABTestEnabled(ABTest abTest) { + return abTest != null && abTest.isEnabled(); + } + + protected List abTests(Account account) { + return abTestsFromAccount(account) + .or(() -> abTestsFromHostConfig(account.getId())) + .orElse(Collections.emptyList()); + } + + private Optional> abTestsFromAccount(Account account) { + return Optional.of(effectiveExecutionPlanFor(account)) + .map(ExecutionPlan::getAbTests) + .map(abTests -> abTests.stream() + .filter(HookStageExecutor::isABTestEnabled) + .toList()); + } + + private Optional> abTestsFromHostConfig(String accountId) { + return Optional.ofNullable(hostExecutionPlan.getAbTests()) + .map(abTests -> abTests.stream() + .filter(HookStageExecutor::isABTestEnabled) + .filter(abTest -> isABTestApplicable(abTest, accountId)) + .toList()); + } + + private static boolean isABTestApplicable(ABTest abTest, String account) { + final Set accounts = abTest.getAccounts(); + return CollectionUtils.isEmpty(accounts) || accounts.contains(account); + } } diff --git a/src/main/java/org/prebid/server/hooks/execution/StageExecutor.java b/src/main/java/org/prebid/server/hooks/execution/StageExecutor.java index d99cd14030f..8dfa03e9a7f 100644 --- a/src/main/java/org/prebid/server/hooks/execution/StageExecutor.java +++ b/src/main/java/org/prebid/server/hooks/execution/StageExecutor.java @@ -7,41 +7,39 @@ import org.prebid.server.hooks.execution.model.HookStageExecutionResult; import org.prebid.server.hooks.execution.model.StageExecutionPlan; import org.prebid.server.hooks.execution.model.StageWithHookType; +import org.prebid.server.hooks.execution.provider.HookProvider; import org.prebid.server.hooks.v1.Hook; import org.prebid.server.hooks.v1.InvocationContext; import java.time.Clock; import java.util.ArrayList; +import java.util.Map; class StageExecutor { - private final HookCatalog hookCatalog; private final Vertx vertx; private final Clock clock; - private final boolean isConfigToInvokeRequired; private StageWithHookType> stage; private String entity; private StageExecutionPlan executionPlan; + private HookProvider hookProvider; private PAYLOAD initialPayload; private InvocationContextProvider invocationContextProvider; private HookExecutionContext hookExecutionContext; private boolean rejectAllowed; + private Map modulesExecution; - private StageExecutor(HookCatalog hookCatalog, Vertx vertx, Clock clock, boolean isConfigToInvokeRequired) { - this.hookCatalog = hookCatalog; + private StageExecutor(Vertx vertx, Clock clock) { this.vertx = vertx; this.clock = clock; - this.isConfigToInvokeRequired = isConfigToInvokeRequired; } public static StageExecutor create( - HookCatalog hookCatalog, Vertx vertx, - Clock clock, - boolean isConfigToInvokeRequired) { + Clock clock) { - return new StageExecutor<>(hookCatalog, vertx, clock, isConfigToInvokeRequired); + return new StageExecutor<>(vertx, clock); } public StageExecutor withStage(StageWithHookType> stage) { @@ -59,6 +57,11 @@ public StageExecutor withExecutionPlan(StageExecutionPlan exec return this; } + public StageExecutor withHookProvider(HookProvider hookProvider) { + this.hookProvider = hookProvider; + return this; + } + public StageExecutor withInitialPayload(PAYLOAD initialPayload) { this.initialPayload = initialPayload; return this; @@ -81,6 +84,11 @@ public StageExecutor withRejectAllowed(boolean rejectAllowed) return this; } + public StageExecutor withModulesExecution(Map modulesExecution) { + this.modulesExecution = modulesExecution; + return this; + } + public Future> execute() { Future> stageFuture = Future.succeededFuture(StageResult.of(initialPayload, entity)); @@ -97,11 +105,10 @@ public Future> execute() { } private Future> executeGroup(ExecutionGroup group, PAYLOAD initialPayload) { - return GroupExecutor.create(vertx, clock, isConfigToInvokeRequired) + return GroupExecutor.create(vertx, clock, modulesExecution) .withGroup(group) .withInitialPayload(initialPayload) - .withHookProvider( - hookId -> hookCatalog.hookById(hookId.getModuleCode(), hookId.getHookImplCode(), stage)) + .withHookProvider(hookProvider) .withInvocationContextProvider(invocationContextProvider) .withHookExecutionContext(hookExecutionContext) .withRejectAllowed(rejectAllowed) diff --git a/src/main/java/org/prebid/server/hooks/execution/model/ABTest.java b/src/main/java/org/prebid/server/hooks/execution/model/ABTest.java new file mode 100644 index 00000000000..67d64190101 --- /dev/null +++ b/src/main/java/org/prebid/server/hooks/execution/model/ABTest.java @@ -0,0 +1,29 @@ +package org.prebid.server.hooks.execution.model; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Value; + +import java.util.Set; + +@Builder +@Value +public class ABTest { + + boolean enabled; + + @JsonProperty("module-code") + @JsonAlias("module_code") + String moduleCode; + + Set accounts; + + @JsonProperty("percent-active") + @JsonAlias("percent_active") + Integer percentActive; + + @JsonProperty("log-analytics-tag") + @JsonAlias("log_analytics_tag") + Boolean logAnalyticsTag; +} diff --git a/src/main/java/org/prebid/server/hooks/execution/model/ExecutionPlan.java b/src/main/java/org/prebid/server/hooks/execution/model/ExecutionPlan.java index 5d0af8b8e23..8d865c26a90 100644 --- a/src/main/java/org/prebid/server/hooks/execution/model/ExecutionPlan.java +++ b/src/main/java/org/prebid/server/hooks/execution/model/ExecutionPlan.java @@ -1,15 +1,20 @@ package org.prebid.server.hooks.execution.model; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Value; import org.prebid.server.model.Endpoint; import java.util.Collections; +import java.util.List; import java.util.Map; @Value(staticConstructor = "of") public class ExecutionPlan { - private static final ExecutionPlan EMPTY = of(Collections.emptyMap()); + private static final ExecutionPlan EMPTY = of(null, Collections.emptyMap()); + + @JsonProperty("abtests") + List abTests; Map endpoints; 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..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,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.exitpoint.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/provider/HookProvider.java b/src/main/java/org/prebid/server/hooks/execution/provider/HookProvider.java new file mode 100644 index 00000000000..83297c26396 --- /dev/null +++ b/src/main/java/org/prebid/server/hooks/execution/provider/HookProvider.java @@ -0,0 +1,11 @@ +package org.prebid.server.hooks.execution.provider; + +import org.prebid.server.hooks.execution.model.HookId; +import org.prebid.server.hooks.v1.Hook; +import org.prebid.server.hooks.v1.InvocationContext; + +import java.util.function.Function; + +public interface HookProvider + extends Function> { +} diff --git a/src/main/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHook.java b/src/main/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHook.java new file mode 100644 index 00000000000..e7b82803f9a --- /dev/null +++ b/src/main/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHook.java @@ -0,0 +1,148 @@ +package org.prebid.server.hooks.execution.provider.abtest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +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.ResultImpl; +import org.prebid.server.hooks.execution.v1.analytics.TagsImpl; +import org.prebid.server.hooks.v1.Hook; +import org.prebid.server.hooks.v1.InvocationAction; +import org.prebid.server.hooks.v1.InvocationContext; +import org.prebid.server.hooks.v1.InvocationResult; +import org.prebid.server.hooks.v1.InvocationStatus; +import org.prebid.server.hooks.v1.PayloadUpdate; +import org.prebid.server.hooks.v1.analytics.Activity; +import org.prebid.server.hooks.v1.analytics.Tags; +import org.prebid.server.util.ListUtil; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class ABTestHook implements Hook { + + private static final String ANALYTICS_ACTIVITY_NAME = "core-module-abtests"; + + private final String moduleName; + private final Hook hook; + private final boolean shouldInvokeHook; + private final boolean logABTestAnalyticsTag; + private final ObjectMapper mapper; + + public ABTestHook(String moduleName, + Hook hook, + boolean shouldInvokeHook, + boolean logABTestAnalyticsTag, + ObjectMapper mapper) { + + this.moduleName = Objects.requireNonNull(moduleName); + this.hook = Objects.requireNonNull(hook); + this.shouldInvokeHook = shouldInvokeHook; + this.logABTestAnalyticsTag = logABTestAnalyticsTag; + this.mapper = Objects.requireNonNull(mapper); + } + + @Override + public String code() { + return hook.code(); + } + + @Override + public Future> call(PAYLOAD payload, CONTEXT invocationContext) { + if (!shouldInvokeHook) { + return skippedResult(); + } + + final Future> invocationResultFuture = hook.call(payload, invocationContext); + return logABTestAnalyticsTag + ? invocationResultFuture.map(this::enrichWithABTestAnalyticsTag) + : invocationResultFuture; + } + + private Future> skippedResult() { + return Future.succeededFuture(InvocationResultImpl.builder() + .status(InvocationStatus.success) + .action(InvocationAction.no_invocation) + .analyticsTags(logABTestAnalyticsTag ? tags("skipped") : null) + .build()); + } + + private Tags tags(String status) { + return TagsImpl.of(Collections.singletonList(ActivityImpl.of( + ANALYTICS_ACTIVITY_NAME, + "success", + Collections.singletonList(ResultImpl.of(status, analyticsValues(), null))))); + } + + private ObjectNode analyticsValues() { + final ObjectNode values = mapper.createObjectNode(); + values.put("module", moduleName); + return values; + } + + private InvocationResult enrichWithABTestAnalyticsTag(InvocationResult invocationResult) { + return new InvocationResultWithAdditionalTags<>(invocationResult, tags("run")); + } + + private record InvocationResultWithAdditionalTags(InvocationResult invocationResult, + Tags additionalTags) + implements InvocationResult { + + @Override + public InvocationStatus status() { + return invocationResult.status(); + } + + @Override + public String message() { + return invocationResult.message(); + } + + @Override + public InvocationAction action() { + return invocationResult.action(); + } + + @Override + public PayloadUpdate payloadUpdate() { + return invocationResult.payloadUpdate(); + } + + @Override + public List errors() { + return invocationResult.errors(); + } + + @Override + public List warnings() { + return invocationResult.warnings(); + } + + @Override + public List debugMessages() { + return invocationResult.debugMessages(); + } + + @Override + public Object moduleContext() { + return invocationResult.moduleContext(); + } + + @Override + public Tags analyticsTags() { + return new TagsUnion(invocationResult.analyticsTags(), additionalTags); + } + } + + private record TagsUnion(Tags left, Tags right) implements Tags { + + @Override + public List activities() { + return left != null + ? ListUtil.union(left.activities(), right.activities()) + : right.activities(); + } + } +} diff --git a/src/main/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHookProvider.java b/src/main/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHookProvider.java new file mode 100644 index 00000000000..6a833ab2833 --- /dev/null +++ b/src/main/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHookProvider.java @@ -0,0 +1,87 @@ +package org.prebid.server.hooks.execution.provider.abtest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.prebid.server.hooks.execution.model.ABTest; +import org.prebid.server.hooks.execution.model.ExecutionAction; +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.StageExecutionOutcome; +import org.prebid.server.hooks.execution.provider.HookProvider; +import org.prebid.server.hooks.v1.Hook; +import org.prebid.server.hooks.v1.InvocationContext; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ThreadLocalRandom; + +public class ABTestHookProvider implements HookProvider { + + private final HookProvider innerHookProvider; + private final List abTests; + private final HookExecutionContext context; + private final ObjectMapper mapper; + + public ABTestHookProvider(HookProvider innerHookProvider, + List abTests, + HookExecutionContext context, + ObjectMapper mapper) { + + this.innerHookProvider = Objects.requireNonNull(innerHookProvider); + this.abTests = Objects.requireNonNull(abTests); + this.context = Objects.requireNonNull(context); + this.mapper = Objects.requireNonNull(mapper); + } + + @Override + public Hook apply(HookId hookId) { + final Hook hook = innerHookProvider.apply(hookId); + + final String moduleCode = hookId.getModuleCode(); + final ABTest abTest = searchForABTest(moduleCode); + if (abTest == null) { + return hook; + } + + return new ABTestHook<>( + moduleCode, + hook, + shouldInvokeHook(moduleCode, abTest), + BooleanUtils.isNotFalse(abTest.getLogAnalyticsTag()), + mapper); + } + + private ABTest searchForABTest(String moduleCode) { + return abTests.stream() + .filter(abTest -> moduleCode.equals(abTest.getModuleCode())) + .findFirst() + .orElse(null); + } + + protected boolean shouldInvokeHook(String moduleCode, ABTest abTest) { + final HookExecutionOutcome hookExecutionOutcome = searchForPreviousExecution(moduleCode); + if (hookExecutionOutcome != null) { + return hookExecutionOutcome.getAction() != ExecutionAction.no_invocation; + } + + final int percent = ObjectUtils.defaultIfNull(abTest.getPercentActive(), 100); + return ThreadLocalRandom.current().nextInt(100) < percent; + } + + private HookExecutionOutcome searchForPreviousExecution(String moduleCode) { + return context.getStageOutcomes().values().stream() + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .map(StageExecutionOutcome::getGroups) + .flatMap(Collection::stream) + .map(GroupExecutionOutcome::getHooks) + .flatMap(Collection::stream) + .filter(hookExecutionOutcome -> hookExecutionOutcome.getHookId().getModuleCode().equals(moduleCode)) + .findFirst() + .orElse(null); + } +} diff --git a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/InvocationResultImpl.java b/src/main/java/org/prebid/server/hooks/execution/v1/InvocationResultImpl.java similarity index 90% rename from extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/InvocationResultImpl.java rename to src/main/java/org/prebid/server/hooks/execution/v1/InvocationResultImpl.java index 4efcb2bd5c2..761aef951ec 100644 --- a/extra/modules/greenbids-real-time-data/src/main/java/org/prebid/server/hooks/modules/greenbids/real/time/data/v1/model/InvocationResultImpl.java +++ b/src/main/java/org/prebid/server/hooks/execution/v1/InvocationResultImpl.java @@ -1,4 +1,4 @@ -package org.prebid.server.hooks.modules.greenbids.real.time.data.v1.model; +package org.prebid.server.hooks.execution.v1; import lombok.Builder; import lombok.Value; diff --git a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/ActivityImpl.java b/src/main/java/org/prebid/server/hooks/execution/v1/analytics/ActivityImpl.java similarity index 82% rename from extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/ActivityImpl.java rename to src/main/java/org/prebid/server/hooks/execution/v1/analytics/ActivityImpl.java index 484489a5e6f..4c9747e16bc 100644 --- a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/ActivityImpl.java +++ b/src/main/java/org/prebid/server/hooks/execution/v1/analytics/ActivityImpl.java @@ -1,4 +1,4 @@ -package org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics; +package org.prebid.server.hooks.execution.v1.analytics; import lombok.Value; import lombok.experimental.Accessors; diff --git a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/AppliedToImpl.java b/src/main/java/org/prebid/server/hooks/execution/v1/analytics/AppliedToImpl.java similarity index 83% rename from extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/AppliedToImpl.java rename to src/main/java/org/prebid/server/hooks/execution/v1/analytics/AppliedToImpl.java index 2971cc40d6e..884603b4717 100644 --- a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/AppliedToImpl.java +++ b/src/main/java/org/prebid/server/hooks/execution/v1/analytics/AppliedToImpl.java @@ -1,4 +1,4 @@ -package org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics; +package org.prebid.server.hooks.execution.v1.analytics; import lombok.Builder; import lombok.Value; @@ -8,8 +8,8 @@ import java.util.List; @Accessors(fluent = true) -@Value @Builder +@Value public class AppliedToImpl implements AppliedTo { List impIds; diff --git a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/ResultImpl.java b/src/main/java/org/prebid/server/hooks/execution/v1/analytics/ResultImpl.java similarity index 84% rename from extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/ResultImpl.java rename to src/main/java/org/prebid/server/hooks/execution/v1/analytics/ResultImpl.java index 5405799e25f..c16397e894c 100644 --- a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/ResultImpl.java +++ b/src/main/java/org/prebid/server/hooks/execution/v1/analytics/ResultImpl.java @@ -1,4 +1,4 @@ -package org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics; +package org.prebid.server.hooks.execution.v1.analytics; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Value; diff --git a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/TagsImpl.java b/src/main/java/org/prebid/server/hooks/execution/v1/analytics/TagsImpl.java similarity index 81% rename from extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/TagsImpl.java rename to src/main/java/org/prebid/server/hooks/execution/v1/analytics/TagsImpl.java index 9f0432b9e2f..f068f28dcef 100644 --- a/extra/modules/ortb2-blocking/src/main/java/org/prebid/server/hooks/modules/ortb2/blocking/v1/model/analytics/TagsImpl.java +++ b/src/main/java/org/prebid/server/hooks/execution/v1/analytics/TagsImpl.java @@ -1,4 +1,4 @@ -package org.prebid.server.hooks.modules.ortb2.blocking.v1.model.analytics; +package org.prebid.server.hooks.execution.v1.analytics; import lombok.Value; import lombok.experimental.Accessors; 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..d57080f6b90 --- /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.exitpoint.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/InvocationResultImpl.java b/src/main/java/org/prebid/server/hooks/v1/InvocationResultImpl.java deleted file mode 100644 index abfe5cf8fb2..00000000000 --- a/src/main/java/org/prebid/server/hooks/v1/InvocationResultImpl.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.prebid.server.hooks.v1; - -import lombok.Builder; -import lombok.Value; -import lombok.experimental.Accessors; -import org.prebid.server.hooks.v1.analytics.Tags; - -import java.util.List; - -@Accessors(fluent = true) -@Builder -@Value -public class InvocationResultImpl implements InvocationResult { - - InvocationStatus status; - - String message; - - InvocationAction action; - - PayloadUpdate payloadUpdate; - - List errors; - - List warnings; - - List debugMessages; - - Object moduleContext; - - Tags analyticsTags; -} diff --git a/src/main/java/org/prebid/server/hooks/v1/exitpoint/ExitpointHook.java b/src/main/java/org/prebid/server/hooks/v1/exitpoint/ExitpointHook.java new file mode 100644 index 00000000000..02e36af17a5 --- /dev/null +++ b/src/main/java/org/prebid/server/hooks/v1/exitpoint/ExitpointHook.java @@ -0,0 +1,7 @@ +package org.prebid.server.hooks.v1.exitpoint; + +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/exitpoint/ExitpointPayload.java b/src/main/java/org/prebid/server/hooks/v1/exitpoint/ExitpointPayload.java new file mode 100644 index 00000000000..ae596949fa0 --- /dev/null +++ b/src/main/java/org/prebid/server/hooks/v1/exitpoint/ExitpointPayload.java @@ -0,0 +1,10 @@ +package org.prebid.server.hooks.v1.exitpoint; + +import io.vertx.core.MultiMap; + +public interface ExitpointPayload { + + MultiMap responseHeaders(); + + String responseBody(); +} 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/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/settings/model/AccountHooksConfiguration.java b/src/main/java/org/prebid/server/settings/model/AccountHooksConfiguration.java index 75d3b03b6e5..f45af371385 100644 --- a/src/main/java/org/prebid/server/settings/model/AccountHooksConfiguration.java +++ b/src/main/java/org/prebid/server/settings/model/AccountHooksConfiguration.java @@ -14,4 +14,6 @@ public class AccountHooksConfiguration { ExecutionPlan executionPlan; Map modules; + + HooksAdminConfig admin; } diff --git a/src/main/java/org/prebid/server/settings/model/HooksAdminConfig.java b/src/main/java/org/prebid/server/settings/model/HooksAdminConfig.java new file mode 100644 index 00000000000..36b12a401f0 --- /dev/null +++ b/src/main/java/org/prebid/server/settings/model/HooksAdminConfig.java @@ -0,0 +1,16 @@ +package org.prebid.server.settings.model; + +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.Builder; +import lombok.Value; + +import java.util.Map; + +@Builder +@Value +public class HooksAdminConfig { + + @JsonAlias("module-execution") + Map moduleExecution; + +} diff --git a/src/main/java/org/prebid/server/spring/config/HooksConfiguration.java b/src/main/java/org/prebid/server/spring/config/HooksConfiguration.java index 64534d119aa..5a05ccb8c8e 100644 --- a/src/main/java/org/prebid/server/spring/config/HooksConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/HooksConfiguration.java @@ -8,6 +8,7 @@ import org.prebid.server.hooks.execution.HookStageExecutor; import org.prebid.server.hooks.v1.Module; import org.prebid.server.json.JacksonMapper; +import org.prebid.server.settings.model.HooksAdminConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -16,6 +17,8 @@ import java.time.Clock; import java.util.Collection; +import java.util.Collections; +import java.util.Optional; @Configuration public class HooksConfiguration { @@ -38,6 +41,9 @@ HookStageExecutor hookStageExecutor(HooksConfigurationProperties hooksConfigurat return HookStageExecutor.create( hooksConfiguration.getHostExecutionPlan(), hooksConfiguration.getDefaultAccountExecutionPlan(), + Optional.ofNullable(hooksConfiguration.getAdmin()) + .map(HooksAdminConfig::getModuleExecution) + .orElseGet(Collections::emptyMap), hookCatalog, timeoutFactory, vertx, @@ -60,5 +66,7 @@ private static class HooksConfigurationProperties { String hostExecutionPlan; String defaultAccountExecutionPlan; + + HooksAdminConfig admin; } } diff --git a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java index 778351b95a9..deaa768320c 100644 --- a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java @@ -54,11 +54,9 @@ import org.prebid.server.auction.privacy.contextfactory.AuctionPrivacyContextFactory; import org.prebid.server.auction.privacy.contextfactory.CookieSyncPrivacyContextFactory; import org.prebid.server.auction.privacy.contextfactory.SetuidPrivacyContextFactory; -import org.prebid.server.auction.privacy.enforcement.ActivityEnforcement; import org.prebid.server.auction.privacy.enforcement.CcpaEnforcement; -import org.prebid.server.auction.privacy.enforcement.CoppaEnforcement; +import org.prebid.server.auction.privacy.enforcement.PrivacyEnforcement; import org.prebid.server.auction.privacy.enforcement.PrivacyEnforcementService; -import org.prebid.server.auction.privacy.enforcement.TcfEnforcement; import org.prebid.server.auction.requestfactory.AmpRequestFactory; import org.prebid.server.auction.requestfactory.AuctionRequestFactory; import org.prebid.server.auction.requestfactory.Ortb2ImplicitParametersResolver; @@ -110,6 +108,7 @@ import org.prebid.server.privacy.gdpr.TcfDefinerService; import org.prebid.server.settings.ApplicationSettings; import org.prebid.server.settings.model.BidValidationEnforcement; +import org.prebid.server.spring.config.model.CacheDefaultTtlProperties; import org.prebid.server.spring.config.model.ExternalConversionProperties; import org.prebid.server.spring.config.model.HttpClientCircuitBreakerProperties; import org.prebid.server.spring.config.model.HttpClientProperties; @@ -796,6 +795,16 @@ BidderErrorNotifier bidderErrorNotifier( metrics); } + @Bean + CacheDefaultTtlProperties cacheDefaultTtlProperties( + @Value("${cache.default-ttl-seconds.banner:300}") Integer bannerTtl, + @Value("${cache.default-ttl-seconds.video:1500}") Integer videoTtl, + @Value("${cache.default-ttl-seconds.audio:1500}") Integer audioTtl, + @Value("${cache.default-ttl-seconds.native:300}") Integer nativeTtl) { + + return CacheDefaultTtlProperties.of(bannerTtl, videoTtl, audioTtl, nativeTtl); + } + @Bean BidResponseCreator bidResponseCreator( CoreCacheService coreCacheService, @@ -811,7 +820,8 @@ BidResponseCreator bidResponseCreator( Clock clock, JacksonMapper mapper, @Value("${cache.banner-ttl-seconds:#{null}}") Integer bannerCacheTtl, - @Value("${cache.video-ttl-seconds:#{null}}") Integer videoCacheTtl) { + @Value("${cache.video-ttl-seconds:#{null}}") Integer videoCacheTtl, + CacheDefaultTtlProperties cacheDefaultTtlProperties) { return new BidResponseCreator( coreCacheService, @@ -826,7 +836,8 @@ BidResponseCreator bidResponseCreator( truncateAttrChars, clock, mapper, - CacheTtl.of(bannerCacheTtl, videoCacheTtl)); + CacheTtl.of(bannerCacheTtl, videoCacheTtl), + cacheDefaultTtlProperties); } @Bean @@ -933,16 +944,8 @@ StoredResponseProcessor storedResponseProcessor(ApplicationSettings applicationS } @Bean - PrivacyEnforcementService privacyEnforcementService(CoppaEnforcement coppaEnforcement, - CcpaEnforcement ccpaEnforcement, - TcfEnforcement tcfEnforcement, - ActivityEnforcement activityEnforcement) { - - return new PrivacyEnforcementService( - coppaEnforcement, - ccpaEnforcement, - tcfEnforcement, - activityEnforcement); + PrivacyEnforcementService privacyEnforcementService(List enforcements) { + return new PrivacyEnforcementService(enforcements); } @Bean 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/model/CacheDefaultTtlProperties.java b/src/main/java/org/prebid/server/spring/config/model/CacheDefaultTtlProperties.java new file mode 100644 index 00000000000..2a3e36b6ef1 --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/model/CacheDefaultTtlProperties.java @@ -0,0 +1,15 @@ +package org.prebid.server.spring.config.model; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class CacheDefaultTtlProperties { + + Integer bannerTtl; + + Integer videoTtl; + + Integer audioTtl; + + Integer nativeTtl; +} 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/groovy/org/prebid/server/functional/model/config/AbTest.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AbTest.groovy new file mode 100644 index 00000000000..baa19a80db4 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/AbTest.groovy @@ -0,0 +1,31 @@ +package org.prebid.server.functional.model.config + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) +class AbTest { + + Boolean enabled + String moduleCode + @JsonProperty("module_code") + String moduleCodeSnakeCase + Set accounts + Integer percentActive + @JsonProperty("percent_active") + Integer percentActiveSnakeCase + Boolean logAnalyticsTag + @JsonProperty("log_analytics_tag") + Boolean logAnalyticsTagSnakeCase + + static AbTest getDefault(String moduleCode, List accounts = null) { + new AbTest(enabled: true, + moduleCode: moduleCode, + accounts: accounts, + percentActive: 0, + logAnalyticsTag: true) + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountHooksConfiguration.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountHooksConfiguration.groovy index 24f3ab97d77..bab4ec983a3 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountHooksConfiguration.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountHooksConfiguration.groovy @@ -13,4 +13,5 @@ class AccountHooksConfiguration { @JsonProperty("execution_plan") ExecutionPlan executionPlanSnakeCase PbsModulesConfig modules + AdminConfig admin } diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AdminConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AdminConfig.groovy new file mode 100644 index 00000000000..755a47bcbaa --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/AdminConfig.groovy @@ -0,0 +1,13 @@ +package org.prebid.server.functional.model.config + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.ToString +import org.prebid.server.functional.model.ModuleName + +@ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) +class AdminConfig { + + Map moduleExecution +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/ExecutionPlan.groovy b/src/test/groovy/org/prebid/server/functional/model/config/ExecutionPlan.groovy index 9e5cfc3ac93..653f8c8cbea 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/ExecutionPlan.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/ExecutionPlan.groovy @@ -1,11 +1,15 @@ package org.prebid.server.functional.model.config +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString import org.prebid.server.functional.model.ModuleName @ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) class ExecutionPlan { + List abTests Map endpoints static ExecutionPlan getSingleEndpointExecutionPlan(Endpoint endpoint, ModuleName moduleName, List stage) { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/ConsentedProvidersSettings.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/ConsentedProvidersSettings.groovy new file mode 100644 index 00000000000..aa7bd511cb2 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/ConsentedProvidersSettings.groovy @@ -0,0 +1,12 @@ +package org.prebid.server.functional.model.request.auction + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) +class ConsentedProvidersSettings { + + String consentedProviders +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/FetchStatus.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/FetchStatus.groovy index c669d61f5a0..6eec49cf39d 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/FetchStatus.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/FetchStatus.groovy @@ -6,7 +6,7 @@ import groovy.transform.ToString @ToString enum FetchStatus { - NONE, SUCCESS, TIMEOUT, INPROGRESS, ERROR, SUCCESS_ALLOW, SUCCESS_BLOCK + NONE, SUCCESS, TIMEOUT, INPROGRESS, ERROR, SUCCESS_ALLOW, SUCCESS_BLOCK, SKIPPED, RUN @JsonValue String getValue() { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy index 59fb0b34c25..9bdb94f007d 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy @@ -4,7 +4,8 @@ enum PublicCountryIp { USA_IP("209.232.44.21", "d646:2414:17b2:f371:9b62:f176:b4c0:51cd"), UKR_IP("193.238.111.14", "3080:f30f:e4bc:0f56:41be:6aab:9d0a:58e2"), - CAN_IP("70.71.245.39", "f9b2:c742:1922:7d4b:7122:c7fc:8b75:98c8") + CAN_IP("70.71.245.39", "f9b2:c742:1922:7d4b:7122:c7fc:8b75:98c8"), + BGR_IP("31.211.128.0", "2002:1fd3:8000:0000:0000:0000:0000:0000") final String v4 final String v6 diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy index 9a9c62fde81..d7e9cb2242e 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy @@ -10,6 +10,7 @@ class RegsExt { @Deprecated(since = "enabling support of ortb 2.6") Integer gdpr + Integer coppa @Deprecated(since = "enabling support of ortb 2.6") String usPrivacy String gpc diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy index e547d8f37ed..af07e197c28 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy @@ -1,8 +1,12 @@ package org.prebid.server.functional.model.request.auction +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) class UserExt { String consent @@ -11,6 +15,9 @@ class UserExt { UserTime time UserExtData data UserExtPrebid prebid + ConsentedProvidersSettings consentedProvidersSettings + @JsonProperty("ConsentedProvidersSettings") + ConsentedProvidersSettings consentedProvidersSettingsCamelCase static UserExt getFPDUserExt() { new UserExt(data: UserExtData.FPDUserExtData) diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/InvocationResult.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/InvocationResult.groovy index 3f1594380f4..c5c1a828f98 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/InvocationResult.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/InvocationResult.groovy @@ -14,4 +14,5 @@ class InvocationResult { ResponseAction action HookId hookId AnalyticsPrebidTag analyticsTags + String message } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/InvocationStatus.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/InvocationStatus.groovy index 257b6287fcf..77c7ffd5ef8 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/InvocationStatus.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/InvocationStatus.groovy @@ -6,7 +6,7 @@ import groovy.transform.ToString @ToString enum InvocationStatus { - SUCCESS, FAILURE + SUCCESS, FAILURE, INVOCATION_FAILURE @JsonValue String getValue() { diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/ModuleActivityName.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/ModuleActivityName.groovy index 3942b170875..8711bd395c6 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/ModuleActivityName.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/ModuleActivityName.groovy @@ -5,7 +5,8 @@ import com.fasterxml.jackson.annotation.JsonValue enum ModuleActivityName { ORTB2_BLOCKING('enforce-blocking'), - REJECT_RICHMEDIA('reject-richmedia') + REJECT_RICHMEDIA('reject-richmedia'), + AB_TESTING('core-module-abtests') @JsonValue final String value diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/ModuleValue.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/ModuleValue.groovy index e76e8fb3f54..9a1e9d1b440 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/ModuleValue.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/ModuleValue.groovy @@ -4,11 +4,13 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.EqualsAndHashCode import groovy.transform.ToString +import org.prebid.server.functional.model.ModuleName @ToString(includeNames = true, ignoreNulls = true) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) @EqualsAndHashCode class ModuleValue { + ModuleName module String richmediaFormat } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/TraceOutcome.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/TraceOutcome.groovy index f1a72a9e266..0f155bf55a1 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/TraceOutcome.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/TraceOutcome.groovy @@ -3,13 +3,12 @@ package org.prebid.server.functional.model.response.auction import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString -import org.prebid.server.functional.model.config.Stage @ToString(includeNames = true, ignoreNulls = true) @JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) class TraceOutcome { - Stage entity + String entity Long executionTimeMillis List groups } diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy index 5629826942b..b3f938a7ca0 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy @@ -91,7 +91,6 @@ class PrebidServerContainer extends GenericContainer { private static String normalizeProperty(String property) { property.replace(".", "_") - .replace("-", "") .replace("[", "_") .replace("]", "_") } diff --git a/src/test/groovy/org/prebid/server/functional/tests/AmpSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AmpSpec.groovy index ac3ba146010..96a78df89a8 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/AmpSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/AmpSpec.groovy @@ -4,9 +4,12 @@ import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.db.StoredResponse import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.ConsentedProvidersSettings import org.prebid.server.functional.model.request.auction.DistributionChannel import org.prebid.server.functional.model.request.auction.Site import org.prebid.server.functional.model.request.auction.StoredAuctionResponse +import org.prebid.server.functional.model.request.auction.User +import org.prebid.server.functional.model.request.auction.UserExt import org.prebid.server.functional.model.response.auction.SeatBid import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils @@ -56,7 +59,7 @@ class AmpSpec extends BaseSpec { assert exception.responseBody == "Invalid request format: request.${channel.value.toLowerCase()} must not exist in AMP stored requests." where: - channel << [DistributionChannel.APP, DistributionChannel.DOOH] + channel << [DistributionChannel.APP, DistributionChannel.DOOH] } def "PBS should return info from the stored response when it's defined in the stored request"() { @@ -180,4 +183,79 @@ class AmpSpec extends BaseSpec { assert bidderRequest.imp[0]?.banner?.format[0]?.weight == ampStoredRequest.imp[0].banner.format[0].weight assert bidderRequest.regs?.gdpr == ampStoredRequest.regs.gdpr } + + def "PBS should pass addtl_consent to user.ext.{consented_providers_settings/ConsentedProvidersSettings}.consented_providers"() { + given: "Default amp request with addtlConsent" + def randomAddtlConsent = PBSUtils.randomString + def ampRequest = AmpRequest.defaultAmpRequest.tap { + addtlConsent = randomAddtlConsent + } + + and: "Save storedRequest into DB" + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + user = new User(ext: new UserExt( + consentedProvidersSettingsCamelCase: new ConsentedProvidersSettings(consentedProviders: PBSUtils.randomString), + consentedProvidersSettings: new ConsentedProvidersSettings(consentedProviders: PBSUtils.randomString))) + } + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + defaultPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain addtl consent" + def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + assert bidderRequest.user.ext.consentedProvidersSettingsCamelCase.consentedProviders == randomAddtlConsent + assert bidderRequest.user.ext.consentedProvidersSettings.consentedProviders == randomAddtlConsent + } + + def "PBS should process original user.ext.{consented_providers_settings/ConsentedProvidersSettings}.consented_providers when ampRequest doesn't contain addtl_consent"() { + given: "Default amp request with addtlConsent" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + addtlConsent = null + } + + and: "Save storedRequest into DB" + def consentProvidersKebabCase = PBSUtils.randomString + def consentProviders = PBSUtils.randomString + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + user = new User(ext: new UserExt( + consentedProvidersSettingsCamelCase: new ConsentedProvidersSettings(consentedProviders: consentProvidersKebabCase), + consentedProvidersSettings: new ConsentedProvidersSettings(consentedProviders: consentProviders))) + } + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + defaultPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain requested consent" + def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + assert bidderRequest.user.ext.consentedProvidersSettingsCamelCase.consentedProviders == consentProvidersKebabCase + assert bidderRequest.user.ext.consentedProvidersSettings.consentedProviders == consentProviders + } + + def "PBS should left user.ext.{consented_providers_settings/ConsentedProvidersSettings}.consented_providers empty when addtl_consent and original fields are empty"() { + given: "Default amp request with addtlConsent" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + addtlConsent = null + } + + and: "Save storedRequest into DB" + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + user = new User(ext: new UserExt( + consentedProvidersSettingsCamelCase: new ConsentedProvidersSettings(consentedProviders: null), + consentedProvidersSettings: new ConsentedProvidersSettings(consentedProviders: null))) + } + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + defaultPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request shouldn't contain consent" + def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + assert !bidderRequest.user.ext.consentedProvidersSettingsCamelCase.consentedProviders + assert !bidderRequest.user.ext.consentedProvidersSettings.consentedProviders + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy index 90c6c764929..79eb960787f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidAdjustmentSpec.groovy @@ -12,6 +12,8 @@ import org.prebid.server.functional.model.request.auction.BidAdjustmentFactors import org.prebid.server.functional.model.request.auction.BidAdjustmentRule import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Imp +import org.prebid.server.functional.model.request.auction.VideoPlacementSubtypes +import org.prebid.server.functional.model.request.auction.VideoPlcmtSubtype import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.service.PrebidServerService @@ -40,8 +42,8 @@ import static org.prebid.server.functional.model.request.auction.BidAdjustmentMe import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.VIDEO_IN_STREAM import static org.prebid.server.functional.model.request.auction.BidAdjustmentMediaType.VIDEO_OUT_STREAM import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE -import static org.prebid.server.functional.model.request.auction.VideoPlacementSubtypes.IN_ARTICLE -import static org.prebid.server.functional.model.request.auction.VideoPlacementSubtypes.IN_STREAM +import static org.prebid.server.functional.model.request.auction.VideoPlacementSubtypes.IN_STREAM as IN_PLACEMENT_STREAM +import static org.prebid.server.functional.model.request.auction.VideoPlcmtSubtype.IN_STREAM as IN_PLCMT_STREAM import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer import static org.prebid.server.functional.util.PBSUtils.getRandomDecimal @@ -56,6 +58,8 @@ class BidAdjustmentSpec extends BaseSpec { private static final Currency DEFAULT_CURRENCY = USD private static final int BID_ADJUST_PRECISION = 4 private static final int PRICE_PRECISION = 3 + private static final VideoPlacementSubtypes RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM = PBSUtils.getRandomEnum(VideoPlacementSubtypes, [IN_PLACEMENT_STREAM]) + private static final VideoPlcmtSubtype RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM = PBSUtils.getRandomEnum(VideoPlcmtSubtype, [IN_PLCMT_STREAM]) private static final Map> DEFAULT_CURRENCY_RATES = [(USD): [(EUR): 0.9124920156948626, (GBP): 0.793776804452961], (GBP): [(USD): 1.2597999770088517, @@ -220,22 +224,43 @@ class BidAdjustmentSpec extends BaseSpec { where: adjustmentType | ruleValue | mediaType | bidRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest @@ -284,22 +309,43 @@ class BidAdjustmentSpec extends BaseSpec { where: adjustmentType | ruleValue | mediaType | bidRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest @@ -341,22 +387,43 @@ class BidAdjustmentSpec extends BaseSpec { where: adjustmentType | ruleValue | mediaType | bidRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest @@ -403,22 +470,43 @@ class BidAdjustmentSpec extends BaseSpec { where: adjustmentType | ruleValue | mediaType | bidRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest MULTIPLIER | getRandomDecimal(MIN_ADJUST_VALUE, MAX_MULTIPLIER_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest CPM | getRandomDecimal(MIN_ADJUST_VALUE, MAX_CPM_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | BANNER | BidRequest.defaultBidRequest - STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | AUDIO | BidRequest.defaultAudioRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | NATIVE | BidRequest.defaultNativeRequest STATIC | getRandomDecimal(MIN_ADJUST_VALUE, MAX_STATIC_ADJUST_VALUE) | ANY | BidRequest.defaultBidRequest @@ -673,40 +761,82 @@ class BidAdjustmentSpec extends BaseSpec { where: adjustmentType | ruleValue | mediaType | bidRequest MULTIPLIER | MIN_ADJUST_VALUE - 1 | BANNER | BidRequest.defaultBidRequest - MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) MULTIPLIER | MIN_ADJUST_VALUE - 1 | AUDIO | BidRequest.defaultAudioRequest MULTIPLIER | MIN_ADJUST_VALUE - 1 | NATIVE | BidRequest.defaultNativeRequest MULTIPLIER | MIN_ADJUST_VALUE - 1 | ANY | BidRequest.defaultNativeRequest MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | BANNER | BidRequest.defaultBidRequest - MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | AUDIO | BidRequest.defaultAudioRequest MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | NATIVE | BidRequest.defaultNativeRequest MULTIPLIER | MAX_MULTIPLIER_ADJUST_VALUE + 1 | ANY | BidRequest.defaultNativeRequest CPM | MIN_ADJUST_VALUE - 1 | BANNER | BidRequest.defaultBidRequest - CPM | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - CPM | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) CPM | MIN_ADJUST_VALUE - 1 | AUDIO | BidRequest.defaultAudioRequest CPM | MIN_ADJUST_VALUE - 1 | NATIVE | BidRequest.defaultNativeRequest CPM | MIN_ADJUST_VALUE - 1 | ANY | BidRequest.defaultNativeRequest CPM | MAX_CPM_ADJUST_VALUE + 1 | BANNER | BidRequest.defaultBidRequest - CPM | MAX_CPM_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - CPM | MAX_CPM_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + CPM | MAX_CPM_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + CPM | MAX_CPM_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + CPM | MAX_CPM_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + CPM | MAX_CPM_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + CPM | MAX_CPM_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | MAX_CPM_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | MAX_CPM_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + CPM | MAX_CPM_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + CPM | MAX_CPM_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) CPM | MAX_CPM_ADJUST_VALUE + 1 | AUDIO | BidRequest.defaultAudioRequest CPM | MAX_CPM_ADJUST_VALUE + 1 | NATIVE | BidRequest.defaultNativeRequest CPM | MAX_CPM_ADJUST_VALUE + 1 | ANY | BidRequest.defaultNativeRequest STATIC | MIN_ADJUST_VALUE - 1 | BANNER | BidRequest.defaultBidRequest - STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | MIN_ADJUST_VALUE - 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) STATIC | MIN_ADJUST_VALUE - 1 | AUDIO | BidRequest.defaultAudioRequest STATIC | MIN_ADJUST_VALUE - 1 | NATIVE | BidRequest.defaultNativeRequest STATIC | MIN_ADJUST_VALUE - 1 | ANY | BidRequest.defaultNativeRequest STATIC | MAX_STATIC_ADJUST_VALUE + 1 | BANNER | BidRequest.defaultBidRequest - STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_STREAM } - STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | BidRequest.defaultVideoRequest.tap { imp.first.video.placement = IN_ARTICLE } + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlacement(IN_PLACEMENT_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmt(IN_PLCMT_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, IN_PLACEMENT_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, IN_PLACEMENT_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_IN_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(IN_PLCMT_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM, RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmtAndPlacement(null, null) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlacement(RANDOM_VIDEO_PLACEMENT_EXCEPT_IN_STREAM) + STATIC | MAX_STATIC_ADJUST_VALUE + 1 | VIDEO_OUT_STREAM | getDefaultVideoRequestWithPlcmt(RANDOM_VIDEO_PLCMT_EXCEPT_IN_STREAM) STATIC | MAX_STATIC_ADJUST_VALUE + 1 | AUDIO | BidRequest.defaultAudioRequest STATIC | MAX_STATIC_ADJUST_VALUE + 1 | NATIVE | BidRequest.defaultNativeRequest STATIC | MAX_STATIC_ADJUST_VALUE + 1 | ANY | BidRequest.defaultNativeRequest @@ -997,4 +1127,30 @@ class BidAdjustmentSpec extends BaseSpec { return originalPrice } } + + private static BidRequest getDefaultVideoRequestWithPlacement(VideoPlacementSubtypes videoPlacementSubtypes) { + BidRequest.defaultVideoRequest.tap { + imp.first.video.tap { + placement = videoPlacementSubtypes + } + } + } + + private static BidRequest getDefaultVideoRequestWithPlcmt(VideoPlcmtSubtype videoPlcmtSubtype) { + BidRequest.defaultVideoRequest.tap { + imp.first.video.tap { + plcmt = videoPlcmtSubtype + } + } + } + + private static BidRequest getDefaultVideoRequestWithPlcmtAndPlacement(VideoPlcmtSubtype videoPlcmtSubtype, + VideoPlacementSubtypes videoPlacementSubtypes) { + BidRequest.defaultVideoRequest.tap { + imp.first.video.tap { + plcmt = videoPlcmtSubtype + placement = videoPlacementSubtypes + } + } + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidExpResponseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidExpResponseSpec.groovy index cc877f847a0..6ca55f4b7bc 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidExpResponseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidExpResponseSpec.groovy @@ -4,17 +4,41 @@ import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.Imp import org.prebid.server.functional.model.request.auction.PrebidCache import org.prebid.server.functional.model.request.auction.PrebidCacheSettings import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils +import static org.prebid.server.functional.model.response.auction.MediaType.BANNER +import static org.prebid.server.functional.model.response.auction.MediaType.VIDEO +import static org.prebid.server.functional.model.response.auction.MediaType.NATIVE +import static org.prebid.server.functional.model.response.auction.MediaType.AUDIO + class BidExpResponseSpec extends BaseSpec { - private static def hostBannerTtl = PBSUtils.randomNumber - private static def hostVideoTtl = PBSUtils.randomNumber - private static def cacheTtlService = pbsServiceFactory.getService(['cache.banner-ttl-seconds': hostBannerTtl as String, - 'cache.video-ttl-seconds' : hostVideoTtl as String]) + private static final def BANNER_TTL_HOST_CACHE = PBSUtils.randomNumber + private static final def VIDEO_TTL_HOST_CACHE = PBSUtils.randomNumber + private static final def BANNER_TTL_DEFAULT_CACHE = PBSUtils.randomNumber + private static final def VIDEO_TTL_DEFAULT_CACHE = PBSUtils.randomNumber + private static final def AUDIO_TTL_DEFAULT_CACHE = PBSUtils.randomNumber + private static final def NATIVE_TTL_DEFAULT_CACHE = PBSUtils.randomNumber + private static final Map CACHE_TTL_HOST_CONFIG = ["cache.banner-ttl-seconds": BANNER_TTL_HOST_CACHE as String, + "cache.video-ttl-seconds" : VIDEO_TTL_HOST_CACHE as String] + private static final Map DEFAULT_CACHE_TTL_CONFIG = ["cache.default-ttl-seconds.banner": BANNER_TTL_DEFAULT_CACHE as String, + "cache.default-ttl-seconds.video" : VIDEO_TTL_DEFAULT_CACHE as String, + "cache.default-ttl-seconds.native": NATIVE_TTL_DEFAULT_CACHE as String, + "cache.default-ttl-seconds.audio" : AUDIO_TTL_DEFAULT_CACHE as String] + private static final Map EMPTY_CACHE_TTL_CONFIG = ["cache.default-ttl-seconds.banner": "", + "cache.default-ttl-seconds.video" : "", + "cache.default-ttl-seconds.native": "", + "cache.default-ttl-seconds.audio" : ""] + private static final Map EMPTY_CACHE_TTL_HOST_CONFIG = ["cache.banner-ttl-seconds": "", + "cache.video-ttl-seconds" : ""] + private static def pbsOnlyHostCacheTtlService = pbsServiceFactory.getService(CACHE_TTL_HOST_CONFIG + EMPTY_CACHE_TTL_CONFIG) + private static def pbsEmptyTtlService = pbsServiceFactory.getService(EMPTY_CACHE_TTL_CONFIG + EMPTY_CACHE_TTL_HOST_CONFIG) + private static def pbsHostAndDefaultCacheTtlService = pbsServiceFactory.getService(CACHE_TTL_HOST_CONFIG + DEFAULT_CACHE_TTL_CONFIG) + def "PBS auction should resolve bid.exp from response that is set by the bidder’s adapter"() { given: "Default basicResponse with exp" @@ -131,25 +155,6 @@ class BidExpResponseSpec extends BaseSpec { assert response.seatbid.bid.first.exp == [bidRequestExp] } - def "PBS auction shouldn't resolve exp from request.ext.prebid.cache for request when it have invalid type"() { - given: "Set bidder response without exp" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid[0].exp = null - } - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bid response shouldn't contain exp data" - assert !response.seatbid.first.bid.first.exp - - where: - bidRequest | cache - BidRequest.defaultBidRequest | new PrebidCache(vastXml: new PrebidCacheSettings(ttlSeconds: PBSUtils.randomNumber)) - BidRequest.defaultVideoRequest | new PrebidCache(bids: new PrebidCacheSettings(ttlSeconds: PBSUtils.randomNumber)) - } - def "PBS auction should resolve exp from account config for banner request when it have value"() { given: "default bidRequest" def bidRequest = BidRequest.defaultBidRequest @@ -173,28 +178,6 @@ class BidExpResponseSpec extends BaseSpec { assert response.seatbid.bid.first.exp == [accountCacheTtl] } - def "PBS auction shouldn't resolve exp from account videoCacheTtl config when bidRequest type doesn't matching"() { - given: "default bidRequest" - def bidRequest = BidRequest.defaultBidRequest - - and: "Account in the DB" - def auctionConfig = new AccountAuctionConfig(videoCacheTtl: PBSUtils.randomNumber) - def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: auctionConfig)) - accountDao.save(account) - - and: "Set bidder response without exp" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid[0].exp = null - } - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bid response shouldn't contain exp data" - assert !response.seatbid.first.bid.first.exp - } - def "PBS auction should resolve exp from account videoCacheTtl config for video request when it have value"() { given: "default bidRequest" def bidRequest = BidRequest.defaultVideoRequest @@ -218,55 +201,15 @@ class BidExpResponseSpec extends BaseSpec { assert response.seatbid.bid.first.exp == [accountCacheTtl] } - def "PBS auction should resolve exp from account bannerCacheTtl config for video request when it have value"() { - given: "default bidRequest" - def bidRequest = BidRequest.defaultVideoRequest - - and: "Account in the DB" - def accountCacheTtl = PBSUtils.randomNumber - def auctionConfig = new AccountAuctionConfig(bannerCacheTtl: accountCacheTtl) - def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: auctionConfig)) - accountDao.save(account) - - and: "Set bidder response without exp" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid[0].exp = null - } - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bid response should contain exp data" - assert response.seatbid.bid.first.exp == [accountCacheTtl] - } - def "PBS auction should resolve exp from global banner config for banner request"() { given: "Default bidRequest" def bidRequest = BidRequest.defaultBidRequest when: "PBS processes auction request" - def response = cacheTtlService.sendAuctionRequest(bidRequest) - - then: "Bid response should contain exp data" - assert response.seatbid.bid.first.exp == [hostBannerTtl] - } - - def "PBS auction should resolve exp from global config for video request based on highest value"() { - given: "Default bidRequest" - def bidRequest = BidRequest.defaultVideoRequest - - and: "Set bidder response without exp" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid[0].exp = null - } - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - def response = cacheTtlService.sendAuctionRequest(bidRequest) + def response = pbsHostAndDefaultCacheTtlService.sendAuctionRequest(bidRequest) then: "Bid response should contain exp data" - assert response.seatbid.bid.first.exp == [Math.max(hostVideoTtl, hostBannerTtl)] + assert response.seatbid.bid.first.exp == [BANNER_TTL_HOST_CACHE] } def "PBS auction should prioritize value from bid.exp rather than request.imp[].exp"() { @@ -356,9 +299,348 @@ class BidExpResponseSpec extends BaseSpec { bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" - def response = cacheTtlService.sendAuctionRequest(bidRequest) + def response = pbsHostAndDefaultCacheTtlService.sendAuctionRequest(bidRequest) then: "Bid response should contain exp data" assert response.seatbid.bid.first.exp == [accountCacheTtl] } + + def "PBS auction should prioritize bid.exp from the response over all other fields from the request and account config"() { + given: "Default bid request with specific imp media type" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0] = Imp.getDefaultImpression(mediaType).tap { + exp = PBSUtils.randomNumber + } + ext.prebid.cache = new PrebidCache( + vastXml: new PrebidCacheSettings(ttlSeconds: PBSUtils.randomNumber), + bids: new PrebidCacheSettings(ttlSeconds: PBSUtils.randomNumber)) + } + + and: "Default bid response with bid.exp" + def randomExp = PBSUtils.randomNumber + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid[0].exp = randomExp + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Account in the DB" + def auctionConfig = new AccountAuctionConfig( + videoCacheTtl: PBSUtils.randomNumber, + bannerCacheTtl: PBSUtils.randomNumber) + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: auctionConfig)) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsHostAndDefaultCacheTtlService.sendAuctionRequest(bidRequest) + + then: "Bid response should contain exp data" + assert response.seatbid.first.bid.first.exp == randomExp + + where: + mediaType << [BANNER, VIDEO, NATIVE, AUDIO] + } + + def "PBS auction shouldn't resolve bid.exp for #mediaType when the response, request, and account config don't include such data"() { + given: "Default bid request with specific imp media type" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0] = Imp.getDefaultImpression(mediaType) + } + + and: "Default bid response with bid.exp" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid[0].exp = null + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsEmptyTtlService.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't contain exp data" + assert !response.seatbid.first.bid.first.exp + + where: + mediaType << [BANNER, VIDEO, NATIVE, AUDIO] + } + + def "PBS auction should prioritize imp.exp and resolve bid.exp for #mediaType when request and account config include multiple exp sources"() { + given: "Default bid request" + def randomExp = PBSUtils.randomNumber + def bidRequest = BidRequest.getDefaultBidRequest().tap { + imp[0] = Imp.getDefaultImpression(mediaType).tap { + exp = randomExp + } + ext.prebid.cache = new PrebidCache( + vastXml: new PrebidCacheSettings(ttlSeconds: PBSUtils.randomNumber), + bids: new PrebidCacheSettings(ttlSeconds: PBSUtils.randomNumber)) + } + + and: "Default bid response without bid.exp" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid[0].exp = null + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Account in the DB" + def auctionConfig = new AccountAuctionConfig( + videoCacheTtl: PBSUtils.randomNumber, + bannerCacheTtl: PBSUtils.randomNumber) + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: auctionConfig)) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsHostAndDefaultCacheTtlService.sendAuctionRequest(bidRequest) + + then: "Bid response should contain exp data" + assert response.seatbid.first.bid.first.exp == randomExp + + where: + mediaType << [BANNER, VIDEO, NATIVE, AUDIO] + } + + def "PBS auction shouldn't resolve bid.exp from ext.prebid.cache.vastxml.ttlseconds when request has #mediaType as mediaType"() { + given: "Default bid request" + def randomExp = PBSUtils.randomNumber + def bidRequest = BidRequest.getDefaultBidRequest().tap { + enableCache() + imp[0] = Imp.getDefaultImpression(mediaType) + ext.prebid.cache = new PrebidCache(vastXml: new PrebidCacheSettings(ttlSeconds: randomExp)) + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Account in the DB" + def auctionConfig = new AccountAuctionConfig( + videoCacheTtl: PBSUtils.randomNumber) + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: auctionConfig)) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsEmptyTtlService.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't contain exp data" + assert !response?.seatbid?.first?.bid?.first?.exp + + where: + mediaType << [BANNER, NATIVE, AUDIO] + } + + def "PBS auction should resolve bid.exp from ext.prebid.cache.vastxml.ttlseconds when request has video as mediaType"() { + given: "Default bid request" + def bidsTtlSeconds = PBSUtils.randomNumber + def vastXmTtlSeconds = bidsTtlSeconds + 1 + def bidRequest = BidRequest.getDefaultBidRequest().tap { + enableCache() + imp[0] = Imp.getDefaultImpression(VIDEO) + + ext.prebid.cache = new PrebidCache( + vastXml: new PrebidCacheSettings(ttlSeconds: vastXmTtlSeconds), + bids: new PrebidCacheSettings(ttlSeconds: bidsTtlSeconds)) + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Account in the DB" + def auctionConfig = new AccountAuctionConfig( + videoCacheTtl: PBSUtils.randomNumber, + bannerCacheTtl: PBSUtils.randomNumber) + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: auctionConfig)) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsHostAndDefaultCacheTtlService.sendAuctionRequest(bidRequest) + + then: "Bid response should contain exp data" + assert response.seatbid.first.bid.first.exp == vastXmTtlSeconds + } + + def "PBS auction should resolve bid.exp when ext.prebid.cache.bids.ttlseconds is specified and no higher-priority fields are present"() { + given: "Default bid request" + def randomExp = PBSUtils.randomNumber + def bidRequest = BidRequest.getDefaultBidRequest().tap { + enableCache() + imp[0] = Imp.getDefaultImpression(mediaType) + ext.prebid.cache = new PrebidCache(bids: new PrebidCacheSettings(ttlSeconds: randomExp)) + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Account in the DB" + def auctionConfig = new AccountAuctionConfig( + videoCacheTtl: PBSUtils.randomNumber, + bannerCacheTtl: PBSUtils.randomNumber) + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: auctionConfig)) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsHostAndDefaultCacheTtlService.sendAuctionRequest(bidRequest) + + then: "Bid response should contain exp data" + assert response.seatbid.first.bid.first.exp == randomExp + + where: + mediaType << [BANNER, VIDEO, NATIVE, AUDIO] + } + + def "PBS auction shouldn't resolve bid.exp when the account config and request imp type do not match"() { + given: "Default bid request" + def bidRequest = BidRequest.getDefaultBidRequest().tap { + imp[0] = Imp.getDefaultImpression(mediaType) + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Account in the DB" + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: auctionConfig)) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsEmptyTtlService.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't contain exp data" + assert !response.seatbid.first.bid.first.exp + + where: + mediaType | auctionConfig + VIDEO | new AccountAuctionConfig(bannerCacheTtl: PBSUtils.randomNumber) + VIDEO | new AccountAuctionConfig(bannerCacheTtl: PBSUtils.randomNumber, videoCacheTtl: null) + BANNER | new AccountAuctionConfig(videoCacheTtl: PBSUtils.randomNumber) + BANNER | new AccountAuctionConfig(bannerCacheTtl: null, videoCacheTtl: PBSUtils.randomNumber) + NATIVE | new AccountAuctionConfig(bannerCacheTtl: PBSUtils.randomNumber, videoCacheTtl: PBSUtils.randomNumber) + NATIVE | new AccountAuctionConfig(bannerCacheTtl: PBSUtils.randomNumber) + NATIVE | new AccountAuctionConfig(videoCacheTtl: PBSUtils.randomNumber) + AUDIO | new AccountAuctionConfig(bannerCacheTtl: PBSUtils.randomNumber, videoCacheTtl: PBSUtils.randomNumber) + AUDIO | new AccountAuctionConfig(bannerCacheTtl: PBSUtils.randomNumber) + AUDIO | new AccountAuctionConfig(videoCacheTtl: PBSUtils.randomNumber) + } + + def "PBS auction shouldn't resolve bid.exp when account config and request imp type match but account config for cache-ttl is not specified"() { + given: "Default bid request" + def bidRequest = BidRequest.getDefaultBidRequest().tap { + enableCache() + imp[0] = Imp.getDefaultImpression(mediaType) + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Account in the DB" + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: new AccountAuctionConfig(bannerCacheTtl: null, videoCacheTtl: null))) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsEmptyTtlService.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't contain exp data" + assert !response.seatbid.first.bid.first.exp + + where: + mediaType << [VIDEO, BANNER, NATIVE, AUDIO] + } + + def "PBS auction should resolve bid.exp when account.auction.{banner/video}-cache-ttl and banner bid specified"() { + given: "Default bid request" + def bidRequest = BidRequest.getDefaultBidRequest().tap { + enableCache() + imp[0] = Imp.getDefaultImpression(mediaType) + } + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Account in the DB" + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(auction: accountAuctionConfig)) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsEmptyTtlService.sendAuctionRequest(bidRequest) + + then: "Bid response should contain exp data" + assert response.seatbid.first.bid.first.exp == accountCacheTtl + + where: + mediaType | accountCacheTtl | accountAuctionConfig + BANNER | PBSUtils.randomNumber | new AccountAuctionConfig(bannerCacheTtl: accountCacheTtl) + VIDEO | PBSUtils.randomNumber | new AccountAuctionConfig(videoCacheTtl: accountCacheTtl) + } + + def "PBS auction should resolve bid.exp when cache.{banner/video}-ttl-seconds config specified"() { + given: "Default bid request" + def bidRequest = BidRequest.getDefaultBidRequest().tap { + imp[0] = Imp.getDefaultImpression(mediaType) + enableCache() + } + + and: "Set bidder response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = pbsOnlyHostCacheTtlService.sendAuctionRequest(bidRequest) + + then: "Bid response should contain exp data" + assert response.seatbid.first.bid.first.exp == expValue + + where: + mediaType | expValue + BANNER | BANNER_TTL_HOST_CACHE + VIDEO | VIDEO_TTL_HOST_CACHE + } + + def "PBS auction shouldn't resolve bid.exp when cache ttl-seconds is specified for #mediaType mediaType request"() { + given: "Default bid request" + def bidRequest = BidRequest.getDefaultBidRequest().tap { + imp[0] = Imp.getDefaultImpression(mediaType) + ext.prebid.cache = new PrebidCache(bids: new PrebidCacheSettings(ttlSeconds: PBSUtils.randomNumber)) + } + + when: "PBS processes auction request" + def response = pbsOnlyHostCacheTtlService.sendAuctionRequest(bidRequest) + + then: "Bid response shouldn't contain exp data" + assert !response.seatbid.first.bid.first.exp + + where: + mediaType << [NATIVE, AUDIO] + } + + def "PBS auction should resolve bid.exp when cache.default-ttl-seconds.{banner,video,audio,native} is specified and no higher-priority fields are present"() { + given: "Prebid server with empty host config and default cache ttl config" + def config = EMPTY_CACHE_TTL_HOST_CONFIG + DEFAULT_CACHE_TTL_CONFIG + def prebidServerService = pbsServiceFactory.getService(config) + + and: "Default bid request" + def bidRequest = BidRequest.getDefaultBidRequest().tap { + imp[0] = Imp.getDefaultImpression(mediaType) + } + + and: "Set bidder response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest) + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = prebidServerService.sendAuctionRequest(bidRequest) + + then: "Bid response should contain exp data" + assert response.seatbid.first.bid.first.exp == bidExpValue + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(config) + + where: + mediaType | bidExpValue + BANNER | BANNER_TTL_DEFAULT_CACHE + VIDEO | VIDEO_TTL_DEFAULT_CACHE + AUDIO | AUDIO_TTL_DEFAULT_CACHE + NATIVE | NATIVE_TTL_DEFAULT_CACHE + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/StoredResponseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/StoredResponseSpec.groovy index fccb14c8bab..767c4b8e544 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/StoredResponseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/StoredResponseSpec.groovy @@ -10,6 +10,7 @@ import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.model.response.auction.ErrorType import org.prebid.server.functional.model.response.auction.SeatBid import org.prebid.server.functional.service.PrebidServerException +import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.util.PBSUtils import spock.lang.PendingFeature @@ -17,6 +18,8 @@ import static org.prebid.server.functional.model.bidder.BidderName.GENERIC class StoredResponseSpec extends BaseSpec { + private final PrebidServerService pbsService = pbsServiceFactory.getService(["cache.default-ttl-seconds.banner": ""]) + @PendingFeature def "PBS should not fail auction with storedAuctionResponse when request bidder params doesn't satisfy json-schema"() { given: "BidRequest with bad bidder datatype and storedAuctionResponse" @@ -33,7 +36,7 @@ class StoredResponseSpec extends BaseSpec { storedResponseDao.save(storedResponse) when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should not contain errors and warnings" assert !response.ext?.errors @@ -56,7 +59,7 @@ class StoredResponseSpec extends BaseSpec { storedResponseDao.save(storedResponse) when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain information from stored auction response" assert response.id == bidRequest.id @@ -82,7 +85,7 @@ class StoredResponseSpec extends BaseSpec { storedResponseDao.save(storedResponse) when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain information from stored bid response" assert response.id == bidRequest.id @@ -111,7 +114,7 @@ class StoredResponseSpec extends BaseSpec { storedResponseDao.save(storedResponse) when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain information from stored bid response and change bid.impId on imp.id" assert response.id == bidRequest.id @@ -140,7 +143,7 @@ class StoredResponseSpec extends BaseSpec { storedResponseDao.save(storedResponse) when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain warning information" assert response.ext?.warnings[ErrorType.PREBID]*.code == [999] @@ -161,7 +164,7 @@ class StoredResponseSpec extends BaseSpec { } when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain same stored auction response as requested" assert response.seatbid == [storedAuctionResponse] @@ -190,7 +193,7 @@ class StoredResponseSpec extends BaseSpec { storedResponseDao.save(storedResponse) when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain same stored auction response as requested" assert response.seatbid == [storedAuctionResponse] @@ -214,7 +217,7 @@ class StoredResponseSpec extends BaseSpec { storedResponseDao.save(storedResponse) when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain same stored auction response as requested" assert response.seatbid @@ -244,7 +247,7 @@ class StoredResponseSpec extends BaseSpec { storedResponseDao.save(storedResponse) when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain warning information" assert response.ext?.warnings[ErrorType.PREBID]*.message.contains('SeatBid can\'t be null in stored response') @@ -257,10 +260,10 @@ class StoredResponseSpec extends BaseSpec { given: "Default basic BidRequest with stored response" def bidRequest = BidRequest.defaultBidRequest def storedAuctionResponse = SeatBid.getStoredResponse(bidRequest) - bidRequest.imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse(seatBidObject: storedAuctionResponse) + bidRequest.imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse(seatBidObject: storedAuctionResponse) when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain same stored auction response as requested" assert convertToComparableSeatBid(response.seatbid) == [storedAuctionResponse] @@ -272,10 +275,10 @@ class StoredResponseSpec extends BaseSpec { def "PBS should throw error when imp.ext.prebid.storedBidResponse.seatbidobj is with empty seatbid"() { given: "Default basic BidRequest with empty stored response" def bidRequest = BidRequest.defaultBidRequest - bidRequest.imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse(seatBidObject: new SeatBid()) + bidRequest.imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse(seatBidObject: new SeatBid()) when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) + pbsService.sendAuctionRequest(bidRequest) then: "PBS throws an exception" def exception = thrown(PrebidServerException) @@ -289,10 +292,10 @@ class StoredResponseSpec extends BaseSpec { def "PBS should throw error when imp.ext.prebid.storedBidResponse.seatbidobj is with empty bids"() { given: "Default basic BidRequest with empty bids for stored response" def bidRequest = BidRequest.defaultBidRequest - bidRequest.imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse(seatBidObject: new SeatBid(bid: [], seat: GENERIC)) + bidRequest.imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse(seatBidObject: new SeatBid(bid: [], seat: GENERIC)) when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) + pbsService.sendAuctionRequest(bidRequest) then: "PBS throws an exception" def exception = thrown(PrebidServerException) @@ -313,7 +316,7 @@ class StoredResponseSpec extends BaseSpec { } when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain same stored auction response as requested" assert convertToComparableSeatBid(response.seatbid) == [storedAuctionResponse] @@ -329,7 +332,7 @@ class StoredResponseSpec extends BaseSpec { } when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain same stored auction response bids as requested" assert convertToComparableSeatBid(response.seatbid).bid.flatten().sort() == @@ -343,7 +346,7 @@ class StoredResponseSpec extends BaseSpec { given: "Default basic BidRequest with stored response" def bidRequest = BidRequest.defaultBidRequest def storedAuctionResponse = SeatBid.getStoredResponse(bidRequest) - bidRequest.tap{ + bidRequest.tap { imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse().tap { seatBidObject = SeatBid.getStoredResponse(bidRequest) } @@ -351,7 +354,7 @@ class StoredResponseSpec extends BaseSpec { } when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + def response = pbsService.sendAuctionRequest(bidRequest) then: "Response should contain same stored auction response as requested" assert response.seatbid == [storedAuctionResponse] diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/AbTestingModuleSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/AbTestingModuleSpec.groovy new file mode 100644 index 00000000000..9ca353e0088 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/module/AbTestingModuleSpec.groovy @@ -0,0 +1,1157 @@ +package org.prebid.server.functional.tests.module + +import org.prebid.server.functional.model.ModuleName +import org.prebid.server.functional.model.config.AbTest +import org.prebid.server.functional.model.config.AccountConfig +import org.prebid.server.functional.model.config.AccountHooksConfiguration +import org.prebid.server.functional.model.config.ExecutionPlan +import org.prebid.server.functional.model.config.Stage +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.FetchStatus +import org.prebid.server.functional.model.request.auction.TraceLevel +import org.prebid.server.functional.model.response.auction.AnalyticResult +import org.prebid.server.functional.model.response.auction.InvocationResult +import org.prebid.server.functional.service.PrebidServerService +import org.prebid.server.functional.util.PBSUtils + +import static org.prebid.server.functional.model.ModuleName.PB_RESPONSE_CORRECTION +import static org.prebid.server.functional.model.config.Endpoint.OPENRTB2_AUCTION +import static org.prebid.server.functional.model.config.ModuleHookImplementation.ORTB2_BLOCKING_BIDDER_REQUEST +import static org.prebid.server.functional.model.config.ModuleHookImplementation.ORTB2_BLOCKING_RAW_BIDDER_RESPONSE +import static org.prebid.server.functional.model.config.ModuleHookImplementation.RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES +import static org.prebid.server.functional.model.config.Stage.ALL_PROCESSED_BID_RESPONSES +import static org.prebid.server.functional.model.config.Stage.BIDDER_REQUEST +import static org.prebid.server.functional.model.config.Stage.RAW_BIDDER_RESPONSE +import static org.prebid.server.functional.model.request.auction.BidRequest.getDefaultBidRequest +import static org.prebid.server.functional.model.response.auction.InvocationStatus.INVOCATION_FAILURE +import static org.prebid.server.functional.model.response.auction.InvocationStatus.SUCCESS +import static org.prebid.server.functional.model.response.auction.ModuleActivityName.AB_TESTING +import static org.prebid.server.functional.model.response.auction.ModuleActivityName.ORTB2_BLOCKING +import static org.prebid.server.functional.model.response.auction.ResponseAction.NO_ACTION +import static org.prebid.server.functional.model.response.auction.ResponseAction.NO_INVOCATION + +class AbTestingModuleSpec extends ModuleBaseSpec { + + private final static String NO_INVOCATION_METRIC = "modules.module.%s.stage.%s.hook.%s.success.no-invocation" + private final static String CALL_METRIC = "modules.module.%s.stage.%s.hook.%s.call" + private final static String EXECUTION_ERROR_METRIC = "modules.module.%s.stage.%s.hook.%s.execution-error" + private final static Integer MIN_PERCENT_AB = 0 + private final static Integer MAX_PERCENT_AB = 100 + private final static String INVALID_HOOK_MESSAGE = "Hook implementation does not exist or disabled" + + private final static Map> ORTB_STAGES = [(BIDDER_REQUEST) : [ModuleName.ORTB2_BLOCKING], + (RAW_BIDDER_RESPONSE): [ModuleName.ORTB2_BLOCKING]] + private final static Map> RESPONSE_STAGES = [(ALL_PROCESSED_BID_RESPONSES): [PB_RESPONSE_CORRECTION]] + private final static Map> MODULES_STAGES = ORTB_STAGES + RESPONSE_STAGES + + private final static Map MULTI_MODULE_CONFIG = getResponseCorrectionConfig() + getOrtb2BlockingSettings() + + ['hooks.host-execution-plan': null] + + private final static PrebidServerService ortbModulePbsService = pbsServiceFactory.getService(getOrtb2BlockingSettings()) + private final static PrebidServerService pbsServiceWithMultipleModules = pbsServiceFactory.getService(MULTI_MODULE_CONFIG) + + def "PBS shouldn't apply a/b test config when config of ab test is disabled"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(ortbModulePbsService) + + and: "Save account with ab test config" + def abTest = AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + enabled = false + } + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + it.abTests = [abTest] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = ortbModulePbsService.sendAuctionRequest(bidRequest) + + then: "PBS response should include trace information about called modules" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION] + } + + and: "Shouldn't include any analytics tags" + assert (invocationResults.analyticsTags.activities.flatten() as List).findAll { it.name != AB_TESTING.value } + + and: "Metric for specified module should be as default call" + def metrics = ortbModulePbsService.sendCollectedMetricsRequest() + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + } + + def "PBS shouldn't apply valid a/b test config when module is disabled"() { + given: "PBS service with disabled module config" + def pbsConfig = getOrtb2BlockingSettings(false) + def prebidServerService = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(prebidServerService) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code)] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = prebidServerService.sendAuctionRequest(bidRequest) + + then: "PBS response should include trace information about called modules" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status == [INVOCATION_FAILURE, INVOCATION_FAILURE] + it.action == [null, null] + it.analyticsTags == [null, null] + it.message == [INVALID_HOOK_MESSAGE, INVALID_HOOK_MESSAGE] + } + + and: "Metric for specified module should be with error call" + def metrics = prebidServerService.sendCollectedMetricsRequest() + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[EXECUTION_ERROR_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[EXECUTION_ERROR_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS shouldn't apply a/b test config when module name is not matched"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(ortbModulePbsService) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + abTests = [AbTest.getDefault(moduleName)] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = ortbModulePbsService.sendAuctionRequest(bidRequest) + + then: "PBS response should include trace information about called modules" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION] + } + + and: "Shouldn't include any analytics tags" + assert (invocationResults.analyticsTags.activities.flatten() as List).findAll { it.name != AB_TESTING.value } + + and: "Metric for specified module should be as default call" + def metrics = ortbModulePbsService.sendCollectedMetricsRequest() + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + + where: + moduleName << [ModuleName.ORTB2_BLOCKING.code.toUpperCase(), PBSUtils.randomString] + } + + def "PBS should apply a/b test config for each module when multiple config are presents and set to allow modules"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + and: "Save account with ab test config" + def ortb2AbTestConfig = AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + it.percentActive = MAX_PERCENT_AB + } + def richMediaAbTestConfig = AbTest.getDefault(PB_RESPONSE_CORRECTION.code).tap { + it.percentActive = MAX_PERCENT_AB + } + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, MODULES_STAGES).tap { + abTests = [ortb2AbTestConfig, richMediaAbTestConfig] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for specified module" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + def ortbBlockingInvocationResults = filterInvocationResultsByModule(invocationResults, ModuleName.ORTB2_BLOCKING) + verifyAll(ortbBlockingInvocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION] + it.analyticsTags.activities.name.flatten().sort() == [ORTB2_BLOCKING, AB_TESTING, AB_TESTING].value.sort() + it.analyticsTags.activities.status.flatten().sort() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS, FetchStatus.SUCCESS].sort() + it.analyticsTags.activities.results.status.flatten().sort() == [FetchStatus.SUCCESS_ALLOW, FetchStatus.RUN, FetchStatus.RUN].value.sort() + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "PBS should not apply ab test config for other module" + def responseCorrectionInvocationResults = filterInvocationResultsByModule(invocationResults, PB_RESPONSE_CORRECTION) + verifyAll(responseCorrectionInvocationResults) { + it.status == [SUCCESS] + it.action == [NO_ACTION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.RUN].value + it.analyticsTags.activities.results.values.module.flatten() == [PB_RESPONSE_CORRECTION] + } + + and: "Metric for allowed to run ortb2blocking module should be updated based on ab test config" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + + and: "Metric for allowed to run response-correction module should be updated based on ab test config" + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + } + + def "PBS should apply a/b test config for each module when multiple config are presents and set to skip modules"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + and: "Save account with ab test config" + def ortb2AbTestConfig = AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + it.percentActive = MIN_PERCENT_AB + } + def richMediaAbTestConfig = AbTest.getDefault(PB_RESPONSE_CORRECTION.code).tap { + it.percentActive = MIN_PERCENT_AB + } + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, MODULES_STAGES).tap { + abTests = [ortb2AbTestConfig, richMediaAbTestConfig] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for ortb2blocking module" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + def ortbBlockingInvocationResults = filterInvocationResultsByModule(invocationResults, ModuleName.ORTB2_BLOCKING) + verifyAll(ortbBlockingInvocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING, AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED, FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "PBS should apply ab test config for response-correction module" + def responseCorrectionInvocationResults = filterInvocationResultsByModule(invocationResults, PB_RESPONSE_CORRECTION) + verifyAll(responseCorrectionInvocationResults) { + it.status == [SUCCESS] + it.action == [NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [PB_RESPONSE_CORRECTION] + } + + and: "Metric for skipped ortb2blocking module should be updated based on ab test config" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert !metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + assert metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + + and: "Metric for skipped response-correction module should be updated based on ab test config" + assert !metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + } + + def "PBS should apply a/b test config for each module when multiple config are presents with different percentage"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + and: "Save account with ab test config" + def ortb2AbTestConfig = AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + it.percentActive = MIN_PERCENT_AB + } + def richMediaAbTestConfig = AbTest.getDefault(PB_RESPONSE_CORRECTION.code).tap { + it.percentActive = MAX_PERCENT_AB + } + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, MODULES_STAGES).tap { + abTests = [ortb2AbTestConfig, richMediaAbTestConfig] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for ortb2blocking module" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + def ortbBlockingInvocationResults = filterInvocationResultsByModule(invocationResults, ModuleName.ORTB2_BLOCKING) + verifyAll(ortbBlockingInvocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING, AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED, FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "PBS should not apply ab test config for response-correction module" + def responseCorrectionInvocationResults = filterInvocationResultsByModule(invocationResults, PB_RESPONSE_CORRECTION) + verifyAll(responseCorrectionInvocationResults) { + it.status == [SUCCESS] + it.action == [NO_ACTION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.RUN].value + it.analyticsTags.activities.results.values.module.flatten() == [PB_RESPONSE_CORRECTION] + } + + and: "Metric for skipped ortb2blocking module should be updated based on ab test config" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + and: "Metric for allowed to run response-correction module should be updated based on ab test config" + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + } + + def "PBS should ignore accounts property for a/b test config when ab test config specialize for specific account"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(ortbModulePbsService) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code, [PBSUtils.randomNumber]).tap { + percentActive = MIN_PERCENT_AB + }] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = ortbModulePbsService.sendAuctionRequest(bidRequest) + + then: "PBS response should include trace information about called modules" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING, AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED, FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "Metric for specified module should be updated based on ab test config" + def metrics = ortbModulePbsService.sendCollectedMetricsRequest() + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + } + + def "PBS should apply a/b test config and run module when config is on max percentage or default value"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(ortbModulePbsService) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + it.percentActive = percentActive + it.percentActiveSnakeCase = percentActiveSnakeCase + }] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = ortbModulePbsService.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for ortb module and run module" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION] + it.analyticsTags.activities.name.flatten().sort() == [ORTB2_BLOCKING, AB_TESTING, AB_TESTING].value.sort() + it.analyticsTags.activities.status.flatten().sort() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS, FetchStatus.SUCCESS].sort() + it.analyticsTags.activities.results.status.flatten().sort() == [FetchStatus.SUCCESS_ALLOW, FetchStatus.RUN, FetchStatus.RUN].value.sort() + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "Metric for specified module should be as default call" + def metrics = ortbModulePbsService.sendCollectedMetricsRequest() + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + + where: + percentActive | percentActiveSnakeCase + MAX_PERCENT_AB | null + null | MAX_PERCENT_AB + null | null + } + + def "PBS should apply a/b test config and skip module when config is on min percentage"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(ortbModulePbsService) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + it.percentActive = percentActive + it.percentActiveSnakeCase = percentActiveSnakeCase + }] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = ortbModulePbsService.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for ortb module and skip this module" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING, AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED, FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "Metric for specified module should be updated based on ab test config" + def metrics = ortbModulePbsService.sendCollectedMetricsRequest() + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + where: + percentActive | percentActiveSnakeCase + MIN_PERCENT_AB | null + null | MIN_PERCENT_AB + } + + def "PBS shouldn't apply a/b test config without warnings and errors when percent config is out of lover range"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(ortbModulePbsService) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + it.percentActive = percentActive + it.percentActiveSnakeCase = percentActiveSnakeCase + }] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = ortbModulePbsService.sendAuctionRequest(bidRequest) + + then: "No error or warning should be emitted" + assert !response.ext.errors + assert !response.ext.warnings + + and: "PBS response should include trace information about called modules" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING, AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED, FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "Metric for specified module should be updated based on ab test config" + def metrics = ortbModulePbsService.sendCollectedMetricsRequest() + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + where: + percentActive | percentActiveSnakeCase + PBSUtils.randomNegativeNumber | null + null | PBSUtils.randomNegativeNumber + } + + def "PBS should apply a/b test config and run module without warnings and errors when percent config is out of appear range"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(ortbModulePbsService) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + it.percentActive = percentActive + it.percentActiveSnakeCase = percentActiveSnakeCase + }] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = ortbModulePbsService.sendAuctionRequest(bidRequest) + + then: "No error or warning should be emitted" + assert !response.ext.errors + assert !response.ext.warnings + + and: "PBS should apply ab test config for ortb module and run it" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION] + + it.analyticsTags.activities.name.flatten().sort() == [ORTB2_BLOCKING, AB_TESTING, AB_TESTING].value.sort() + it.analyticsTags.activities.status.flatten().sort() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS, FetchStatus.SUCCESS].sort() + it.analyticsTags.activities.results.status.flatten().sort() == [FetchStatus.SUCCESS_ALLOW, FetchStatus.RUN, FetchStatus.RUN].value.sort() + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "Metric for specified module should be as default call" + def metrics = ortbModulePbsService.sendCollectedMetricsRequest() + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + + where: + percentActive | percentActiveSnakeCase + PBSUtils.getRandomNumber(MAX_PERCENT_AB) | null + null | PBSUtils.getRandomNumber(MAX_PERCENT_AB) + } + + def "PBS should include analytics tags when a/b test config when logAnalyticsTag is enabled or empty"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(ortbModulePbsService) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + percentActive = MIN_PERCENT_AB + it.logAnalyticsTag = logAnalyticsTag + it.logAnalyticsTagSnakeCase = logAnalyticsTagSnakeCase + }] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = ortbModulePbsService.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for specified module without analytics tags" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING, AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED, FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "Metric for specified module should be updated based on ab test config" + def metrics = ortbModulePbsService.sendCollectedMetricsRequest() + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + where: + logAnalyticsTag | logAnalyticsTagSnakeCase + true | null + null | true + null | null + } + + def "PBS shouldn't include analytics tags when a/b test config when logAnalyticsTag is disabled and is applied by percentage"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(ortbModulePbsService) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + percentActive = MIN_PERCENT_AB + it.logAnalyticsTag = logAnalyticsTag + it.logAnalyticsTagSnakeCase = logAnalyticsTagSnakeCase + }] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = ortbModulePbsService.sendAuctionRequest(bidRequest) + + then: "PBS response should include trace information about called modules" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION] + } + + and: "Shouldn't include any analytics tags" + assert !invocationResults?.analyticsTags?.any() + + and: "Metric for specified module should be updated based on ab test config" + def metrics = ortbModulePbsService.sendCollectedMetricsRequest() + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + where: + logAnalyticsTag | logAnalyticsTagSnakeCase + false | null + null | false + } + + def "PBS shouldn't include analytics tags when a/b test config when logAnalyticsTag is disabled and is non-applied by percentage"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(ortbModulePbsService) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + percentActive = MAX_PERCENT_AB + it.logAnalyticsTag = logAnalyticsTag + it.logAnalyticsTagSnakeCase = logAnalyticsTagSnakeCase + }] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = ortbModulePbsService.sendAuctionRequest(bidRequest) + + then: "PBS response should include trace information about called modules" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION] + } + + and: "Shouldn't include any analytics tags" + assert (invocationResults.analyticsTags.activities.flatten() as List).findAll { it.name != AB_TESTING.value } + + and: "Metric for specified module should be as default call" + def metrics = ortbModulePbsService.sendCollectedMetricsRequest() + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + + where: + logAnalyticsTag | logAnalyticsTagSnakeCase + false | null + null | false + } + + def "PBS shouldn't apply analytics tags for all module stages when module contain multiple stages"() { + given: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(ortbModulePbsService) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, ORTB_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + percentActive = PBSUtils.getRandomNumber(MIN_PERCENT_AB, MAX_PERCENT_AB) + }] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = ortbModulePbsService.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for all stages of specified module" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + verifyAll(invocationResults) { + it.status.every { status -> status == it.status.first() } + it.action.every { action -> action == it.action.first() } + } + + and: "All resonances have same analytics" + def abTestingInvocationResults = (invocationResults.analyticsTags.activities.flatten() as List).findAll { it.name == AB_TESTING.value } + verifyAll(abTestingInvocationResults) { + it.status.flatten().every { status -> status == it.status.flatten().first() } + it.results.status.flatten().every { status -> status == it.results.status.flatten().first() } + it.results.values.module.flatten().every { module -> module == it.results.values.module.flatten().first() } + } + } + + def "PBS should apply a/b test config from host config when accounts is not specified when account config and default account doesn't include a/b test config"() { + given: "PBS service with specific ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, MODULES_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code, accouns).tap { + percentActive = MIN_PERCENT_AB + }] + } + def pbsConfig = MULTI_MODULE_CONFIG + ['hooks.host-execution-plan': encode(executionPlan)] + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for specified module" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + def ortbBlockingInvocationResults = filterInvocationResultsByModule(invocationResults, ModuleName.ORTB2_BLOCKING) + verifyAll(ortbBlockingInvocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING, AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED, FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "PBS should not apply ab test config for other module" + def responseCorrectionInvocationResults = filterInvocationResultsByModule(invocationResults, PB_RESPONSE_CORRECTION) + verifyAll(responseCorrectionInvocationResults) { + it.status == [SUCCESS] + it.action == [NO_ACTION] + + it.analyticsTags.every { it == null } + } + + and: "Metric for specified module should be updated based on ab test config" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + and: "Metric for non specified module should be as default call" + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + + where: + accouns << [null, []] + } + + def "PBS should apply a/b test config from host config for specific accounts and only specified module when account config and default account doesn't include a/b test config"() { + given: "PBS service with specific ab test config" + def accountId = PBSUtils.randomNumber + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, MODULES_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code, [PBSUtils.randomNumber, accountId]).tap { + percentActive = MIN_PERCENT_AB + }] + } + def pbsConfig = MULTI_MODULE_CONFIG + ['hooks.host-execution-plan': encode(executionPlan)] + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = defaultBidRequest.tap { + ext.prebid.trace = TraceLevel.VERBOSE + setAccountId(accountId as String) + } + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for specified module" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + def ortbBlockingInvocationResults = filterInvocationResultsByModule(invocationResults, ModuleName.ORTB2_BLOCKING) + verifyAll(ortbBlockingInvocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING, AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED, FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "PBS should not apply ab test config for other module" + def responseCorrectionInvocationResults = filterInvocationResultsByModule(invocationResults, PB_RESPONSE_CORRECTION) + verifyAll(responseCorrectionInvocationResults) { + it.status == [SUCCESS] + it.action == [NO_ACTION] + + it.analyticsTags.every { it == null } + } + + and: "Metric for specified module should be updated based on ab test config" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + and: "Metric for non specified module should be as default call" + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS should apply a/b test config from host config for specific account and general config when account config and default account doesn't include a/b test config"() { + given: "PBS service with specific ab test config" + def accountId = PBSUtils.randomNumber + def ortb2AbTestConfig = AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code, []).tap { + it.percentActive = MIN_PERCENT_AB + } + def richMediaAbTestConfig = AbTest.getDefault(PB_RESPONSE_CORRECTION.code, [accountId]).tap { + it.percentActive = MIN_PERCENT_AB + } + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, MODULES_STAGES).tap { + abTests = [ortb2AbTestConfig, richMediaAbTestConfig] + } + def pbsConfig = MULTI_MODULE_CONFIG + ['hooks.host-execution-plan': encode(executionPlan)] + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = defaultBidRequest.tap { + ext.prebid.trace = TraceLevel.VERBOSE + setAccountId(accountId as String) + } + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for ortb2blocking module" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + def ortbBlockingInvocationResults = filterInvocationResultsByModule(invocationResults, ModuleName.ORTB2_BLOCKING) + verifyAll(ortbBlockingInvocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING, AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED, FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "PBS should apply ab test config for response-correction module" + def responseCorrectionInvocationResults = filterInvocationResultsByModule(invocationResults, PB_RESPONSE_CORRECTION) + verifyAll(responseCorrectionInvocationResults) { + it.status == [SUCCESS] + it.action == [NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [PB_RESPONSE_CORRECTION] + } + + and: "Metric for skipped ortb2blocking module should be updated based on ab test config" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert !metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + assert metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + + and: "Metric for skipped response-correction module should be updated based on ab test config" + assert !metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS shouldn't apply a/b test config from host config for non specified accounts when account config and default account doesn't include a/b test config"() { + given: "PBS service with specific ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, MODULES_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code, [PBSUtils.randomNumber]).tap { + percentActive = MIN_PERCENT_AB + }] + } + def pbsConfig = MULTI_MODULE_CONFIG + ['hooks.host-execution-plan': encode(executionPlan)] + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for specified module" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + def ortbBlockingInvocationResults = filterInvocationResultsByModule(invocationResults, ModuleName.ORTB2_BLOCKING) + verifyAll(ortbBlockingInvocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION] + + it.analyticsTags.activities.name.flatten() == [ORTB2_BLOCKING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SUCCESS_ALLOW].value + it.analyticsTags.activities.results.values.module.flatten().every { it == null } + } + + and: "PBS should not apply ab test config for other module" + def responseCorrectionInvocationResults = filterInvocationResultsByModule(invocationResults, PB_RESPONSE_CORRECTION) + verifyAll(responseCorrectionInvocationResults) { + it.status == [SUCCESS] + it.action == [NO_ACTION] + + it.analyticsTags.every { it == null } + } + + and: "Metric for specified module should be as default call" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + + and: "Metric for non specified module should be as default call" + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS should prioritise a/b test config from default account and only specified module when host and default account contains a/b test configs"() { + given: "PBS service with specific ab test config" + def accountExecutionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, MODULES_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + percentActive = MIN_PERCENT_AB + }] + } + def defaultAccountConfigSettings = AccountConfig.defaultAccountConfig.tap { + hooks = new AccountHooksConfiguration(executionPlan: accountExecutionPlan) + } + + def hostExecutionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, MODULES_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code)] + } + def pbsConfig = MULTI_MODULE_CONFIG + ['hooks.host-execution-plan': encode(hostExecutionPlan)] + ["settings.default-account-config": encode(defaultAccountConfigSettings)] + + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS should apply ab test config for specified module and call it based on all execution plans" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + def ortbBlockingInvocationResults = filterInvocationResultsByModule(invocationResults, ModuleName.ORTB2_BLOCKING) + verifyAll(ortbBlockingInvocationResults) { + it.status == [SUCCESS, SUCCESS, SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION, NO_INVOCATION, NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING, AB_TESTING, AB_TESTING, AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS, FetchStatus.SUCCESS, FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED, FetchStatus.SKIPPED, FetchStatus.SKIPPED, FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "PBS should not apply ab test config for other modules and call them based on all execution plans" + def responseCorrectionInvocationResults = filterInvocationResultsByModule(invocationResults, PB_RESPONSE_CORRECTION) + verifyAll(responseCorrectionInvocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION] + + it.analyticsTags.every { it == null } + } + + and: "Metric for specified module should be updated based on ab test config" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 2 + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 2 + + and: "Metric for non specified module should be as default call" + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 2 + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 2 + + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS should prioritise a/b test config from account over default account and only specified module when specific account and default account contains a/b test configs"() { + given: "PBS service with specific ab test config" + def accountExecutionPlan = new ExecutionPlan(abTests: [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code)]) + def defaultAccountConfigSettings = AccountConfig.defaultAccountConfig.tap { + hooks = new AccountHooksConfiguration(executionPlan: accountExecutionPlan) + } + + def pbsConfig = MULTI_MODULE_CONFIG + ["settings.default-account-config": encode(defaultAccountConfigSettings)] + + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = getBidRequestWithTrace() + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + and: "Save account with ab test config" + def executionPlan = ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, MODULES_STAGES).tap { + abTests = [AbTest.getDefault(ModuleName.ORTB2_BLOCKING.code).tap { + percentActive = MIN_PERCENT_AB + }] + } + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(executionPlan: executionPlan)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS response should include trace information about called modules" + def invocationResults = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List + + and: "PBS should apply ab test config for specified module" + def ortbBlockingInvocationResults = filterInvocationResultsByModule(invocationResults, ModuleName.ORTB2_BLOCKING) + verifyAll(ortbBlockingInvocationResults) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_INVOCATION, NO_INVOCATION] + it.analyticsTags.activities.name.flatten() == [AB_TESTING, AB_TESTING].value + it.analyticsTags.activities.status.flatten() == [FetchStatus.SUCCESS, FetchStatus.SUCCESS] + it.analyticsTags.activities.results.status.flatten() == [FetchStatus.SKIPPED, FetchStatus.SKIPPED].value + it.analyticsTags.activities.results.values.module.flatten() == [ModuleName.ORTB2_BLOCKING, ModuleName.ORTB2_BLOCKING] + } + + and: "PBS should not apply ab test config for other module" + def responseCorrectionInvocationResults = filterInvocationResultsByModule(invocationResults, PB_RESPONSE_CORRECTION) + verifyAll(responseCorrectionInvocationResults) { + it.status == [SUCCESS] + it.action == [NO_ACTION] + + it.analyticsTags.every { it == null } + } + + and: "Metric for specified module should be updated based on ab test config" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NO_INVOCATION_METRIC.formatted(ModuleName.ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + and: "Metric for non specified module should be as default call" + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + private static List filterInvocationResultsByModule(List invocationResults, ModuleName moduleName) { + invocationResults.findAll { it.hookId.moduleCode == moduleName.code } + } + + private static BidRequest getBidRequestWithTrace() { + defaultBidRequest.tap { + ext.prebid.trace = TraceLevel.VERBOSE + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/GeneralModuleSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/GeneralModuleSpec.groovy index fa2929665a6..1ce411e8d36 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/module/GeneralModuleSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/module/GeneralModuleSpec.groovy @@ -1,10 +1,14 @@ package org.prebid.server.functional.tests.module +import org.prebid.server.functional.model.ModuleName import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountHooksConfiguration +import org.prebid.server.functional.model.config.AdminConfig import org.prebid.server.functional.model.config.ExecutionPlan +import org.prebid.server.functional.model.config.Ortb2BlockingConfig import org.prebid.server.functional.model.config.PbResponseCorrection import org.prebid.server.functional.model.config.PbsModulesConfig +import org.prebid.server.functional.model.config.Stage import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.request.auction.RichmediaFilter import org.prebid.server.functional.model.request.auction.TraceLevel @@ -12,29 +16,37 @@ import org.prebid.server.functional.model.response.auction.InvocationResult import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.util.PBSUtils -import static org.prebid.server.functional.model.ModuleName.PB_RESPONSE_CORRECTION +import static org.prebid.server.functional.model.ModuleName.ORTB2_BLOCKING import static org.prebid.server.functional.model.ModuleName.PB_RICHMEDIA_FILTER import static org.prebid.server.functional.model.config.Endpoint.OPENRTB2_AUCTION +import static org.prebid.server.functional.model.config.ModuleHookImplementation.ORTB2_BLOCKING_BIDDER_REQUEST +import static org.prebid.server.functional.model.config.ModuleHookImplementation.ORTB2_BLOCKING_RAW_BIDDER_RESPONSE import static org.prebid.server.functional.model.config.ModuleHookImplementation.PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES -import static org.prebid.server.functional.model.config.ModuleHookImplementation.RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES import static org.prebid.server.functional.model.config.Stage.ALL_PROCESSED_BID_RESPONSES +import static org.prebid.server.functional.model.config.Stage.BIDDER_REQUEST +import static org.prebid.server.functional.model.config.Stage.RAW_BIDDER_RESPONSE import static org.prebid.server.functional.model.request.auction.BidRequest.getDefaultBidRequest import static org.prebid.server.functional.model.response.auction.InvocationStatus.SUCCESS import static org.prebid.server.functional.model.response.auction.ResponseAction.NO_ACTION -import static org.prebid.server.functional.model.response.auction.ResponseAction.NO_INVOCATION class GeneralModuleSpec extends ModuleBaseSpec { - private final static String NO_INVOCATION_METRIC = "modules.module.%s.stage.%s.hook.%s.success.no-invocation" private final static String CALL_METRIC = "modules.module.%s.stage.%s.hook.%s.call" + private final static String NOOP_METRIC = "modules.module.%s.stage.%s.hook.%s.success.noop" private final static Map DISABLED_INVOKE_CONFIG = ['settings.modules.require-config-to-invoke': 'false'] private final static Map ENABLED_INVOKE_CONFIG = ['settings.modules.require-config-to-invoke': 'true'] - private final static Map MULTY_MODULE_CONFIG = getRichMediaFilterSettings(PBSUtils.randomString) + getResponseCorrectionConfig() + - ['hooks.host-execution-plan': encode(ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, [(ALL_PROCESSED_BID_RESPONSES): [PB_RICHMEDIA_FILTER, PB_RESPONSE_CORRECTION]]))] - private final static PrebidServerService pbsServiceWithMultipleModule = pbsServiceFactory.getService(MULTY_MODULE_CONFIG + DISABLED_INVOKE_CONFIG) - private final static PrebidServerService pbsServiceWithMultipleModuleWithRequireInvoke = pbsServiceFactory.getService(MULTY_MODULE_CONFIG + ENABLED_INVOKE_CONFIG) + private final static Map> ORTB_STAGES = [(BIDDER_REQUEST) : [ORTB2_BLOCKING], + (RAW_BIDDER_RESPONSE): [ORTB2_BLOCKING]] + private final static Map> RESPONSE_STAGES = [(ALL_PROCESSED_BID_RESPONSES): [PB_RICHMEDIA_FILTER]] + private final static Map> MODULES_STAGES = ORTB_STAGES + RESPONSE_STAGES + private final static Map MULTI_MODULE_CONFIG = getRichMediaFilterSettings(PBSUtils.randomString) + + getOrtb2BlockingSettings() + + ['hooks.host-execution-plan': encode(ExecutionPlan.getSingleEndpointExecutionPlan(OPENRTB2_AUCTION, MODULES_STAGES))] + + private final static PrebidServerService pbsServiceWithMultipleModule = pbsServiceFactory.getService(MULTI_MODULE_CONFIG + DISABLED_INVOKE_CONFIG) + private final static PrebidServerService pbsServiceWithMultipleModuleWithRequireInvoke = pbsServiceFactory.getService(MULTI_MODULE_CONFIG + ENABLED_INVOKE_CONFIG) def "PBS should call all modules and traces response when account config is empty and require-config-to-invoke is disabled"() { given: "Default bid request with verbose trace" @@ -55,19 +67,21 @@ class GeneralModuleSpec extends ModuleBaseSpec { then: "PBS response should include trace information about called modules" verifyAll(response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List) { - it.status == [SUCCESS, SUCCESS] - it.action == [NO_ACTION, NO_ACTION] - it.hookId.moduleCode.sort() == [PB_RICHMEDIA_FILTER, PB_RESPONSE_CORRECTION].code.sort() + it.status == [SUCCESS, SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION, NO_ACTION] + it.hookId.moduleCode.sort() == [ORTB2_BLOCKING, ORTB2_BLOCKING, PB_RICHMEDIA_FILTER].code.sort() } - and: "no-invocation metrics shouldn't be updated" + and: "Ortb2blocking module call metrics should be updated" def metrics = pbsServiceWithMultipleModule.sendCollectedMetricsRequest() - assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] - assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 - and: "hook call metrics should be updated" + and: "RB-Richmedia-Filter module call metrics should be updated" assert metrics[CALL_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 - assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[NOOP_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 where: modulesConfig << [null, new PbsModulesConfig()] @@ -93,19 +107,21 @@ class GeneralModuleSpec extends ModuleBaseSpec { then: "PBS response should include trace information about called modules" verifyAll(response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List) { - it.status == [SUCCESS, SUCCESS] - it.action == [NO_ACTION, NO_ACTION] - it.hookId.moduleCode.sort() == [PB_RICHMEDIA_FILTER, PB_RESPONSE_CORRECTION].code.sort() + it.status == [SUCCESS, SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION, NO_ACTION] + it.hookId.moduleCode.sort() == [PB_RICHMEDIA_FILTER, ORTB2_BLOCKING, ORTB2_BLOCKING].code.sort() } - and: "no-invocation metrics shouldn't be updated" + and: "Ortb2blocking module call metrics should be updated" def metrics = pbsServiceWithMultipleModule.sendCollectedMetricsRequest() - assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] - assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 - and: "hook call metrics should be updated" + and: "RB-Richmedia-Filter module call metrics should be updated" assert metrics[CALL_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 - assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[NOOP_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 where: pbRichmediaFilterConfig | pbResponseCorrectionConfig @@ -116,41 +132,47 @@ class GeneralModuleSpec extends ModuleBaseSpec { new RichmediaFilter(filterMraid: true) | new PbResponseCorrection(enabled: true) } - def "PBS shouldn't call any modules and traces that in response when account config is empty and require-config-to-invoke is enabled"() { - given: "Default bid request with verbose trace" + def "PBS should call all modules and traces response when default-account includes modules config and require-config-to-invoke is enabled"() { + given: "PBS service with module config" + def pbsModulesConfig = new PbsModulesConfig(pbRichmediaFilter: new RichmediaFilter(), ortb2Blocking: new Ortb2BlockingConfig()) + def defaultAccountConfigSettings = AccountConfig.defaultAccountConfig.tap { + hooks = new AccountHooksConfiguration(modules: pbsModulesConfig) + } + + def pbsConfig = MULTI_MODULE_CONFIG + ENABLED_INVOKE_CONFIG + ["settings.default-account-config": encode(defaultAccountConfigSettings)] + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" def bidRequest = defaultBidRequest.tap { ext.prebid.trace = TraceLevel.VERBOSE } - and: "Save account without modules config" - def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(modules: modulesConfig)) - def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) - accountDao.save(account) - and: "Flush metrics" - flushMetrics(pbsServiceWithMultipleModuleWithRequireInvoke) + flushMetrics(pbsServiceWithMultipleModules) when: "PBS processes auction request" - def response = pbsServiceWithMultipleModuleWithRequireInvoke.sendAuctionRequest(bidRequest) + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) - then: "PBS response should include trace information about no-called modules" + then: "PBS response should include trace information about called modules" verifyAll(response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List) { - it.status == [SUCCESS, SUCCESS] - it.action == [NO_INVOCATION, NO_INVOCATION] - it.hookId.moduleCode.sort() == [PB_RICHMEDIA_FILTER, PB_RESPONSE_CORRECTION].code.sort() + it.status == [SUCCESS, SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION, NO_ACTION] + it.hookId.moduleCode.sort() == [PB_RICHMEDIA_FILTER, ORTB2_BLOCKING, ORTB2_BLOCKING].code.sort() } - and: "no-invocation metrics should be updated" - def metrics = pbsServiceWithMultipleModuleWithRequireInvoke.sendCollectedMetricsRequest() - assert metrics[NO_INVOCATION_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 - assert metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + and: "Ortb2blocking module call metrics should be updated" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 - and: "hook call metrics shouldn't be updated" - assert !metrics[CALL_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] - assert !metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + and: "RB-Richmedia-Filter module call metrics should be updated" + assert metrics[CALL_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[NOOP_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 - where: - modulesConfig << [null, new PbsModulesConfig()] + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) } def "PBS should call all modules and traces response when account includes modules config and require-config-to-invoke is enabled"() { @@ -160,7 +182,7 @@ class GeneralModuleSpec extends ModuleBaseSpec { } and: "Save account with enabled response correction module" - def pbsModulesConfig = new PbsModulesConfig(pbRichmediaFilter: pbRichmediaFilterConfig, pbResponseCorrection: pbResponseCorrectionConfig) + def pbsModulesConfig = new PbsModulesConfig(pbRichmediaFilter: pbRichmediaFilterConfig, ortb2Blocking: ortb2BlockingConfig) def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(modules: pbsModulesConfig)) def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) accountDao.save(account) @@ -173,27 +195,29 @@ class GeneralModuleSpec extends ModuleBaseSpec { then: "PBS response should include trace information about called modules" verifyAll(response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List) { - it.status == [SUCCESS, SUCCESS] - it.action == [NO_ACTION, NO_ACTION] - it.hookId.moduleCode.sort() == [PB_RICHMEDIA_FILTER, PB_RESPONSE_CORRECTION].code.sort() + it.status == [SUCCESS, SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION, NO_ACTION] + it.hookId.moduleCode.sort() == [PB_RICHMEDIA_FILTER, ORTB2_BLOCKING, ORTB2_BLOCKING].code.sort() } - and: "no-invocation metrics shouldn't be updated" + and: "Ortb2blocking module call metrics should be updated" def metrics = pbsServiceWithMultipleModuleWithRequireInvoke.sendCollectedMetricsRequest() - assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] - assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 - and: "hook call metrics should be updated" + and: "RB-Richmedia-Filter module call metrics should be updated" assert metrics[CALL_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 - assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[NOOP_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 where: - pbRichmediaFilterConfig | pbResponseCorrectionConfig - new RichmediaFilter() | new PbResponseCorrection() - new RichmediaFilter() | new PbResponseCorrection(enabled: false) - new RichmediaFilter() | new PbResponseCorrection(enabled: true) - new RichmediaFilter(filterMraid: true) | new PbResponseCorrection() - new RichmediaFilter(filterMraid: true) | new PbResponseCorrection(enabled: true) + pbRichmediaFilterConfig | ortb2BlockingConfig + new RichmediaFilter() | new Ortb2BlockingConfig() + new RichmediaFilter() | new Ortb2BlockingConfig(attributes: [:] as Map) + new RichmediaFilter() | new Ortb2BlockingConfig(attributes: [:] as Map) + new RichmediaFilter(filterMraid: true) | new Ortb2BlockingConfig() + new RichmediaFilter(filterMraid: true) | new Ortb2BlockingConfig(attributes: [:] as Map) } def "PBS should call specified module and traces response when account config includes that module and require-config-to-invoke is enabled"() { @@ -203,7 +227,7 @@ class GeneralModuleSpec extends ModuleBaseSpec { } and: "Save account with enabled response correction module" - def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(modules: new PbsModulesConfig(pbResponseCorrection: new PbResponseCorrection()))) + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(modules: new PbsModulesConfig(pbRichmediaFilter: new RichmediaFilter()))) def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) accountDao.save(account) @@ -215,26 +239,260 @@ class GeneralModuleSpec extends ModuleBaseSpec { then: "PBS response should include trace information about called module" def invocationTrace = response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List - verifyAll(invocationTrace.findAll { it -> it.hookId.moduleCode == PB_RESPONSE_CORRECTION.code }) { + verifyAll(invocationTrace.findAll { it -> it.hookId.moduleCode == PB_RICHMEDIA_FILTER.code }) { it.status == [SUCCESS] it.action == [NO_ACTION] - it.hookId.moduleCode.sort() == [PB_RESPONSE_CORRECTION].code.sort() + it.hookId.moduleCode.sort() == [PB_RICHMEDIA_FILTER].code.sort() } - and: "PBS response should include trace information about no-called module" - verifyAll(invocationTrace.findAll { it -> it.hookId.moduleCode == PB_RICHMEDIA_FILTER.code }) { - it.status == [SUCCESS] - it.action == [NO_INVOCATION] - it.hookId.moduleCode.sort() == [PB_RICHMEDIA_FILTER].code.sort() + and: "Ortb2blocking module call metrics should be updated" + def metrics = pbsServiceWithMultipleModuleWithRequireInvoke.sendCollectedMetricsRequest() + assert !metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + assert !metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + + and: "RB-Richmedia-Filter module call metrics should be updated" + assert metrics[CALL_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 + assert metrics[NOOP_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 + } + + def "PBS shouldn't call any modules and traces that in response when account config is empty and require-config-to-invoke is enabled"() { + given: "Default bid request with verbose trace" + def bidRequest = defaultBidRequest.tap { + ext.prebid.trace = TraceLevel.VERBOSE + } + + and: "Save account without modules config" + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(modules: modulesConfig)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModuleWithRequireInvoke) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModuleWithRequireInvoke.sendAuctionRequest(bidRequest) + + then: "PBS response shouldn't include trace information about no-called modules" + assert !response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() + + and: "Ortb2blocking module call metrics shouldn't be updated" + def metrics = pbsServiceWithMultipleModuleWithRequireInvoke.sendCollectedMetricsRequest() + assert !metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + assert !metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + + and: "RB-Richmedia-Filter module call metrics shouldn't be updated" + assert !metrics[CALL_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NOOP_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] + + where: + modulesConfig << [null, new PbsModulesConfig()] + } + + def "PBS should call all modules without account config when modules enabled in module-execution host config"() { + given: "PBS service with module-execution config" + def pbsConfig = MULTI_MODULE_CONFIG + ENABLED_INVOKE_CONFIG + + [("hooks.admin.module-execution.${ORTB2_BLOCKING.code}".toString()): 'true'] + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = defaultBidRequest.tap { + ext.prebid.trace = TraceLevel.VERBOSE + } + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS response should include trace information about called modules" + verifyAll(response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION] + it.hookId.moduleCode.sort() == [ORTB2_BLOCKING, ORTB2_BLOCKING].code.sort() } - and: "Richmedia module metrics should be updated" + and: "Ortb2blocking module call metrics should be updated" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + and: "RB-Richmedia-Filter module call metrics shouldn't be updated" + assert !metrics[CALL_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NOOP_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS shouldn't call any module without account config when modules disabled in module-execution host config"() { + given: "PBS service with module-execution config" + def pbsConfig = MULTI_MODULE_CONFIG + ENABLED_INVOKE_CONFIG + + [("hooks.admin.module-execution.${ORTB2_BLOCKING.code}".toString()): 'false'] + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = defaultBidRequest.tap { + ext.prebid.trace = TraceLevel.VERBOSE + } + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS response shouldn't include trace information about no-called modules" + assert !response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() + + and: "Ortb2blocking module call metrics shouldn't be updated" def metrics = pbsServiceWithMultipleModuleWithRequireInvoke.sendCollectedMetricsRequest() - assert metrics[NO_INVOCATION_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] == 1 + assert !metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + assert !metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS should call module without account config when default-account module-execution config enabled module"() { + given: "PBS service with module-execution and default account configs" + def defaultAccountConfigSettings = AccountConfig.defaultAccountConfig.tap { + hooks = new AccountHooksConfiguration(admin: new AdminConfig(moduleExecution: [(ORTB2_BLOCKING): true])) + } + def pbsConfig = MULTI_MODULE_CONFIG + ENABLED_INVOKE_CONFIG + ["settings.default-account-config": encode(defaultAccountConfigSettings)] + + [("hooks.admin.module-execution.${ORTB2_BLOCKING.code}".toString()): 'false'] + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = defaultBidRequest.tap { + ext.prebid.trace = TraceLevel.VERBOSE + } + + and: "Save account without modules config" + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(modules: null)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS response shouldn't include trace information about no-called modules" + assert !response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() + + and: "Ortb2blocking module call metrics shouldn't be updated" + def metrics = pbsServiceWithMultipleModuleWithRequireInvoke.sendCollectedMetricsRequest() + assert !metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + assert !metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + + and: "RB-Richmedia-Filter module call metrics shouldn't be updated" + assert !metrics[CALL_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NOOP_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS shouldn't call any modules without account config when default-account module-execution config not enabling module"() { + given: "PBS service with module-execution and default account configs" + def defaultAccountConfigSettings = AccountConfig.defaultAccountConfig.tap { + hooks = new AccountHooksConfiguration(admin: new AdminConfig(moduleExecution: [(ORTB2_BLOCKING): moduleExecutionStatus])) + } + def pbsConfig = MULTI_MODULE_CONFIG + ENABLED_INVOKE_CONFIG + ["settings.default-account-config": encode(defaultAccountConfigSettings)] + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = defaultBidRequest.tap { + ext.prebid.trace = TraceLevel.VERBOSE + } + + and: "Save account without modules config" + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(modules: null)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS response shouldn't include trace information about no-called modules" + assert !response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() + + and: "Ortb2blocking module call metrics shouldn't be updated" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert !metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + assert !metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] + assert !metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] + + and: "RB-Richmedia-Filter module call metrics shouldn't be updated" + assert !metrics[CALL_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NOOP_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + + where: + moduleExecutionStatus << [false, null] + } + + def "PBS should prioritize specific account module-execution config over default-account module-execution config when both are present"() { + given: "PBS service with default account config" + def defaultAccountConfigSettings = AccountConfig.defaultAccountConfig.tap { + hooks = new AccountHooksConfiguration(admin: new AdminConfig(moduleExecution: [(ORTB2_BLOCKING): false])) + } + def pbsConfig = MULTI_MODULE_CONFIG + ENABLED_INVOKE_CONFIG + ["settings.default-account-config": encode(defaultAccountConfigSettings)] + def pbsServiceWithMultipleModules = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request with verbose trace" + def bidRequest = defaultBidRequest.tap { + ext.prebid.trace = TraceLevel.VERBOSE + } + + and: "Save account without modules config" + def accountConfig = new AccountConfig(hooks: new AccountHooksConfiguration(admin: new AdminConfig(moduleExecution: [(ORTB2_BLOCKING): true]))) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(pbsServiceWithMultipleModules) + + when: "PBS processes auction request" + def response = pbsServiceWithMultipleModules.sendAuctionRequest(bidRequest) + + then: "PBS response should include trace information about called modules" + verifyAll(response?.ext?.prebid?.modules?.trace?.stages?.outcomes?.groups?.invocationResults?.flatten() as List) { + it.status == [SUCCESS, SUCCESS] + it.action == [NO_ACTION, NO_ACTION] + it.hookId.moduleCode.sort() == [ORTB2_BLOCKING, ORTB2_BLOCKING].code.sort() + } + + and: "Ortb2blocking module call metrics should be updated" + def metrics = pbsServiceWithMultipleModules.sendCollectedMetricsRequest() + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[CALL_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, BIDDER_REQUEST.metricValue, ORTB2_BLOCKING_BIDDER_REQUEST.code)] == 1 + assert metrics[NOOP_METRIC.formatted(ORTB2_BLOCKING.code, RAW_BIDDER_RESPONSE.metricValue, ORTB2_BLOCKING_RAW_BIDDER_RESPONSE.code)] == 1 + + and: "RB-Richmedia-Filter module call metrics shouldn't be updated" assert !metrics[CALL_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] + assert !metrics[NOOP_METRIC.formatted(PB_RICHMEDIA_FILTER.code, ALL_PROCESSED_BID_RESPONSES.metricValue, PB_RICHMEDIA_FILTER_ALL_PROCESSED_RESPONSES.code)] - and: "Response-correction module metrics should be updated" - assert !metrics[NO_INVOCATION_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] - assert metrics[CALL_METRIC.formatted(PB_RESPONSE_CORRECTION.code, ALL_PROCESSED_BID_RESPONSES.metricValue, RESPONSE_CORRECTION_ALL_PROCESSED_RESPONSES.code)] == 1 + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAmpSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAmpSpec.groovy index d4cfc7bbda9..34242d43cec 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAmpSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAmpSpec.groovy @@ -8,7 +8,11 @@ import org.prebid.server.functional.model.config.AccountPrivacyConfig import org.prebid.server.functional.model.config.PurposeConfig import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.pricefloors.Country import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.DistributionChannel +import org.prebid.server.functional.model.request.auction.Regs +import org.prebid.server.functional.model.request.auction.RegsExt import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.testcontainers.container.PrebidServerContainer import org.prebid.server.functional.util.PBSUtils @@ -27,6 +31,7 @@ import static org.prebid.server.functional.model.config.Purpose.P4 import static org.prebid.server.functional.model.config.PurposeEnforcement.BASIC import static org.prebid.server.functional.model.config.PurposeEnforcement.NO import static org.prebid.server.functional.model.mock.services.vendorlist.GvlSpecificationVersion.V3 +import static org.prebid.server.functional.model.pricefloors.Country.BULGARIA import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ADAPTER_DISALLOWED_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_REQUEST_DISALLOWED_COUNT import static org.prebid.server.functional.model.request.amp.ConsentType.BOGUS @@ -36,6 +41,7 @@ import static org.prebid.server.functional.model.request.auction.ActivityType.FE import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_EIDS import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_PRECISE_GEO import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_UFPD +import static org.prebid.server.functional.model.request.auction.PublicCountryIp.BGR_IP import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID import static org.prebid.server.functional.util.privacy.CcpaConsent.Signal.ENFORCED import static org.prebid.server.functional.util.privacy.TcfConsent.GENERIC_VENDOR_ID @@ -686,4 +692,162 @@ class GdprAmpSpec extends PrivacyBaseSpec { where: tcfPolicyVersion << [TCF_POLICY_V4, TCF_POLICY_V5] } + + def "PBS should process with GDPR enforcement when GDPR and COPPA configurations are present in request"() { + given: "Valid consent string without basic ads" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + + and: "Amp default request" + def ampRequest = getGdprAmpRequest(validConsentString) + + and: "Bid request with gdpr and coppa config" + def ampStoredRequest = getGdprBidRequest(DistributionChannel.SITE, validConsentString).tap { + regs = new Regs(gdpr: gdpr, coppa: coppa, ext: new RegsExt(gdpr: extGdpr, coppa: extCoppa)) + setAccountId(ampRequest.account) + } + + and: "Save account config without eea countries into DB" + def accountGdprConfig = new AccountGdprConfig(enabled: true, eeaCountries: PBSUtils.getRandomEnum(Country.class, [BULGARIA])) + def account = getAccountWithGdpr(ampRequest.account, accountGdprConfig) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Flush metrics" + flushMetrics(privacyPbsService) + + when: "PBS processes amp request" + privacyPbsService.sendAmpRequest(ampRequest) + + then: "Bidder shouldn't be called" + assert !bidder.getBidderRequests(ampStoredRequest.id) + + then: "Metrics processed across activities should be updated" + def metrics = privacyPbsService.sendCollectedMetricsRequest() + assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(ampStoredRequest, FETCH_BIDS)] == 1 + assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(ampStoredRequest, FETCH_BIDS)] == 1 + + where: + gdpr | coppa | extGdpr | extCoppa + 1 | 1 | 1 | 1 + 1 | 1 | 1 | 0 + 1 | 1 | 1 | null + 1 | 1 | 0 | 1 + 1 | 1 | 0 | 0 + 1 | 1 | 0 | null + 1 | 1 | null | 1 + 1 | 1 | null | 0 + 1 | 1 | null | null + 1 | 0 | 1 | 1 + 1 | 0 | 1 | 0 + 1 | 0 | 1 | null + 1 | 0 | 0 | 1 + 1 | 0 | 0 | 0 + 1 | 0 | 0 | null + 1 | 0 | null | 1 + 1 | 0 | null | 0 + 1 | 0 | null | null + 1 | null | 1 | 1 + 1 | null | 1 | 0 + 1 | null | 1 | null + 1 | null | 0 | 1 + 1 | null | 0 | 0 + 1 | null | 0 | null + 1 | null | null | 1 + 1 | null | null | 0 + 1 | null | null | null + + null | 1 | 1 | 1 + null | 1 | 1 | 0 + null | 1 | 1 | null + null | 0 | 1 | 1 + null | 0 | 1 | 0 + null | 0 | 1 | null + null | null | 1 | 1 + null | null | 1 | 0 + null | null | 1 | null + } + + def "PBS should process with GDPR enforcement when request comes from EEA IP with COPPA enabled"() { + given: "Valid consent string without basic ads" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + + and: "Amp default request" + def ampRequest = getGdprAmpRequest(validConsentString) + + and: "Bid request with gdpr and coppa config" + def ampStoredRequest = getGdprBidRequest(DistributionChannel.SITE, validConsentString).tap { + regs = new Regs(gdpr: 1, coppa: 1, ext: new RegsExt(gdpr: 1, coppa: 1)) + device.geo.country = requestCountry + device.geo.region = null + device.ip = requestIpV4 + device.ipv6 = requestIpV6 + } + + and: "Save account config without eea countries into DB" + def accountGdprConfig = new AccountGdprConfig(enabled: true, eeaCountries: accountCountry) + def account = getAccountWithGdpr(ampRequest.account, accountGdprConfig) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Flush metrics" + flushMetrics(privacyPbsService) + + when: "PBS processes amp request" + privacyPbsService.sendAmpRequest(ampRequest, header) + + then: "Bidder shouldn't be called" + assert !bidder.getBidderRequests(ampStoredRequest.id) + + then: "Metrics processed across activities should be updated" + def metrics = privacyPbsService.sendCollectedMetricsRequest() + assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(ampStoredRequest, FETCH_BIDS)] == 1 + assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(ampStoredRequest, FETCH_BIDS)] == 1 + + where: + requestCountry | accountCountry | requestIpV4 | requestIpV6 | header + BULGARIA | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | null | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | null | null | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | null | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + null | null | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | null | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | null | null | ["X-Forwarded-For": BGR_IP.v4] + null | null | null | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | [:] + BULGARIA | null | BGR_IP.v4 | BGR_IP.v6 | [:] + BULGARIA | BULGARIA | BGR_IP.v4 | null | [:] + BULGARIA | null | BGR_IP.v4 | null | [:] + BULGARIA | BULGARIA | null | BGR_IP.v6 | [:] + BULGARIA | null | null | BGR_IP.v6 | [:] + BULGARIA | BULGARIA | null | null | [:] + BULGARIA | null | null | null | [:] + null | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | [:] + null | null | BGR_IP.v4 | BGR_IP.v6 | [:] + null | BULGARIA | BGR_IP.v4 | null | [:] + null | null | BGR_IP.v4 | null | [:] + null | BULGARIA | null | BGR_IP.v6 | [:] + null | null | null | BGR_IP.v6 | [:] + null | BULGARIA | null | null | [:] + null | null | null | null | [:] + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy index c4a21342ab9..66003fa2716 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy @@ -5,7 +5,10 @@ import org.prebid.server.functional.model.ChannelType import org.prebid.server.functional.model.config.AccountGdprConfig import org.prebid.server.functional.model.config.PurposeConfig import org.prebid.server.functional.model.config.PurposeEnforcement +import org.prebid.server.functional.model.pricefloors.Country import org.prebid.server.functional.model.request.auction.DistributionChannel +import org.prebid.server.functional.model.request.auction.Regs +import org.prebid.server.functional.model.request.auction.RegsExt import org.prebid.server.functional.model.response.auction.ErrorType import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.testcontainers.container.PrebidServerContainer @@ -36,6 +39,7 @@ import static org.prebid.server.functional.model.request.auction.ActivityType.TR import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_PRECISE_GEO import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_UFPD import static org.prebid.server.functional.model.request.auction.Prebid.Channel +import static org.prebid.server.functional.model.request.auction.PublicCountryIp.BGR_IP import static org.prebid.server.functional.model.request.auction.TraceLevel.BASIC import static org.prebid.server.functional.model.request.auction.TraceLevel.VERBOSE import static org.prebid.server.functional.model.response.auction.BidRejectionReason.REQUEST_BLOCKED_PRIVACY @@ -805,4 +809,147 @@ class GdprAuctionSpec extends PrivacyBaseSpec { where: tcfPolicyVersion << [TCF_POLICY_V4, TCF_POLICY_V5] } + + def "PBS should process with GDPR enforcement when GDPR and COPPA configurations are present in request"() { + given: "Valid consent string without basic ads" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + + and: "Bid request with gdpr and coppa config" + def bidRequest = getGdprBidRequest(DistributionChannel.APP, validConsentString).tap { + regs = new Regs(gdpr: gdpr, coppa: coppa, ext: new RegsExt(gdpr: extGdpr, coppa: extCoppa)) + } + + and: "Save account config without eea countries into DB" + def accountGdprConfig = new AccountGdprConfig(enabled: true, eeaCountries: PBSUtils.getRandomEnum(Country.class, [BULGARIA])) + def account = getAccountWithGdpr(bidRequest.accountId, accountGdprConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(privacyPbsService) + + when: "PBS processes auction request" + privacyPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder shouldn't be called" + assert !bidder.getBidderRequests(bidRequest.id) + + then: "Metrics processed across activities should be updated" + def metrics = privacyPbsService.sendCollectedMetricsRequest() + assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(bidRequest, FETCH_BIDS)] == 1 + assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(bidRequest, FETCH_BIDS)] == 1 + + where: + gdpr | coppa | extGdpr | extCoppa + 1 | 1 | 1 | 1 + 1 | 1 | 1 | 0 + 1 | 1 | 1 | null + 1 | 1 | 0 | 1 + 1 | 1 | 0 | 0 + 1 | 1 | 0 | null + 1 | 1 | null | 1 + 1 | 1 | null | 0 + 1 | 1 | null | null + 1 | 0 | 1 | 1 + 1 | 0 | 1 | 0 + 1 | 0 | 1 | null + 1 | 0 | 0 | 1 + 1 | 0 | 0 | 0 + 1 | 0 | 0 | null + 1 | 0 | null | 1 + 1 | 0 | null | 0 + 1 | 0 | null | null + 1 | null | 1 | 1 + 1 | null | 1 | 0 + 1 | null | 1 | null + 1 | null | 0 | 1 + 1 | null | 0 | 0 + 1 | null | 0 | null + 1 | null | null | 1 + 1 | null | null | 0 + 1 | null | null | null + + null | 1 | 1 | 1 + null | 1 | 1 | 0 + null | 1 | 1 | null + null | 0 | 1 | 1 + null | 0 | 1 | 0 + null | 0 | 1 | null + null | null | 1 | 1 + null | null | 1 | 0 + null | null | 1 | null + } + + def "PBS should process with GDPR enforcement when request comes from EEA IP with COPPA enabled"() { + given: "Valid consent string without basic ads" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + + and: "Bid request with gdpr and coppa config" + def bidRequest = getGdprBidRequest(DistributionChannel.APP, validConsentString).tap { + regs = new Regs(gdpr: 1, coppa: 1, ext: new RegsExt(gdpr: 1, coppa: 1)) + device.geo.country = requestCountry + device.geo.region = null + device.ip = requestIpV4 + device.ipv6 = requestIpV6 + } + + and: "Save account config without eea countries into DB" + def accountGdprConfig = new AccountGdprConfig(enabled: true, eeaCountries: accountCountry) + def account = getAccountWithGdpr(bidRequest.accountId, accountGdprConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(privacyPbsService) + + when: "PBS processes auction request" + privacyPbsService.sendAuctionRequest(bidRequest, header) + + then: "Bidder shouldn't be called" + assert !bidder.getBidderRequests(bidRequest.id) + + then: "Metrics processed across activities should be updated" + def metrics = privacyPbsService.sendCollectedMetricsRequest() + assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(bidRequest, FETCH_BIDS)] == 1 + assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(bidRequest, FETCH_BIDS)] == 1 + + where: + requestCountry | accountCountry | requestIpV4 | requestIpV6 | header + BULGARIA | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | null | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | null | null | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | null | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + null | null | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | null | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | null | null | ["X-Forwarded-For": BGR_IP.v4] + null | null | null | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | [:] + BULGARIA | null | BGR_IP.v4 | BGR_IP.v6 | [:] + BULGARIA | BULGARIA | BGR_IP.v4 | null | [:] + BULGARIA | null | BGR_IP.v4 | null | [:] + BULGARIA | BULGARIA | null | BGR_IP.v6 | [:] + BULGARIA | null | null | BGR_IP.v6 | [:] + BULGARIA | BULGARIA | null | null | [:] + BULGARIA | null | null | null | [:] + null | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | [:] + null | null | BGR_IP.v4 | BGR_IP.v6 | [:] + null | BULGARIA | BGR_IP.v4 | null | [:] + null | null | BGR_IP.v4 | null | [:] + null | BULGARIA | null | BGR_IP.v6 | [:] + null | null | null | BGR_IP.v6 | [:] + null | BULGARIA | null | null | [:] + null | null | null | null | [:] + } } 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..ab16f5a1b40 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 @@ -45,9 +45,9 @@ 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.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.ResultImpl; +import org.prebid.server.hooks.execution.v1.analytics.TagsImpl; import org.prebid.server.json.EncodeException; import org.prebid.server.json.JacksonMapper; import org.prebid.server.model.HttpRequestContext; diff --git a/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java b/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java index 0e3d0220ba1..daf9e2d4ff6 100644 --- a/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java +++ b/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java @@ -20,8 +20,6 @@ import com.iab.openrtb.response.Response; import com.iab.openrtb.response.SeatBid; import io.vertx.core.Future; -import lombok.Value; -import lombok.experimental.Accessors; import org.apache.commons.collections4.MapUtils; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.BeforeEach; @@ -67,7 +65,7 @@ import org.prebid.server.hooks.execution.HookStageExecutor; import org.prebid.server.hooks.execution.model.HookStageExecutionResult; import org.prebid.server.hooks.execution.v1.bidder.AllProcessedBidResponsesPayloadImpl; -import org.prebid.server.hooks.v1.bidder.BidderResponsePayload; +import org.prebid.server.hooks.execution.v1.bidder.BidderResponsePayloadImpl; import org.prebid.server.identity.IdGenerator; import org.prebid.server.identity.IdGeneratorType; import org.prebid.server.proto.openrtb.ext.ExtIncludeBrandCategory; @@ -113,6 +111,7 @@ import org.prebid.server.settings.model.AccountAuctionEventConfig; import org.prebid.server.settings.model.AccountEventsConfig; import org.prebid.server.settings.model.VideoStoredDataResult; +import org.prebid.server.spring.config.model.CacheDefaultTtlProperties; import org.prebid.server.vast.VastModifier; import java.math.BigDecimal; @@ -156,6 +155,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidAdservertargetingRule.Source.xStatic; +import static org.prebid.server.proto.openrtb.ext.response.BidType.audio; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.video; import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; @@ -190,6 +190,8 @@ public class BidResponseCreatorTest extends VertxTest { private ActivityInfrastructure activityInfrastructure; @Mock(strictness = LENIENT) private CacheTtl mediaTypeCacheTtl; + @Mock(strictness = LENIENT) + private CacheDefaultTtlProperties cacheDefaultProperties; @Spy private WinningBidComparatorFactory winningBidComparatorFactory; @@ -209,6 +211,11 @@ public void setUp() { given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(null); given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(null); + given(cacheDefaultProperties.getBannerTtl()).willReturn(null); + given(cacheDefaultProperties.getVideoTtl()).willReturn(null); + given(cacheDefaultProperties.getAudioTtl()).willReturn(null); + given(cacheDefaultProperties.getNativeTtl()).willReturn(null); + given(categoryMappingService.createCategoryMapping(any(), any(), any())) .willAnswer(invocationOnMock -> Future.succeededFuture( CategoryMappingResult.of(emptyMap(), emptyMap(), invocationOnMock.getArgument(0), null))); @@ -1640,7 +1647,8 @@ public void shouldTruncateTargetingKeywordsByGlobalConfig() { 20, clock, jacksonMapper, - mediaTypeCacheTtl); + mediaTypeCacheTtl, + cacheDefaultProperties); // when final BidResponse bidResponse = target.create(auctionContext, CACHE_INFO, MULTI_BIDS).result(); @@ -3807,7 +3815,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromBid() { final Imp imp = Imp.builder().id("impId").exp(20).build(); final List bidderResponses = asList(BidderResponse.of( "bidder1", - givenSeatBid(BidderBid.of(bid, banner, "USD")), + givenSeatBid(BidderBid.of(bid, video, "USD")), 100)); final BidRequestCacheInfo cacheInfo = BidRequestCacheInfo.builder() @@ -3815,17 +3823,18 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromBid() { .shouldCacheBids(true) .shouldCacheVideoBids(true) .cacheBidsTtl(30) - .cacheVideoBidsTtl(40) + .cacheVideoBidsTtl(31) .build(); final AuctionContext auctionContext = givenAuctionContext( givenBidRequest(builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() - .events(mapper.createObjectNode()) - .build())), imp), + .events(mapper.createObjectNode()) + .build())), imp), builder -> builder.account(Account.builder() .id("accountId") .auction(AccountAuctionConfig.builder() - .bannerCacheTtl(60) + .bannerCacheTtl(40) + .videoCacheTtl(41) .events(AccountEventsConfig.of(true)) .build()) .build())) @@ -3834,6 +3843,11 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromBid() { // just a stub to get through method call chain givenCacheServiceResult(singletonList(CacheInfo.empty())); given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(50); + given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(51); + given(cacheDefaultProperties.getBannerTtl()).willReturn(60); + given(cacheDefaultProperties.getVideoTtl()).willReturn(61); + given(cacheDefaultProperties.getAudioTtl()).willReturn(62); + given(cacheDefaultProperties.getNativeTtl()).willReturn(63); // when final Future response = target.create(auctionContext, cacheInfo, MULTI_BIDS); @@ -3855,6 +3869,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromBid() { final List capturedBidInfo = bidsArgumentCaptor.getValue(); assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(10); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsOnly(10); assertThat(contextArgumentCaptor.getValue()) .satisfies(context -> { assertThat(context.isShouldCacheBids()).isTrue(); @@ -3869,7 +3884,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromImp() { final Imp imp = Imp.builder().id("impId").exp(20).build(); final List bidderResponses = asList(BidderResponse.of( "bidder1", - givenSeatBid(BidderBid.of(bid, banner, "USD")), + givenSeatBid(BidderBid.of(bid, video, "USD")), 100)); final BidRequestCacheInfo cacheInfo = BidRequestCacheInfo.builder() @@ -3877,7 +3892,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromImp() { .shouldCacheBids(true) .shouldCacheVideoBids(true) .cacheBidsTtl(30) - .cacheVideoBidsTtl(40) + .cacheVideoBidsTtl(31) .build(); final AuctionContext auctionContext = givenAuctionContext( @@ -3887,7 +3902,8 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromImp() { builder -> builder.account(Account.builder() .id("accountId") .auction(AccountAuctionConfig.builder() - .bannerCacheTtl(60) + .bannerCacheTtl(40) + .videoCacheTtl(41) .events(AccountEventsConfig.of(true)) .build()) .build())) @@ -3896,6 +3912,11 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromImp() { // just a stub to get through method call chain givenCacheServiceResult(singletonList(CacheInfo.empty())); given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(50); + given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(51); + given(cacheDefaultProperties.getBannerTtl()).willReturn(60); + given(cacheDefaultProperties.getVideoTtl()).willReturn(61); + given(cacheDefaultProperties.getAudioTtl()).willReturn(62); + given(cacheDefaultProperties.getNativeTtl()).willReturn(63); // when final Future response = target.create(auctionContext, cacheInfo, MULTI_BIDS); @@ -3917,6 +3938,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromImp() { final List capturedBidInfo = bidsArgumentCaptor.getValue(); assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(20); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsOnly(20); assertThat(contextArgumentCaptor.getValue()) .satisfies(context -> { assertThat(context.isShouldCacheBids()).isTrue(); @@ -3931,7 +3953,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromRequest() { final Imp imp = Imp.builder().id("impId").exp(null).build(); final List bidderResponses = asList(BidderResponse.of( "bidder1", - givenSeatBid(BidderBid.of(bid, banner, "USD")), + givenSeatBid(BidderBid.of(bid, video, "USD")), 100)); final BidRequestCacheInfo cacheInfo = BidRequestCacheInfo.builder() @@ -3939,7 +3961,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromRequest() { .shouldCacheBids(true) .shouldCacheVideoBids(true) .cacheBidsTtl(30) - .cacheVideoBidsTtl(40) + .cacheVideoBidsTtl(31) .build(); final AuctionContext auctionContext = givenAuctionContext( @@ -3949,7 +3971,8 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromRequest() { builder -> builder.account(Account.builder() .id("accountId") .auction(AccountAuctionConfig.builder() - .bannerCacheTtl(60) + .bannerCacheTtl(40) + .videoCacheTtl(41) .events(AccountEventsConfig.of(true)) .build()) .build())) @@ -3958,6 +3981,11 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromRequest() { // just a stub to get through method call chain givenCacheServiceResult(singletonList(CacheInfo.empty())); given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(50); + given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(51); + given(cacheDefaultProperties.getBannerTtl()).willReturn(60); + given(cacheDefaultProperties.getVideoTtl()).willReturn(61); + given(cacheDefaultProperties.getAudioTtl()).willReturn(62); + given(cacheDefaultProperties.getNativeTtl()).willReturn(63); // when final Future response = target.create(auctionContext, cacheInfo, MULTI_BIDS); @@ -3968,7 +3996,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromRequest() { assertThat(response.succeeded()).isTrue(); assertThat(response.result().getSeatbid()).flatExtracting(SeatBid::getBid).extracting(Bid::getExp) - .containsExactly(30); + .containsExactly(31); verify(coreCacheService).cacheBidsOpenrtb( bidsArgumentCaptor.capture(), @@ -3979,6 +4007,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromRequest() { final List capturedBidInfo = bidsArgumentCaptor.getValue(); assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(30); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsOnly(31); assertThat(contextArgumentCaptor.getValue()) .satisfies(context -> { assertThat(context.isShouldCacheBids()).isTrue(); @@ -3987,7 +4016,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromRequest() { } @Test - public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromAccountBannerTtl() { + public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromAccountBannerTtlForBannerBid() { // given final Bid bid = Bid.builder().id("bidId").impid("impId").exp(null).price(BigDecimal.valueOf(5.67)).build(); final Imp imp = Imp.builder().id("impId").exp(null).build(); @@ -4001,7 +4030,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromAccountBanne .shouldCacheBids(true) .shouldCacheVideoBids(true) .cacheBidsTtl(null) - .cacheVideoBidsTtl(40) + .cacheVideoBidsTtl(31) .build(); final AuctionContext auctionContext = givenAuctionContext( @@ -4011,7 +4040,8 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromAccountBanne builder -> builder.account(Account.builder() .id("accountId") .auction(AccountAuctionConfig.builder() - .bannerCacheTtl(60) + .bannerCacheTtl(40) + .videoCacheTtl(41) .events(AccountEventsConfig.of(true)) .build()) .build())) @@ -4020,6 +4050,11 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromAccountBanne // just a stub to get through method call chain givenCacheServiceResult(singletonList(CacheInfo.empty())); given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(50); + given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(51); + given(cacheDefaultProperties.getBannerTtl()).willReturn(60); + given(cacheDefaultProperties.getVideoTtl()).willReturn(61); + given(cacheDefaultProperties.getAudioTtl()).willReturn(62); + given(cacheDefaultProperties.getNativeTtl()).willReturn(63); // when final Future response = target.create(auctionContext, cacheInfo, MULTI_BIDS); @@ -4030,7 +4065,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromAccountBanne assertThat(response.succeeded()).isTrue(); assertThat(response.result().getSeatbid()).flatExtracting(SeatBid::getBid).extracting(Bid::getExp) - .containsExactly(60); + .containsExactly(40); verify(coreCacheService).cacheBidsOpenrtb( bidsArgumentCaptor.capture(), @@ -4040,7 +4075,8 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromAccountBanne final List capturedBidInfo = bidsArgumentCaptor.getValue(); assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); - assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(60); + assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(40); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsNull(); assertThat(contextArgumentCaptor.getValue()) .satisfies(context -> { assertThat(context.isShouldCacheBids()).isTrue(); @@ -4049,7 +4085,76 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromAccountBanne } @Test - public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromMediaTypeTtl() { + public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromAccountVideoTtlForVideoBid() { + // given + final Bid bid = Bid.builder().id("bidId").impid("impId").exp(null).price(BigDecimal.valueOf(5.67)).build(); + final Imp imp = Imp.builder().id("impId").exp(null).build(); + final List bidderResponses = asList(BidderResponse.of( + "bidder1", + givenSeatBid(BidderBid.of(bid, video, "USD")), + 100)); + + final BidRequestCacheInfo cacheInfo = BidRequestCacheInfo.builder() + .doCaching(true) + .shouldCacheBids(true) + .shouldCacheVideoBids(true) + .cacheBidsTtl(null) + .cacheVideoBidsTtl(null) + .build(); + + final AuctionContext auctionContext = givenAuctionContext( + givenBidRequest(builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() + .events(mapper.createObjectNode()) + .build())), imp), + builder -> builder.account(Account.builder() + .id("accountId") + .auction(AccountAuctionConfig.builder() + .bannerCacheTtl(40) + .videoCacheTtl(41) + .events(AccountEventsConfig.of(true)) + .build()) + .build())) + .with(toAuctionParticipant(bidderResponses)); + + // just a stub to get through method call chain + givenCacheServiceResult(singletonList(CacheInfo.empty())); + given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(50); + given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(51); + given(cacheDefaultProperties.getBannerTtl()).willReturn(60); + given(cacheDefaultProperties.getVideoTtl()).willReturn(61); + given(cacheDefaultProperties.getAudioTtl()).willReturn(62); + given(cacheDefaultProperties.getNativeTtl()).willReturn(63); + + // when + final Future response = target.create(auctionContext, cacheInfo, MULTI_BIDS); + + // then + final ArgumentCaptor contextArgumentCaptor = ArgumentCaptor.forClass(CacheContext.class); + final ArgumentCaptor> bidsArgumentCaptor = ArgumentCaptor.forClass(List.class); + + assertThat(response.succeeded()).isTrue(); + assertThat(response.result().getSeatbid()).flatExtracting(SeatBid::getBid).extracting(Bid::getExp) + .containsExactly(41); + + verify(coreCacheService).cacheBidsOpenrtb( + bidsArgumentCaptor.capture(), + same(auctionContext), + contextArgumentCaptor.capture(), + any()); + + final List capturedBidInfo = bidsArgumentCaptor.getValue(); + assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); + assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(41); + assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(41); + assertThat(contextArgumentCaptor.getValue()) + .satisfies(context -> { + assertThat(context.isShouldCacheBids()).isTrue(); + assertThat(context.isShouldCacheVideoBids()).isTrue(); + }); + } + + @Test + public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromMediaTypeTtlForBannerBid() { // given final Bid bid = Bid.builder().id("bidId").impid("impId").exp(null).price(BigDecimal.valueOf(5.67)).build(); final Imp imp = Imp.builder().id("impId").exp(null).build(); @@ -4063,7 +4168,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromMediaTypeTtl .shouldCacheBids(true) .shouldCacheVideoBids(true) .cacheBidsTtl(null) - .cacheVideoBidsTtl(40) + .cacheVideoBidsTtl(null) .build(); final AuctionContext auctionContext = givenAuctionContext( @@ -4074,6 +4179,7 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromMediaTypeTtl .id("accountId") .auction(AccountAuctionConfig.builder() .bannerCacheTtl(null) + .videoCacheTtl(41) .events(AccountEventsConfig.of(true)) .build()) .build())) @@ -4082,6 +4188,11 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromMediaTypeTtl // just a stub to get through method call chain givenCacheServiceResult(singletonList(CacheInfo.empty())); given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(50); + given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(51); + given(cacheDefaultProperties.getBannerTtl()).willReturn(60); + given(cacheDefaultProperties.getVideoTtl()).willReturn(61); + given(cacheDefaultProperties.getAudioTtl()).willReturn(62); + given(cacheDefaultProperties.getNativeTtl()).willReturn(63); // when final Future response = target.create(auctionContext, cacheInfo, MULTI_BIDS); @@ -4103,6 +4214,352 @@ public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromMediaTypeTtl final List capturedBidInfo = bidsArgumentCaptor.getValue(); assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(50); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsNull(); + assertThat(contextArgumentCaptor.getValue()) + .satisfies(context -> { + assertThat(context.isShouldCacheBids()).isTrue(); + assertThat(context.isShouldCacheVideoBids()).isTrue(); + }); + } + + @Test + public void createShouldSendCacheRequestWithExpectedTtlAndSetTtlFromMediaTypeTtlForVideoBid() { + // given + final Bid bid = Bid.builder().id("bidId").impid("impId").exp(null).price(BigDecimal.valueOf(5.67)).build(); + final Imp imp = Imp.builder().id("impId").exp(null).build(); + final List bidderResponses = asList(BidderResponse.of( + "bidder1", + givenSeatBid(BidderBid.of(bid, video, "USD")), + 100)); + + final BidRequestCacheInfo cacheInfo = BidRequestCacheInfo.builder() + .doCaching(true) + .shouldCacheBids(true) + .shouldCacheVideoBids(true) + .cacheBidsTtl(null) + .cacheVideoBidsTtl(null) + .build(); + + final AuctionContext auctionContext = givenAuctionContext( + givenBidRequest(builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() + .events(mapper.createObjectNode()) + .build())), imp), + builder -> builder.account(Account.builder() + .id("accountId") + .auction(AccountAuctionConfig.builder() + .bannerCacheTtl(40) + .videoCacheTtl(null) + .events(AccountEventsConfig.of(true)) + .build()) + .build())) + .with(toAuctionParticipant(bidderResponses)); + + // just a stub to get through method call chain + givenCacheServiceResult(singletonList(CacheInfo.empty())); + given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(50); + given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(51); + given(cacheDefaultProperties.getBannerTtl()).willReturn(60); + given(cacheDefaultProperties.getVideoTtl()).willReturn(61); + given(cacheDefaultProperties.getAudioTtl()).willReturn(62); + given(cacheDefaultProperties.getNativeTtl()).willReturn(63); + + // when + final Future response = target.create(auctionContext, cacheInfo, MULTI_BIDS); + + // then + final ArgumentCaptor contextArgumentCaptor = ArgumentCaptor.forClass(CacheContext.class); + final ArgumentCaptor> bidsArgumentCaptor = ArgumentCaptor.forClass(List.class); + + assertThat(response.succeeded()).isTrue(); + assertThat(response.result().getSeatbid()).flatExtracting(SeatBid::getBid).extracting(Bid::getExp) + .containsExactly(51); + + verify(coreCacheService).cacheBidsOpenrtb( + bidsArgumentCaptor.capture(), + same(auctionContext), + contextArgumentCaptor.capture(), + any()); + + final List capturedBidInfo = bidsArgumentCaptor.getValue(); + assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); + assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(51); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsOnly(51); + assertThat(contextArgumentCaptor.getValue()) + .satisfies(context -> { + assertThat(context.isShouldCacheBids()).isTrue(); + assertThat(context.isShouldCacheVideoBids()).isTrue(); + }); + } + + @Test + public void createShouldSendCacheRequestWithExpectedTtlAndSetDefaultTtlForBannerBid() { + // given + final Bid bid = Bid.builder().id("bidId").impid("impId").exp(null).price(BigDecimal.valueOf(5.67)).build(); + final Imp imp = Imp.builder().id("impId").exp(null).build(); + final List bidderResponses = asList(BidderResponse.of( + "bidder1", + givenSeatBid(BidderBid.of(bid, banner, "USD")), + 100)); + + final BidRequestCacheInfo cacheInfo = BidRequestCacheInfo.builder() + .doCaching(true) + .shouldCacheBids(true) + .shouldCacheVideoBids(true) + .cacheBidsTtl(null) + .cacheVideoBidsTtl(null) + .build(); + + final AuctionContext auctionContext = givenAuctionContext( + givenBidRequest(builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() + .events(mapper.createObjectNode()) + .build())), imp), + builder -> builder.account(Account.builder() + .id("accountId") + .auction(AccountAuctionConfig.builder() + .bannerCacheTtl(null) + .videoCacheTtl(41) + .events(AccountEventsConfig.of(true)) + .build()) + .build())) + .with(toAuctionParticipant(bidderResponses)); + + // just a stub to get through method call chain + givenCacheServiceResult(singletonList(CacheInfo.empty())); + given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(null); + given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(51); + given(cacheDefaultProperties.getBannerTtl()).willReturn(60); + given(cacheDefaultProperties.getVideoTtl()).willReturn(61); + given(cacheDefaultProperties.getAudioTtl()).willReturn(62); + given(cacheDefaultProperties.getNativeTtl()).willReturn(63); + + // when + final Future response = target.create(auctionContext, cacheInfo, MULTI_BIDS); + + // then + final ArgumentCaptor contextArgumentCaptor = ArgumentCaptor.forClass(CacheContext.class); + final ArgumentCaptor> bidsArgumentCaptor = ArgumentCaptor.forClass(List.class); + + assertThat(response.succeeded()).isTrue(); + assertThat(response.result().getSeatbid()).flatExtracting(SeatBid::getBid).extracting(Bid::getExp) + .containsExactly(60); + + verify(coreCacheService).cacheBidsOpenrtb( + bidsArgumentCaptor.capture(), + same(auctionContext), + contextArgumentCaptor.capture(), + any()); + + final List capturedBidInfo = bidsArgumentCaptor.getValue(); + assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); + assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(60); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsNull(); + assertThat(contextArgumentCaptor.getValue()) + .satisfies(context -> { + assertThat(context.isShouldCacheBids()).isTrue(); + assertThat(context.isShouldCacheVideoBids()).isTrue(); + }); + } + + @Test + public void createShouldSendCacheRequestWithExpectedTtlAndSetDefaultTtlForVideoBid() { + // given + final Bid bid = Bid.builder().id("bidId").impid("impId").exp(null).price(BigDecimal.valueOf(5.67)).build(); + final Imp imp = Imp.builder().id("impId").exp(null).build(); + final List bidderResponses = asList(BidderResponse.of( + "bidder1", + givenSeatBid(BidderBid.of(bid, video, "USD")), + 100)); + + final BidRequestCacheInfo cacheInfo = BidRequestCacheInfo.builder() + .doCaching(true) + .shouldCacheBids(true) + .shouldCacheVideoBids(true) + .cacheBidsTtl(null) + .cacheVideoBidsTtl(null) + .build(); + + final AuctionContext auctionContext = givenAuctionContext( + givenBidRequest(builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() + .events(mapper.createObjectNode()) + .build())), imp), + builder -> builder.account(Account.builder() + .id("accountId") + .auction(AccountAuctionConfig.builder() + .bannerCacheTtl(40) + .videoCacheTtl(null) + .events(AccountEventsConfig.of(true)) + .build()) + .build())) + .with(toAuctionParticipant(bidderResponses)); + + // just a stub to get through method call chain + givenCacheServiceResult(singletonList(CacheInfo.empty())); + given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(50); + given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(null); + given(cacheDefaultProperties.getBannerTtl()).willReturn(60); + given(cacheDefaultProperties.getVideoTtl()).willReturn(61); + given(cacheDefaultProperties.getAudioTtl()).willReturn(62); + given(cacheDefaultProperties.getNativeTtl()).willReturn(63); + + // when + final Future response = target.create(auctionContext, cacheInfo, MULTI_BIDS); + + // then + final ArgumentCaptor contextArgumentCaptor = ArgumentCaptor.forClass(CacheContext.class); + final ArgumentCaptor> bidsArgumentCaptor = ArgumentCaptor.forClass(List.class); + + assertThat(response.succeeded()).isTrue(); + assertThat(response.result().getSeatbid()).flatExtracting(SeatBid::getBid).extracting(Bid::getExp) + .containsExactly(61); + + verify(coreCacheService).cacheBidsOpenrtb( + bidsArgumentCaptor.capture(), + same(auctionContext), + contextArgumentCaptor.capture(), + any()); + + final List capturedBidInfo = bidsArgumentCaptor.getValue(); + assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); + assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(61); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsOnly(61); + assertThat(contextArgumentCaptor.getValue()) + .satisfies(context -> { + assertThat(context.isShouldCacheBids()).isTrue(); + assertThat(context.isShouldCacheVideoBids()).isTrue(); + }); + } + + @Test + public void createShouldSendCacheRequestWithExpectedTtlAndSetDefaultTtlForAudioBid() { + // given + final Bid bid = Bid.builder().id("bidId").impid("impId").exp(null).price(BigDecimal.valueOf(5.67)).build(); + final Imp imp = Imp.builder().id("impId").exp(null).build(); + final List bidderResponses = asList(BidderResponse.of( + "bidder1", + givenSeatBid(BidderBid.of(bid, audio, "USD")), + 100)); + + final BidRequestCacheInfo cacheInfo = BidRequestCacheInfo.builder() + .doCaching(true) + .shouldCacheBids(true) + .shouldCacheVideoBids(true) + .cacheBidsTtl(null) + .cacheVideoBidsTtl(null) + .build(); + + final AuctionContext auctionContext = givenAuctionContext( + givenBidRequest(builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() + .events(mapper.createObjectNode()) + .build())), imp), + builder -> builder.account(Account.builder() + .id("accountId") + .auction(AccountAuctionConfig.builder() + .bannerCacheTtl(40) + .videoCacheTtl(41) + .events(AccountEventsConfig.of(true)) + .build()) + .build())) + .with(toAuctionParticipant(bidderResponses)); + + // just a stub to get through method call chain + givenCacheServiceResult(singletonList(CacheInfo.empty())); + given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(50); + given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(51); + given(cacheDefaultProperties.getBannerTtl()).willReturn(60); + given(cacheDefaultProperties.getVideoTtl()).willReturn(61); + given(cacheDefaultProperties.getAudioTtl()).willReturn(62); + given(cacheDefaultProperties.getNativeTtl()).willReturn(63); + + // when + final Future response = target.create(auctionContext, cacheInfo, MULTI_BIDS); + + // then + final ArgumentCaptor contextArgumentCaptor = ArgumentCaptor.forClass(CacheContext.class); + final ArgumentCaptor> bidsArgumentCaptor = ArgumentCaptor.forClass(List.class); + + assertThat(response.succeeded()).isTrue(); + assertThat(response.result().getSeatbid()).flatExtracting(SeatBid::getBid).extracting(Bid::getExp) + .containsExactly(62); + + verify(coreCacheService).cacheBidsOpenrtb( + bidsArgumentCaptor.capture(), + same(auctionContext), + contextArgumentCaptor.capture(), + any()); + + final List capturedBidInfo = bidsArgumentCaptor.getValue(); + assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); + assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(62); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsNull(); + assertThat(contextArgumentCaptor.getValue()) + .satisfies(context -> { + assertThat(context.isShouldCacheBids()).isTrue(); + assertThat(context.isShouldCacheVideoBids()).isTrue(); + }); + } + + @Test + public void createShouldSendCacheRequestWithExpectedTtlAndSetDefaultTtlForNativeBid() { + // given + final Bid bid = Bid.builder().id("bidId").impid("impId").exp(null).price(BigDecimal.valueOf(5.67)).build(); + final Imp imp = Imp.builder().id("impId").exp(null).build(); + final List bidderResponses = asList(BidderResponse.of( + "bidder1", + givenSeatBid(BidderBid.of(bid, xNative, "USD")), + 100)); + + final BidRequestCacheInfo cacheInfo = BidRequestCacheInfo.builder() + .doCaching(true) + .shouldCacheBids(true) + .shouldCacheVideoBids(true) + .cacheBidsTtl(null) + .cacheVideoBidsTtl(null) + .build(); + + final AuctionContext auctionContext = givenAuctionContext( + givenBidRequest(builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() + .events(mapper.createObjectNode()) + .build())), imp), + builder -> builder.account(Account.builder() + .id("accountId") + .auction(AccountAuctionConfig.builder() + .bannerCacheTtl(40) + .videoCacheTtl(41) + .events(AccountEventsConfig.of(true)) + .build()) + .build())) + .with(toAuctionParticipant(bidderResponses)); + + // just a stub to get through method call chain + givenCacheServiceResult(singletonList(CacheInfo.empty())); + given(mediaTypeCacheTtl.getBannerCacheTtl()).willReturn(50); + given(mediaTypeCacheTtl.getVideoCacheTtl()).willReturn(51); + given(cacheDefaultProperties.getBannerTtl()).willReturn(60); + given(cacheDefaultProperties.getVideoTtl()).willReturn(61); + given(cacheDefaultProperties.getAudioTtl()).willReturn(62); + given(cacheDefaultProperties.getNativeTtl()).willReturn(63); + + // when + final Future response = target.create(auctionContext, cacheInfo, MULTI_BIDS); + + // then + final ArgumentCaptor contextArgumentCaptor = ArgumentCaptor.forClass(CacheContext.class); + final ArgumentCaptor> bidsArgumentCaptor = ArgumentCaptor.forClass(List.class); + + assertThat(response.succeeded()).isTrue(); + assertThat(response.result().getSeatbid()).flatExtracting(SeatBid::getBid).extracting(Bid::getExp) + .containsExactly(63); + + verify(coreCacheService).cacheBidsOpenrtb( + bidsArgumentCaptor.capture(), + same(auctionContext), + contextArgumentCaptor.capture(), + any()); + + final List capturedBidInfo = bidsArgumentCaptor.getValue(); + assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); + assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsOnly(63); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsNull(); assertThat(contextArgumentCaptor.getValue()) .satisfies(context -> { assertThat(context.isShouldCacheBids()).isTrue(); @@ -4277,7 +4734,7 @@ public void createShouldSendCacheRequestWithVideoBidWithTtlMaxOfTtlAndVideoTtl() final List capturedBidInfo = bidsArgumentCaptor.getValue(); assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsExactly(30); - assertThat(capturedBidInfo).extracting(BidInfo::getVideoTtl).containsExactly(40); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsExactly(40); assertThat(contextArgumentCaptor.getValue()) .satisfies(context -> { assertThat(context.isShouldCacheBids()).isTrue(); @@ -4336,7 +4793,7 @@ public void createShouldSendCacheRequestWithBannerBidWithTtlMaxOfTtlAndVideoTtl( final List capturedBidInfo = bidsArgumentCaptor.getValue(); assertThat(capturedBidInfo).extracting(bidInfo -> bidInfo.getBid().getId()).containsOnly("bidId"); assertThat(capturedBidInfo).extracting(BidInfo::getTtl).containsExactly(30); - assertThat(capturedBidInfo).extracting(BidInfo::getVideoTtl).containsOnlyNulls(); + assertThat(capturedBidInfo).extracting(BidInfo::getVastTtl).containsOnlyNulls(); assertThat(contextArgumentCaptor.getValue()) .satisfies(context -> { assertThat(context.isShouldCacheBids()).isTrue(); @@ -4576,7 +5033,8 @@ private BidResponseCreator givenBidResponseCreator(int truncateAttrChars) { truncateAttrChars, clock, jacksonMapper, - mediaTypeCacheTtl); + mediaTypeCacheTtl, + cacheDefaultProperties); } private static String toTargetingByKey(Bid bid, String targetingKey) { @@ -4604,11 +5062,4 @@ private static ObjectNode extWithTargeting(String targetBidderCode, Map List mutableList(T... values) { return Arrays.stream(values).collect(Collectors.toCollection(ArrayList::new)); } - - @Accessors(fluent = true) - @Value(staticConstructor = "of") - private static class BidderResponsePayloadImpl implements BidderResponsePayload { - - List bids; - } } diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index c50377f0667..8c1c20ac35e 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -84,13 +84,13 @@ 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.auction.AuctionResponsePayloadImpl; import org.prebid.server.hooks.execution.v1.bidder.BidderRequestPayloadImpl; import org.prebid.server.hooks.execution.v1.bidder.BidderResponsePayloadImpl; -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.CriteriaLogManager; import org.prebid.server.log.HttpInteractionLogger; import org.prebid.server.metric.MetricName; @@ -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; @@ -3567,124 +3565,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..4d90cc637d7 --- /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.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; +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/privacy/enforcement/ActivityEnforcementTest.java b/src/test/java/org/prebid/server/auction/privacy/enforcement/ActivityEnforcementTest.java index a6c2e3f3ae9..6e4662a501d 100644 --- a/src/test/java/org/prebid/server/auction/privacy/enforcement/ActivityEnforcementTest.java +++ b/src/test/java/org/prebid/server/auction/privacy/enforcement/ActivityEnforcementTest.java @@ -9,6 +9,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.activity.infrastructure.ActivityInfrastructure; +import org.prebid.server.auction.BidderAliases; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidderPrivacyResult; import org.prebid.server.auction.privacy.enforcement.mask.UserFpdActivityMask; @@ -32,6 +33,9 @@ public class ActivityEnforcementTest { @Mock private ActivityInfrastructure activityInfrastructure; + @Mock + private BidderAliases bidderAliases; + @BeforeEach public void setUp() { target = new ActivityEnforcement(userFpdActivityMask); @@ -58,7 +62,8 @@ public void enforceShouldReturnExpectedResult() { final AuctionContext context = givenAuctionContext(); // when - final List result = target.enforce(singletonList(bidderPrivacyResult), context).result(); + final List result = + target.enforce(context, bidderAliases, singletonList(bidderPrivacyResult)).result(); //then assertThat(result).allSatisfy(privacyResult -> { diff --git a/src/test/java/org/prebid/server/auction/privacy/enforcement/CcpaEnforcementTest.java b/src/test/java/org/prebid/server/auction/privacy/enforcement/CcpaEnforcementTest.java index 5459e232386..9bf76d427f9 100644 --- a/src/test/java/org/prebid/server/auction/privacy/enforcement/CcpaEnforcementTest.java +++ b/src/test/java/org/prebid/server/auction/privacy/enforcement/CcpaEnforcementTest.java @@ -30,7 +30,6 @@ import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.function.UnaryOperator; @@ -89,16 +88,17 @@ public void setUp() { } @Test - public void enforceShouldReturnEmptyListWhenCcpaNotEnforced() { + public void enforceShouldNotModifyListWhenCcpaIsNotEnforced() { // given final AuctionContext auctionContext = givenAuctionContext(context -> context .privacyContext(PrivacyContext.of(Privacy.builder().ccpa(Ccpa.of("1YN-")).build(), null, null))); + final List initialResults = givenPrivacyResults(givenUser(), givenDevice()); // when - final List result = target.enforce(auctionContext, null, aliases).result(); + final List result = target.enforce(auctionContext, aliases, initialResults).result(); // then - assertThat(result).isEmpty(); + assertThat(result).containsExactlyInAnyOrderElementsOf(initialResults); verify(metrics).updatePrivacyCcpaMetrics( eq(activityInfrastructure), eq(true), @@ -112,13 +112,15 @@ public void enforceShouldConsiderEnforceCcpaConfigurationProperty() { // given final AuctionContext auctionContext = givenAuctionContext(context -> context.account(Account.empty("id"))); + final List initialResults = givenPrivacyResults(givenUser(), givenDevice()); + target = new CcpaEnforcement(userFpdCcpaMask, bidderCatalog, metrics, false); // when - final List result = target.enforce(auctionContext, null, aliases).result(); + final List result = target.enforce(auctionContext, aliases, initialResults).result(); // then - assertThat(result).isEmpty(); + assertThat(result).containsExactlyInAnyOrderElementsOf(initialResults); verify(metrics).updatePrivacyCcpaMetrics( eq(activityInfrastructure), eq(true), @@ -136,12 +138,13 @@ public void enforceShouldConsiderAccountCcpaEnabledProperty() { .ccpa(AccountCcpaConfig.builder().enabled(false).build()) .build()) .build())); + final List initialResults = givenPrivacyResults(givenUser(), givenDevice()); // when - final List result = target.enforce(auctionContext, null, aliases).result(); + final List result = target.enforce(auctionContext, aliases, initialResults).result(); // then - assertThat(result).isEmpty(); + assertThat(result).containsExactlyInAnyOrderElementsOf(initialResults); verify(metrics).updatePrivacyCcpaMetrics( eq(activityInfrastructure), eq(true), @@ -155,12 +158,13 @@ public void enforceShouldConsiderAccountCcpaEnabledForRequestTypeProperty() { // given final AuctionContext auctionContext = givenAuctionContext(context -> context .requestTypeMetric(MetricName.openrtb2app)); + final List initialResults = givenPrivacyResults(givenUser(), givenDevice()); // when - final List result = target.enforce(auctionContext, null, aliases).result(); + final List result = target.enforce(auctionContext, aliases, initialResults).result(); // then - assertThat(result).isEmpty(); + assertThat(result).containsExactlyInAnyOrderElementsOf(initialResults); verify(metrics).updatePrivacyCcpaMetrics( eq(activityInfrastructure), eq(true), @@ -174,21 +178,21 @@ public void enforceShouldTreatAllBiddersAsNoSale() { // given final AuctionContext auctionContext = givenAuctionContext(context -> context .bidRequest(BidRequest.builder() - .device(Device.builder().ip("originalDevice").build()) + .device(givenDevice()) .ext(ExtRequest.of(ExtRequestPrebid.builder() .nosale(singletonList("*")) .build())) .build())); - final Map bidderToUser = Map.of( - "bidder", User.builder().id("originalUser").build(), - "noSale", User.builder().id("originalUser").build()); + final List initialResults = List.of( + BidderPrivacyResult.builder().requestBidder("bidder").user(givenUser()).device(givenDevice()).build(), + BidderPrivacyResult.builder().requestBidder("noSale").user(givenUser()).device(givenDevice()).build()); // when - final List result = target.enforce(auctionContext, bidderToUser, aliases).result(); + final List result = target.enforce(auctionContext, aliases, initialResults).result(); // then - assertThat(result).isEmpty(); + assertThat(result).containsExactlyInAnyOrderElementsOf(initialResults); verify(metrics).updatePrivacyCcpaMetrics( eq(activityInfrastructure), eq(true), @@ -222,15 +226,23 @@ public void enforceShouldSkipNoSaleBiddersAndNotEnforcedByBidderConfig() { final AuctionContext auctionContext = givenAuctionContext(identity()); - final Map bidderToUser = Map.of( - "bidderAlias", User.builder().id("originalUser").build(), - "noSale", User.builder().id("originalUser").build()); + final List initialResults = List.of( + BidderPrivacyResult.builder() + .requestBidder("bidderAlias") + .user(givenUser()) + .device(givenDevice()) + .build(), + BidderPrivacyResult.builder() + .requestBidder("noSale") + .user(givenUser()) + .device(givenDevice()) + .build()); // when - final List result = target.enforce(auctionContext, bidderToUser, aliases).result(); + final List result = target.enforce(auctionContext, aliases, initialResults).result(); // then - assertThat(result).isEmpty(); + assertThat(result).containsExactlyInAnyOrderElementsOf(initialResults); verify(metrics).updatePrivacyCcpaMetrics( eq(activityInfrastructure), eq(true), @@ -250,20 +262,18 @@ public void enforceShouldReturnExpectedResult() { final AuctionContext auctionContext = givenAuctionContext(identity()); - final Map bidderToUser = Map.of( - "bidder", User.builder().id("originalUser").build(), - "noSale", User.builder().id("originalUser").build()); + final List initialResults = List.of( + BidderPrivacyResult.builder().requestBidder("bidder").user(givenUser()).device(givenDevice()).build(), + BidderPrivacyResult.builder().requestBidder("noSale").user(givenUser()).device(givenDevice()).build()); // when - final List result = target.enforce(auctionContext, bidderToUser, aliases).result(); + final List result = target.enforce(auctionContext, aliases, initialResults).result(); // then - assertThat(result) - .hasSize(1) - .allSatisfy(privacyResult -> { - assertThat(privacyResult.getUser()).isSameAs(maskedUser); - assertThat(privacyResult.getDevice()).isSameAs(maskedDevice); - }); + assertThat(result).containsExactlyInAnyOrder( + BidderPrivacyResult.builder().requestBidder("bidder").user(maskedUser).device(maskedDevice).build(), + BidderPrivacyResult.builder().requestBidder("noSale").user(givenUser()).device(givenDevice()).build()); + verify(metrics).updatePrivacyCcpaMetrics( eq(activityInfrastructure), eq(true), @@ -278,7 +288,7 @@ private AuctionContext givenAuctionContext( final AuctionContext.AuctionContextBuilder initialContext = AuctionContext.builder() .activityInfrastructure(activityInfrastructure) .bidRequest(BidRequest.builder() - .device(Device.builder().ip("originalDevice").build()) + .device(givenDevice()) .ext(ExtRequest.of(ExtRequestPrebid.builder() .nosale(singletonList("noSale")) .build())) @@ -301,4 +311,16 @@ private AuctionContext givenAuctionContext( return auctionContextCustomizer.apply(initialContext).build(); } + + private static List givenPrivacyResults(User user, Device device) { + return singletonList(BidderPrivacyResult.builder().requestBidder("bidder").user(user).device(device).build()); + } + + private static User givenUser() { + return User.builder().id("originalUser").build(); + } + + private static Device givenDevice() { + return Device.builder().ip("originalDevice").build(); + } } diff --git a/src/test/java/org/prebid/server/auction/privacy/enforcement/CoppaEnforcementTest.java b/src/test/java/org/prebid/server/auction/privacy/enforcement/CoppaEnforcementTest.java index 4b7e7de8628..9e9d47d0488 100644 --- a/src/test/java/org/prebid/server/auction/privacy/enforcement/CoppaEnforcementTest.java +++ b/src/test/java/org/prebid/server/auction/privacy/enforcement/CoppaEnforcementTest.java @@ -9,6 +9,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.activity.infrastructure.ActivityInfrastructure; +import org.prebid.server.auction.BidderAliases; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidderPrivacyResult; import org.prebid.server.auction.privacy.enforcement.mask.UserFpdCoppaMask; @@ -17,13 +18,14 @@ import org.prebid.server.privacy.model.PrivacyContext; import java.util.List; -import java.util.Map; import java.util.Set; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; @ExtendWith(MockitoExtension.class) public class CoppaEnforcementTest { @@ -34,6 +36,8 @@ public class CoppaEnforcementTest { private Metrics metrics; @Mock private ActivityInfrastructure activityInfrastructure; + @Mock + private BidderAliases bidderAliases; private CoppaEnforcement target; @@ -43,29 +47,33 @@ public void setUp() { } @Test - public void isApplicableShouldReturnFalse() { + public void enforceShouldNotMaskDataWhenNotApplicable() { // given final AuctionContext auctionContext = AuctionContext.builder() + .activityInfrastructure(activityInfrastructure) .privacyContext(PrivacyContext.of(Privacy.builder().coppa(0).build(), null, null)) + .bidRequest(BidRequest.builder().build()) .build(); - // when and then - assertThat(target.isApplicable(auctionContext)).isFalse(); - } + final List initialResults = singletonList( + BidderPrivacyResult.builder() + .requestBidder("bidder") + .user(User.builder().id("originalUser").build()) + .device(Device.builder().ip("originalDevice").build()) + .build()); - @Test - public void isApplicableShouldReturnTrue() { - // given - final AuctionContext auctionContext = AuctionContext.builder() - .privacyContext(PrivacyContext.of(Privacy.builder().coppa(1).build(), null, null)) - .build(); + // when + final List results = + target.enforce(auctionContext, bidderAliases, initialResults).result(); - // when and then - assertThat(target.isApplicable(auctionContext)).isTrue(); + // then + assertThat(results).containsExactlyInAnyOrderElementsOf(initialResults); + verifyNoInteractions(userFpdCoppaMask); + verifyNoInteractions(metrics); } @Test - public void enforceShouldReturnExpectedResultAndEmitMetrics() { + public void enforceShouldMaskDataAndEmitMetricsWhenApplicable() { // given final User maskedUser = User.builder().id("maskedUser").build(); final Device maskedDevice = Device.builder().ip("maskedDevice").build(); @@ -75,12 +83,19 @@ public void enforceShouldReturnExpectedResultAndEmitMetrics() { final AuctionContext auctionContext = AuctionContext.builder() .activityInfrastructure(activityInfrastructure) - .bidRequest(BidRequest.builder().device(Device.builder().ip("originalDevice").build()).build()) + .privacyContext(PrivacyContext.of(Privacy.builder().coppa(1).build(), null, null)) + .bidRequest(BidRequest.builder().build()) .build(); - final Map bidderToUser = Map.of("bidder", User.builder().id("originalUser").build()); + + final List initialResults = singletonList( + BidderPrivacyResult.builder() + .requestBidder("bidder") + .user(User.builder().id("originalUser").build()) + .device(Device.builder().ip("originalDevice").build()) + .build()); // when - final List result = target.enforce(auctionContext, bidderToUser).result(); + final List result = target.enforce(auctionContext, bidderAliases, initialResults).result(); // then assertThat(result).allSatisfy(privacyResult -> { diff --git a/src/test/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcementServiceTest.java b/src/test/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcementServiceTest.java index f1a49f91738..4333eebb5d8 100644 --- a/src/test/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcementServiceTest.java +++ b/src/test/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcementServiceTest.java @@ -1,98 +1,70 @@ package org.prebid.server.auction.privacy.enforcement; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Device; import com.iab.openrtb.request.User; import io.vertx.core.Future; -import org.apache.commons.collections4.ListUtils; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.auction.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidderPrivacyResult; import java.util.List; import java.util.Map; -import static java.util.Collections.singleton; import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import static java.util.Collections.singletonMap; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; +import static org.prebid.server.assertion.FutureAssertion.assertThat; @ExtendWith(MockitoExtension.class) public class PrivacyEnforcementServiceTest { @Mock - private CoppaEnforcement coppaEnforcement; - @Mock - private CcpaEnforcement ccpaEnforcement; - @Mock - private TcfEnforcement tcfEnforcement; - @Mock - private ActivityEnforcement activityEnforcement; - - private PrivacyEnforcementService target; + private PrivacyEnforcement firstEnforcement; - @BeforeEach - public void setUp() { - target = new PrivacyEnforcementService( - coppaEnforcement, - ccpaEnforcement, - tcfEnforcement, - activityEnforcement); - } - - @Test - public void maskShouldUseCoppaEnforcementIfApplicable() { - // given - given(coppaEnforcement.isApplicable(any())).willReturn(true); - - final List bidderPrivacyResults = singletonList(null); - given(coppaEnforcement.enforce(any(), any())).willReturn(Future.succeededFuture(bidderPrivacyResults)); - - // when - final List result = target.mask(null, null, null).result(); + @Mock + private PrivacyEnforcement secondEnforcement; - // then - assertThat(result).isSameAs(bidderPrivacyResults); - verifyNoInteractions(ccpaEnforcement); - verifyNoInteractions(tcfEnforcement); - verifyNoInteractions(activityEnforcement); - } + @Mock + private BidderAliases bidderAliases; @Test - public void maskShouldReturnExpectedResult() { + public void maskShouldPassBidderPrivacyThroughAllEnforcements() { // given - given(coppaEnforcement.isApplicable(any())).willReturn(false); + final BidderPrivacyResult expectedResult = BidderPrivacyResult.builder() + .requestBidder("bidder") + .user(User.EMPTY) + .device(Device.builder().build()) + .build(); - given(ccpaEnforcement.enforce(any(), any(), any())).willReturn(Future.succeededFuture( - singletonList(BidderPrivacyResult.builder().requestBidder("bidder1").build()))); + given(firstEnforcement.enforce(any(), any(), any())) + .willReturn(Future.succeededFuture(singletonList(expectedResult))); + given(secondEnforcement.enforce(any(), any(), any())) + .willReturn(Future.succeededFuture(singletonList(expectedResult))); - given(tcfEnforcement.enforce(any(), any(), eq(singleton("bidder0")), any())) - .willReturn(Future.succeededFuture( - singletonList(BidderPrivacyResult.builder().requestBidder("bidder0").build()))); + final PrivacyEnforcementService target = new PrivacyEnforcementService( + List.of(firstEnforcement, secondEnforcement)); - given(activityEnforcement.enforce(any(), any())) - .willAnswer(invocation -> Future.succeededFuture(ListUtils.union( - invocation.getArgument(0), - singletonList(BidderPrivacyResult.builder().requestBidder("bidder2").build())))); + final AuctionContext auctionContext = AuctionContext.builder() + .bidRequest(BidRequest.builder().device(Device.builder().build()).build()) + .build(); - final Map bidderToUser = Map.of( - "bidder0", User.builder().build(), - "bidder1", User.builder().build()); + final User user = User.builder().id("originalUser").build(); + final Map bidderToUser = singletonMap("bidder", user); // when - final List result = target.mask(null, bidderToUser, null).result(); + final Future> result = target.mask(auctionContext, bidderToUser, bidderAliases); // then - assertThat(result).containsExactly( - BidderPrivacyResult.builder().requestBidder("bidder1").build(), - BidderPrivacyResult.builder().requestBidder("bidder0").build(), - BidderPrivacyResult.builder().requestBidder("bidder2").build()); - verify(coppaEnforcement, times(0)).enforce(any(), any()); + assertThat(result) + .isSucceeded() + .unwrap() + .asList() + .containsExactlyInAnyOrder(expectedResult); } } diff --git a/src/test/java/org/prebid/server/auction/privacy/enforcement/TcfEnforcementTest.java b/src/test/java/org/prebid/server/auction/privacy/enforcement/TcfEnforcementTest.java index 0dad839fc87..0ed33b4cce3 100644 --- a/src/test/java/org/prebid/server/auction/privacy/enforcement/TcfEnforcementTest.java +++ b/src/test/java/org/prebid/server/auction/privacy/enforcement/TcfEnforcementTest.java @@ -119,20 +119,20 @@ public void enforceShouldEmitExpectedMetricsWhenUserAndDeviceHavePrivacyData() { "bidder6", givenEnforcementAction(PrivacyEnforcementAction::setMaskDeviceInfo), "bidder7", givenEnforcementAction(PrivacyEnforcementAction::setRemoveUserFpd))); - final AuctionContext auctionContext = givenAuctionContext(givenDeviceWithPrivacyData()); - final Map bidderToUser = Map.of( - "bidder0", givenUserWithPrivacyData(), - "bidder1Alias", givenUserWithPrivacyData(), - "bidder2", givenUserWithPrivacyData(), - "bidder3", givenUserWithPrivacyData(), - "bidder4", givenUserWithPrivacyData(), - "bidder5", givenUserWithPrivacyData(), - "bidder6", givenUserWithPrivacyData(), - "bidder7", givenUserWithPrivacyData()); - final Set bidders = Set.of(); + final Device device = givenDeviceWithPrivacyData(); + final AuctionContext auctionContext = givenAuctionContext(device); + final List initialResults = List.of( + givenBidderPrivacyResult("bidder0", givenUserWithPrivacyData(), device), + givenBidderPrivacyResult("bidder1Alias", givenUserWithPrivacyData(), device), + givenBidderPrivacyResult("bidder2", givenUserWithPrivacyData(), device), + givenBidderPrivacyResult("bidder3", givenUserWithPrivacyData(), device), + givenBidderPrivacyResult("bidder4", givenUserWithPrivacyData(), device), + givenBidderPrivacyResult("bidder5", givenUserWithPrivacyData(), device), + givenBidderPrivacyResult("bidder6", givenUserWithPrivacyData(), device), + givenBidderPrivacyResult("bidder7", givenUserWithPrivacyData(), device)); // when - target.enforce(auctionContext, bidderToUser, bidders, aliases); + target.enforce(auctionContext, aliases, initialResults); // then verifyMetric("bidder0", false, false, false, false, false, true); @@ -155,14 +155,13 @@ public void enforceShouldEmitExpectedMetricsWhenUserHasPrivacyData() { "bidder2", givenEnforcementAction(PrivacyEnforcementAction::setRemoveUserFpd))); final AuctionContext auctionContext = givenAuctionContext(givenDeviceWithNoPrivacyData()); - final Map bidderToUser = Map.of( - "bidder0", givenUserWithPrivacyData(), - "bidder1", givenUserWithPrivacyData(), - "bidder2", givenUserWithPrivacyData()); - final Set bidders = Set.of(); + final List initialResults = List.of( + givenBidderPrivacyResult("bidder0", givenUserWithPrivacyData(), givenDeviceWithNoPrivacyData()), + givenBidderPrivacyResult("bidder1", givenUserWithPrivacyData(), givenDeviceWithNoPrivacyData()), + givenBidderPrivacyResult("bidder2", givenUserWithPrivacyData(), givenDeviceWithNoPrivacyData())); // when - target.enforce(auctionContext, bidderToUser, bidders, aliases); + target.enforce(auctionContext, aliases, initialResults); // then verifyMetric("bidder0", false, false, true, false, false, false); @@ -179,17 +178,17 @@ public void enforceShouldEmitExpectedMetricsWhenDeviceHavePrivacyData() { "bidder2", givenEnforcementAction(PrivacyEnforcementAction::setMaskDeviceInfo), "bidder3", givenEnforcementAction(PrivacyEnforcementAction::setRemoveUserFpd))); - final AuctionContext auctionContext = givenAuctionContext(givenDeviceWithPrivacyData()); - final Map bidderToUser = Map.of( - "bidder0", givenUserWithNoPrivacyData(), - "bidder1", givenUserWithNoPrivacyData(), - "bidder2", givenUserWithNoPrivacyData(), - "bidder3", givenUserWithNoPrivacyData(), - "bidder4", givenUserWithNoPrivacyData()); - final Set bidders = Set.of(); + final Device device = givenDeviceWithPrivacyData(); + final AuctionContext auctionContext = givenAuctionContext(device); + final List initialResults = List.of( + givenBidderPrivacyResult("bidder0", givenUserWithNoPrivacyData(), device), + givenBidderPrivacyResult("bidder1", givenUserWithNoPrivacyData(), device), + givenBidderPrivacyResult("bidder2", givenUserWithNoPrivacyData(), device), + givenBidderPrivacyResult("bidder3", givenUserWithNoPrivacyData(), device), + givenBidderPrivacyResult("bidder4", givenUserWithNoPrivacyData(), device)); // when - target.enforce(auctionContext, bidderToUser, bidders, aliases); + target.enforce(auctionContext, aliases, initialResults); // then verifyMetric("bidder0", false, false, true, false, false, true); @@ -207,14 +206,14 @@ public void enforceShouldEmitExpectedMetricsWhenUserAndDeviceDoNotHavePrivacyDat PrivacyEnforcementAction::setRemoveUserFpd, PrivacyEnforcementAction::setMaskDeviceInfo))); - final AuctionContext auctionContext = givenAuctionContext(givenDeviceWithNoPrivacyData()); - final Map bidderToUser = Map.of( - "bidder0", givenUserWithNoPrivacyData(), - "bidder1", givenUserWithNoPrivacyData()); - final Set bidders = Set.of(); + final Device device = givenDeviceWithNoPrivacyData(); + final AuctionContext auctionContext = givenAuctionContext(device); + final List initialResults = List.of( + givenBidderPrivacyResult("bidder0", givenUserWithNoPrivacyData(), device), + givenBidderPrivacyResult("bidder1", givenUserWithNoPrivacyData(), device)); // when - target.enforce(auctionContext, bidderToUser, bidders, aliases); + target.enforce(auctionContext, aliases, initialResults); // then verifyMetric("bidder0", false, false, false, false, false, false); @@ -226,12 +225,13 @@ public void enforceShouldEmitPrivacyLmtMetric() { // give givenPrivacyEnforcementActions(Map.of("bidder", givenEnforcementAction())); - final AuctionContext auctionContext = givenAuctionContext(givenDeviceWithPrivacyData()); - final Map bidderToUser = Map.of("bidder", givenUserWithPrivacyData()); - final Set bidders = Set.of(); + final Device device = givenDeviceWithPrivacyData(); + final AuctionContext auctionContext = givenAuctionContext(device); + final List initialResults = List.of( + givenBidderPrivacyResult("bidder", givenUserWithPrivacyData(), device)); // when - target.enforce(auctionContext, bidderToUser, bidders, aliases); + target.enforce(auctionContext, aliases, initialResults); // then verifyMetric("bidder", false, false, false, false, false, true); @@ -242,12 +242,13 @@ public void enforceShouldNotEmitPrivacyLmtMetricWhenLmtNot1() { // give givenPrivacyEnforcementActions(Map.of("bidder", givenEnforcementAction())); - final AuctionContext auctionContext = givenAuctionContext(givenDeviceWithNoPrivacyData()); - final Map bidderToUser = Map.of("bidder", givenUserWithPrivacyData()); - final Set bidders = Set.of(); + final Device device = givenDeviceWithNoPrivacyData(); + final AuctionContext auctionContext = givenAuctionContext(device); + final List initialResults = List.of( + givenBidderPrivacyResult("bidder", givenUserWithPrivacyData(), device)); // when - target.enforce(auctionContext, bidderToUser, bidders, aliases); + target.enforce(auctionContext, aliases, initialResults); // then verifyMetric("bidder", false, false, false, false, false, false); @@ -259,14 +260,15 @@ public void enforceShouldNotEmitPrivacyLmtMetricWhenLmtNotEnforced() { // give givenPrivacyEnforcementActions(Map.of("bidder", givenEnforcementAction())); - final AuctionContext auctionContext = givenAuctionContext(givenDeviceWithPrivacyData()); - final Map bidderToUser = Map.of("bidder", givenUserWithPrivacyData()); - final Set bidders = Set.of(); + final Device device = givenDeviceWithPrivacyData(); + final AuctionContext auctionContext = givenAuctionContext(device); + final List initialResults = List.of( + givenBidderPrivacyResult("bidder", givenUserWithPrivacyData(), device)); target = new TcfEnforcement(tcfDefinerService, userFpdTcfMask, bidderCatalog, metrics, false); // when - target.enforce(auctionContext, bidderToUser, bidders, aliases); + target.enforce(auctionContext, aliases, initialResults); // then verifyMetric("bidder", false, false, false, false, false, false); @@ -297,15 +299,15 @@ public void enforceShouldMaskUserAndDeviceWhenRestrictionsEnforcedAndLmtNotEnabl PrivacyEnforcementAction::setBlockAnalyticsReport), "bidder2", givenEnforcementAction())); - final AuctionContext context = givenAuctionContext(givenDeviceWithNoPrivacyData()); - final Map bidderToUser = Map.of( - "bidder0", givenUserWithPrivacyData(), - "bidder1", givenUserWithPrivacyData(), - "bidder2", givenUserWithPrivacyData()); - final Set bidders = Set.of("bidder0", "bidder1", "bidder2"); + final Device device = givenDeviceWithNoPrivacyData(); + final AuctionContext context = givenAuctionContext(device); + final List initialResults = List.of( + givenBidderPrivacyResult("bidder0", givenUserWithPrivacyData(), device), + givenBidderPrivacyResult("bidder1", givenUserWithPrivacyData(), device), + givenBidderPrivacyResult("bidder2", givenUserWithPrivacyData(), device)); // when - final List result = target.enforce(context, bidderToUser, bidders, aliases).result(); + final List result = target.enforce(context, aliases, initialResults).result(); // then assertThat(result).containsExactlyInAnyOrder( @@ -343,12 +345,13 @@ public void enforceShouldMaskUserAndDeviceWhenRestrictionsNotEnforcedAndLmtEnabl givenPrivacyEnforcementActions(Map.of("bidder", givenEnforcementAction())); - final AuctionContext context = givenAuctionContext(givenDeviceWithPrivacyData()); - final Map bidderToUser = Map.of("bidder", givenUserWithPrivacyData()); - final Set bidders = Set.of("bidder"); + final Device device = givenDeviceWithPrivacyData(); + final AuctionContext context = givenAuctionContext(device); + final List initialResults = List.of( + givenBidderPrivacyResult("bidder", givenUserWithPrivacyData(), device)); // when - final List result = target.enforce(context, bidderToUser, bidders, aliases).result(); + final List result = target.enforce(context, aliases, initialResults).result(); // then assertThat(result).containsExactly( @@ -378,6 +381,10 @@ private AuctionContext givenAuctionContext(Device device) { .build(); } + private static BidderPrivacyResult givenBidderPrivacyResult(String bidder, User user, Device device) { + return BidderPrivacyResult.builder().requestBidder(bidder).user(user).device(device).build(); + } + private static Device givenDeviceWithPrivacyData() { return Device.builder() .ip("originalDevice") 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/bidadjustments/BidAdjustmentsProcessorTest.java b/src/test/java/org/prebid/server/bidadjustments/BidAdjustmentsProcessorTest.java index 33cfe50e09c..2affb167eef 100644 --- a/src/test/java/org/prebid/server/bidadjustments/BidAdjustmentsProcessorTest.java +++ b/src/test/java/org/prebid/server/bidadjustments/BidAdjustmentsProcessorTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import org.prebid.server.VertxTest; import org.prebid.server.auction.adjustment.BidAdjustmentFactorResolver; import org.prebid.server.auction.model.AuctionParticipation; @@ -47,7 +49,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; -import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -57,13 +58,14 @@ import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; @ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) public class BidAdjustmentsProcessorTest extends VertxTest { - @Mock(strictness = LENIENT) + @Mock private CurrencyConversionService currencyService; - @Mock(strictness = LENIENT) + @Mock private BidAdjustmentFactorResolver bidAdjustmentFactorResolver; - @Mock(strictness = LENIENT) + @Mock private BidAdjustmentsResolver bidAdjustmentsResolver; private BidAdjustmentsProcessor target; @@ -469,7 +471,7 @@ public void shouldReturnBidsWithAdjustedPricesWithVideoInstreamMediaTypeIfVideoP } @Test - public void shouldReturnBidsWithAdjustedPricesWithVideoInstreamMediaTypeIfVideoPlacementIsMissing() { + public void shouldReturnBidsWithAdjustedPricesWithVideoInstreamMediaTypeIfVideoPlcmtEqualsOne() { // given final BidderResponse bidderResponse = BidderResponse.of( "bidder", @@ -491,6 +493,58 @@ public void shouldReturnBidsWithAdjustedPricesWithVideoInstreamMediaTypeIfVideoP given(bidAdjustmentFactorResolver.resolve(ImpMediaType.video, givenAdjustments, "bidder")) .willReturn(BigDecimal.valueOf(3.456)); + final BidRequest bidRequest = givenBidRequest(singletonList(givenImp(singletonMap("bidder", 2), impBuilder -> + impBuilder.id("123").video(Video.builder().plcmt(1).build()))), + builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() + .aliases(emptyMap()) + .bidadjustmentfactors(givenAdjustments) + .auctiontimestamp(1000L) + .build()))); + + final AuctionParticipation auctionParticipation = givenAuctionParticipation(bidderResponse, bidRequest); + + // when + final AuctionParticipation result = target.enrichWithAdjustedBids( + auctionParticipation, bidRequest, givenBidAdjustments()); + + // then + assertThat(result.getBidderResponse().getSeatBid().getBids()) + .extracting(BidderBid::getBid) + .extracting(Bid::getPrice) + .containsExactly(BigDecimal.valueOf(6.912)); + + verify(bidAdjustmentsResolver).resolve( + eq(Price.of("USD", BigDecimal.valueOf(6.912))), + eq(bidRequest), + eq(givenBidAdjustments()), + eq(ImpMediaType.video_instream), + eq("bidder"), + eq("dealId")); + } + + @Test + public void shouldReturnBidsWithAdjustedPricesWithVideoOutstreamMediaTypeIfVideoPlacementAndPlcmtIsMissing() { + // given + final BidderResponse bidderResponse = BidderResponse.of( + "bidder", + BidderSeatBid.builder() + .bids(List.of( + givenBidderBid(Bid.builder() + .impid("123") + .price(BigDecimal.valueOf(2)) + .dealid("dealId") + .build(), + "USD", video))) + .build(), + 1); + + final ExtRequestBidAdjustmentFactors givenAdjustments = ExtRequestBidAdjustmentFactors.builder() + .mediatypes(new EnumMap<>(singletonMap(ImpMediaType.video, + singletonMap("bidder", BigDecimal.valueOf(3.456))))) + .build(); + given(bidAdjustmentFactorResolver.resolve(ImpMediaType.video_outstream, givenAdjustments, "bidder")) + .willReturn(BigDecimal.valueOf(3.456)); + final BidRequest bidRequest = givenBidRequest(singletonList(givenImp(singletonMap("bidder", 2), impBuilder -> impBuilder.id("123").video(Video.builder().build()))), builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() @@ -514,13 +568,13 @@ public void shouldReturnBidsWithAdjustedPricesWithVideoInstreamMediaTypeIfVideoP eq(Price.of("USD", BigDecimal.valueOf(6.912))), eq(bidRequest), eq(givenBidAdjustments()), - eq(ImpMediaType.video_instream), + eq(ImpMediaType.video_outstream), eq("bidder"), eq("dealId")); } @Test - public void shouldReturnBidAdjustmentMediaTypeNullIfImpIdNotEqualBidImpId() { + public void shouldReturnBidAdjustmentMediaTypeVideoOutstreamIfImpIdNotEqualBidImpId() { // given final BidderResponse bidderResponse = BidderResponse.of( "bidder", @@ -560,11 +614,12 @@ public void shouldReturnBidAdjustmentMediaTypeNullIfImpIdNotEqualBidImpId() { .extracting(Bid::getPrice) .containsExactly(BigDecimal.valueOf(2)); + verify(bidAdjustmentFactorResolver).resolve(ImpMediaType.video_outstream, givenAdjustments, "bidder"); verify(bidAdjustmentsResolver).resolve( eq(Price.of("USD", BigDecimal.valueOf(2))), eq(bidRequest), eq(givenBidAdjustments()), - eq(ImpMediaType.video_instream), + eq(ImpMediaType.video_outstream), eq("bidder"), eq("dealId")); } 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..aee1397e4c9 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.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.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,317 @@ 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); + + 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); + + 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 bidResponse = ampEvent.getBidResponse(); + final ExtModulesTraceAnalyticsTags expectedAnalyticsTags = ExtModulesTraceAnalyticsTags.of(singletonList( + ExtModulesTraceAnalyticsActivity.of( + "some-activity", + "success", + singletonList(ExtModulesTraceAnalyticsResult.of( + "success", + mapper.createObjectNode(), + givenExtModulesTraceAnalyticsAppliedTo()))))); + assertThat(bidResponse.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(expectedAnalyticsTags) + .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 bidResponse = ampEvent.getBidResponse(); + assertThat(bidResponse.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 +1363,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..1618caea8d2 100644 --- a/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/openrtb2/AuctionHandlerTest.java @@ -1,5 +1,6 @@ package org.prebid.server.handler.openrtb2; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; @@ -21,9 +22,11 @@ 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,10 +36,26 @@ 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.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.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.request.ExtGranularityRange; import org.prebid.server.proto.openrtb.ext.request.ExtMediaTypePriceGranularity; @@ -44,18 +63,36 @@ 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.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.function.UnaryOperator.identity; @@ -93,8 +130,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 @@ -118,24 +159,34 @@ 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()); 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 +201,7 @@ public void shouldSetRequestTypeMetricToAuctionContext() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then final AuctionContext auctionContext = captureAuctionContext(); @@ -168,7 +219,7 @@ public void shouldUseTimeoutFromAuctionContext() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(captureAuctionContext()) @@ -194,7 +245,7 @@ public void shouldAddPrebidVersionResponseHeader() { .build())); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(httpResponse.headers()) @@ -218,7 +269,7 @@ public void shouldAddObserveBrowsingTopicsResponseHeader() { .build())); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(httpResponse.headers()) @@ -240,7 +291,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 +311,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 +330,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 +349,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 +368,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 +387,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 +408,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 +429,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 +459,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 +542,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 +575,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 +593,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 +610,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 +632,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 +650,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 +669,7 @@ public void shouldIncrementImpTypesMetrics() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateImpTypesMetrics(same(imps)); @@ -564,7 +682,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 +695,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 +722,7 @@ public void shouldUpdateRequestTimeMetric() { }); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTimeMetric(eq(MetricName.request_time), eq(500L)); @@ -617,7 +735,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 +759,7 @@ public void shouldUpdateNetworkErrorMetric() { }); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then verify(metrics).updateRequestTypeMetric(eq(MetricName.openrtb2web), eq(MetricName.networkerr)); @@ -658,7 +776,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 +795,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 +808,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 +817,7 @@ public void shouldPassBadRequestEventToAnalyticsReporterIfBidRequestIsInvalid() .status(400) .errors(singletonList("Invalid request format: Request is invalid")) .build()); + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -714,7 +833,7 @@ public void shouldPassInternalServerErrorEventToAnalyticsReporterIfAuctionFails( .willThrow(new RuntimeException("Unexpected exception")); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then final AuctionEvent auctionEvent = captureAuctionEvent(); @@ -728,6 +847,8 @@ public void shouldPassInternalServerErrorEventToAnalyticsReporterIfAuctionFails( .status(500) .errors(singletonList("Unexpected exception")) .build()); + + verifyNoInteractions(hooksMetricsService, hookStageExecutor); } @Test @@ -742,22 +863,71 @@ 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()); + + 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")); - assertThat(auctionEvent).isEqualTo(AuctionEvent.builder() - .httpContext(givenHttpContext()) - .auctionContext(expectedAuctionContext) - .bidResponse(BidResponse.builder().build()) - .status(200) - .errors(emptyList()) - .build()); + 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()); + + 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 +944,7 @@ public void shouldTolerateDuplicateQueryParamNames() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then final AuctionEvent auctionEvent = captureAuctionEvent(); @@ -798,7 +968,7 @@ public void shouldTolerateDuplicateHeaderNames() { givenHoldAuction(BidResponse.builder().build()); // when - auctionHandler.handle(routingContext); + target.handle(routingContext); // then final AuctionEvent auctionEvent = captureAuctionEvent(); @@ -820,17 +990,236 @@ 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 bidResponse = auctionEvent.getBidResponse(); + final ExtModulesTraceAnalyticsTags expectedAnalyticsTags = ExtModulesTraceAnalyticsTags.of(singletonList( + ExtModulesTraceAnalyticsActivity.of( + "some-activity", + "success", + singletonList(ExtModulesTraceAnalyticsResult.of( + "success", + mapper.createObjectNode(), + givenExtModulesTraceAnalyticsAppliedTo()))))); + assertThat(bidResponse.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(expectedAnalyticsTags) + .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 bidResponse = auctionEvent.getBidResponse(); + assertThat(bidResponse.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 +1251,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..64efc34c093 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; @@ -56,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; @@ -86,8 +95,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 +120,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 +152,7 @@ public void shouldUseTimeoutFromAuctionContext() { givenHoldAuction(BidResponse.builder().build()); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(captureAuctionContext()) @@ -154,7 +176,7 @@ public void shouldAddPrebidVersionResponseHeader() { .build())); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(httpResponse.headers()) @@ -176,7 +198,7 @@ public void shouldAddObserveBrowsingTopicsResponseHeader() { .build())); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then assertThat(httpResponse.headers()) @@ -196,7 +218,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 +236,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 +251,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 +270,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 +287,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,10 +306,10 @@ public void shouldRespondWithBidResponse() { .willReturn(VideoResponse.of(emptyList(), null)); // when - videoHandler.handle(routingContext); + 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) @@ -291,6 +317,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, times(2)).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 +392,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 +410,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 +427,7 @@ public void shouldCacheDebugLogWhenNoBidsWereReturnedAndDoesNotAddErrorToVideoEv .willReturn(VideoResponse.of(emptyList(), null)); // when - videoHandler.handle(routingContext); + target.handle(routingContext); // then verify(coreCacheService).cacheVideoDebugLog(any(), anyInt()); @@ -377,6 +461,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/hooks/execution/HookCatalogTest.java b/src/test/java/org/prebid/server/hooks/execution/HookCatalogTest.java index 108605efe58..330e779a304 100644 --- a/src/test/java/org/prebid/server/hooks/execution/HookCatalogTest.java +++ b/src/test/java/org/prebid/server/hooks/execution/HookCatalogTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.hooks.execution.model.HookId; import org.prebid.server.hooks.execution.model.StageWithHookType; import org.prebid.server.hooks.v1.Hook; import org.prebid.server.hooks.v1.InvocationContext; @@ -19,6 +20,7 @@ import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -41,23 +43,17 @@ public void setUp() { } @Test - public void hookByIdShouldTolerateUnknownModule() { - // when - final EntrypointHook foundHook = hookCatalog.hookById( - "unknown-module", null, StageWithHookType.ENTRYPOINT); - - // then - assertThat(foundHook).isNull(); + public void hookByIdShouldThrowExceptionOnUnknownModule() { + // when and then + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> + hookCatalog.hookById(HookId.of("unknown-module", null), StageWithHookType.ENTRYPOINT)); } @Test - public void hookByIdShouldTolerateUnknownHook() { - // when - final EntrypointHook foundHook = hookCatalog.hookById( - "sample-module", "unknown-hook", StageWithHookType.ENTRYPOINT); - - // then - assertThat(foundHook).isNull(); + public void hookByIdShouldThrowExceptionOnUnknownHook() { + // when and then + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> + hookCatalog.hookById(HookId.of("sample-module", "unknown-hook"), StageWithHookType.ENTRYPOINT)); } @Test @@ -67,7 +63,7 @@ public void hookByIdShouldReturnEntrypointHook() { // when final EntrypointHook foundHook = hookCatalog.hookById( - "sample-module", "sample-hook", StageWithHookType.ENTRYPOINT); + HookId.of("sample-module", "sample-hook"), StageWithHookType.ENTRYPOINT); // then assertThat(foundHook).isNotNull() @@ -82,7 +78,7 @@ public void hookByIdShouldReturnRawAuctionRequestHook() { // when final RawAuctionRequestHook foundHook = hookCatalog.hookById( - "sample-module", "sample-hook", StageWithHookType.RAW_AUCTION_REQUEST); + HookId.of("sample-module", "sample-hook"), StageWithHookType.RAW_AUCTION_REQUEST); // then assertThat(foundHook).isNotNull() @@ -97,7 +93,7 @@ public void hookByIdShouldReturnProcessedAuctionRequestHook() { // when final ProcessedAuctionRequestHook foundHook = hookCatalog.hookById( - "sample-module", "sample-hook", StageWithHookType.PROCESSED_AUCTION_REQUEST); + HookId.of("sample-module", "sample-hook"), StageWithHookType.PROCESSED_AUCTION_REQUEST); // then assertThat(foundHook).isNotNull() @@ -112,7 +108,7 @@ public void hookByIdShouldReturnBidderRequestHook() { // when final BidderRequestHook foundHook = hookCatalog.hookById( - "sample-module", "sample-hook", StageWithHookType.BIDDER_REQUEST); + HookId.of("sample-module", "sample-hook"), StageWithHookType.BIDDER_REQUEST); // then assertThat(foundHook).isNotNull() @@ -127,7 +123,7 @@ public void hookByIdShouldReturnRawBidderResponseHook() { // when final RawBidderResponseHook foundHook = hookCatalog.hookById( - "sample-module", "sample-hook", StageWithHookType.RAW_BIDDER_RESPONSE); + HookId.of("sample-module", "sample-hook"), StageWithHookType.RAW_BIDDER_RESPONSE); // then assertThat(foundHook).isNotNull() @@ -142,7 +138,7 @@ public void hookByIdShouldReturnProcessedBidderResponseHook() { // when final ProcessedBidderResponseHook foundHook = hookCatalog.hookById( - "sample-module", "sample-hook", StageWithHookType.PROCESSED_BIDDER_RESPONSE); + HookId.of("sample-module", "sample-hook"), StageWithHookType.PROCESSED_BIDDER_RESPONSE); // then assertThat(foundHook).isNotNull() @@ -157,7 +153,7 @@ public void hookByIdShouldReturnAuctionResponseHook() { // when final AuctionResponseHook foundHook = hookCatalog.hookById( - "sample-module", "sample-hook", StageWithHookType.AUCTION_RESPONSE); + HookId.of("sample-module", "sample-hook"), StageWithHookType.AUCTION_RESPONSE); // then assertThat(foundHook).isNotNull() 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..7f1d29925c1 100644 --- a/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java +++ b/src/test/java/org/prebid/server/hooks/execution/HookStageExecutorTest.java @@ -2,10 +2,12 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.User; import com.iab.openrtb.response.Bid; 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; @@ -19,6 +21,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.VertxTest; @@ -29,6 +32,7 @@ import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderSeatBid; import org.prebid.server.execution.timeout.TimeoutFactory; +import org.prebid.server.hooks.execution.model.ABTest; import org.prebid.server.hooks.execution.model.EndpointExecutionPlan; import org.prebid.server.hooks.execution.model.ExecutionAction; import org.prebid.server.hooks.execution.model.ExecutionGroup; @@ -43,22 +47,23 @@ import org.prebid.server.hooks.execution.model.StageExecutionOutcome; import org.prebid.server.hooks.execution.model.StageExecutionPlan; import org.prebid.server.hooks.execution.model.StageWithHookType; +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.auction.AuctionRequestPayloadImpl; import org.prebid.server.hooks.execution.v1.auction.AuctionResponsePayloadImpl; import org.prebid.server.hooks.execution.v1.bidder.AllProcessedBidResponsesPayloadImpl; 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; -import org.prebid.server.hooks.v1.InvocationResultImpl; import org.prebid.server.hooks.v1.InvocationResultUtils; 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.auction.AuctionRequestPayload; import org.prebid.server.hooks.v1.auction.AuctionResponseHook; @@ -75,14 +80,18 @@ 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.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; import org.prebid.server.settings.model.Account; import org.prebid.server.settings.model.AccountHooksConfiguration; +import org.prebid.server.settings.model.HooksAdminConfig; import java.time.Clock; import java.time.ZoneOffset; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -93,13 +102,14 @@ 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 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; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; @@ -150,8 +160,8 @@ public void creationShouldFailWhenHostExecutionPlanHasUnknownHook() { HookId.of("module-alpha", "hook-a"), HookId.of("module-beta", "hook-a"))))))))); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.ENTRYPOINT))) - .willReturn(null); + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.ENTRYPOINT))) + .willThrow(new IllegalArgumentException("Exception.")); givenEntrypointHook("module-beta", "hook-a", immediateHook(InvocationResultUtils.noAction())); @@ -173,8 +183,8 @@ public void creationShouldFailWhenDefaultAccountExecutionPlanHasUnknownHook() { HookId.of("module-alpha", "hook-a"), HookId.of("module-beta", "hook-a"))))))))); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.ENTRYPOINT))) - .willReturn(null); + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.ENTRYPOINT))) + .willThrow(new IllegalArgumentException("Exception.")); givenEntrypointHook("module-beta", "hook-a", immediateHook(InvocationResultUtils.noAction())); @@ -402,6 +412,70 @@ public void shouldBypassEntrypointHooksWhenNoPlanForStage(VertxTestContext conte })); } + @Test + public void shouldBypassEntrypointHooksThatAreDisabled(VertxTestContext context) { + // given + givenEntrypointHook( + "module-alpha", + "hook-a", + immediateHook(InvocationResultUtils.succeeded( + payload -> EntrypointPayloadImpl.of( + payload.queryParams(), payload.headers(), payload.body() + "-abc"), + "moduleAlphaContext"))); + + givenEntrypointHook( + "module-alpha", + "hook-b", + delayedHook(InvocationResultUtils.succeeded(payload -> EntrypointPayloadImpl.of( + payload.queryParams(), payload.headers(), payload.body() + "-def")), 40)); + + givenEntrypointHook( + "module-beta", + "hook-a", + delayedHook(InvocationResultUtils.succeeded(payload -> EntrypointPayloadImpl.of( + payload.queryParams(), payload.headers(), payload.body() + "-ghi")), 80)); + + givenEntrypointHook( + "module-beta", + "hook-b", + immediateHook(InvocationResultUtils.succeeded( + payload -> EntrypointPayloadImpl.of( + payload.queryParams(), payload.headers(), payload.body() + "-jkl"), + "moduleBetaContext"))); + + final HookStageExecutor executor = HookStageExecutor.create( + executionPlan(singletonMap( + Endpoint.openrtb2_auction, + EndpointExecutionPlan.of(singletonMap(Stage.entrypoint, execPlanTwoGroupsTwoHooksEach())))), + null, + Map.of("module-alpha", false), + hookCatalog, + timeoutFactory, + vertx, + clock, + jacksonMapper, + false); + + final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + + // when + final Future> future = executor.executeEntrypointStage( + CaseInsensitiveMultiMap.empty(), + CaseInsensitiveMultiMap.empty(), + "body", + hookExecutionContext); + + // then + future.onComplete(context.succeeding(result -> { + assertThat(result).isNotNull(); + assertThat(result.isShouldReject()).isFalse(); + assertThat(result.getPayload()).isNotNull().satisfies(payload -> + assertThat(payload.body()).isEqualTo("body-ghi-jkl")); + + context.completeNow(); + })); + } + @Test public void shouldExecuteEntrypointHooksToleratingMisbehavingHooks(VertxTestContext context) { // given @@ -1046,13 +1120,13 @@ public void shouldExecuteEntrypointHooksAndPassInvocationContext(VertxTestContex // given final EntrypointHookImpl hookImpl = spy( EntrypointHookImpl.of(immediateHook(InvocationResultUtils.succeeded(identity())))); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.ENTRYPOINT))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.ENTRYPOINT))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-b"), eq(StageWithHookType.ENTRYPOINT))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-b"), eq(StageWithHookType.ENTRYPOINT))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-a"), eq(StageWithHookType.ENTRYPOINT))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-a"), eq(StageWithHookType.ENTRYPOINT))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-b"), eq(StageWithHookType.ENTRYPOINT))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-b"), eq(StageWithHookType.ENTRYPOINT))) .willReturn(hookImpl); final HookStageExecutor executor = createExecutor( @@ -1109,7 +1183,7 @@ public void shouldExecuteRawAuctionRequestHooksWhenNoExecutionPlanInAccount(Vert // given final RawAuctionRequestHookImpl hookImpl = spy( RawAuctionRequestHookImpl.of(immediateHook(InvocationResultUtils.noAction()))); - given(hookCatalog.hookById(anyString(), anyString(), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(any(), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); final String hostPlan = executionPlan(singletonMap( @@ -1141,9 +1215,9 @@ public void shouldExecuteRawAuctionRequestHooksWhenNoExecutionPlanInAccount(Vert verify(hookImpl, times(2)).call(any(), any()); verify(hookCatalog, times(2)) - .hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST)); + .hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST)); verify(hookCatalog, times(2)) - .hookById(eq("module-alpha"), eq("hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST)); + .hookById(eqHook("module-alpha", "hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST)); context.completeNow(); })); @@ -1154,7 +1228,7 @@ public void shouldExecuteRawAuctionRequestHooksWhenAccountOverridesExecutionPlan // given final RawAuctionRequestHookImpl hookImpl = spy( RawAuctionRequestHookImpl.of(immediateHook(InvocationResultUtils.noAction()))); - given(hookCatalog.hookById(anyString(), anyString(), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(any(), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); final String hostPlan = executionPlan(singletonMap( @@ -1170,14 +1244,14 @@ public void shouldExecuteRawAuctionRequestHooksWhenAccountOverridesExecutionPlan final HookStageExecutor executor = createExecutor(hostPlan, defaultAccountPlan); final BidRequest bidRequest = BidRequest.builder().build(); - final ExecutionPlan accountPlan = ExecutionPlan.of(singletonMap( + final ExecutionPlan accountPlan = ExecutionPlan.of(emptyList(), singletonMap( Endpoint.openrtb2_auction, EndpointExecutionPlan.of(singletonMap( Stage.raw_auction_request, execPlanOneGroupOneHook("module-beta", "hook-b"))))); final Account account = Account.builder() .id("accountId") - .hooks(AccountHooksConfiguration.of(accountPlan, null)) + .hooks(AccountHooksConfiguration.of(accountPlan, null, null)) .build(); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); @@ -1197,11 +1271,11 @@ public void shouldExecuteRawAuctionRequestHooksWhenAccountOverridesExecutionPlan verify(hookImpl, times(2)).call(any(), any()); verify(hookCatalog, times(2)) - .hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST)); + .hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST)); verify(hookCatalog) - .hookById(eq("module-alpha"), eq("hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST)); + .hookById(eqHook("module-alpha", "hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST)); verify(hookCatalog) - .hookById(eq("module-beta"), eq("hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST)); + .hookById(eqHook("module-beta", "hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST)); context.completeNow(); })); @@ -1210,8 +1284,8 @@ public void shouldExecuteRawAuctionRequestHooksWhenAccountOverridesExecutionPlan @Test public void shouldExecuteRawAuctionRequestHooksToleratingUnknownHookInAccountPlan(VertxTestContext context) { // given - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) - .willReturn(null); + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + .willThrow(new IllegalArgumentException("Hook implementation does not exist or disabled")); givenRawAuctionRequestHook( "module-beta", @@ -1221,7 +1295,7 @@ public void shouldExecuteRawAuctionRequestHooksToleratingUnknownHookInAccountPla final HookStageExecutor executor = createExecutor(null, null); - final ExecutionPlan accountPlan = ExecutionPlan.of(singletonMap( + final ExecutionPlan accountPlan = ExecutionPlan.of(emptyList(), singletonMap( Endpoint.openrtb2_auction, EndpointExecutionPlan.of(singletonMap( Stage.raw_auction_request, @@ -1233,7 +1307,7 @@ public void shouldExecuteRawAuctionRequestHooksToleratingUnknownHookInAccountPla HookId.of("module-beta", "hook-a"))))))))); final Account account = Account.builder() .id("accountId") - .hooks(AccountHooksConfiguration.of(accountPlan, null)) + .hooks(AccountHooksConfiguration.of(accountPlan, null, null)) .build(); final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); @@ -1289,7 +1363,7 @@ public void shouldExecuteRawAuctionRequestHooksToleratingUnknownHookInAccountPla } @Test - public void shouldNotExecuteRawAuctionRequestHooksWhenAccountConfigIsRequiredButAbsent(VertxTestContext context) { + public void shouldNotExecuteRawAuctionRequestHooksWhenAccountConfigIsNotRequired(VertxTestContext context) { // given givenRawAuctionRequestHook( "module-alpha", @@ -1298,11 +1372,122 @@ public void shouldNotExecuteRawAuctionRequestHooksWhenAccountConfigIsRequiredBut payload.bidRequest().toBuilder().at(1).build())))); givenRawAuctionRequestHook( - "module-alpha", + "module-beta", + "hook-a", + immediateHook(InvocationResultUtils.succeeded(payload -> AuctionRequestPayloadImpl.of( + payload.bidRequest().toBuilder().test(1).build())))); + + givenRawAuctionRequestHook( + "module-gamma", "hook-b", immediateHook(InvocationResultUtils.succeeded(payload -> AuctionRequestPayloadImpl.of( payload.bidRequest().toBuilder().id("id").build())))); + givenRawAuctionRequestHook( + "module-delta", + "hook-b", + immediateHook(InvocationResultUtils.succeeded(payload -> AuctionRequestPayloadImpl.of( + payload.bidRequest().toBuilder().tmax(1000L).build())))); + + givenRawAuctionRequestHook( + "module-epsilon", + "hook-a", + immediateHook(InvocationResultUtils.succeeded(payload -> AuctionRequestPayloadImpl.of( + payload.bidRequest().toBuilder().site(Site.builder().build()).build())))); + + givenRawAuctionRequestHook( + "module-zeta", + "hook-b", + immediateHook(InvocationResultUtils.succeeded(payload -> AuctionRequestPayloadImpl.of( + payload.bidRequest().toBuilder().user(User.builder().build()).build())))); + + final StageExecutionPlan stageExecutionPlan = StageExecutionPlan.of(asList( + ExecutionGroup.of( + 200L, + asList( + HookId.of("module-alpha", "hook-a"), + HookId.of("module-beta", "hook-a"), + HookId.of("module-epsilon", "hook-a"))), + ExecutionGroup.of( + 200L, + asList( + HookId.of("module-gamma", "hook-b"), + HookId.of("module-delta", "hook-b"), + HookId.of("module-zeta", "hook-b"))))); + + final String hostExecutionPlan = executionPlan(singletonMap( + Endpoint.openrtb2_auction, + EndpointExecutionPlan.of(singletonMap(Stage.raw_auction_request, stageExecutionPlan)))); + + final HookStageExecutor executor = HookStageExecutor.create( + hostExecutionPlan, + null, + Map.of("module-epsilon", true, "module-zeta", false), + hookCatalog, + timeoutFactory, + vertx, + clock, + jacksonMapper, + false); + + final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + + // when + final BidRequest givenBidRequest = BidRequest.builder().build(); + final Future> future = executor.executeRawAuctionRequestStage( + AuctionContext.builder() + .bidRequest(givenBidRequest) + .account(Account.builder() + .id("accountId") + .hooks(AccountHooksConfiguration.of( + null, + Map.of("module-alpha", mapper.createObjectNode(), + "module-beta", mapper.createObjectNode(), + "module-gamma", mapper.createObjectNode(), + "module-zeta", mapper.createObjectNode()), + HooksAdminConfig.builder() + .moduleExecution(Map.of( + "module-alpha", true, + "module-beta", false, + "module-epsilon", false)) + .build())) + .build()) + .hookExecutionContext(hookExecutionContext) + .debugContext(DebugContext.empty()) + .build()); + + // then + future.onComplete(context.succeeding(result -> { + assertThat(result).isNotNull(); + assertThat(result.getPayload()).isNotNull().satisfies(payload -> + assertThat(payload.bidRequest()).isEqualTo(BidRequest.builder() + .at(1) + .id("id") + .tmax(1000L) + .site(Site.builder().build()) + .build())); + + assertThat(hookExecutionContext.getStageOutcomes()) + .hasEntrySatisfying( + Stage.raw_auction_request, + stageOutcomes -> assertThat(stageOutcomes) + .hasSize(1) + .extracting(StageExecutionOutcome::getEntity) + .containsOnly("auction-request")); + + context.completeNow(); + })); + } + + @Test + public void shouldExecuteRawAuctionRequestHooksWhenAccountConfigIsRequired(VertxTestContext context) { + // given + givenRawAuctionRequestHook( + "module-alpha", + "hook-a", + immediateHook(InvocationResultUtils.succeeded(payload -> AuctionRequestPayloadImpl.of( + payload.bidRequest().toBuilder().at(1).build())))); + givenRawAuctionRequestHook( "module-beta", "hook-a", @@ -1310,20 +1495,51 @@ public void shouldNotExecuteRawAuctionRequestHooksWhenAccountConfigIsRequiredBut payload.bidRequest().toBuilder().test(1).build())))); givenRawAuctionRequestHook( - "module-beta", + "module-gamma", + "hook-b", + immediateHook(InvocationResultUtils.succeeded(payload -> AuctionRequestPayloadImpl.of( + payload.bidRequest().toBuilder().id("id").build())))); + + givenRawAuctionRequestHook( + "module-delta", "hook-b", immediateHook(InvocationResultUtils.succeeded(payload -> AuctionRequestPayloadImpl.of( payload.bidRequest().toBuilder().tmax(1000L).build())))); + givenRawAuctionRequestHook( + "module-epsilon", + "hook-a", + immediateHook(InvocationResultUtils.succeeded(payload -> AuctionRequestPayloadImpl.of( + payload.bidRequest().toBuilder().site(Site.builder().build()).build())))); + + givenRawAuctionRequestHook( + "module-zeta", + "hook-b", + immediateHook(InvocationResultUtils.succeeded(payload -> AuctionRequestPayloadImpl.of( + payload.bidRequest().toBuilder().user(User.builder().build()).build())))); + + final StageExecutionPlan stageExecutionPlan = StageExecutionPlan.of(asList( + ExecutionGroup.of( + 200L, + asList( + HookId.of("module-alpha", "hook-a"), + HookId.of("module-beta", "hook-a"), + HookId.of("module-epsilon", "hook-a"))), + ExecutionGroup.of( + 200L, + asList( + HookId.of("module-gamma", "hook-b"), + HookId.of("module-delta", "hook-b"), + HookId.of("module-zeta", "hook-b"))))); + final String hostExecutionPlan = executionPlan(singletonMap( Endpoint.openrtb2_auction, - EndpointExecutionPlan.of(singletonMap( - Stage.raw_auction_request, - execPlanTwoGroupsTwoHooksEach())))); + EndpointExecutionPlan.of(singletonMap(Stage.raw_auction_request, stageExecutionPlan)))); final HookStageExecutor executor = HookStageExecutor.create( hostExecutionPlan, null, + Map.of("module-epsilon", true, "module-zeta", false), hookCatalog, timeoutFactory, vertx, @@ -1338,7 +1554,21 @@ public void shouldNotExecuteRawAuctionRequestHooksWhenAccountConfigIsRequiredBut final Future> future = executor.executeRawAuctionRequestStage( AuctionContext.builder() .bidRequest(givenBidRequest) - .account(Account.empty("accountId")) + .account(Account.builder() + .id("accountId") + .hooks(AccountHooksConfiguration.of( + null, + Map.of("module-alpha", mapper.createObjectNode(), + "module-beta", mapper.createObjectNode(), + "module-gamma", mapper.createObjectNode(), + "module-zeta", mapper.createObjectNode()), + HooksAdminConfig.builder() + .moduleExecution(Map.of( + "module-alpha", true, + "module-beta", false, + "module-epsilon", false)) + .build())) + .build()) .hookExecutionContext(hookExecutionContext) .debugContext(DebugContext.empty()) .build()); @@ -1346,9 +1576,12 @@ public void shouldNotExecuteRawAuctionRequestHooksWhenAccountConfigIsRequiredBut // then future.onComplete(context.succeeding(result -> { assertThat(result).isNotNull(); - assertThat(result.getPayload()).isNotNull() - .extracting(AuctionRequestPayload::bidRequest) - .isEqualTo(givenBidRequest); + assertThat(result.getPayload()).isNotNull().satisfies(payload -> + assertThat(payload.bidRequest()).isEqualTo(BidRequest.builder() + .at(1) + .id("id") + .site(Site.builder().build()) + .build())); assertThat(hookExecutionContext.getStageOutcomes()) .hasEntrySatisfying( @@ -1435,13 +1668,13 @@ public void shouldExecuteRawAuctionRequestHooksAndPassAuctionInvocationContext(V // given final RawAuctionRequestHookImpl hookImpl = spy( RawAuctionRequestHookImpl.of(immediateHook(InvocationResultUtils.succeeded(identity())))); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); final HookStageExecutor executor = createExecutor( @@ -1464,7 +1697,7 @@ public void shouldExecuteRawAuctionRequestHooksAndPassAuctionInvocationContext(V AuctionContext.builder() .bidRequest(BidRequest.builder().build()) .account(Account.builder() - .hooks(AccountHooksConfiguration.of(null, accountModulesConfiguration)) + .hooks(AccountHooksConfiguration.of(null, accountModulesConfiguration, null)) .build()) .hookExecutionContext(hookExecutionContext) .debugContext(DebugContext.empty()) @@ -1525,17 +1758,17 @@ public void shouldExecuteRawAuctionRequestHooksAndPassModuleContextBetweenHooks( .build())); return promise.future(); })); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-c"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-c"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-a"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-b"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-c"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-c"), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(hookImpl); final HookStageExecutor executor = createExecutor( @@ -1708,13 +1941,13 @@ public void shouldExecuteProcessedAuctionRequestHooksAndPassAuctionInvocationCon // given final ProcessedAuctionRequestHookImpl hookImpl = spy( ProcessedAuctionRequestHookImpl.of(immediateHook(InvocationResultUtils.succeeded(identity())))); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-b"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-b"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-a"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-a"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-b"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-b"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) .willReturn(hookImpl); final HookStageExecutor executor = createExecutor( @@ -1738,7 +1971,7 @@ public void shouldExecuteProcessedAuctionRequestHooksAndPassAuctionInvocationCon AuctionContext.builder() .bidRequest(BidRequest.builder().build()) .account(Account.builder() - .hooks(AccountHooksConfiguration.of(null, accountModulesConfiguration)) + .hooks(AccountHooksConfiguration.of(null, accountModulesConfiguration, null)) .build()) .hookExecutionContext(hookExecutionContext) .debugContext(DebugContext.empty()) @@ -1799,17 +2032,17 @@ public void shouldExecuteProcessedAuctionRequestHooksAndPassModuleContextBetween .build())); return promise.future(); })); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-b"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-b"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-c"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-c"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-a"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-a"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-b"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-b"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) .willReturn(hookImpl); - given(hookCatalog.hookById(eq("module-beta"), eq("hook-c"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook("module-beta", "hook-c"), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) .willReturn(hookImpl); final HookStageExecutor executor = createExecutor( @@ -2004,7 +2237,7 @@ public void shouldExecuteBidderRequestHooksAndPassBidderInvocationContext(VertxT // given final BidderRequestHookImpl hookImpl = spy( BidderRequestHookImpl.of(immediateHook(InvocationResultUtils.succeeded(identity())))); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.BIDDER_REQUEST))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.BIDDER_REQUEST))) .willReturn(hookImpl); final HookStageExecutor executor = createExecutor( @@ -2025,7 +2258,7 @@ public void shouldExecuteBidderRequestHooksAndPassBidderInvocationContext(VertxT .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()) @@ -2141,7 +2374,7 @@ public void shouldExecuteRawBidderResponseHooksHappyPath(VertxTestContext contex checkpoint1.flag(); })); - CompositeFuture.join(future1, future2).onComplete(context.succeeding(result -> { + Future.join(future1, future2).onComplete(context.succeeding(result -> { assertThat(hookExecutionContext.getStageOutcomes()) .hasEntrySatisfying( Stage.raw_bidder_response, @@ -2159,7 +2392,7 @@ public void shouldExecuteRawBidderResponseHooksAndPassBidderInvocationContext(Ve // given final RawBidderResponseHookImpl hookImpl = spy( RawBidderResponseHookImpl.of(immediateHook(InvocationResultUtils.succeeded(identity())))); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.RAW_BIDDER_RESPONSE))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.RAW_BIDDER_RESPONSE))) .willReturn(hookImpl); final HookStageExecutor executor = createExecutor( @@ -2180,7 +2413,7 @@ public void shouldExecuteRawBidderResponseHooksAndPassBidderInvocationContext(Ve .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()) @@ -2317,7 +2550,7 @@ public void shouldExecuteProcessedBidderResponseHooksAndPassBidderInvocationCont // given final ProcessedBidderResponseHookImpl hookImpl = spy( ProcessedBidderResponseHookImpl.of(immediateHook(InvocationResultUtils.succeeded(identity())))); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.PROCESSED_BIDDER_RESPONSE))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.PROCESSED_BIDDER_RESPONSE))) .willReturn(hookImpl); final HookStageExecutor executor = createExecutor( @@ -2341,7 +2574,7 @@ public void shouldExecuteProcessedBidderResponseHooksAndPassBidderInvocationCont .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()) @@ -2482,7 +2715,7 @@ public void shouldExecuteAllProcessedBidResponsesHooksAndPassAuctionInvocationCo // given final AllProcessedBidResponsesHookImpl hookImpl = spy( AllProcessedBidResponsesHookImpl.of(immediateHook(InvocationResultUtils.succeeded(identity())))); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.ALL_PROCESSED_BID_RESPONSES))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.ALL_PROCESSED_BID_RESPONSES))) .willReturn(hookImpl); final HookStageExecutor executor = createExecutor( @@ -2506,7 +2739,7 @@ public void shouldExecuteAllProcessedBidResponsesHooksAndPassAuctionInvocationCo .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()) @@ -2630,7 +2863,7 @@ public void shouldExecuteAuctionResponseHooksAndPassAuctionInvocationContext(Ver // given final AuctionResponseHookImpl hookImpl = spy( AuctionResponseHookImpl.of(immediateHook(InvocationResultUtils.succeeded(identity())))); - given(hookCatalog.hookById(eq("module-alpha"), eq("hook-a"), eq(StageWithHookType.AUCTION_RESPONSE))) + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.AUCTION_RESPONSE))) .willReturn(hookImpl); final HookStageExecutor executor = createExecutor( @@ -2648,7 +2881,7 @@ public void shouldExecuteAuctionResponseHooksAndPassAuctionInvocationContext(Ver .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()) @@ -2670,6 +2903,48 @@ null, singletonMap("module-alpha", mapper.createObjectNode()))) })); } + @Test + public void shouldExecuteAuctionResponseHooksAndTolerateNullAccount(VertxTestContext context) { + // given + final AuctionResponseHookImpl hookImpl = spy( + AuctionResponseHookImpl.of(immediateHook(InvocationResultUtils.succeeded(identity())))); + given(hookCatalog.hookById(eqHook("module-alpha", "hook-a"), eq(StageWithHookType.AUCTION_RESPONSE))) + .willReturn(hookImpl); + + final HookStageExecutor executor = createExecutor( + executionPlan(singletonMap( + Endpoint.openrtb2_auction, + EndpointExecutionPlan.of(singletonMap( + Stage.auction_response, execPlanOneGroupOneHook("module-alpha", "hook-a")))))); + + final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + + // when + final Future> future = executor.executeAuctionResponseStage( + BidResponse.builder().build(), + AuctionContext.builder() + .bidRequest(BidRequest.builder().build()) + .account(null) + .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()).isNull(); + }); + + context.completeNow(); + })); + } + @Test public void shouldExecuteAuctionResponseHooksAndIgnoreRejection(VertxTestContext context) { // given @@ -2726,8 +3001,263 @@ 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(eqHook("module-alpha", "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()), null)) + .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(); + })); + } + + @Test + public void abTestsForEntrypointStageShouldReturnEnabledTests() { + // given + final HookStageExecutor executor = createExecutor(executionPlan(asList( + ABTest.builder().enabled(true).accounts(singleton("1")).build(), + ABTest.builder().enabled(false).accounts(singleton("1")).build(), + ABTest.builder().enabled(false).accounts(singleton("2")).build(), + ABTest.builder().enabled(true).build()))); + + // when + final List abTests = executor.abTestsForEntrypointStage(); + + // then + assertThat(abTests) + .hasSize(2) + .extracting(ABTest::isEnabled) + .containsOnly(true); + } + + @Test + public void abTestsShouldReturnEnabledTestsFromAccount() { + // given + final HookStageExecutor executor = createExecutor(executionPlan(asList( + ABTest.builder().enabled(true).accounts(singleton("1")).build(), + ABTest.builder().enabled(false).accounts(singleton("1")).build(), + ABTest.builder().enabled(false).accounts(singleton("2")).build(), + ABTest.builder().enabled(true).build()))); + + final Account account = Account.builder() + .id("1") + .hooks(AccountHooksConfiguration.of( + ExecutionPlan.of( + asList( + ABTest.builder().enabled(true).accounts(singleton("3")).build(), + ABTest.builder().enabled(false).accounts(singleton("4")).build(), + ABTest.builder().enabled(true).build()), + emptyMap()), + emptyMap(), + null)) + .build(); + + // when + final List abTests = executor.abTests(account); + + // then + assertThat(abTests).containsExactly( + ABTest.builder().enabled(true).accounts(singleton("3")).build(), + ABTest.builder().enabled(true).build()); + } + + @Test + public void abTestsShouldReturnEnabledTestsFromHost() { + // given + final HookStageExecutor executor = createExecutor( + executionPlan(asList( + ABTest.builder().enabled(true).accounts(singleton("1")).build(), + ABTest.builder().enabled(false).accounts(singleton("1")).build(), + ABTest.builder().enabled(false).accounts(singleton("2")).build(), + ABTest.builder().enabled(true).build())), + jacksonMapper.encodeToString(ExecutionPlan.empty())); + + final Account account = Account.builder() + .id("1") + .build(); + + // when + final List abTests = executor.abTests(account); + + // then + assertThat(abTests).containsExactly( + ABTest.builder().enabled(true).accounts(singleton("1")).build(), + ABTest.builder().enabled(true).build()); + } + private String executionPlan(Map endpoints) { - return jacksonMapper.encodeToString(ExecutionPlan.of(endpoints)); + return jacksonMapper.encodeToString(ExecutionPlan.of(null, endpoints)); + } + + private String executionPlan(List abTests) { + return jacksonMapper.encodeToString(ExecutionPlan.of(abTests, emptyMap())); } private static StageExecutionPlan execPlanTwoGroupsTwoHooksEach() { @@ -2756,7 +3286,7 @@ private void givenEntrypointHook( String hookImplCode, BiFunction>> delegate) { - given(hookCatalog.hookById(eq(moduleCode), eq(hookImplCode), eq(StageWithHookType.ENTRYPOINT))) + given(hookCatalog.hookById(eqHook(moduleCode, hookImplCode), eq(StageWithHookType.ENTRYPOINT))) .willReturn(EntrypointHookImpl.of(delegate)); } @@ -2768,7 +3298,7 @@ private void givenRawAuctionRequestHook( AuctionInvocationContext, Future>> delegate) { - given(hookCatalog.hookById(eq(moduleCode), eq(hookImplCode), eq(StageWithHookType.RAW_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook(moduleCode, hookImplCode), eq(StageWithHookType.RAW_AUCTION_REQUEST))) .willReturn(RawAuctionRequestHookImpl.of(delegate)); } @@ -2780,7 +3310,7 @@ private void givenProcessedAuctionRequestHook( AuctionInvocationContext, Future>> delegate) { - given(hookCatalog.hookById(eq(moduleCode), eq(hookImplCode), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) + given(hookCatalog.hookById(eqHook(moduleCode, hookImplCode), eq(StageWithHookType.PROCESSED_AUCTION_REQUEST))) .willReturn(ProcessedAuctionRequestHookImpl.of(delegate)); } @@ -2792,7 +3322,7 @@ private void givenBidderRequestHook( BidderInvocationContext, Future>> delegate) { - given(hookCatalog.hookById(eq(moduleCode), eq(hookImplCode), eq(StageWithHookType.BIDDER_REQUEST))) + given(hookCatalog.hookById(eqHook(moduleCode, hookImplCode), eq(StageWithHookType.BIDDER_REQUEST))) .willReturn(BidderRequestHookImpl.of(delegate)); } @@ -2804,7 +3334,7 @@ private void givenRawBidderResponseHook( BidderInvocationContext, Future>> delegate) { - given(hookCatalog.hookById(eq(moduleCode), eq(hookImplCode), eq(StageWithHookType.RAW_BIDDER_RESPONSE))) + given(hookCatalog.hookById(eqHook(moduleCode, hookImplCode), eq(StageWithHookType.RAW_BIDDER_RESPONSE))) .willReturn(RawBidderResponseHookImpl.of(delegate)); } @@ -2816,7 +3346,7 @@ private void givenProcessedBidderResponseHook( BidderInvocationContext, Future>> delegate) { - given(hookCatalog.hookById(eq(moduleCode), eq(hookImplCode), eq(StageWithHookType.PROCESSED_BIDDER_RESPONSE))) + given(hookCatalog.hookById(eqHook(moduleCode, hookImplCode), eq(StageWithHookType.PROCESSED_BIDDER_RESPONSE))) .willReturn(ProcessedBidderResponseHookImpl.of(delegate)); } @@ -2828,7 +3358,7 @@ private void givenAllProcessedBidderResponsesHook( AuctionInvocationContext, Future>> delegate) { - given(hookCatalog.hookById(eq(moduleCode), eq(hookImplCode), eq(StageWithHookType.ALL_PROCESSED_BID_RESPONSES))) + given(hookCatalog.hookById(eqHook(moduleCode, hookImplCode), eq(StageWithHookType.ALL_PROCESSED_BID_RESPONSES))) .willReturn(AllProcessedBidResponsesHookImpl.of(delegate)); } @@ -2840,10 +3370,22 @@ private void givenAuctionResponseHook( AuctionInvocationContext, Future>> delegate) { - given(hookCatalog.hookById(eq(moduleCode), eq(hookImplCode), eq(StageWithHookType.AUCTION_RESPONSE))) + given(hookCatalog.hookById(eqHook(moduleCode, hookImplCode), eq(StageWithHookType.AUCTION_RESPONSE))) .willReturn(AuctionResponseHookImpl.of(delegate)); } + private void givenExitpointHook( + String moduleCode, + String hookImplCode, + BiFunction< + ExitpointPayload, + AuctionInvocationContext, + Future>> delegate) { + + given(hookCatalog.hookById(eqHook(moduleCode, hookImplCode), eq(StageWithHookType.EXITPOINT))) + .willReturn(ExitpointHookImpl.of(delegate)); + } + private BiFunction>> delayedHook( InvocationResult result, int delay) { @@ -2861,6 +3403,10 @@ private BiFunction Future.succeededFuture(result); } + private static HookId eqHook(String moduleCode, String hookCode) { + return ArgumentMatchers.eq(HookId.of(moduleCode, hookCode)); + } + private HookStageExecutor createExecutor(String hostExecutionPlan) { return createExecutor(hostExecutionPlan, null); } @@ -2869,6 +3415,7 @@ private HookStageExecutor createExecutor(String hostExecutionPlan, String defaul return HookStageExecutor.create( hostExecutionPlan, defaultAccountExecutionPlan, + Collections.emptyMap(), hookCatalog, timeoutFactory, vertx, @@ -3066,4 +3613,28 @@ public String code() { return code; } } + + @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; + } + } } diff --git a/src/test/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHookProviderTest.java b/src/test/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHookProviderTest.java new file mode 100644 index 00000000000..fa6f3291267 --- /dev/null +++ b/src/test/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHookProviderTest.java @@ -0,0 +1,132 @@ +package org.prebid.server.hooks.execution.provider.abtest; + +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.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.prebid.server.VertxTest; +import org.prebid.server.hooks.execution.model.ABTest; +import org.prebid.server.hooks.execution.model.ExecutionAction; +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.execution.provider.HookProvider; +import org.prebid.server.hooks.v1.Hook; +import org.prebid.server.hooks.v1.InvocationContext; +import org.prebid.server.model.Endpoint; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class ABTestHookProviderTest extends VertxTest { + + @Mock + private HookProvider innerHookProvider; + + @Mock + private Hook innerHook; + + @BeforeEach + public void setUp() { + given(innerHookProvider.apply(any())).willReturn(innerHook); + } + + @Test + public void applyShouldReturnOriginalHookIfNoABTestFound() { + // given + final HookProvider target = new ABTestHookProvider<>( + innerHookProvider, + singletonList(ABTest.builder().moduleCode("otherModule").build()), + HookExecutionContext.of(Endpoint.openrtb2_auction), + mapper); + + // when + final Hook result = target.apply(hookId()); + + // then + verify(innerHookProvider).apply(any()); + verifyNoInteractions(innerHook); + assertThat(result).isSameAs(innerHook); + } + + @Test + public void applyShouldReturnWrappedHook() { + // given + final HookProvider target = new ABTestHookProvider<>( + innerHookProvider, + singletonList(ABTest.builder().moduleCode("module").build()), + HookExecutionContext.of(Endpoint.openrtb2_auction), + mapper); + + // when + final Hook result = target.apply(hookId()); + + // then + verify(innerHookProvider).apply(any()); + verifyNoInteractions(innerHook); + assertThat(result).isInstanceOf(ABTestHook.class); + } + + @Test + public void shouldInvokeHookShouldReturnTrueIfThereIsAPreviousInvocation() { + // given + final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + hookExecutionContext.getStageOutcomes().put(Stage.entrypoint, singletonList( + StageExecutionOutcome.of("entity", singletonList(GroupExecutionOutcome.of(singletonList( + HookExecutionOutcome.builder() + .hookId(hookId()) + .action(ExecutionAction.update) + .build())))))); + + final ABTestHookProvider target = new ABTestHookProvider<>( + innerHookProvider, + emptyList(), + hookExecutionContext, + mapper); + + // when and then + verifyNoInteractions(innerHookProvider); + verifyNoInteractions(innerHook); + assertThat(target.shouldInvokeHook("module", null)).isTrue(); + } + + @Test + public void shouldInvokeHookShouldReturnFalseIfThereIsAPreviousExecutionWithoutInvocation() { + // given + final HookExecutionContext hookExecutionContext = HookExecutionContext.of(Endpoint.openrtb2_auction); + hookExecutionContext.getStageOutcomes().put(Stage.entrypoint, singletonList( + StageExecutionOutcome.of("entity", singletonList(GroupExecutionOutcome.of(singletonList( + HookExecutionOutcome.builder() + .hookId(hookId()) + .action(ExecutionAction.no_invocation) + .build())))))); + + final ABTestHookProvider target = new ABTestHookProvider<>( + innerHookProvider, + emptyList(), + hookExecutionContext, + mapper); + + // when and then + verifyNoInteractions(innerHookProvider); + verifyNoInteractions(innerHook); + assertThat(target.shouldInvokeHook("module", null)).isFalse(); + } + + private static HookId hookId() { + return HookId.of("module", "hook"); + } +} diff --git a/src/test/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHookTest.java b/src/test/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHookTest.java new file mode 100644 index 00000000000..f0d129cb5db --- /dev/null +++ b/src/test/java/org/prebid/server/hooks/execution/provider/abtest/ABTestHookTest.java @@ -0,0 +1,183 @@ +package org.prebid.server.hooks.execution.provider.abtest; + +import io.vertx.core.Future; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.VertxTest; +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.TagsImpl; +import org.prebid.server.hooks.v1.Hook; +import org.prebid.server.hooks.v1.InvocationAction; +import org.prebid.server.hooks.v1.InvocationContext; +import org.prebid.server.hooks.v1.InvocationResult; +import org.prebid.server.hooks.v1.InvocationResultUtils; +import org.prebid.server.hooks.v1.InvocationStatus; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.prebid.server.hooks.v1.PayloadUpdate.identity; + +@ExtendWith(MockitoExtension.class) +public class ABTestHookTest extends VertxTest { + + @Mock + private Hook innerHook; + + @Mock + private Object payload; + + @Mock + private InvocationContext invocationContext; + + @Test + public void codeShouldReturnSameHookCode() { + // given + given(innerHook.code()).willReturn("code"); + + final Hook target = new ABTestHook<>( + "module", + innerHook, + false, + false, + mapper); + + // when and then + assertThat(target.code()).isEqualTo("code"); + } + + @Test + public void callShouldReturnSkippedResultWithoutTags() { + // given + final Hook target = new ABTestHook<>( + "module", + innerHook, + false, + false, + mapper); + + // when + final InvocationResult invocationResult = target.call(payload, invocationContext).result(); + + // then + assertThat(invocationResult.status()).isEqualTo(InvocationStatus.success); + assertThat(invocationResult.action()).isEqualTo(InvocationAction.no_invocation); + assertThat(invocationResult.analyticsTags()).isNull(); + } + + @Test + public void callShouldReturnSkippedResultWithTags() { + // given + final Hook target = new ABTestHook<>( + "module", + innerHook, + false, + true, + mapper); + + // when + final InvocationResult invocationResult = target.call(payload, invocationContext).result(); + + // then + assertThat(invocationResult.status()).isEqualTo(InvocationStatus.success); + assertThat(invocationResult.action()).isEqualTo(InvocationAction.no_invocation); + assertThat(invocationResult.analyticsTags().activities()).hasSize(1).allSatisfy(activity -> { + assertThat(activity.name()).isEqualTo("core-module-abtests"); + assertThat(activity.status()).isEqualTo("success"); + assertThat(activity.results()).hasSize(1).allSatisfy(result -> { + assertThat(result.status()).isEqualTo("skipped"); + assertThat(result.values()).isEqualTo(mapper.createObjectNode().put("module", "module")); + }); + }); + } + + @Test + public void callShouldReturnRunResultWithoutTags() { + // given + final Hook target = new ABTestHook<>( + "module", + innerHook, + true, + false, + mapper); + + final InvocationResult innerHookInvocationResult = spy(InvocationResultUtils.succeeded(identity())); + final Future> innerHookResult = Future.succeededFuture(innerHookInvocationResult); + given(innerHook.call(any(), any())).willReturn(innerHookResult); + + // when + final Future> result = target.call(payload, invocationContext); + + // then + verify(innerHook).call(same(payload), same(invocationContext)); + verifyNoInteractions(innerHookInvocationResult); + assertThat(result).isSameAs(innerHookResult); + } + + @Test + public void callShouldReturnRunResultWithTags() { + // given + final Hook target = new ABTestHook<>( + "module", + innerHook, + true, + true, + mapper); + + final InvocationResult innerHookInvocationResult = spy(InvocationResultImpl.builder() + .status(InvocationStatus.success) + .message("message") + .action(InvocationAction.update) + .payloadUpdate(identity()) + .errors(singletonList("error")) + .warnings(singletonList("warning")) + .debugMessages(singletonList("debugMessages")) + .moduleContext(new Object()) + .analyticsTags(TagsImpl.of(asList( + ActivityImpl.of("activity0", null, null), + ActivityImpl.of("activity1", null, null)))) + .build()); + given(innerHook.call(any(), any())).willReturn(Future.succeededFuture(innerHookInvocationResult)); + + // when + final Future> result = target.call(payload, invocationContext); + + // then + verify(innerHook).call(same(payload), same(invocationContext)); + verifyNoInteractions(innerHookInvocationResult); + + final InvocationResult invocationResult = result.result(); + assertThat(invocationResult.status()).isSameAs(innerHookInvocationResult.status()); + assertThat(invocationResult.message()).isSameAs(innerHookInvocationResult.message()); + assertThat(invocationResult.action()).isSameAs(innerHookInvocationResult.action()); + assertThat(invocationResult.payloadUpdate()).isSameAs(innerHookInvocationResult.payloadUpdate()); + assertThat(invocationResult.errors()).isSameAs(innerHookInvocationResult.errors()); + assertThat(invocationResult.warnings()).isSameAs(innerHookInvocationResult.warnings()); + assertThat(invocationResult.debugMessages()).isSameAs(innerHookInvocationResult.debugMessages()); + assertThat(invocationResult.moduleContext()).isSameAs(innerHookInvocationResult.moduleContext()); + assertThat(invocationResult.analyticsTags().activities()).satisfies(activities -> { + for (int i = 0; i < activities.size() - 1; i++) { + assertThat(activities.get(i)).isSameAs(innerHookInvocationResult.analyticsTags().activities().get(i)); + } + + assertThat(activities.getLast()).satisfies(activity -> { + assertThat(activity.name()).isEqualTo("core-module-abtests"); + assertThat(activity.status()).isEqualTo("success"); + assertThat(activity.results()).hasSize(1).allSatisfy(activityResult -> { + assertThat(activityResult.status()).isEqualTo("run"); + assertThat(activityResult.values()) + .isEqualTo(mapper.createObjectNode().put("module", "module")); + }); + }); + }); + } +} diff --git a/src/test/java/org/prebid/server/hooks/v1/InvocationResultUtils.java b/src/test/java/org/prebid/server/hooks/v1/InvocationResultUtils.java index ce3e9ca74cb..71d21ce9596 100644 --- a/src/test/java/org/prebid/server/hooks/v1/InvocationResultUtils.java +++ b/src/test/java/org/prebid/server/hooks/v1/InvocationResultUtils.java @@ -1,5 +1,7 @@ package org.prebid.server.hooks.v1; +import org.prebid.server.hooks.execution.v1.InvocationResultImpl; + public class InvocationResultUtils { private InvocationResultUtils() { diff --git a/src/test/java/org/prebid/server/hooks/v1/analytics/ActivityImpl.java b/src/test/java/org/prebid/server/hooks/v1/analytics/ActivityImpl.java deleted file mode 100644 index 0965bef2b40..00000000000 --- a/src/test/java/org/prebid/server/hooks/v1/analytics/ActivityImpl.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.prebid.server.hooks.v1.analytics; - -import lombok.Value; -import lombok.experimental.Accessors; - -import java.util.List; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class ActivityImpl implements Activity { - - String name; - - String status; - - List results; -} diff --git a/src/test/java/org/prebid/server/hooks/v1/analytics/AppliedToImpl.java b/src/test/java/org/prebid/server/hooks/v1/analytics/AppliedToImpl.java deleted file mode 100644 index 810313936a8..00000000000 --- a/src/test/java/org/prebid/server/hooks/v1/analytics/AppliedToImpl.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.prebid.server.hooks.v1.analytics; - -import lombok.Builder; -import lombok.Value; -import lombok.experimental.Accessors; - -import java.util.List; - -@Accessors(fluent = true) -@Builder -@Value -public class AppliedToImpl implements AppliedTo { - - List impIds; - - List bidders; - - boolean request; - - boolean response; - - List bidIds; -} diff --git a/src/test/java/org/prebid/server/hooks/v1/analytics/ResultImpl.java b/src/test/java/org/prebid/server/hooks/v1/analytics/ResultImpl.java deleted file mode 100644 index 3558a22c3cd..00000000000 --- a/src/test/java/org/prebid/server/hooks/v1/analytics/ResultImpl.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.prebid.server.hooks.v1.analytics; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Value; -import lombok.experimental.Accessors; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class ResultImpl implements Result { - - String status; - - ObjectNode values; - - AppliedTo appliedTo; -} diff --git a/src/test/java/org/prebid/server/hooks/v1/analytics/TagsImpl.java b/src/test/java/org/prebid/server/hooks/v1/analytics/TagsImpl.java deleted file mode 100644 index 92278f2469c..00000000000 --- a/src/test/java/org/prebid/server/hooks/v1/analytics/TagsImpl.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.prebid.server.hooks.v1.analytics; - -import lombok.Value; -import lombok.experimental.Accessors; - -import java.util.List; - -@Accessors(fluent = true) -@Value(staticConstructor = "of") -public class TagsImpl implements Tags { - - List activities; -} 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..902f12c6589 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.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; @@ -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 new file mode 100644 index 00000000000..82a494e7158 --- /dev/null +++ b/src/test/java/org/prebid/server/it/hooks/SampleItExitpointHook.java @@ -0,0 +1,80 @@ +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.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.InvocationStatus; +import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; +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; +import java.util.Collections; +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 = invocationContext.auctionContext().getBidResponse(); + final List seatBids = updateBids(bidResponse.getSeatbid()); + final BidResponse updatedResponse = bidResponse.toBuilder().seatbid(seatBids).build(); + + 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) { + 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/java/org/prebid/server/it/hooks/SampleItRawAuctionRequestHook.java b/src/test/java/org/prebid/server/it/hooks/SampleItRawAuctionRequestHook.java index c1054166dc1..f843083b0fb 100644 --- a/src/test/java/org/prebid/server/it/hooks/SampleItRawAuctionRequestHook.java +++ b/src/test/java/org/prebid/server/it/hooks/SampleItRawAuctionRequestHook.java @@ -4,15 +4,15 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; 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.auction.AuctionRequestPayloadImpl; 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.auction.AuctionRequestPayload; import org.prebid.server.hooks.v1.auction.RawAuctionRequestHook; 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 f4943895116..47b1da61b37 100644 --- a/src/test/java/org/prebid/server/metric/MetricsTest.java +++ b/src/test/java/org/prebid/server/metric/MetricsTest.java @@ -1213,6 +1213,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()) @@ -1272,6 +1275,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 diff --git a/src/test/resources/org/prebid/server/it/amp/test-cache-request.json b/src/test/resources/org/prebid/server/it/amp/test-cache-request.json index 4908b67e9c1..fe8eba5c934 100644 --- a/src/test/resources/org/prebid/server/it/amp/test-cache-request.json +++ b/src/test/resources/org/prebid/server/it/amp/test-cache-request.json @@ -27,7 +27,8 @@ "origbidcpm": 12.09 } }, - "aid":"tid" + "aid":"tid", + "ttlseconds": 300 }, { "type": "json", @@ -60,7 +61,8 @@ "origbidcur": "USD" } }, - "aid":"tid" + "aid":"tid", + "ttlseconds": 300 } ] } diff --git a/src/test/resources/org/prebid/server/it/cache/update/test-auction-response.json b/src/test/resources/org/prebid/server/it/cache/update/test-auction-response.json index 9d49c702f5e..e6127bea4f0 100644 --- a/src/test/resources/org/prebid/server/it/cache/update/test-auction-response.json +++ b/src/test/resources/org/prebid/server/it/cache/update/test-auction-response.json @@ -11,6 +11,7 @@ "crid": "crid2", "w": 120, "h": 600, + "exp": 300, "ext": { "prebid": { "type": "banner", @@ -35,6 +36,7 @@ { "id": "31124", "impid": "impId-video-cache-update", + "exp": 1500, "price": 3, "adm": "adm1", "crid": "crid1", 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/openrtb2/33across/test-auction-33across-response.json b/src/test/resources/org/prebid/server/it/openrtb2/33across/test-auction-33across-response.json index f086c053112..b7a0ac4311d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/33across/test-auction-33across-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/33across/test-auction-33across-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/aax/test-auction-aax-response.json b/src/test/resources/org/prebid/server/it/openrtb2/aax/test-auction-aax-response.json index c223e8f56d3..f6f8aa08087 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/aax/test-auction-aax-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/aax/test-auction-aax-response.json @@ -6,6 +6,7 @@ { "id": "randomid", "impid": "test-imp-id", + "exp": 300, "price": 0.5, "adm": "some-test-ad", "adid": "12345678", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/aceex/test-auction-aceex-response.json b/src/test/resources/org/prebid/server/it/openrtb2/aceex/test-auction-aceex-response.json index f80400fe5d1..b9b61a696c0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/aceex/test-auction-aceex-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/aceex/test-auction-aceex-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/acuityads/test-auction-acuityads-response.json b/src/test/resources/org/prebid/server/it/openrtb2/acuityads/test-auction-acuityads-response.json index ba9ea7db56d..a0bab7d6cc2 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/acuityads/test-auction-acuityads-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/acuityads/test-auction-acuityads-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adelement/test-auction-adelement-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adelement/test-auction-adelement-response.json index ee0b96cb442..0c48f3a8431 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adelement/test-auction-adelement-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adelement/test-auction-adelement-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.5, "adm": "some-test-ad", "adid": "12345678", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adf/test-auction-adf-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adf/test-auction-adf-response.json index 320108794b5..f4417095fa3 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adf/test-auction-adf-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adf/test-auction-adf-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 11.393, "adomain": [ ], @@ -22,6 +23,7 @@ { "id": "bid_id_banner", "impid": "imp_id_banner", + "exp": 300, "price": 11.393, "adomain": [], "adm": "", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adgeneration/test-auction-adgeneration-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adgeneration/test-auction-adgeneration-response.json index 05c116d4aa2..25ebe533dcc 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adgeneration/test-auction-adgeneration-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adgeneration/test-auction-adgeneration-response.json @@ -6,6 +6,7 @@ { "id": "id", "impid": "id", + "exp": 300, "price": 46.6, "adm": "", "crid": "Dummy_supership.jp", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adhese/test-auction-adhese-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adhese/test-auction-adhese-response.json index 9895195c325..f5fd5214de2 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adhese/test-auction-adhese-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adhese/test-auction-adhese-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 2.184, "adm": "
\"\"
", "crid": "demo-424", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adkernel/test-auction-adkernel-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adkernel/test-auction-adkernel-response.json index 92b00eb8c2b..a6a8e2bd0c8 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adkernel/test-auction-adkernel-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adkernel/test-auction-adkernel-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 2.25, "adm": "", "adid": "2002", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adkerneladn/test-adkerneladn-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adkerneladn/test-adkerneladn-bid-response.json index 9f868cc7baa..53df688de45 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adkerneladn/test-adkerneladn-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adkerneladn/test-adkerneladn-bid-response.json @@ -24,4 +24,4 @@ } ], "bidid": "bid_id" -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adkerneladn/test-auction-adkerneladn-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adkerneladn/test-auction-adkerneladn-response.json index f1d38a7780f..9563c1ff672 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adkerneladn/test-auction-adkerneladn-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adkerneladn/test-auction-adkerneladn-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.5, "adm": "adm021", "adid": "19005", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adman/test-auction-adman-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adman/test-auction-adman-response.json index 8d448c61116..8884760961e 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adman/test-auction-adman-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adman/test-auction-adman-response.json @@ -6,6 +6,7 @@ { "id": "bid_id1", "impid": "imp_id1", + "exp": 300, "price": 1.25, "adm": "adm001", "crid": "crid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/admatic/test-auction-admatic-response.json b/src/test/resources/org/prebid/server/it/openrtb2/admatic/test-auction-admatic-response.json index 6ad0d2f637f..0277bb7f78d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/admatic/test-auction-admatic-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/admatic/test-auction-admatic-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/admixer/test-admixer-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/admixer/test-admixer-bid-response.json index 5561b33da3b..aceadcc04ac 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/admixer/test-admixer-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/admixer/test-admixer-bid-response.json @@ -17,4 +17,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/admixer/test-auction-admixer-response.json b/src/test/resources/org/prebid/server/it/openrtb2/admixer/test-auction-admixer-response.json index 4352d750af3..75f33a522f6 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/admixer/test-auction-admixer-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/admixer/test-auction-admixer-response.json @@ -16,6 +16,7 @@ }, "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01 } ], diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adnuntius/test-auction-adnuntius-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adnuntius/test-auction-adnuntius-response.json index beffca0d359..61bac864a49 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adnuntius/test-auction-adnuntius-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adnuntius/test-auction-adnuntius-response.json @@ -6,6 +6,7 @@ { "id": "some_ad_id", "impid": "imp_id", + "exp": 300, "price": 42420.00, "adm": "some_html", "adid": "some_ad_id", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-response.json index 76c4005d49c..3f62c1fb7db 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-response.json @@ -6,6 +6,7 @@ { "id": "adoceanmyaozpniqismex", "impid": "imp_id", + "exp": 300, "price": 10, "adm": " ", "crid": "0af345b42983cc4bc0", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-adoppler-bid-response-1.json b/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-adoppler-bid-response-1.json index ba80a545eed..4edc56ade74 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-adoppler-bid-response-1.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-adoppler-bid-response-1.json @@ -26,4 +26,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-auction-adoppler-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-auction-adoppler-response.json index db0b1aeaa49..7822b00cdbb 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-auction-adoppler-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-auction-adoppler-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adot/test-auction-adot-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adot/test-auction-adot-response.json index dcff8f22c64..b2c52bb7d9e 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adot/test-auction-adot-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adot/test-auction-adot-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.16346, "adm": "some-test-ad", "crid": "crid001", @@ -36,4 +37,4 @@ "auctiontimestamp": 1626182712962 } } -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adpone/test-adpone-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adpone/test-adpone-bid-response.json index 7f09ff7886b..1682aad2c48 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adpone/test-adpone-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adpone/test-adpone-bid-response.json @@ -17,4 +17,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adpone/test-auction-adpone-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adpone/test-auction-adpone-response.json index e24dfce9f48..3c4fc34a311 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adpone/test-auction-adpone-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adpone/test-auction-adpone-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 6.66, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-auction-adprime-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-auction-adprime-response.json index 5b7e94562fd..073a812bcdb 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-auction-adprime-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-auction-adprime-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.25, "adm": "adm001", "crid": "crid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-response.json index 0d05040052c..1d301f75624 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-response.json @@ -6,6 +6,7 @@ { "id": "22e26bd9a702bc1", "impid": "22e26bd9a702bc", + "exp": 300, "price": 1.090, "adm": "Tag_Example", "adomain": [ diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adrino/test-auction-adrino-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adrino/test-auction-adrino-response.json index d480aae971a..e1341dbdcca 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adrino/test-auction-adrino-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adrino/test-auction-adrino-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adid": "adid001", "cid": "cid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adsyield/test-auction-adsyield-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adsyield/test-auction-adsyield-response.json index b9d85d4e632..855d418643f 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adsyield/test-auction-adsyield-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adsyield/test-auction-adsyield-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adtarget/test-adtarget-bid-response-1.json b/src/test/resources/org/prebid/server/it/openrtb2/adtarget/test-adtarget-bid-response-1.json index 03c5ee91218..d5b04833e91 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adtarget/test-adtarget-bid-response-1.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adtarget/test-adtarget-bid-response-1.json @@ -17,4 +17,4 @@ "group": 0 } ] -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adtarget/test-auction-adtarget-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adtarget/test-auction-adtarget-response.json index 23465125a4e..809b063e228 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adtarget/test-auction-adtarget-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adtarget/test-auction-adtarget-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 8.43, "adm": "adm14", "crid": "crid14", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adtelligent/test-adtelligent-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adtelligent/test-adtelligent-bid-response.json index 15d06b7c923..1da8f18279d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adtelligent/test-adtelligent-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adtelligent/test-adtelligent-bid-response.json @@ -17,4 +17,4 @@ "group": 0 } ] -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adtelligent/test-auction-adtelligent-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adtelligent/test-auction-adtelligent-response.json index 458b300cb66..b73512ad65c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adtelligent/test-auction-adtelligent-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adtelligent/test-auction-adtelligent-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 8.43, "adm": "adm14", "crid": "crid14", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adtonos/test-auction-adtonos-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adtonos/test-auction-adtonos-response.json index e6795976a7f..c5bfdb6d592 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adtonos/test-auction-adtonos-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adtonos/test-auction-adtonos-response.json @@ -7,6 +7,7 @@ "id": "bid_id", "mtype": 1, "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adtrgtme/test-auction-adtrgtme-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adtrgtme/test-auction-adtrgtme-response.json index 60c2ad42bab..d786080f717 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adtrgtme/test-auction-adtrgtme-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adtrgtme/test-auction-adtrgtme-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "h": 250, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/advangelists/test-auction-advangelists-response.json b/src/test/resources/org/prebid/server/it/openrtb2/advangelists/test-auction-advangelists-response.json index df8ec148aa1..92ba70c5c42 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/advangelists/test-auction-advangelists-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/advangelists/test-auction-advangelists-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adview/test-auction-adview-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adview/test-auction-adview-response.json index eccc7f38dec..a6c118e0913 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adview/test-auction-adview-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adview/test-auction-adview-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adxcg/test-auction-adxcg-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adxcg/test-auction-adxcg-response.json index 81b8aa40e9e..0961b1f67ec 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adxcg/test-auction-adxcg-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adxcg/test-auction-adxcg-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adyoulike/test-adyoulike-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adyoulike/test-adyoulike-bid-response.json index e291739474c..a4c0edc3e09 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adyoulike/test-adyoulike-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adyoulike/test-adyoulike-bid-response.json @@ -17,4 +17,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adyoulike/test-auction-adyoulike-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adyoulike/test-auction-adyoulike-response.json index ec08af30179..96aa18de5d8 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adyoulike/test-auction-adyoulike-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adyoulike/test-auction-adyoulike-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/aidem/test-auction-aidem-response.json b/src/test/resources/org/prebid/server/it/openrtb2/aidem/test-auction-aidem-response.json index 1dff571757c..f5ee4e08e9c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/aidem/test-auction-aidem-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/aidem/test-auction-aidem-response.json @@ -7,6 +7,7 @@ "id": "bid_id", "mtype": 1, "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/aja/test-aja-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/aja/test-aja-bid-response.json index d2f3908c4b3..413a3ffe241 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/aja/test-aja-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/aja/test-aja-bid-response.json @@ -17,4 +17,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/aja/test-auction-aja-response.json b/src/test/resources/org/prebid/server/it/openrtb2/aja/test-auction-aja-response.json index 5b8ce2dfb32..7010f75f5e6 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/aja/test-auction-aja-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/aja/test-auction-aja-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 10, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-response.json index e291739474c..a4c0edc3e09 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-response.json @@ -17,4 +17,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-response.json b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-response.json index f3b649ebbec..b61aebbccd6 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/alkimi/test-auction-alkimi-response.json b/src/test/resources/org/prebid/server/it/openrtb2/alkimi/test-auction-alkimi-response.json index ca0b59e06b3..b8ccdb2d3b3 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/alkimi/test-auction-alkimi-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/alkimi/test-auction-alkimi-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/amx/test-auction-amx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/amx/test-auction-amx-response.json index dc3186c5778..ec393ab7ca0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/amx/test-auction-amx-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/amx/test-auction-amx-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/apacdex/test-auction-apacdex-response.json b/src/test/resources/org/prebid/server/it/openrtb2/apacdex/test-auction-apacdex-response.json index e9c1f602280..2122bfc623a 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/apacdex/test-auction-apacdex-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/apacdex/test-auction-apacdex-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-cache-request.json b/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-cache-request.json index da99c54e188..15ae12d04c5 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-cache-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/appnexus/test-video-cache-request.json @@ -4,19 +4,22 @@ "type": "xml", "value": "some-test-ad-3", "aid": "bid_id", - "key": "2.0_IAB10-1_0s_{{uuid}}" + "key": "2.0_IAB10-1_0s_{{uuid}}", + "ttlseconds": 1500 }, { "type": "xml", "value": "some-test-ad", "aid": "bid_id", - "key": "5.5_IAB20-3_0s_{{uuid}}" + "key": "5.5_IAB20-3_0s_{{uuid}}", + "ttlseconds": 1500 }, { "type": "xml", "value": "some-test-ad-2", "aid": "bid_id", - "key": "2.5_IAB18-5_0s_{{uuid}}" + "key": "2.5_IAB18-5_0s_{{uuid}}", + "ttlseconds": 1500 } ] } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/appush/test-auction-appush-response.json b/src/test/resources/org/prebid/server/it/openrtb2/appush/test-auction-appush-response.json index 4a71755b12d..f673f770ad2 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/appush/test-auction-appush-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/appush/test-auction-appush-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/aso/test-auction-aso-response.json b/src/test/resources/org/prebid/server/it/openrtb2/aso/test-auction-aso-response.json index cef76b6cec9..6b9a19e7187 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/aso/test-auction-aso-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/aso/test-auction-aso-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 4.7, "adm": "adm6_4.7", "nurl": "nurl_4.7", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/audiencenetwork/test-auction-audiencenetwork-response.json b/src/test/resources/org/prebid/server/it/openrtb2/audiencenetwork/test-auction-audiencenetwork-response.json index 376d88dbf44..e1d18a2ecb5 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/audiencenetwork/test-auction-audiencenetwork-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/audiencenetwork/test-auction-audiencenetwork-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 9.0, "adm": "{\"bid_id\":\"10\"}", "adid": "10", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/automatad/test-auction-automatad-response.json b/src/test/resources/org/prebid/server/it/openrtb2/automatad/test-auction-automatad-response.json index 64383cd93de..c4c971e466e 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/automatad/test-auction-automatad-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/automatad/test-auction-automatad-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/avocet/test-auction-avocet-response.json b/src/test/resources/org/prebid/server/it/openrtb2/avocet/test-auction-avocet-response.json index cc260a44b94..6fca036d997 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/avocet/test-auction-avocet-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/avocet/test-auction-avocet-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 0.5, "adm": "some-test-ad", "adid": "29681110", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/axis/test-auction-axis-response.json b/src/test/resources/org/prebid/server/it/openrtb2/axis/test-auction-axis-response.json index 37c3691752c..676eb7d802a 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/axis/test-auction-axis-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/axis/test-auction-axis-response.json @@ -6,6 +6,7 @@ { "id": "bid_id1", "impid": "imp_id1", + "exp": 300, "price": 1.25, "adm": "adm001", "crid": "crid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/axonix/test-auction-axonix-response.json b/src/test/resources/org/prebid/server/it/openrtb2/axonix/test-auction-axonix-response.json index 31a15adc1f9..e0e02fc7381 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/axonix/test-auction-axonix-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/axonix/test-auction-axonix-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bcmint/test-auction-bcmint-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bcmint/test-auction-bcmint-response.json index 1ca1cb7607c..c591ef97cfd 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bcmint/test-auction-bcmint-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bcmint/test-auction-bcmint-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 4.7, "adm": "adm6_4.7", "nurl": "nurl_4.7", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/beachfront/test-auction-beachfront-response.json b/src/test/resources/org/prebid/server/it/openrtb2/beachfront/test-auction-beachfront-response.json index 5338cc8c4d4..5e10a21b5de 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/beachfront/test-auction-beachfront-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/beachfront/test-auction-beachfront-response.json @@ -6,6 +6,7 @@ { "id": "imp_idBanner", "impid": "imp_id", + "exp": 300, "price": 2.942807912826538, "adm": "
", "crid": "crid_3", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/beintoo/test-auction-beintoo-response.json b/src/test/resources/org/prebid/server/it/openrtb2/beintoo/test-auction-beintoo-response.json index 9419a85783d..bed5b0e3939 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/beintoo/test-auction-beintoo-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/beintoo/test-auction-beintoo-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "bid_id", + "exp": 300, "price": 2.942808, "adid": "94395500", "crid": "94395500", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bematterfull/test-auction-bematterfull-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bematterfull/test-auction-bematterfull-response.json index 4b0e7de3f44..1d2bac9f898 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bematterfull/test-auction-bematterfull-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bematterfull/test-auction-bematterfull-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 4.7, "adm": "adm6", "crid": "crid6", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/between/test-auction-between-response.json b/src/test/resources/org/prebid/server/it/openrtb2/between/test-auction-between-response.json index ed208dd5654..cf2a6e0d031 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/between/test-auction-between-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/between/test-auction-between-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/beyondmedia/test-auction-beyondmedia-response.json b/src/test/resources/org/prebid/server/it/openrtb2/beyondmedia/test-auction-beyondmedia-response.json index 605deba4cb1..e63089babb2 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/beyondmedia/test-auction-beyondmedia-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/beyondmedia/test-auction-beyondmedia-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bidagency/test-auction-bidagency-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bidagency/test-auction-bidagency-response.json index 80cfe99af9e..fdba12487c7 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bidagency/test-auction-bidagency-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bidagency/test-auction-bidagency-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 4.7, "adm": "adm6_4.7", "nurl": "nurl_4.7", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bidmachine/test-auction-bidmachine-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bidmachine/test-auction-bidmachine-response.json index eb4e503494f..0ea56280b80 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bidmachine/test-auction-bidmachine-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bidmachine/test-auction-bidmachine-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bidmatic/test-auction-bidmatic-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bidmatic/test-auction-bidmatic-response.json index a45f9eeb3c9..ba0b73cfaf1 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bidmatic/test-auction-bidmatic-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bidmatic/test-auction-bidmatic-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 8.43, "adm": "adm14", "crid": "crid14", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bidmyadz/test-auction-bidmyadz-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bidmyadz/test-auction-bidmyadz-response.json index ba657da0438..399a568d088 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bidmyadz/test-auction-bidmyadz-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bidmyadz/test-auction-bidmyadz-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bidscube/test-auction-bidscube-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bidscube/test-auction-bidscube-response.json index 8cb8a61c009..8c8b0128e98 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bidscube/test-auction-bidscube-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bidscube/test-auction-bidscube-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bidstack/test-auction-bidstack-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bidstack/test-auction-bidstack-response.json index 523bdbbcda4..9428b19fd0a 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bidstack/test-auction-bidstack-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bidstack/test-auction-bidstack-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 3.33, "adid": "adid001", "adm": "", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-response.json index 287c05c61aa..013cd170712 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "mtype": 1, "price": 3.33, "adm": "adm001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/blasto/test-auction-blasto-response.json b/src/test/resources/org/prebid/server/it/openrtb2/blasto/test-auction-blasto-response.json index 9bf200e6d9d..22a67229971 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/blasto/test-auction-blasto-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/blasto/test-auction-blasto-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bliink/test-auction-bliink-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bliink/test-auction-bliink-response.json index de26e4363ea..f4ddf9222af 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bliink/test-auction-bliink-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bliink/test-auction-bliink-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bluesea/test-auction-bluesea-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bluesea/test-auction-bluesea-response.json index 939897d89a1..71412aa8e6c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bluesea/test-auction-bluesea-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bluesea/test-auction-bluesea-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bmtm/test-auction-bmtm-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bmtm/test-auction-bmtm-response.json index 1d36233fd2d..cbbae431606 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bmtm/test-auction-bmtm-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bmtm/test-auction-bmtm-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/boldwin/test-auction-boldwin-response.json b/src/test/resources/org/prebid/server/it/openrtb2/boldwin/test-auction-boldwin-response.json index e9242e76699..4d5d8f5ce9e 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/boldwin/test-auction-boldwin-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/boldwin/test-auction-boldwin-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/brave/test-auction-brave-response.json b/src/test/resources/org/prebid/server/it/openrtb2/brave/test-auction-brave-response.json index 39819c16e18..43de8398d78 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/brave/test-auction-brave-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/brave/test-auction-brave-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "adid", "cid": "cid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bwx/test-auction-bwx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bwx/test-auction-bwx-response.json index 6029a55596f..6107fe586a3 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/bwx/test-auction-bwx-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/bwx/test-auction-bwx-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "mtype": 1, "adid": "adid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/cadentaperturemx/test-auction-cadentaperturemx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/cadentaperturemx/test-auction-cadentaperturemx-response.json index c5b97cc0914..f2f43ba2c1d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/cadentaperturemx/test-auction-cadentaperturemx-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/cadentaperturemx/test-auction-cadentaperturemx-response.json @@ -6,6 +6,7 @@ { "id": "imp_id", "impid": "imp_id", + "exp": 300, "price": 2.942808, "adm": "
", "adid": "94395500", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/ccx/test-auction-ccx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/ccx/test-auction-ccx-response.json index 28eb0bc9e9d..7fcafb472c7 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/ccx/test-auction-ccx-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/ccx/test-auction-ccx-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/cointraffic/test-auction-cointraffic-response.json b/src/test/resources/org/prebid/server/it/openrtb2/cointraffic/test-auction-cointraffic-response.json index 4837ee79517..06fadbf0b09 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/cointraffic/test-auction-cointraffic-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/cointraffic/test-auction-cointraffic-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/coinzilla/test-auction-coinzilla-response.json b/src/test/resources/org/prebid/server/it/openrtb2/coinzilla/test-auction-coinzilla-response.json index cbad770f0e5..9f59c942b28 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/coinzilla/test-auction-coinzilla-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/coinzilla/test-auction-coinzilla-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 6.66, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/colossus/aliases/test-auction-colossusssp-response.json b/src/test/resources/org/prebid/server/it/openrtb2/colossus/aliases/test-auction-colossusssp-response.json index 3491c77189e..f551e12bbe4 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/colossus/aliases/test-auction-colossusssp-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/colossus/aliases/test-auction-colossusssp-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.25, "adm": "adm001", "crid": "crid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/colossus/test-auction-colossus-response.json b/src/test/resources/org/prebid/server/it/openrtb2/colossus/test-auction-colossus-response.json index c4d521102b5..d7914b5fb58 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/colossus/test-auction-colossus-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/colossus/test-auction-colossus-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.25, "adm": "adm001", "crid": "crid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/compass/test-auction-compass-response.json b/src/test/resources/org/prebid/server/it/openrtb2/compass/test-auction-compass-response.json index 0da511e197e..20f86d6a348 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/compass/test-auction-compass-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/compass/test-auction-compass-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/concert/test-auction-concert-response.json b/src/test/resources/org/prebid/server/it/openrtb2/concert/test-auction-concert-response.json index aadc8da3482..666a177b9c1 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/concert/test-auction-concert-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/concert/test-auction-concert-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "mtype": 1, "price": 3.33, "adm": "adm001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/connectad/test-auction-connectad-response.json b/src/test/resources/org/prebid/server/it/openrtb2/connectad/test-auction-connectad-response.json index 9aa7d077c16..d24913feeef 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/connectad/test-auction-connectad-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/connectad/test-auction-connectad-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adm": "hi", "cid": "test_cid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/consumable/test-auction-consumable-response.json b/src/test/resources/org/prebid/server/it/openrtb2/consumable/test-auction-consumable-response.json index 1290f6775fa..a28d27cf14a 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/consumable/test-auction-consumable-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/consumable/test-auction-consumable-response.json @@ -7,6 +7,7 @@ { "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", + "exp": 300, "price": 0.500000, "adm": "some-test-ad", "crid": "crid_10", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/copper6/test-auction-copper6-response.json b/src/test/resources/org/prebid/server/it/openrtb2/copper6/test-auction-copper6-response.json index 464f7df6b6a..3ac3ccb1672 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/copper6/test-auction-copper6-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/copper6/test-auction-copper6-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 8.43, "adm": "adm14", "crid": "crid14", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/copper6ssp/test-auction-copper6ssp-response.json b/src/test/resources/org/prebid/server/it/openrtb2/copper6ssp/test-auction-copper6ssp-response.json index fb24eb9368c..52b36682c8a 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/copper6ssp/test-auction-copper6ssp-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/copper6ssp/test-auction-copper6ssp-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/cpmstar/test-auction-cpmstar-response.json b/src/test/resources/org/prebid/server/it/openrtb2/cpmstar/test-auction-cpmstar-response.json index 9444279b4a6..6a685a06ba4 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/cpmstar/test-auction-cpmstar-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/cpmstar/test-auction-cpmstar-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/criteo/test-auction-criteo-response.json b/src/test/resources/org/prebid/server/it/openrtb2/criteo/test-auction-criteo-response.json index fb1c7803346..9d7e6b1ca5b 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/criteo/test-auction-criteo-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/criteo/test-auction-criteo-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/datablocks/test-auction-datablocks-response.json b/src/test/resources/org/prebid/server/it/openrtb2/datablocks/test-auction-datablocks-response.json index 7ce6a9dd1ae..a73407d0bb8 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/datablocks/test-auction-datablocks-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/datablocks/test-auction-datablocks-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 7.77, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/decenterads/test-auction-decenterads-response.json b/src/test/resources/org/prebid/server/it/openrtb2/decenterads/test-auction-decenterads-response.json index dbbff620bf4..6b058b84507 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/decenterads/test-auction-decenterads-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/decenterads/test-auction-decenterads-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.25, "adm": "adm001", "crid": "crid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/deepintent/test-auction-deepintent-response.json b/src/test/resources/org/prebid/server/it/openrtb2/deepintent/test-auction-deepintent-response.json index 8f1dfac20f0..223095836df 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/deepintent/test-auction-deepintent-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/deepintent/test-auction-deepintent-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/definemedia/test-auction-definemedia-response.json b/src/test/resources/org/prebid/server/it/openrtb2/definemedia/test-auction-definemedia-response.json index d24a92228d1..34d7cd3fbf2 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/definemedia/test-auction-definemedia-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/definemedia/test-auction-definemedia-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 11.393, "adomain": [ ], diff --git a/src/test/resources/org/prebid/server/it/openrtb2/dianomi/test-auction-dianomi-response.json b/src/test/resources/org/prebid/server/it/openrtb2/dianomi/test-auction-dianomi-response.json index 40de103c524..0725b710372 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/dianomi/test-auction-dianomi-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/dianomi/test-auction-dianomi-response.json @@ -6,6 +6,7 @@ { "id": "bid_id_banner", "impid": "imp_id_banner", + "exp": 300, "price": 11.393, "adomain": [], "adm": "", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/displayio/test-auction-displayio-response.json b/src/test/resources/org/prebid/server/it/openrtb2/displayio/test-auction-displayio-response.json index 8c5b4ea599e..ad84acca12c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/displayio/test-auction-displayio-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/displayio/test-auction-displayio-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/dmx/test-auction-dmx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/dmx/test-auction-dmx-response.json index c34b9f61946..e7b10ebdda4 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/dmx/test-auction-dmx-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/dmx/test-auction-dmx-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "cid": "test_cid", "crid": "test_banner_crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/driftpixel/test-auction-driftpixel-response.json b/src/test/resources/org/prebid/server/it/openrtb2/driftpixel/test-auction-driftpixel-response.json index 81da7b92978..d28852c4ccd 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/driftpixel/test-auction-driftpixel-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/driftpixel/test-auction-driftpixel-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/dxkulture/test-auction-dxkulture-response.json b/src/test/resources/org/prebid/server/it/openrtb2/dxkulture/test-auction-dxkulture-response.json index 0c6bd826b02..b2442775de6 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/dxkulture/test-auction-dxkulture-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/dxkulture/test-auction-dxkulture-response.json @@ -7,6 +7,7 @@ "id": "bid_id", "mtype": 1, "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/edge226/test-auction-edge226-response.json b/src/test/resources/org/prebid/server/it/openrtb2/edge226/test-auction-edge226-response.json index ce119ca7efd..2b9d8f83ca4 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/edge226/test-auction-edge226-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/edge226/test-auction-edge226-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/embimedia/test-auction-embimedia-response.json b/src/test/resources/org/prebid/server/it/openrtb2/embimedia/test-auction-embimedia-response.json index 5de88ba03df..930aa7c8b06 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/embimedia/test-auction-embimedia-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/embimedia/test-auction-embimedia-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/emtv/test-auction-emtv-response.json b/src/test/resources/org/prebid/server/it/openrtb2/emtv/test-auction-emtv-response.json index c71dd2560c2..3795c5a9c62 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/emtv/test-auction-emtv-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/emtv/test-auction-emtv-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/emxdigital/test-auction-emxdigital-response.json b/src/test/resources/org/prebid/server/it/openrtb2/emxdigital/test-auction-emxdigital-response.json index 6d2e3cf823c..552b0f3a934 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/emxdigital/test-auction-emxdigital-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/emxdigital/test-auction-emxdigital-response.json @@ -6,6 +6,7 @@ { "id": "imp_id", "impid": "imp_id", + "exp": 300, "price": 2.942808, "adm": "
", "adid": "94395500", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-auction-eplanning-response.json b/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-auction-eplanning-response.json index b3b8e50e406..ce65aba9a9b 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-auction-eplanning-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-auction-eplanning-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.5, "adm": "
test
", "adid": "imp_id", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/epom/test-auction-epom-response.json b/src/test/resources/org/prebid/server/it/openrtb2/epom/test-auction-epom-response.json index 16a9acaefd1..f10ab8e286e 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/epom/test-auction-epom-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/epom/test-auction-epom-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/epsilon/alias/test-auction-epsilon-response.json b/src/test/resources/org/prebid/server/it/openrtb2/epsilon/alias/test-auction-epsilon-response.json index aadd13302aa..3dd393badc9 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/epsilon/alias/test-auction-epsilon-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/epsilon/alias/test-auction-epsilon-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 5.0, "adm": "adm4", "crid": "crid4", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/epsilon/test-auction-epsilon-response.json b/src/test/resources/org/prebid/server/it/openrtb2/epsilon/test-auction-epsilon-response.json index 8cb45ddcbac..4aa8c6d0985 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/epsilon/test-auction-epsilon-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/epsilon/test-auction-epsilon-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 6.0, "adm": "adm4", "crid": "crid4", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/escalax/test-auction-escalax-response.json b/src/test/resources/org/prebid/server/it/openrtb2/escalax/test-auction-escalax-response.json index 0aa7a90e2d4..7f2babf609e 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/escalax/test-auction-escalax-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/escalax/test-auction-escalax-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/evolution/test-auction-evolution-response.json b/src/test/resources/org/prebid/server/it/openrtb2/evolution/test-auction-evolution-response.json index 1d694702c90..d0043247edf 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/evolution/test-auction-evolution-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/evolution/test-auction-evolution-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/felixads/test-auction-felixads-response.json b/src/test/resources/org/prebid/server/it/openrtb2/felixads/test-auction-felixads-response.json index ae5c74865aa..0b63bd03f57 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/felixads/test-auction-felixads-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/felixads/test-auction-felixads-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/filmzie/test-auction-filmzie-response.json b/src/test/resources/org/prebid/server/it/openrtb2/filmzie/test-auction-filmzie-response.json index 4a1aac1a8c5..e2c3508d1ab 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/filmzie/test-auction-filmzie-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/filmzie/test-auction-filmzie-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/finative/test-auction-finative-response.json b/src/test/resources/org/prebid/server/it/openrtb2/finative/test-auction-finative-response.json index 4246d1a5016..e055b56bb65 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/finative/test-auction-finative-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/finative/test-auction-finative-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 11.393, "adm": "some adm price 10", "adomain": [ diff --git a/src/test/resources/org/prebid/server/it/openrtb2/flipp/test-auction-flipp-response.json b/src/test/resources/org/prebid/server/it/openrtb2/flipp/test-auction-flipp-response.json index a6d946f403d..da840110dcf 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/flipp/test-auction-flipp-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/flipp/test-auction-flipp-response.json @@ -6,6 +6,7 @@ { "id": "183599115", "impid": "imp_id", + "exp": 300, "price": 12.34, "adm": "creativeContent", "crid": "81325690", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/freewheelssp/test-auction-freewheelssp-response.json b/src/test/resources/org/prebid/server/it/openrtb2/freewheelssp/test-auction-freewheelssp-response.json index 834a4af5e9d..d583e32cc4f 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/freewheelssp/test-auction-freewheelssp-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/freewheelssp/test-auction-freewheelssp-response.json @@ -6,6 +6,7 @@ { "id": "12345_freewheelssp-test_1", "impid": "imp-1", + "exp": 1500, "price": 1.0, "adid": "7857", "adm": "", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/frvradn/test-auction-frvradn-response.json b/src/test/resources/org/prebid/server/it/openrtb2/frvradn/test-auction-frvradn-response.json index fb69a968abd..683c42863d0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/frvradn/test-auction-frvradn-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/frvradn/test-auction-frvradn-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/gamma/test-auction-gamma-response.json b/src/test/resources/org/prebid/server/it/openrtb2/gamma/test-auction-gamma-response.json index ca56cb19b7e..ce0e41775aa 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/gamma/test-auction-gamma-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/gamma/test-auction-gamma-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.5, "adm": "some-test-ad", "adid": "29681110", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/gamoshi/test-auction-gamoshi-response.json b/src/test/resources/org/prebid/server/it/openrtb2/gamoshi/test-auction-gamoshi-response.json index c88d409c2c2..4d6ea3f9060 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/gamoshi/test-auction-gamoshi-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/gamoshi/test-auction-gamoshi-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/generic/test-auction-generic-response.json b/src/test/resources/org/prebid/server/it/openrtb2/generic/test-auction-generic-response.json index 808b06e512e..8f2d2e4407c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/generic/test-auction-generic-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/generic/test-auction-generic-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-auction-generic-request.json b/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-auction-generic-request.json index a0d9014043a..7dc036ee1d4 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-auction-generic-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-auction-generic-request.json @@ -8,7 +8,8 @@ "mimes" ], "w": 300, - "h": 250 + "h": 250, + "placement": 1 }, "ext": { "prebid": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-auction-generic-response.json b/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-auction-generic-response.json index 552408995c8..4ee1ff6a6c8 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-auction-generic-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-auction-generic-response.json @@ -6,6 +6,7 @@ { "id": "bid001", "impid": "impId001", + "exp": 1500, "price": 2.997, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-cache-generic-request.json b/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-cache-generic-request.json index 1b5c1802325..a6d65dfcae0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-cache-generic-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-cache-generic-request.json @@ -36,7 +36,8 @@ "origbidcpm": 3.33 } }, - "aid": "tid" + "aid": "tid", + "ttlseconds" : 1500 } ] } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-generic-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-generic-bid-request.json index d1ae2f0fce0..754ed9ddfff 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-generic-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/generic_core_functionality/test-generic-bid-request.json @@ -3,6 +3,7 @@ "imp" : [ { "id": "impId001", "video": { + "placement": 1, "mimes": [ "mimes" ], diff --git a/src/test/resources/org/prebid/server/it/openrtb2/globalsun/test-auction-globalsun-response.json b/src/test/resources/org/prebid/server/it/openrtb2/globalsun/test-auction-globalsun-response.json index 7ab5cf7f347..505a3ca4b5c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/globalsun/test-auction-globalsun-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/globalsun/test-auction-globalsun-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-response.json b/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-response.json index 4554400f4b1..728dccb2b13 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/greedygame/test-auction-greedygame-response.json b/src/test/resources/org/prebid/server/it/openrtb2/greedygame/test-auction-greedygame-response.json index df8a43a904f..7d19f2a3f5c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/greedygame/test-auction-greedygame-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/greedygame/test-auction-greedygame-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/grid/test-auction-grid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/grid/test-auction-grid-response.json index a71711d597d..f6a034ca8a3 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/grid/test-auction-grid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/grid/test-auction-grid-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/gumgum/test-auction-gumgum-response.json b/src/test/resources/org/prebid/server/it/openrtb2/gumgum/test-auction-gumgum-response.json index 682e43e4516..7c131a1f180 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/gumgum/test-auction-gumgum-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/gumgum/test-auction-gumgum-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.25, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_app_promotion_type/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_app_promotion_type/test-huaweiads-auction-response.json index 95248ad02c0..2068eb254c5 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_app_promotion_type/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_app_promotion_type/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58025103", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "h": 300, "w": 250, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_ch_endpoint/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_ch_endpoint/test-huaweiads-auction-response.json index c576adccb02..931449bc021 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_ch_endpoint/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_ch_endpoint/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58025103", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "h": 300, "w": 250, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_eu_endpoint/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_eu_endpoint/test-huaweiads-auction-response.json index c576adccb02..931449bc021 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_eu_endpoint/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_eu_endpoint/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58025103", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "h": 300, "w": 250, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_imei/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_imei/test-huaweiads-auction-response.json index c576adccb02..931449bc021 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_imei/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_imei/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58025103", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "h": 300, "w": 250, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_interstitial_type/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_interstitial_type/test-huaweiads-auction-response.json index 1ad60895c93..3f3b9084e45 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_interstitial_type/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_interstitial_type/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58025103", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "h": 300, "w": 250, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_mccmnc/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_mccmnc/test-huaweiads-auction-response.json index 033288ceb0e..d6d29d2c614 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_mccmnc/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_mccmnc/test-huaweiads-auction-response.json @@ -29,6 +29,7 @@ "h": 250, "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "w": 300, "nurl":"" diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_non_integer_mccmnc/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_non_integer_mccmnc/test-huaweiads-auction-response.json index 033288ceb0e..d6d29d2c614 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_non_integer_mccmnc/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_non_integer_mccmnc/test-huaweiads-auction-response.json @@ -29,6 +29,7 @@ "h": 250, "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "w": 300, "nurl":"" diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_not_app_promotion_type/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_not_app_promotion_type/test-huaweiads-auction-response.json index 3e1b8422400..05a00845f55 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_not_app_promotion_type/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_not_app_promotion_type/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58025103", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "h": 300, "w": 250, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_ru_endpoint/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_ru_endpoint/test-huaweiads-auction-response.json index 2b08c0e9f75..dee5e9c6ebb 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_ru_endpoint/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_ru_endpoint/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58025103", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "h": 250, "w": 300, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_with_user_geo/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_with_user_geo/test-huaweiads-auction-response.json index 033288ceb0e..d6d29d2c614 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_with_user_geo/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_with_user_geo/test-huaweiads-auction-response.json @@ -29,6 +29,7 @@ "h": 250, "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "w": 300, "nurl":"" diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_without_device_geo/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_without_device_geo/test-huaweiads-auction-response.json index 033288ceb0e..d6d29d2c614 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_without_device_geo/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_without_device_geo/test-huaweiads-auction-response.json @@ -29,6 +29,7 @@ "h": 250, "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "w": 300, "nurl":"" diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_without_userext/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_without_userext/test-huaweiads-auction-response.json index 621a43422cf..2fcaffd7d5b 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_without_userext/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_without_userext/test-huaweiads-auction-response.json @@ -29,6 +29,7 @@ "h": 300, "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "w": 250, "nurl":"" diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_wrong_mccmnc/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_wrong_mccmnc/test-huaweiads-auction-response.json index 033288ceb0e..d6d29d2c614 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_wrong_mccmnc/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/banner_wrong_mccmnc/test-huaweiads-auction-response.json @@ -29,6 +29,7 @@ "h": 250, "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "w": 300, "nurl":"" diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_include_video/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_include_video/test-huaweiads-auction-response.json index aa5fa9e4250..78e93b06659 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_include_video/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_include_video/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58022259", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "h": 500, "w": 600, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_single_image/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_single_image/test-huaweiads-auction-response.json index dde9fbcb4ea..3da566123a6 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_single_image/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_single_image/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58022259", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "h": 1280, "w": 720, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_three_image/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_three_image/test-huaweiads-auction-response.json index 22f804c5841..8253f6b3e62 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_three_image/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_three_image/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58022259", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "h": 350, "w": 400, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_three_image_include_icon/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_three_image_include_icon/test-huaweiads-auction-response.json index 898142d244c..b67ae7292e1 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_three_image_include_icon/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/native_three_image_include_icon/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58022259", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "h": 350, "w": 400, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/simple_video/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/simple_video/test-huaweiads-auction-response.json index a32f2cdc011..928b8e37d12 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/simple_video/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/simple_video/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58001445", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 1500, "price": 0.404, "h": 1280, "w": 720, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/test-huaweiads-auction-response.json index 048960f1312..c59dbe20672 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/test-huaweiads-auction-response.json @@ -29,6 +29,7 @@ "h": 300, "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 0.404, "w": 250, "nurl": "" diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_interstitial_type/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_interstitial_type/test-huaweiads-auction-response.json index 84bfd612d2e..2d7403f3e7e 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_interstitial_type/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_interstitial_type/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58001445", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 1500, "price": 0.404, "h": 500, "w": 600, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_no_icons_no_images/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_no_icons_no_images/test-huaweiads-auction-response.json index 84bfd612d2e..2d7403f3e7e 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_no_icons_no_images/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_no_icons_no_images/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58001445", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 1500, "price": 0.404, "h": 500, "w": 600, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_with_icon/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_with_icon/test-huaweiads-auction-response.json index edf5ff6ca1e..72686361101 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_with_icon/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_with_icon/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58001445", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 1500, "price": 0.404, "h": 500, "w": 600, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_with_images/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_with_images/test-huaweiads-auction-response.json index aa9c0cccf38..9425d23926c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_with_images/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_rewarded_type_with_images/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58001445", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 1500, "price": 0.404, "h": 500, "w": 600, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_roll_type/test-huaweiads-auction-response.json b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_roll_type/test-huaweiads-auction-response.json index dde2af86099..16f6247161f 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_roll_type/test-huaweiads-auction-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/huaweiads/video_roll_type/test-huaweiads-auction-response.json @@ -21,6 +21,7 @@ "crid": "58001445", "id": "test-imp-id", "impid": "test-imp-id", + "exp": 1500, "price": 0.404, "h": 1280, "w": 720, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/iionads/test-auction-iionads-response.json b/src/test/resources/org/prebid/server/it/openrtb2/iionads/test-auction-iionads-response.json index e5e6af0ffa2..e37c977df53 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/iionads/test-auction-iionads-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/iionads/test-auction-iionads-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/imds/test-auction-imds-response.json b/src/test/resources/org/prebid/server/it/openrtb2/imds/test-auction-imds-response.json index 42b87323685..8eae0a3d518 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/imds/test-auction-imds-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/imds/test-auction-imds-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 7.77, "adm": "adm001", "adid": "adid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-response.json b/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-response.json index 4bfd0bcee03..9ea7cb8766f 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/improvedigital/test-auction-improvedigital-response.json b/src/test/resources/org/prebid/server/it/openrtb2/improvedigital/test-auction-improvedigital-response.json index 2ed506e5a4c..a826a40c221 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/improvedigital/test-auction-improvedigital-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/improvedigital/test-auction-improvedigital-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.25, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/indicue/test-auction-indicue-response.json b/src/test/resources/org/prebid/server/it/openrtb2/indicue/test-auction-indicue-response.json index c561c6a98e8..6361cafe796 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/indicue/test-auction-indicue-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/indicue/test-auction-indicue-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 8.43, "adm": "adm14", "crid": "crid14", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/infytv/test-auction-infytv-response.json b/src/test/resources/org/prebid/server/it/openrtb2/infytv/test-auction-infytv-response.json index d5d69df907e..55bd555ed6b 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/infytv/test-auction-infytv-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/infytv/test-auction-infytv-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 8.43, "adm": "adm14", "crid": "crid14", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/inmobi/test-auction-inmobi-response.json b/src/test/resources/org/prebid/server/it/openrtb2/inmobi/test-auction-inmobi-response.json index ea2a8cebd9e..d2d1bd207fa 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/inmobi/test-auction-inmobi-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/inmobi/test-auction-inmobi-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/interactiveoffers/test-auction-interactiveoffers-response.json b/src/test/resources/org/prebid/server/it/openrtb2/interactiveoffers/test-auction-interactiveoffers-response.json index 174c7a0894b..3100814d919 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/interactiveoffers/test-auction-interactiveoffers-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/interactiveoffers/test-auction-interactiveoffers-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 10, "adomain": [ ], @@ -33,4 +34,4 @@ "auctiontimestamp": 0 } } -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/intertech/test-auction-intertech-response.json b/src/test/resources/org/prebid/server/it/openrtb2/intertech/test-auction-intertech-response.json index 9255d5d9323..9a0fc145939 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/intertech/test-auction-intertech-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/intertech/test-auction-intertech-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/invibes/test-auction-invibes-response.json b/src/test/resources/org/prebid/server/it/openrtb2/invibes/test-auction-invibes-response.json index c07c5d21b49..4322850fa9d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/invibes/test-auction-invibes-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/invibes/test-auction-invibes-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.3, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/iqx/test-auction-iqx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/iqx/test-auction-iqx-response.json index 76ef74b808d..3fc5c87e169 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/iqx/test-auction-iqx-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/iqx/test-auction-iqx-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "mtype": 1, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-auction-iqzone-response.json b/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-auction-iqzone-response.json index 4a6e48aac57..b73fb55b074 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-auction-iqzone-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-auction-iqzone-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "mtype": 1, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/ix/test-auction-ix-response.json b/src/test/resources/org/prebid/server/it/openrtb2/ix/test-auction-ix-response.json index 5be6fc4788c..1ecdc0136a1 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/ix/test-auction-ix-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/ix/test-auction-ix-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 4.7, "adm": "adm6", "crid": "crid6", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/jdpmedia/test-auction-jdpmedia-response.json b/src/test/resources/org/prebid/server/it/openrtb2/jdpmedia/test-auction-jdpmedia-response.json index 48fed77c3e7..31a01fdf368 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/jdpmedia/test-auction-jdpmedia-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/jdpmedia/test-auction-jdpmedia-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/jixie/test-auction-jixie-response.json b/src/test/resources/org/prebid/server/it/openrtb2/jixie/test-auction-jixie-response.json index f9c2484affa..196ed6b59e7 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/jixie/test-auction-jixie-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/jixie/test-auction-jixie-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kargo/test-auction-kargo-response.json b/src/test/resources/org/prebid/server/it/openrtb2/kargo/test-auction-kargo-response.json index 10481c0accb..2589f6a4a4d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/kargo/test-auction-kargo-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/kargo/test-auction-kargo-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kayzen/test-auction-kayzen-response.json b/src/test/resources/org/prebid/server/it/openrtb2/kayzen/test-auction-kayzen-response.json index 6b513b46072..aaccad9be2d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/kayzen/test-auction-kayzen-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/kayzen/test-auction-kayzen-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kidoz/test-auction-kidoz-response.json b/src/test/resources/org/prebid/server/it/openrtb2/kidoz/test-auction-kidoz-response.json index 668b51a85a8..5cec9fef037 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/kidoz/test-auction-kidoz-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/kidoz/test-auction-kidoz-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kiviads/test-auction-kiviads-response.json b/src/test/resources/org/prebid/server/it/openrtb2/kiviads/test-auction-kiviads-response.json index 046aeaa3005..07da2713c33 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/kiviads/test-auction-kiviads-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/kiviads/test-auction-kiviads-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/krushmedia/test-auction-krushmedia-response.json b/src/test/resources/org/prebid/server/it/openrtb2/krushmedia/test-auction-krushmedia-response.json index 25228273c75..56f25641c69 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/krushmedia/test-auction-krushmedia-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/krushmedia/test-auction-krushmedia-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "adid", "cid": "cid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/lemmaDigital/test-auction-lemmaDigital-response.json b/src/test/resources/org/prebid/server/it/openrtb2/lemmaDigital/test-auction-lemmaDigital-response.json index a21706f9abf..59dc0706b2d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/lemmaDigital/test-auction-lemmaDigital-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/lemmaDigital/test-auction-lemmaDigital-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/liftoff/test-auction-liftoff-response.json b/src/test/resources/org/prebid/server/it/openrtb2/liftoff/test-auction-liftoff-response.json index 999b4184dbd..e90d9b5f6aa 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/liftoff/test-auction-liftoff-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/liftoff/test-auction-liftoff-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 3.33, "adid": "adid001", "adm": "", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/limelightDigital/test-auction-limelightDigital-response.json b/src/test/resources/org/prebid/server/it/openrtb2/limelightDigital/test-auction-limelightDigital-response.json index 409ecdcd328..5bf9bad0853 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/limelightDigital/test-auction-limelightDigital-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/limelightDigital/test-auction-limelightDigital-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/lmkiviads/test-auction-lmkiviads-response.json b/src/test/resources/org/prebid/server/it/openrtb2/lmkiviads/test-auction-lmkiviads-response.json index 1036e1e84d0..200e13238f8 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/lmkiviads/test-auction-lmkiviads-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/lmkiviads/test-auction-lmkiviads-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/lockerdome/test-auction-lockerdome-response.json b/src/test/resources/org/prebid/server/it/openrtb2/lockerdome/test-auction-lockerdome-response.json index 0433389b511..d1ae959bebe 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/lockerdome/test-auction-lockerdome-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/lockerdome/test-auction-lockerdome-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 7.35, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/logan/test-auction-logan-response.json b/src/test/resources/org/prebid/server/it/openrtb2/logan/test-auction-logan-response.json index 5079a616a00..f5503cf319d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/logan/test-auction-logan-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/logan/test-auction-logan-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/logicad/test-auction-logicad-response.json b/src/test/resources/org/prebid/server/it/openrtb2/logicad/test-auction-logicad-response.json index dd7aa14d18c..4319787685b 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/logicad/test-auction-logicad-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/logicad/test-auction-logicad-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "adid", "cid": "cid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/loopme/test-auction-loopme-response.json b/src/test/resources/org/prebid/server/it/openrtb2/loopme/test-auction-loopme-response.json index 2c86435fb5b..30397fe4de4 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/loopme/test-auction-loopme-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/loopme/test-auction-loopme-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/loyal/test-auction-loyal-response.json b/src/test/resources/org/prebid/server/it/openrtb2/loyal/test-auction-loyal-response.json index 80cbbdf2ebb..58b5b47ee41 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/loyal/test-auction-loyal-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/loyal/test-auction-loyal-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "mtype": 1, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/lunamedia/test-auction-lunamedia-response.json b/src/test/resources/org/prebid/server/it/openrtb2/lunamedia/test-auction-lunamedia-response.json index a7c3e9ba1e2..194301eae99 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/lunamedia/test-auction-lunamedia-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/lunamedia/test-auction-lunamedia-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "adid", "cid": "cid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/mabidder/test-auction-mabidder-response.json b/src/test/resources/org/prebid/server/it/openrtb2/mabidder/test-auction-mabidder-response.json index dacba278abe..28e33d7e084 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/mabidder/test-auction-mabidder-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/mabidder/test-auction-mabidder-response.json @@ -6,6 +6,7 @@ { "id": "test-imp-id", "impid": "test-imp-id", + "exp": 300, "price": 2.734, "adm": "", "adomain": [ diff --git a/src/test/resources/org/prebid/server/it/openrtb2/madvertise/test-auction-madvertise-response.json b/src/test/resources/org/prebid/server/it/openrtb2/madvertise/test-auction-madvertise-response.json index c7c4540bd47..1c31dddaea0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/madvertise/test-auction-madvertise-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/madvertise/test-auction-madvertise-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/magnite/test-auction-magnite-response.json b/src/test/resources/org/prebid/server/it/openrtb2/magnite/test-auction-magnite-response.json index fd8cbf0a699..111e147d710 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/magnite/test-auction-magnite-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/magnite/test-auction-magnite-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/markapp/test-auction-markapp-response.json b/src/test/resources/org/prebid/server/it/openrtb2/markapp/test-auction-markapp-response.json index dad8c29bdf5..a6bccc7525a 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/markapp/test-auction-markapp-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/markapp/test-auction-markapp-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/marsmedia/test-auction-marsmedia-response.json b/src/test/resources/org/prebid/server/it/openrtb2/marsmedia/test-auction-marsmedia-response.json index 8bd2fdc004e..b2536b23494 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/marsmedia/test-auction-marsmedia-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/marsmedia/test-auction-marsmedia-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 7.35, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/mediago/test-auction-mediago-response.json b/src/test/resources/org/prebid/server/it/openrtb2/mediago/test-auction-mediago-response.json index 5c5f50670a2..7eb299b660f 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/mediago/test-auction-mediago-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/mediago/test-auction-mediago-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/medianet/test-auction-medianet-response.json b/src/test/resources/org/prebid/server/it/openrtb2/medianet/test-auction-medianet-response.json index 14ebb3b62f8..55b8dba2496 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/medianet/test-auction-medianet-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/medianet/test-auction-medianet-response.json @@ -6,6 +6,7 @@ { "id": "randomid", "impid": "test-imp-id", + "exp": 300, "price": 0.5, "adm": "some-test-ad", "adid": "12345678", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/melozen/test-auction-melozen-response.json b/src/test/resources/org/prebid/server/it/openrtb2/melozen/test-auction-melozen-response.json index 42c6696f9bb..a810ba066a1 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/melozen/test-auction-melozen-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/melozen/test-auction-melozen-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/metax/test-auction-metax-response.json b/src/test/resources/org/prebid/server/it/openrtb2/metax/test-auction-metax-response.json index b37ebc3ebea..82cc50b31be 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/metax/test-auction-metax-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/metax/test-auction-metax-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/mgid/test-auction-mgid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/mgid/test-auction-mgid-response.json index 89dc1790afe..f2c474641aa 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/mgid/test-auction-mgid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/mgid/test-auction-mgid-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.5, "nurl": "nurl", "adm": "some-test-ad", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/mgidx/test-auction-mgidx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/mgidx/test-auction-mgidx-response.json index 217cac91e56..d24c8274ed9 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/mgidx/test-auction-mgidx-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/mgidx/test-auction-mgidx-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/minutemedia/test-auction-minutemedia-response.json b/src/test/resources/org/prebid/server/it/openrtb2/minutemedia/test-auction-minutemedia-response.json index 898532edc4b..10cdbd0a6f5 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/minutemedia/test-auction-minutemedia-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/minutemedia/test-auction-minutemedia-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "banner_imp_id", + "exp": 300, "price": 0.5, "adm": "some-test-ad", "adid": "29681110", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/missena/test-auction-missena-response.json b/src/test/resources/org/prebid/server/it/openrtb2/missena/test-auction-missena-response.json index d76e9f071e5..28f9caabf7e 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/missena/test-auction-missena-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/missena/test-auction-missena-response.json @@ -6,6 +6,7 @@ { "id": "request_id", "impid": "imp_id", + "exp": 300, "price": 10.2, "adm": "adm", "crid": "id", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/mobfoxpb/test-auction-mobfoxpb-response.json b/src/test/resources/org/prebid/server/it/openrtb2/mobfoxpb/test-auction-mobfoxpb-response.json index 1c1fbe7791b..fb8a20ae2b0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/mobfoxpb/test-auction-mobfoxpb-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/mobfoxpb/test-auction-mobfoxpb-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "", "cid": "test_cid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/mobilefuse/test-auction-mobilefuse-response.json b/src/test/resources/org/prebid/server/it/openrtb2/mobilefuse/test-auction-mobilefuse-response.json index d4ff118ad3c..3f22980426a 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/mobilefuse/test-auction-mobilefuse-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/mobilefuse/test-auction-mobilefuse-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adm": "hi", "cid": "test_cid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/motorik/test-auction-motorik-response.json b/src/test/resources/org/prebid/server/it/openrtb2/motorik/test-auction-motorik-response.json index 17ba2b420b7..fba432c5be4 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/motorik/test-auction-motorik-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/motorik/test-auction-motorik-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-auction-generic-genericAlias-response.json b/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-auction-generic-genericAlias-response.json index bfd9600aa75..1e4f6f4a489 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-auction-generic-genericAlias-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-auction-generic-genericAlias-response.json @@ -11,7 +11,7 @@ "crid": "crid1", "w": 300, "h": 250, - "exp": 120, + "exp": 1500, "ext": { "prebid": { "type": "video", @@ -68,7 +68,7 @@ "crid": "crid1", "w": 300, "h": 250, - "exp": 120, + "exp": 1500, "ext": { "prebid": { "type": "video", @@ -128,7 +128,7 @@ "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110", "cid": "958", "crid": "29681110", - "exp": 120, + "exp": 1500, "ext": { "prebid": { "type": "video", @@ -179,7 +179,7 @@ "iurl": "http://nym1-ib.adnxs.com/cr?id=69595837", "cid": "958", "crid": "69595837", - "exp": 120, + "exp": 1500, "ext": { "prebid": { "type": "video", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-cache-generic-genericAlias-request.json b/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-cache-generic-genericAlias-request.json index 2d951abdbab..770ee7c9d39 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-cache-generic-genericAlias-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/multi_bid/test-cache-generic-genericAlias-request.json @@ -32,7 +32,8 @@ }, "wurl": "http://localhost:8080/event?t=win&b=21521324&a=5001&aid=tid&ts=1000&bidder=generic&f=i&int=" }, - "aid": "tid" + "aid": "tid", + "ttlseconds": 1500 }, { "type": "json", @@ -68,7 +69,8 @@ }, "wurl": "http://localhost:8080/event?t=win&b=7706636740145184841&a=5001&aid=tid&ts=1000&bidder=genericAlias&f=i&int=" }, - "aid": "tid" + "aid": "tid", + "ttlseconds": 1500 }, { "type": "json", @@ -102,7 +104,8 @@ }, "wurl": "http://localhost:8080/event?t=win&b=880290288&a=5001&aid=tid&ts=1000&bidder=generic&f=i&int=" }, - "aid": "tid" + "aid": "tid", + "ttlseconds": 1500 }, { "type": "json", @@ -138,7 +141,8 @@ }, "wurl": "http://localhost:8080/event?t=win&b=222214214214&a=5001&aid=tid&ts=1000&bidder=genericAlias&f=i&int=" }, - "aid": "tid" + "aid": "tid", + "ttlseconds": 1500 }, { "type": "xml", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/nextmillennium/test-auction-nextmillennium-response.json b/src/test/resources/org/prebid/server/it/openrtb2/nextmillennium/test-auction-nextmillennium-response.json index 71068f2cb94..be5cf8b9277 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/nextmillennium/test-auction-nextmillennium-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/nextmillennium/test-auction-nextmillennium-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "mtype": 1, "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/nobid/test-auction-nobid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/nobid/test-auction-nobid-response.json index fc7edb8ce86..ebeea62aba0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/nobid/test-auction-nobid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/nobid/test-auction-nobid-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "adid", "cid": "cid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/oms/test-auction-oms-response.json b/src/test/resources/org/prebid/server/it/openrtb2/oms/test-auction-oms-response.json index f6a94e868a8..315114b4f75 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/oms/test-auction-oms-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/oms/test-auction-oms-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/onetag/test-auction-onetag-response.json b/src/test/resources/org/prebid/server/it/openrtb2/onetag/test-auction-onetag-response.json index 80373e03cb7..4496bc2e4b6 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/onetag/test-auction-onetag-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/onetag/test-auction-onetag-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "adid", "cid": "cid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/openweb/test-auction-openweb-response.json b/src/test/resources/org/prebid/server/it/openrtb2/openweb/test-auction-openweb-response.json index 53761fd9c76..a996e780237 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/openweb/test-auction-openweb-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/openweb/test-auction-openweb-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "mtype": 1, "price": 5.78, "adm": "adm00", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/openx/test-auction-openx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/openx/test-auction-openx-response.json index 8ffbc819833..0c22b5e89b9 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/openx/test-auction-openx-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/openx/test-auction-openx-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 5.78, "adm": "adm00", "crid": "crid00", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/operaads/test-auction-operaads-response.json b/src/test/resources/org/prebid/server/it/openrtb2/operaads/test-auction-operaads-response.json index 2c7b4e4f22c..91f9dc59e5c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/operaads/test-auction-operaads-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/operaads/test-auction-operaads-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/oraki/test-auction-oraki-response.json b/src/test/resources/org/prebid/server/it/openrtb2/oraki/test-auction-oraki-response.json index 6871f609875..4bbd63fe482 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/oraki/test-auction-oraki-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/oraki/test-auction-oraki-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/orbidder/test-auction-orbidder-response.json b/src/test/resources/org/prebid/server/it/openrtb2/orbidder/test-auction-orbidder-response.json index 1a260bfa518..40a02aff7f5 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/orbidder/test-auction-orbidder-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/orbidder/test-auction-orbidder-response.json @@ -16,6 +16,7 @@ }, "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "mtype": 1 } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/outbrain/test-auction-outbrain-response.json b/src/test/resources/org/prebid/server/it/openrtb2/outbrain/test-auction-outbrain-response.json index 6c19a2a44b7..cb7fbc34fe8 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/outbrain/test-auction-outbrain-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/outbrain/test-auction-outbrain-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/ownadx/test-auction-ownadx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/ownadx/test-auction-ownadx-response.json index 38e6c451540..d7a74ef6d6d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/ownadx/test-auction-ownadx-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/ownadx/test-auction-ownadx-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "mtype": 1, "price": 3.33, "adm": "adm001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/pangle/test-auction-pangle-response.json b/src/test/resources/org/prebid/server/it/openrtb2/pangle/test-auction-pangle-response.json index 7a0cf377706..b0995267c08 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/pangle/test-auction-pangle-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/pangle/test-auction-pangle-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 1.25, "adm": "adm001", "crid": "crid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/pgam/test-auction-pgam-response.json b/src/test/resources/org/prebid/server/it/openrtb2/pgam/test-auction-pgam-response.json index 79f17402a81..82e6a8182df 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/pgam/test-auction-pgam-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/pgam/test-auction-pgam-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 8.43, "adm": "adm14", "crid": "crid14", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/pgamssp/test-auction-pgamssp-response.json b/src/test/resources/org/prebid/server/it/openrtb2/pgamssp/test-auction-pgamssp-response.json index f2c7121be84..303e8a7826c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/pgamssp/test-auction-pgamssp-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/pgamssp/test-auction-pgamssp-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.01, "adid": "2068416", "cid": "8048", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/playdigo/test-auction-playdigo-response.json b/src/test/resources/org/prebid/server/it/openrtb2/playdigo/test-auction-playdigo-response.json index 1c26c09c8c7..8bfc8b3f515 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/playdigo/test-auction-playdigo-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/playdigo/test-auction-playdigo-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "mtype": 1, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/preciso/test-auction-preciso-response.json b/src/test/resources/org/prebid/server/it/openrtb2/preciso/test-auction-preciso-response.json index 2b363db2f29..c26bf519990 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/preciso/test-auction-preciso-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/preciso/test-auction-preciso-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.5, "adm": "some-test-ad", "adid": "12345678", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-auction-pubmatic-response.json b/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-auction-pubmatic-response.json index 53731b03bf0..4df8cf0c723 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-auction-pubmatic-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-auction-pubmatic-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "test-imp-id", + "exp": 1500, "price": 4.75, "adm": "adm9", "crid": "crid9", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/pubnative/test-auction-pubnative-response.json b/src/test/resources/org/prebid/server/it/openrtb2/pubnative/test-auction-pubnative-response.json index 7b7334c52fe..d263e2b1f43 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/pubnative/test-auction-pubnative-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/pubnative/test-auction-pubnative-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/pubrise/test-auction-pubrise-response.json b/src/test/resources/org/prebid/server/it/openrtb2/pubrise/test-auction-pubrise-response.json index a49d1f99e75..752b5519b40 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/pubrise/test-auction-pubrise-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/pubrise/test-auction-pubrise-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/pulsepoint/test-auction-pulsepoint-response.json b/src/test/resources/org/prebid/server/it/openrtb2/pulsepoint/test-auction-pulsepoint-response.json index 332e1cae3ac..88c15aebf71 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/pulsepoint/test-auction-pulsepoint-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/pulsepoint/test-auction-pulsepoint-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 4.75, "adm": "adm8", "crid": "crid8", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/pwbid/test-auction-pwbid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/pwbid/test-auction-pwbid-response.json index 0f38f603a90..b6c810ede52 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/pwbid/test-auction-pwbid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/pwbid/test-auction-pwbid-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 8.43, "adm": "adm14", "crid": "crid14", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/qt/test-auction-qt-response.json b/src/test/resources/org/prebid/server/it/openrtb2/qt/test-auction-qt-response.json index 4ebd8ee119a..16db7e67c68 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/qt/test-auction-qt-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/qt/test-auction-qt-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 1.25, "adm": "adm001", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json index 101455149d7..06a8c3170aa 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "crid": "creativeId", "mtype": 1, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/relevantdigital/test-auction-relevantdigital-response.json b/src/test/resources/org/prebid/server/it/openrtb2/relevantdigital/test-auction-relevantdigital-response.json index 9e1e479b4ed..d3531b49cca 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/relevantdigital/test-auction-relevantdigital-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/relevantdigital/test-auction-relevantdigital-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 1500, "price": 5.78, "adm": "adm", "crid": "crid", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-response.json b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-response.json index c3de29e49d4..f595ea39c9f 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 3.33, "adm": "adm001", "adid": "adid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/revcontent/test-auction-revcontent-response.json b/src/test/resources/org/prebid/server/it/openrtb2/revcontent/test-auction-revcontent-response.json index a2acc3d4df5..4e6047c3738 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/revcontent/test-auction-revcontent-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/revcontent/test-auction-revcontent-response.json @@ -6,6 +6,7 @@ { "id": "bid_id", "impid": "imp_id", + "exp": 300, "price": 0.5, "adm": "