diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
index 125eafe5d40..3f90c1377d2 100644
--- a/.github/workflows/pullrequest.yml
+++ b/.github/workflows/pullrequest.yml
@@ -36,4 +36,4 @@ jobs:
${{ runner.os }}-maven-
- name: Build with Maven
- run: mvn -B package --file extra/pom.xml
+ run: mvn -B verify --file extra/pom.xml
diff --git a/.gitignore b/.gitignore
index 8c65eb25152..49495464430 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,5 @@
target/
.DS_Store
+
+.allure/
diff --git a/.maven-dockerinclude b/.maven-dockerinclude
new file mode 100644
index 00000000000..4653fc53688
--- /dev/null
+++ b/.maven-dockerinclude
@@ -0,0 +1,2 @@
+target/*.jar
+src/main/docker/*
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000000..ae1d10bc210
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,15 @@
+FROM openjdk:11-jdk-slim
+
+WORKDIR /app/prebid-server
+
+VOLUME /app/prebid-server/conf
+VOLUME /app/prebid-server/data
+
+COPY src/main/docker/run.sh ./
+COPY src/main/docker/application.yaml ./
+COPY target/prebid-server.jar ./
+
+EXPOSE 8080
+EXPOSE 8060
+
+ENTRYPOINT [ "/app/prebid-server/run.sh" ]
diff --git a/pom.xml b/pom.xml
index a2b9873c30b..9f20528094e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,6 +55,11 @@
9.4.43.v20210629
3.0.6
1.4.196
+ 1.3-groovy-2.5
+ 1.15.3
+ 5.11.2
+ 2.14.0
+ 1.9.7
3.1.0
@@ -63,7 +68,11 @@
0.8.2
2.2.4
3.8.0
- 2.22.1
+ 2.22.2
+ ${maven-surefire-plugin.version}
+ 0.36.0
+ 1.12.1
+ 2.10.0
@@ -195,6 +204,18 @@
jackson-module-afterburner
${jackson.version}
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ ${jackson.version}
+ test
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ ${jackson.version}
+ test
+
com.networknt
json-schema-validator
@@ -400,6 +421,17 @@
${restassured.version}
test
+
+ org.spockframework
+ spock-core
+ ${spock.version}
+ test
+
+
+ org.hibernate
+ hibernate-core
+ test
+
io.vertx
@@ -413,6 +445,41 @@
${h2.version}
test
+
+ org.testcontainers
+ testcontainers
+ ${testcontainers.version}
+ test
+
+
+ org.testcontainers
+ spock
+ ${testcontainers.version}
+ test
+
+
+ org.testcontainers
+ mockserver
+ ${testcontainers.version}
+ test
+
+
+ org.testcontainers
+ mysql
+ ${testcontainers.version}
+ test
+
+
+ org.mock-server
+ mockserver-client-java
+ ${mockserver-client.version}
+ test
+
+
+ io.qameta.allure
+ allure-spock
+ ${allure.version}
+
@@ -429,6 +496,11 @@
maven-surefire-plugin
${maven-surefire-plugin.version}
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ ${maven-failsafe-plugin.version}
+
org.apache.maven.plugins
maven-checkstyle-plugin
@@ -451,6 +523,11 @@
+
+ org.codehaus.gmavenplus
+ gmavenplus-plugin
+ ${gmavenplus-plugin.version}
+
@@ -576,8 +653,72 @@
+
+ io.fabric8
+ docker-maven-plugin
+ ${docker-maven-plugin.version}
+
+
+ build-containers
+ pre-integration-test
+
+ build
+
+
+
+
+
+ org.codehaus.gmavenplus
+ gmavenplus-plugin
+
+
+
+ compileTests
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
+
+
+ target/allure-results
+ ${mockserver-client.version}
+
+
+
+
+
+ integration-test
+ verify
+
+
+
+ **/*Spec.java
+
+
+
+
+
+
+ org.aspectj
+ aspectjweaver
+ ${aspectj.version}
+
+
+
+
+ io.qameta.allure
+ allure-maven
+ ${allure-maven.version}
+
+
diff --git a/src/main/docker/application.yaml b/src/main/docker/application.yaml
new file mode 100644
index 00000000000..458f9b4d25e
--- /dev/null
+++ b/src/main/docker/application.yaml
@@ -0,0 +1,4 @@
+gdpr:
+ vendorlist:
+ v2:
+ cache-dir: /app/prebid-server/data/vendorlist-v2
diff --git a/src/main/docker/run.sh b/src/main/docker/run.sh
new file mode 100755
index 00000000000..54f73437643
--- /dev/null
+++ b/src/main/docker/run.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+exec java \
+ -Dvertx.cacheDirBase=/app/prebid-server/data/.vertx \
+ -Dspring.config.additional-location=/app/prebid-server/,/app/prebid-server/conf/ \
+ ${JAVA_OPTS} \
+ -jar \
+ /app/prebid-server/prebid-server.jar
diff --git a/src/test/groovy/.editorconfig b/src/test/groovy/.editorconfig
new file mode 100644
index 00000000000..63243037b4d
--- /dev/null
+++ b/src/test/groovy/.editorconfig
@@ -0,0 +1,41 @@
+[{*.gant, *.gradle, *.groovy, *.gson, *.gy}]
+ij_groovy_align_multiline_chained_methods = true
+ij_groovy_array_initializer_wrap = normal
+ij_groovy_binary_operation_wrap = normal
+ij_groovy_blank_lines_after_class_header = 1
+ij_groovy_blank_lines_after_imports = 1
+ij_groovy_blank_lines_after_package = 1
+ij_groovy_blank_lines_around_class = 1
+ij_groovy_blank_lines_around_field = 0
+ij_groovy_blank_lines_around_field_in_interface = 0
+ij_groovy_blank_lines_around_method = 1
+ij_groovy_blank_lines_around_method_in_interface = 1
+ij_groovy_blank_lines_before_imports = 1
+ij_groovy_blank_lines_before_method_body = 0
+ij_groovy_blank_lines_before_package = 0
+ij_groovy_class_count_to_use_import_on_demand = 999
+ij_groovy_do_while_brace_force = always
+ij_groovy_extends_list_wrap = normal
+ij_groovy_for_brace_force = always
+ij_groovy_for_statement_wrap = normal
+ij_groovy_if_brace_force = always
+ij_groovy_imports_layout = *, |, java.**, |, $*
+ij_groovy_insert_inner_class_imports = true
+ij_groovy_keep_blank_lines_before_right_brace = 2
+ij_groovy_keep_blank_lines_in_code = 1
+ij_groovy_keep_blank_lines_in_declarations = 2
+ij_groovy_keep_control_statement_in_one_line = false
+ij_groovy_keep_line_breaks = true
+ij_groovy_keep_multiple_expressions_in_one_line = false
+ij_groovy_keep_simple_blocks_in_one_line = false
+ij_groovy_keep_simple_classes_in_one_line = true
+ij_groovy_keep_simple_lambdas_in_one_line = true
+ij_groovy_keep_simple_methods_in_one_line = true
+ij_groovy_layout_static_imports_separately = true
+ij_groovy_line_comment_at_first_column = true
+ij_groovy_method_parameters_wrap = on_every_item
+ij_groovy_names_count_to_use_import_on_demand = 999
+ij_groovy_ternary_operation_wrap = normal
+ij_groovy_throws_keyword_wrap = normal
+ij_groovy_use_flying_geese_braces = true
+ij_groovy_while_brace_force = always
diff --git a/src/test/groovy/org/prebid/server/functional/AmpSpec.groovy b/src/test/groovy/org/prebid/server/functional/AmpSpec.groovy
new file mode 100644
index 00000000000..a2c15ff86e5
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/AmpSpec.groovy
@@ -0,0 +1,124 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.model.db.StoredRequest
+import org.prebid.server.functional.model.request.amp.AmpRequest
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.service.PrebidServerService
+import org.prebid.server.functional.util.PBSUtils
+import spock.lang.Shared
+import spock.lang.Unroll
+
+class AmpSpec extends BaseSpec {
+
+ private static final int MAX_TIMEOUT = 5000
+ private static final int DEFAULT_TIMEOUT = PBSUtils.getRandomNumber(0, MAX_TIMEOUT)
+
+ @Shared
+ PrebidServerService prebidServerService = pbsServiceFactory.getService(["auction.max-timeout-ms" : MAX_TIMEOUT as String,
+ "auction.default-timeout-ms": DEFAULT_TIMEOUT as String])
+
+ def "PBS should apply timeout from stored request when it's not specified in the request"() {
+ given: "Default AMP request without timeout"
+ def ampRequest = AmpRequest.defaultAmpRequest.tap {
+ timeout = null
+ }
+
+ and: "Default stored request with timeout"
+ def timeout = PBSUtils.getRandomNumber(0, MAX_TIMEOUT)
+ def ampStoredRequest = BidRequest.defaultStoredRequest.tap {
+ tmax = timeout
+ }
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ when: "PBS processes amp request"
+ prebidServerService.sendAmpRequest(ampRequest)
+
+ then: "Bidder request should contain timeout from the stored request"
+ def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id)
+ assert bidderRequest.tmax == timeout as Long
+ }
+
+ @Unroll
+ def "PBS should prefer timeout from the request when stored request timeout is #tmax"() {
+ given: "Default AMP request with timeout"
+ def timeout = PBSUtils.getRandomNumber(0, MAX_TIMEOUT)
+ def ampRequest = AmpRequest.defaultAmpRequest.tap {
+ it.timeout = timeout
+ }
+
+ and: "Default stored request"
+ def ampStoredRequest = BidRequest.defaultStoredRequest.tap {
+ it.tmax = tmax
+ }
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ when: "PBS processes amp request"
+ prebidServerService.sendAmpRequest(ampRequest)
+
+ then: "Bidder request should contain timeout from the request"
+ def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id)
+ assert bidderRequest.tmax == timeout as Long
+
+ where:
+ tmax << [null, PBSUtils.getRandomNumber(0, MAX_TIMEOUT)]
+ }
+
+ @Unroll
+ def "PBS should honor max timeout from the settings"() {
+ given: "Default AMP request"
+ def ampRequest = AmpRequest.defaultAmpRequest.tap {
+ it.timeout = ampRequestTimeout
+ }
+
+ and: "Default stored request"
+ def ampStoredRequest = BidRequest.defaultStoredRequest.tap {
+ it.tmax = storedRequestTimeout
+ }
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ when: "PBS processes amp request"
+ prebidServerService.sendAmpRequest(ampRequest)
+
+ then: "Bidder request timeout should correspond to the maximum from the settings"
+ def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id)
+ assert bidderRequest.tmax == MAX_TIMEOUT as Long
+
+ where:
+ ampRequestTimeout || storedRequestTimeout
+ MAX_TIMEOUT + 1 || null
+ null || MAX_TIMEOUT + 1
+ MAX_TIMEOUT + 1 || MAX_TIMEOUT + 1
+ }
+
+ def "PBS should honor default timeout"() {
+ given: "Default AMP request without timeout"
+ def ampRequest = AmpRequest.defaultAmpRequest.tap {
+ it.timeout = null
+ }
+
+ and: "Default stored request without timeout"
+ def ampStoredRequest = BidRequest.defaultStoredRequest.tap {
+ it.tmax = null
+ }
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ when: "PBS processes amp request"
+ prebidServerService.sendAmpRequest(ampRequest)
+
+ then: "Bidder request timeout should correspond to the maximum from the settings"
+ def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id)
+ assert bidderRequest.tmax == DEFAULT_TIMEOUT as Long
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/AnalyticsSpec.groovy b/src/test/groovy/org/prebid/server/functional/AnalyticsSpec.groovy
new file mode 100644
index 00000000000..a11c759db9c
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/AnalyticsSpec.groovy
@@ -0,0 +1,38 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.model.mock.services.pubstack.PubStackResponse
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.testcontainers.Dependencies
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.testcontainers.scaffolding.PubStackAnalytics
+import spock.lang.Retry
+import spock.lang.Shared
+
+@Retry
+@PBSTest
+class AnalyticsSpec extends BaseSpec {
+
+ private static final String scopeId = UUID.randomUUID()
+
+ @Shared
+ PubStackAnalytics analytics = new PubStackAnalytics(Dependencies.networkServiceContainer, mapper).tap {
+ it.setResponse(PubStackResponse.getDefaultPubStackResponse(scopeId, Dependencies.networkServiceContainer.rootUri))
+ }
+
+ def "PBS should send PubStack analytics when analytics.pubstack.enabled=true"() {
+ given: "Pbs config"
+ def pbsService = pbsServiceFactory.getService(pbsServiceFactory.pubstackAnalyticsConfig(scopeId))
+
+ and: "Basic bid request"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ and: "Initial request count"
+ def analyticsRequestCount = analytics.requestCount
+
+ when: "PBS processes auction request"
+ pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Analytics request body should be not empty"
+ assert analytics.requestCount == analyticsRequestCount + 1
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy
new file mode 100644
index 00000000000..1dfa054409f
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy
@@ -0,0 +1,48 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.repository.HibernateRepositoryService
+import org.prebid.server.functional.repository.dao.AccountDao
+import org.prebid.server.functional.repository.dao.ConfigDao
+import org.prebid.server.functional.repository.dao.StoredImpDao
+import org.prebid.server.functional.repository.dao.StoredRequestDao
+import org.prebid.server.functional.repository.dao.StoredResponseDao
+import org.prebid.server.functional.service.PrebidServerService
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.testcontainers.PbsServiceFactory
+import org.prebid.server.functional.testcontainers.scaffolding.Bidder
+import org.prebid.server.functional.testcontainers.scaffolding.PrebidCache
+import org.prebid.server.functional.util.ObjectMapperWrapper
+import spock.lang.Specification
+
+import static org.prebid.server.functional.testcontainers.Dependencies.mysqlContainer
+import static org.prebid.server.functional.testcontainers.Dependencies.networkServiceContainer
+import static org.prebid.server.functional.testcontainers.Dependencies.objectMapperWrapper
+
+@PBSTest
+abstract class BaseSpec extends Specification {
+
+ protected static final ObjectMapperWrapper mapper = objectMapperWrapper
+ protected static final PbsServiceFactory pbsServiceFactory = new PbsServiceFactory(networkServiceContainer, objectMapperWrapper)
+ protected static final Bidder bidder = new Bidder(networkServiceContainer, objectMapperWrapper)
+ protected static final PrebidCache prebidCache = new PrebidCache(networkServiceContainer, objectMapperWrapper)
+ protected static final PrebidServerService defaultPbsService = pbsServiceFactory.getService([:])
+
+ protected static final HibernateRepositoryService repository = new HibernateRepositoryService(mysqlContainer)
+ protected static final AccountDao accountDao = repository.accountDao
+ protected static final ConfigDao configDao = repository.configDao
+ protected static final StoredImpDao storedImp = repository.storedImpDao
+ protected static final StoredRequestDao storedRequestDao = repository.storedRequestDao
+ protected static final StoredResponseDao storedResponseDao = repository.storedResponseDao
+
+ def setupSpec() {
+ prebidCache.setResponse()
+ bidder.setResponse()
+ }
+
+ def cleanupSpec() {
+ bidder.reset()
+ prebidCache.reset()
+ repository.removeAllDatabaseData()
+ pbsServiceFactory.stopContainers()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/BidValidationSpec.groovy b/src/test/groovy/org/prebid/server/functional/BidValidationSpec.groovy
new file mode 100644
index 00000000000..853cbf9e175
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/BidValidationSpec.groovy
@@ -0,0 +1,196 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.model.bidder.BidderName
+import org.prebid.server.functional.model.request.auction.App
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.model.request.auction.MultiBid
+import org.prebid.server.functional.model.request.auction.Site
+import org.prebid.server.functional.model.response.auction.Bid
+import org.prebid.server.functional.model.response.auction.BidResponse
+import org.prebid.server.functional.model.response.auction.ErrorType
+import org.prebid.server.functional.service.PrebidServerException
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.util.PBSUtils
+import spock.lang.PendingFeature
+import spock.lang.Unroll
+
+@PBSTest
+class BidValidationSpec extends BaseSpec {
+
+ @PendingFeature
+ def "PBS should return error type invalid bid when bid does not pass validation with error"() {
+ given: "Default basic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ and: "Default basic bid with seatbid[].bid[].price = null"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ bidResponse.seatbid.first().bid.first().price = null
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain basic fields"
+ assert response.ext?.errors[ErrorType.GENERIC]*.code == [5]
+ assert response.ext?.errors[ErrorType.GENERIC]*.message ==
+ ["Bid \"${bidResponse.seatbid.first().bid.first().id}\" does not contain a 'price'" as String]
+ }
+
+ def "PBS should remove site object and emit warning when both site and app present, debug mode is enabled"() {
+ given: "Default basic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.site = new Site(id: null, name: PBSUtils.randomString, page: null)
+ bidRequest.ext.prebid.debug = 1
+
+ and: "Set app"
+ bidRequest.app = App.defaultApp
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should not contain site"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert !bidderRequest.site
+
+ and: "Response should contain debug warning"
+ assert response.ext?.warnings[ErrorType.PREBID]*.message ==
+ ["BidRequest contains app and site. Removed site object"]
+ }
+
+ def "PBS should remove site object and emit warning when both site and app present, debug mode is disabled"() {
+ given: "Default basic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.site = new Site(id: null, name: PBSUtils.randomString, page: null)
+ bidRequest.ext.prebid.debug = 0
+
+ and: "Set app"
+ bidRequest.app = App.defaultApp
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should not contain site"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert !bidderRequest.site
+
+ and: "Response should contain debug warning"
+ assert response.ext?.warnings[ErrorType.PREBID]*.message ==
+ ["BidRequest contains app and site. Removed site object"]
+ }
+
+ def "PBS should validate site when it is present"() {
+ given: "Default basic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.site = new Site(id: null, name: PBSUtils.randomString, page: null)
+ bidRequest.ext.prebid.debug = 1
+
+ when: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Request should fail with error"
+ def exception = thrown(PrebidServerException)
+ assert exception.responseBody.contains("request.site should include at least one of request.site.id or request.site.page")
+ }
+
+ def "PBS should treat bids with 0 price as valid when deal id is present"() {
+ given: "Default basic BidRequest with generic bidder"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = 1
+
+ and: "Bid response with 0 price bid"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ bidResponse.seatbid.first().bid.first().dealid = PBSUtils.randomNumber
+ bidResponse.seatbid.first().bid.first().price = 0
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Zero price bid should be present in the PBS response"
+ assert response.seatbid?.first()?.bid*.id == [bidResponse.seatbid.first().bid.first().id]
+
+ and: "No errors should be emitted in the debug"
+ assert !response.ext?.errors
+ assert !response.ext?.warnings
+ }
+
+ @Unroll
+ def "PBS should drop invalid bid and emit debug error when bid price is #bidPrice and deal id is #dealId"() {
+ given: "Default basic BidRequest with generic bidder"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = 1
+
+ and: "Bid response"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ def bid = bidResponse.seatbid.first().bid.first()
+ bid.dealid = dealId
+ bid.price = bidPrice
+ def bidId = bid.id
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Invalid bid should be deleted"
+ assert response.seatbid.size() == 0
+
+ and: "PBS should emit an error"
+ assert response.ext?.warnings[ErrorType.PREBID]*.code == [999]
+ assert response.ext?.warnings[ErrorType.PREBID]*.message ==
+ ["Dropped bid '$bidId'. Does not contain a positive (or zero if there is a deal) 'price'" as String]
+
+ where:
+ bidPrice | dealId
+ PBSUtils.randomNegativeNumber | null
+ PBSUtils.randomNegativeNumber | PBSUtils.randomNumber
+ 0 | null
+ null | PBSUtils.randomNumber
+ null | null
+ }
+
+ @Unroll
+ def "PBS should only drop invalid bid without discarding whole seat"() {
+ given: "Default basic BidRequest with generic bidder"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = 1
+ bidRequest.ext.prebid.multibid = [new MultiBid(bidder: BidderName.GENERIC.value, maxBids: 2)]
+
+ and: "Bid response with 2 bids"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ bidResponse.seatbid[0].bid << Bid.getDefaultBid(bidRequest.imp.first())
+
+ and: "One of the bids is invalid"
+ def invalidBid = bidResponse.seatbid.first().bid.first()
+ invalidBid.dealid = dealId
+ invalidBid.price = bidPrice
+ def validBidId = bidResponse.seatbid.first().bid.last().id
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Invalid bids should be deleted"
+ assert response.seatbid?.first()?.bid*.id == [validBidId]
+
+ and: "PBS should emit an error"
+ assert response.ext?.warnings[ErrorType.PREBID]*.code == [999]
+ assert response.ext?.warnings[ErrorType.PREBID]*.message ==
+ ["Dropped bid '$invalidBid.id'. Does not contain a positive (or zero if there is a deal) 'price'" as String]
+
+ where:
+ bidPrice | dealId
+ PBSUtils.randomNegativeNumber | null
+ PBSUtils.randomNegativeNumber | PBSUtils.randomNumber
+ 0 | null
+ null | PBSUtils.randomNumber
+ null | null
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/BidderParamsSpec.groovy b/src/test/groovy/org/prebid/server/functional/BidderParamsSpec.groovy
new file mode 100644
index 00000000000..228baf16ef2
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/BidderParamsSpec.groovy
@@ -0,0 +1,276 @@
+package org.prebid.server.functional
+
+import io.qameta.allure.Issue
+import org.prebid.server.functional.model.bidder.BidderName
+import org.prebid.server.functional.model.bidder.Generic
+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.Device
+import org.prebid.server.functional.model.request.auction.Geo
+import org.prebid.server.functional.model.request.auction.RegsExt
+import org.prebid.server.functional.model.request.vtrack.VtrackRequest
+import org.prebid.server.functional.model.request.vtrack.xml.Vast
+import org.prebid.server.functional.model.response.auction.ErrorType
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.util.PBSUtils
+import spock.lang.Unroll
+
+import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS
+
+@PBSTest
+class BidderParamsSpec extends BaseSpec {
+
+ @Unroll
+ def "PBS should send request to bidder when adapter-defaults.enabled = #adapterDefault and adapters.BIDDER.enabled = #generic"() {
+ given: "PBS with adapter configuration"
+ def pbsService = pbsServiceFactory.getService(adapterConfig)
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ when: "PBS processes auction request"
+ def response = pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain httpcalls"
+ assert response?.ext?.debug?.httpcalls[BidderName.GENERIC.value]
+
+ and: "Response should not contain error"
+ assert !response?.ext?.errors
+
+ where:
+ adapterDefault | generic | adapterConfig
+ "true" | "true" | ["adapter-defaults.enabled" : adapterDefault,
+ "adapters.facebook.enabled" : "false",
+ "adapters.brightroll.enabled": "false",
+ "adapters.generic.enabled" : generic]
+ "false" | "true" | ["adapter-defaults.enabled": adapterDefault,
+ "adapters.generic.enabled": generic]
+ }
+
+ @Unroll
+ def "PBS should not send request to bidder and emit error when adapter-defaults.enabled = #adapterDefault and adapters.BIDDER.enabled = #generic"() {
+ given: "PBS with adapter configuration"
+ def pbsService = pbsServiceFactory.getService(adapterConfig)
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ when: "PBS processes auction request"
+ def response = pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain error"
+ assert response?.ext?.errors[ErrorType.GENERIC]*.code == [2]
+
+ where:
+ adapterDefault | generic | adapterConfig
+ "false" | "false" | ["adapter-defaults.enabled": adapterDefault,
+ "adapters.generic.enabled": generic]
+ "true" | "false" | ["adapter-defaults.enabled" : adapterDefault,
+ "adapters.facebook.enabled" : "false",
+ "adapters.brightroll.enabled": "false",
+ "adapters.generic.enabled" : generic]
+ }
+
+ @Unroll
+ def "PBS should modify vast xml when adapter-defaults.modifying-vast-xml-allowed = #adapterDefault and BIDDER.modifying-vast-xml-allowed = #generic"() {
+ given: "PBS with adapter configuration"
+ def pbsService = pbsServiceFactory.getService(["adapter-defaults.modifying-vast-xml-allowed": adapterDefault,
+ "adapters.generic.modifying-vast-xml-allowed": generic])
+
+ and: "Default VtrackRequest"
+ String payload = PBSUtils.randomString
+ def request = VtrackRequest.getDefaultVtrackRequest(mapper.encodeXml(Vast.getDefaultVastModel(payload)))
+ def accountId = PBSUtils.randomNumber
+
+ and: "Account in the DB"
+ def account = new Account(uuid: accountId, eventsEnabled: true)
+ accountDao.save(account)
+
+ when: "PBS processes vtrack request"
+ pbsService.sendVtrackRequest(request, accountId.toString())
+
+ then: "vast xml is modified"
+ def prebidCacheRequest = prebidCache.getXmlRecordedRequestsBody(payload)
+ assert prebidCacheRequest.size() == 1
+ assert prebidCacheRequest.first().contains("/event?t=imp&b=${request.puts[0].bidid}&a=$accountId&bidder=${request.puts[0].bidder}")
+
+ where:
+ adapterDefault | generic
+ "true" | "true"
+ "false" | "true"
+ }
+
+ @Unroll
+ def "PBS should not modify vast xml when adapter-defaults.modifying-vast-xml-allowed = #adapterDefault and BIDDER.modifying-vast-xml-allowed = #generic"() {
+ given: "PBS with adapter configuration"
+ def pbsService = pbsServiceFactory.getService(["adapter-defaults.modifying-vast-xml-allowed": adapterDefault,
+ "adapters.generic.modifying-vast-xml-allowed": generic])
+
+ and: "Default VtrackRequest"
+ String payload = PBSUtils.randomString
+ def request = VtrackRequest.getDefaultVtrackRequest(mapper.encodeXml(Vast.getDefaultVastModel(payload)))
+ def accountId = PBSUtils.randomNumber
+
+ and: "Account in the DB"
+ def account = new Account(uuid: accountId, eventsEnabled: true)
+ accountDao.save(account)
+
+ when: "PBS processes vtrack request"
+ pbsService.sendVtrackRequest(request, accountId.toString())
+
+ then: "vast xml is not modified"
+ def prebidCacheRequest = prebidCache.getXmlRecordedRequestsBody(payload)
+ assert prebidCacheRequest.size() == 1
+ assert !prebidCacheRequest.first().contains("/event?t=imp&b=${request.puts[0].bidid}&a=$accountId&bidder=${request.puts[0].bidder}")
+
+ where:
+ adapterDefault | generic
+ "true" | "false"
+ "false" | "false"
+ }
+
+ @Unroll
+ def "PBS should mask values when adapter-defaults.pbs-enforces-ccpa = #adapterDefault settings when BIDDER.pbs-enforces-ccpa = #generic"() {
+ given: "PBS with adapter configuration"
+ def pbsService = pbsServiceFactory.getService(["adapter-defaults.pbs-enforces-ccpa": adapterDefault,
+ "adapters.generic.pbs-enforces-ccpa": generic])
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ def valid_ccpa = "1YYY"
+ bidRequest.regs.ext = new RegsExt(gdpr: 0, usPrivacy: valid_ccpa)
+ def lat = PBSUtils.getFractionalRandomNumber(0, 90)
+ def lon = PBSUtils.getFractionalRandomNumber(0, 90)
+ bidRequest.device = new Device(geo: new Geo(lat: lat, lon: lon))
+
+ when: "PBS processes auction request"
+ pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain masked values"
+ def bidderRequests = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequests.device?.geo?.lat == PBSUtils.getRoundFractionalNumber(lat, 2)
+ assert bidderRequests.device?.geo?.lon == PBSUtils.getRoundFractionalNumber(lon, 2)
+
+ where:
+ adapterDefault | generic
+ "true" | "true"
+ "false" | "true"
+ }
+
+ @Unroll
+ def "PBS should not mask values when adapter-defaults.pbs-enforces-ccpa = #adapterDefault settings when BIDDER.pbs-enforces-ccpa = #generic"() {
+ given: "PBS with adapter configuration"
+ def pbsService = pbsServiceFactory.getService(["adapter-defaults.pbs-enforces-ccpa": adapterDefault,
+ "adapters.generic.pbs-enforces-ccpa": generic])
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ def valid_ccpa = "1YYY"
+ bidRequest.regs.ext = new RegsExt(gdpr: 0, usPrivacy: valid_ccpa)
+ def lat = PBSUtils.getFractionalRandomNumber(0, 90)
+ def lon = PBSUtils.getFractionalRandomNumber(0, 90)
+ bidRequest.device = new Device(geo: new Geo(lat: lat, lon: lon))
+
+ when: "PBS processes auction request"
+ pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain not masked values"
+ def bidderRequests = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequests.device?.geo?.lat == lat
+ assert bidderRequests.device?.geo?.lon == lon
+
+ where:
+ adapterDefault | generic
+ "true" | "false"
+ "false" | "false"
+ }
+
+ def "PBS should prefer bidder params from imp[*].ext.prebid.bidder.BIDDER when ext.prebid.bidderparams.BIDDER is specified"() {
+ given: "Default basic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ def firstParam = PBSUtils.randomNumber
+ bidRequest.imp.first().ext.prebid.bidder.generic = new Generic(firstParam: firstParam)
+
+ and: "Set bidderParam to bidRequest"
+ bidRequest.ext.prebid.bidderParams = [(BidderName.GENERIC): [firstParam: PBSUtils.randomNumber]]
+
+ when: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain zoneId value from imp[*].ext.prebid.bidder.BIDDER"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.imp[0]?.ext?.bidder?.firstParam == firstParam
+ }
+
+ def "PBS should send bidder params from imp[*].ext.prebid.bidder.BIDDER when ext.prebid.bidderparams.BIDDER isn't specified"() {
+ given: "Default basic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ def firstParam = PBSUtils.randomNumber
+ bidRequest.imp.first().ext.prebid.bidder.generic = new Generic(firstParam: firstParam)
+
+ and: "Set bidderParam = null to bidRequest"
+ bidRequest.ext.prebid.bidderParams = null
+
+ when: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain zoneId value from imp[*].ext.prebid.bidder.BIDDER"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.imp[0]?.ext?.bidder?.firstParam == firstParam
+ }
+
+ def "PBS should merge bidder params from imp[*].ext.prebid.bidder.BIDDER and ext.prebid.bidderparams.BIDDER"() {
+ given: "Default basic BidRequest with zoneId = null"
+ def bidRequest = BidRequest.defaultBidRequest
+ def firstParam = PBSUtils.randomNumber
+ bidRequest.imp.first().ext.prebid.bidder.generic = new Generic(firstParam: firstParam)
+
+ and: "Set bidderParam to bidRequest"
+ def secondParam = PBSUtils.randomNumber
+ bidRequest.ext.prebid.bidderParams = [(BidderName.GENERIC): [secondParam: secondParam]]
+
+ when: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should merge bidder params"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.imp[0]?.ext?.bidder?.firstParam == firstParam
+ assert bidderRequest.imp[0]?.ext?.bidder?.secondParam == secondParam
+ }
+
+ def "PBS should only send bidder params from ext.prebid.bidderparams.BIDDER to specified bidder"() {
+ given: "Default basic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ def firstParam = PBSUtils.randomNumber
+ bidRequest.imp.first().ext.prebid.bidder.generic = new Generic(firstParam: firstParam)
+ bidRequest.imp.first().ext.prebid.bidder.appNexus = null
+
+ and: "Set bidderParam to bidRequest"
+ bidRequest.ext.prebid.bidderParams = [(APPNEXUS): [placement_id: PBSUtils.randomNumber]]
+
+ when: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response shouldn't contain bidder param from another biddder"
+ bidder.getBidderRequest(bidRequest.id)
+ }
+
+ // TODO: create same test for enabled circuit breaker
+ @Issue("https://github.com/prebid/prebid-server-java/issues/1478")
+ def "PBS should emit warning when bidder endpoint is invalid"() {
+ given: "Pbs config"
+ def pbsService = pbsServiceFactory.getService(["adapters.generic.enabled" : "true",
+ "adapters.generic.endpoint" : "https://",
+ "http-client.circuit-breaker.enabled": "false"])
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ when: "PBS processes auction request"
+ def response = pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain error"
+ assert response.ext?.errors[ErrorType.GENERIC]*.code == [999]
+ assert response.ext?.errors[ErrorType.GENERIC]*.message == ["no empty host accepted"]
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/CcpaSpec.groovy b/src/test/groovy/org/prebid/server/functional/CcpaSpec.groovy
new file mode 100644
index 00000000000..35d7eba8de6
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/CcpaSpec.groovy
@@ -0,0 +1,68 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.model.config.AccountCcpaConfig
+import org.prebid.server.functional.model.config.AccountConfig
+import org.prebid.server.functional.model.config.AccountPrivacyConfig
+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.Device
+import org.prebid.server.functional.model.request.auction.Geo
+import org.prebid.server.functional.model.request.auction.RegsExt
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.util.PBSUtils
+
+@PBSTest
+class CcpaSpec extends BaseSpec {
+
+ // TODO: extend ccpa test with actual fields that we should mask
+ def "PBS should mask publisher info when privacy.ccpa.enabled = true in account config"() {
+ given: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ def valid_ccpa = "1YYY"
+ bidRequest.regs.ext = new RegsExt(gdpr: 0, usPrivacy: valid_ccpa)
+ def lat = PBSUtils.getFractionalRandomNumber(0, 90)
+ def lon = PBSUtils.getFractionalRandomNumber(0, 90)
+ bidRequest.device = new Device(geo: new Geo(lat: lat, lon: lon))
+
+ and: "Save account config into DB"
+ def ccpa = new AccountCcpaConfig(enabled: true)
+ def privacy = new AccountPrivacyConfig(ccpa: ccpa)
+ def accountConfig = new AccountConfig(privacy: privacy)
+ def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain masked values"
+ def bidderRequests = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequests.device?.geo?.lat == PBSUtils.getRoundFractionalNumber(lat, 2)
+ assert bidderRequests.device?.geo?.lon == PBSUtils.getRoundFractionalNumber(lon, 2)
+ }
+
+ // TODO: extend this ccpa test with actual fields that we should mask
+ def "PBS should not mask publisher info when privacy.ccpa.enabled = false in account config"() {
+ given: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ def valid_ccpa = "1YYY"
+ bidRequest.regs.ext = new RegsExt(gdpr: 0, usPrivacy: valid_ccpa)
+ def lat = PBSUtils.getFractionalRandomNumber(0, 90)
+ def lon = PBSUtils.getFractionalRandomNumber(0, 90)
+ bidRequest.device = new Device(geo: new Geo(lat: lat, lon: lon))
+
+ and: "Save account config into DB"
+ def ccpa = new AccountCcpaConfig(enabled: false)
+ def privacy = new AccountPrivacyConfig(ccpa: ccpa)
+ def accountConfig = new AccountConfig(privacy: privacy)
+ def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain not masked values"
+ def bidderRequests = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequests.device?.geo?.lat == lat
+ assert bidderRequests.device?.geo?.lon == lon
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/DealsSpec.groovy b/src/test/groovy/org/prebid/server/functional/DealsSpec.groovy
new file mode 100644
index 00000000000..eb970d44328
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/DealsSpec.groovy
@@ -0,0 +1,162 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.model.request.auction.MultiBid
+import org.prebid.server.functional.model.request.auction.Targeting
+import org.prebid.server.functional.model.response.auction.Bid
+import org.prebid.server.functional.model.response.auction.BidResponse
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.util.PBSUtils
+import spock.lang.Unroll
+
+@PBSTest
+class DealsSpec extends BaseSpec {
+
+ @Unroll
+ def "PBS should choose bid with deal when preferdeals flag equal true"() {
+ given: "Default basic BidRequest with generic bidder with preferdeals = true"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.targeting = new Targeting(preferdeals: true)
+
+ and: "Bid response with 2 bids"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ bidResponse.seatbid.first().bid.add(Bid.getDefaultBid(bidRequest.imp.first()))
+
+ and: "One of the bids has dealid"
+ bidResponse.seatbid.first().bid.first().dealid = PBSUtils.randomNumber
+ bidResponse.seatbid.first().bid.first().price = dealBidPrice
+ bidResponse.seatbid.first().bid.last().price = bidPrice
+ def dealBidId = bidResponse.seatbid.first().bid.first().id
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "PBS should choose bid with deal"
+ assert response.seatbid?.first()?.bid?.size() == 1
+ assert response.seatbid?.first()?.bid?.first()?.id == dealBidId
+ assert response.seatbid?.first()?.bid?.first()?.price == dealBidPrice
+
+ where:
+ bidPrice | dealBidPrice
+ 2 | bidPrice + 1
+ 2 | bidPrice - 1
+ 0 | 0
+ }
+
+ def "PBS should choose higher bid from two bids with deals"() {
+ given: "Default basic BidRequest with generic bidder with preferdeals = true"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.targeting = new Targeting(preferdeals: true)
+
+ and: "Bid response with 2 bids"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ bidResponse.seatbid.first().bid.add(Bid.getDefaultBid(bidRequest.imp.first()))
+
+ and: "Both of the bids have dealid"
+ bidResponse.seatbid.first().bid.each { it.dealid = PBSUtils.randomNumber }
+
+ and: "Set price for bids"
+ def winningBidPrice = bidResponse.seatbid.first().bid.first().price + 1
+ bidResponse.seatbid.first().bid.last().price = winningBidPrice
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "PBS should choose bid with higher deal price"
+ assert response.seatbid?.first()?.bid?.size() == 1
+ assert response.seatbid?.first()?.bid?.first()?.price == winningBidPrice
+ }
+
+ def "PBS should choose higher bid from two without deals"() {
+ given: "Default basic BidRequest with generic bidder with preferdeals = true"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.targeting = new Targeting(preferdeals: true)
+
+ and: "Bid response with 2 bids"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ bidResponse.seatbid.first().bid.add(Bid.getDefaultBid(bidRequest.imp.first()))
+
+ and: "Set price for bids"
+ def winningBidPrice = bidResponse.seatbid.first().bid.first().price + 1
+ bidResponse.seatbid.first().bid.last().price = winningBidPrice
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "PBS should choose bid with higher deal price"
+ assert response.seatbid?.first()?.bid?.size() == 1
+ assert response.seatbid?.first()?.bid?.first()?.price == winningBidPrice
+ }
+
+ def "PBS should prefer bids with dealid when multibid is enabled"() {
+ given: "Default basic BidRequest with generic bidder with preferdeals = true"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.targeting = new Targeting(preferdeals: true)
+
+ and: "Set maxbids = 2 for default bidder"
+ def maxBids = 2
+ def multiBid = new MultiBid(bidder: "generic", maxBids: maxBids)
+ bidRequest.ext.prebid.multibid = [multiBid]
+
+ and: "Bid response with 2 bids"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ bidResponse.seatbid.first().bid.add(Bid.getDefaultBid(bidRequest.imp.first()))
+
+ and: "Both of the bids have dealid"
+ bidResponse.seatbid.first().bid.each { it.dealid = PBSUtils.randomNumber }
+
+ and: "Set price for bids"
+ def higherBidPrice = bidResponse.seatbid.first().bid.first().price + 1
+ bidResponse.seatbid.first().bid.last().price = higherBidPrice
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain maxBids bids with deal"
+ def bidPrices = response.seatbid?.first()?.bid?.collect { it.price }
+ assert bidPrices == bidResponse.seatbid.first().bid.collect { it.price }.sort().reverse()
+ }
+
+ @Unroll
+ def "PBS should not choose lower deal price with preferdeals equal #preferdeals flag"() {
+ given: "Default basic BidRequest with generic bidder with preferdeals"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.targeting = new Targeting(preferdeals: preferdeals)
+
+ and: "Bid response with 2 bids"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ bidResponse.seatbid.first().bid.add(Bid.getDefaultBid(bidRequest.imp.first()))
+
+ and: "One of the bids has dealid"
+ bidResponse.seatbid.first().bid.first().dealid = PBSUtils.randomNumber
+
+ and: "Set price for bids"
+ def winningBidPrice = bidResponse.seatbid.first().bid.first().price + 1
+ bidResponse.seatbid.first().bid.last().price = winningBidPrice
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "PBS should choose bid with bid with higher price"
+ assert response.seatbid?.first()?.bid?.size() == 1
+ assert response.seatbid?.first()?.bid?.first()?.price == winningBidPrice
+
+ where:
+ preferdeals << [false, null]
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/DebugSpec.groovy b/src/test/groovy/org/prebid/server/functional/DebugSpec.groovy
new file mode 100644
index 00000000000..2454ffbebed
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/DebugSpec.groovy
@@ -0,0 +1,312 @@
+package org.prebid.server.functional
+
+import org.apache.commons.lang3.StringUtils
+import org.prebid.server.functional.model.bidder.BidderName
+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.db.StoredRequest
+import org.prebid.server.functional.model.request.amp.AmpRequest
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.model.response.auction.ErrorType
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.util.PBSUtils
+import spock.lang.PendingFeature
+import spock.lang.Unroll
+
+@PBSTest
+class DebugSpec extends BaseSpec {
+
+ private static final String overrideToken = PBSUtils.randomString
+
+ @Unroll
+ def "PBS should return debug information when debug flag is #debug and test flag is #test"() {
+ given: "Default BidRequest with test flag"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = debug
+ bidRequest.test = test
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain ext.debug"
+ assert response.ext?.debug
+
+ where:
+ debug | test
+ 1 | null
+ 1 | 0
+ null | 1
+ }
+
+ @Unroll
+ def "PBS shouldn't return debug information when debug flag is #debug and test flag is #test"() {
+ given: "Default BidRequest with test flag"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = test
+ bidRequest.test = test
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response shouldn't contain ext.debug"
+ assert !response.ext?.debug
+
+ where:
+ debug | test
+ 0 | null
+ null | 0
+ }
+
+ def "PBS should not return debug information when bidder-level setting debug.allowed = false"() {
+ given: "Pbs config"
+ def pbsService = pbsServiceFactory.getService(["adapters.generic.debug.allow": "false"])
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = 1
+
+ when: "PBS processes auction request"
+ def response = pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should not contain ext.debug"
+ assert !response.ext?.debug?.httpcalls
+
+ and: "Response should contain specific code and text in ext.warnings.general"
+ assert response.ext?.warnings[ErrorType.PREBID]?.collect { it.code } == [999] // [10003]
+ assert response.ext?.warnings[ErrorType.PREBID]?.collect { it.message } ==
+ ["Debug turned off for bidder: $BidderName.GENERIC.value" as String]
+ }
+
+ def "PBS should return debug information when bidder-level setting debug.allowed = true"() {
+ given: "Pbs config"
+ def pbsService = pbsServiceFactory.getService(["adapters.generic.debug.allow": "true"])
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = 1
+
+ when: "PBS processes auction request"
+ def response = pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain ext.debug"
+ assert response.ext?.debug?.httpcalls[BidderName.GENERIC.value]
+
+ and: "Response should not contain ext.warnings"
+ assert !response.ext?.warnings
+ }
+
+ def "PBS should not return debug information when bidder-level setting debug.allowed = false is overridden by account-level setting debug-allowed = false"() {
+ given: "Pbs config"
+ def pbsService = pbsServiceFactory.getService(["adapters.generic.debug.allow": "false"])
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = 1
+
+ and: "Account in the DB"
+ def accountConfig = new AccountConfig(auction: new AccountAuctionConfig(debugAllow: false))
+ def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ def response = pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should not contain ext.debug"
+ assert !response.ext?.debug
+
+ and: "Response should contain specific code and text in ext.warnings.general"
+ //TODO change to 10002 after updating debug warnings
+ assert response.ext?.warnings[ErrorType.PREBID]?.collect { it.code } == [999]
+ //TODO possibly change message after clarifications
+ assert response.ext?.warnings[ErrorType.PREBID]?.collect { it.message } ==
+ ["Debug turned off for account"]
+ }
+
+ def "PBS should not return debug information when bidder-level setting debug.allowed = false is overridden by account-level setting debug-allowed = true"() {
+ given: "Pbs config"
+ def pbsService = pbsServiceFactory.getService(["adapters.generic.debug.allow": "false"])
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = 1
+
+ and: "Account in the DB"
+ def accountConfig = new AccountConfig(auction: new AccountAuctionConfig(debugAllow: true))
+ def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ def response = pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should not contain ext.debug"
+ assert !response.ext?.debug?.httpcalls
+
+ and: "Response should contain specific code and text in ext.warnings.general"
+ //TODO change to 10003 after updating debug warnings
+ assert response.ext?.warnings[ErrorType.PREBID]?.collect { it.code } == [999]
+ assert response.ext?.warnings[ErrorType.PREBID]?.collect { it.message } ==
+ ["Debug turned off for bidder: $BidderName.GENERIC.value" as String]
+ }
+
+ def "PBS should not return debug information when bidder-level setting debug.allowed = true is overridden by account-level setting debug-allowed = false"() {
+ given: "Pbs config"
+ def pbsService = pbsServiceFactory.getService(["adapters.generic.debug.allow": "true"])
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = 1
+
+ and: "Account in the DB"
+ def accountConfig = new AccountConfig(auction: new AccountAuctionConfig(debugAllow: false))
+ def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ def response = pbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should not contain ext.debug"
+ assert !response.ext?.debug
+
+ and: "Response should contain specific code and text in ext.warnings.general"
+ //TODO change to 10002 after updating debug warnings
+ assert response.ext?.warnings[ErrorType.PREBID]?.collect { it.code } == [999]
+ assert response.ext?.warnings[ErrorType.PREBID]?.collect { it.message } == ["Debug turned off for account"]
+ }
+
+ def "PBS should use default values = true for bidder-level setting debug.allow and account-level setting debug-allowed when they are not specified"() {
+ given: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = 1
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain ext.debug"
+ assert response.ext?.debug?.httpcalls[BidderName.GENERIC.value]
+
+ and: "Response should not contain ext.warnings"
+ assert !response.ext?.warnings
+ }
+
+ @Unroll
+ def "PBS should return debug information when bidder-level setting debug.allowed = #debugAllowedConfig and account-level setting debug-allowed = #debugAllowedAccount is overridden by x-pbs-debug-override header"() {
+ given: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = 1
+
+ and: "Account in the DB"
+ def accountConfig = new AccountConfig(auction: new AccountAuctionConfig(debugAllow: debugAllowedAccount))
+ def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ def response = pbdService.sendAuctionRequest(bidRequest, ["x-pbs-debug-override": overrideToken])
+
+ then: "Response should contain ext.debug"
+ assert response.ext?.debug?.httpcalls[BidderName.GENERIC.value]
+
+ and: "Response should not contain ext.warnings"
+ assert !response.ext?.warnings
+
+ where:
+ debugAllowedConfig | debugAllowedAccount | pbdService
+ false | true | pbsServiceFactory.getService(["debug.override-token" : overrideToken,
+ "adapters.generic.debug.allow": "false"])
+ true | false | pbsServiceFactory.getService(["debug.override-token" : overrideToken,
+ "adapters.generic.debug.allow": "true"])
+ false | false | pbsServiceFactory.getService(["debug.override-token" : overrideToken,
+ "adapters.generic.debug.allow": "false"])
+ }
+
+ @Unroll
+ def "PBS should not return debug information when x-pbs-debug-override header is incorrect"() {
+ given: "Pbs config"
+ def pbsService = pbsServiceFactory.getService(["debug.override-token": overrideToken])
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.debug = 1
+
+ and: "Account in the DB"
+ def accountConfig = new AccountConfig(auction: new AccountAuctionConfig(debugAllow: false))
+ def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig)
+ accountDao.save(account)
+
+ when: "PBS processes auction request"
+ def response = pbsService.sendAuctionRequest(bidRequest, ["x-pbs-debug-override": headerValue])
+
+ then: "Response should not contain ext.debug"
+ assert !response.ext?.debug
+
+ and: "Response should contain specific code and text in ext.warnings.general"
+ //TODO change to 10002 after updating debug warnings
+ assert response.ext?.warnings[ErrorType.PREBID]?.collect { it.code } == [999]
+ assert response.ext?.warnings[ErrorType.PREBID]?.collect { it.message } == ["Debug turned off for account"]
+
+ where:
+ headerValue << [StringUtils.swapCase(overrideToken), PBSUtils.randomString]
+ }
+
+ @PendingFeature
+ @Unroll
+ def "PBS AMP should return debug information when request flag is #requestDebug and store request flag is #storedRequestDebug"() {
+ given: "Default AMP request"
+ def ampRequest = AmpRequest.defaultAmpRequest.tap {
+ debug = requestDebug
+ }
+
+ and: "Default stored request"
+ def ampStoredRequest = BidRequest.defaultStoredRequest.tap {
+ ext.prebid.debug = storedRequestDebug
+ }
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ when: "PBS processes amp request"
+ def response = defaultPbsService.sendAmpRequest(ampRequest)
+
+ then: "Response should contain debug information"
+ assert response.debug
+
+ where:
+ requestDebug || storedRequestDebug
+ 1 || 0
+ 1 || 1
+ 1 || null
+ null || 1
+ }
+
+ @Unroll
+ def "PBS AMP shouldn't return debug information when request flag is #requestDebug and stored request flag is #storedRequestDebug"() {
+ given: "Default AMP request"
+ def ampRequest = AmpRequest.defaultAmpRequest.tap {
+ debug = requestDebug
+ }
+
+ and: "Default stored request"
+ def ampStoredRequest = BidRequest.defaultStoredRequest.tap {
+ ext.prebid.debug = storedRequestDebug
+ }
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ when: "PBS processes amp request"
+ def response = defaultPbsService.sendAmpRequest(ampRequest)
+
+ then: "Response shouldn't contain debug information"
+ assert !response.debug
+
+ where:
+ requestDebug || storedRequestDebug
+ 0 || 1
+ 0 || 0
+ 0 || null
+ null || 0
+ null || null
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/HttpInteractionSpec.groovy b/src/test/groovy/org/prebid/server/functional/HttpInteractionSpec.groovy
new file mode 100644
index 00000000000..63beb4df3cf
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/HttpInteractionSpec.groovy
@@ -0,0 +1,120 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.model.bidder.BidderName
+import org.prebid.server.functional.model.bidder.Generic
+import org.prebid.server.functional.model.bidder.Rubicon
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.model.request.logging.httpinteraction.HttpInteractionRequest
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.util.PBSUtils
+
+import java.time.Instant
+
+import static org.prebid.server.functional.model.bidder.BidderName.GENERIC
+import static org.prebid.server.functional.model.bidder.BidderName.RUBICON
+
+@PBSTest
+class HttpInteractionSpec extends BaseSpec {
+
+ def "PBS should only log request to the specified adapter"() {
+ given: "Test start time"
+ def startTime = Instant.now()
+
+ and: "Default httpInteractionRequest with specified bidder"
+ def request = HttpInteractionRequest.defaultHttpInteractionRequest
+ request.bidder = GENERIC
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.imp.first().ext.prebid.bidder.generic = new Generic()
+ bidRequest.imp.first().ext.prebid.bidder.rubicon = new Rubicon(accountId: PBSUtils.randomNumber,
+ siteId: PBSUtils.randomNumber, zoneId: PBSUtils.randomNumber)
+
+ when: "PBS processes bidders params request"
+ defaultPbsService.sendLoggingHttpInteractionRequest(request)
+
+ and: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "PBS log should contain request to allowed adapter"
+ def logs = defaultPbsService.getLogsByTime(startTime)
+ def genericBidderLogs = getLogsByBidder(logs, GENERIC)
+ def rubiconBidderLogs = getLogsByBidder(logs, RUBICON)
+ assert getLogsByText(genericBidderLogs, bidRequest.id).size() == 1
+ assert getLogsByText(rubiconBidderLogs, bidRequest.id).size() == 0
+ }
+
+ def "PBS should not log request to adapter when it is not allowed"() {
+ given: "Test start time"
+ def startTime = Instant.now()
+
+ and: "Default basic generic BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.imp.first().ext.prebid.bidder.generic = new Generic()
+
+ when: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "PBS log should not contain request to adapter"
+ def logs = defaultPbsService.getLogsByTime(startTime)
+ def genericBidderLogs = getLogsByBidder(logs, GENERIC)
+ assert getLogsByText(genericBidderLogs, bidRequest.id).size() == 0
+ }
+
+ def "PBS log request to specific adapter should contain only bid params for the named bidder"() {
+ given: "Test start time"
+ def startTime = Instant.now()
+
+ and: "Default httpInteractionRequest with specified bidder"
+ def request = HttpInteractionRequest.defaultHttpInteractionRequest
+ request.bidder = GENERIC
+
+ and: "Default basic generic BidRequest with rubicon"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.imp.first().ext.prebid.bidder.generic = new Generic()
+ bidRequest.imp.first().ext.prebid.bidder.rubicon = new Rubicon(accountId: PBSUtils.randomNumber,
+ siteId: PBSUtils.randomNumber, zoneId: PBSUtils.randomNumber)
+
+ when: "PBS processes bidders params request"
+ defaultPbsService.sendLoggingHttpInteractionRequest(request)
+
+ and: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Extract request from logs"
+ def logs = defaultPbsService.getLogsByTime(startTime)
+ assert logs.size() > 0
+ def genericBidderLogs = getLogsByBidder(logs, GENERIC)
+ assert genericBidderLogs.size() > 0
+ def idLogs = getLogsByText(genericBidderLogs, bidRequest.id)
+ assert idLogs.size() == 1
+
+ def requestLog = getRequestFromLog(idLogs.first(), request.bidder.value)
+ def retrievedRequest = mapper.decode(requestLog, BidRequest)
+
+ and: "Logged request should not contain bidder parameters in imp[].ext.prebid.bidder.BIDDER"
+ assert !retrievedRequest?.imp?.first()?.ext?.prebid?.bidder?.generic
+ assert !retrievedRequest?.imp?.first()?.ext?.prebid?.bidder?.rubicon
+
+ and: "Logged request should contain bidder parameters in imp[].ext.BIDDER"
+ assert retrievedRequest?.imp?.first()?.ext?.generic
+
+ and: "Logged request should contain only bidder parameters for the named bidder"
+ assert !retrievedRequest?.imp?.first()?.ext?.rubicon
+ }
+
+ private static List getLogsByBidder(List logs, BidderName bidderName) {
+ logs.findAll { it.contains("Request body to ${bidderName.value}:") }
+ }
+
+ private static List getLogsByText(List logs, String text) {
+ logs.findAll { it.contains(text) }
+ }
+
+ private static String getRequestFromLog(String log, String bidderName) {
+ def logText = "Request body to ${bidderName}:"
+
+ log.substring(log.indexOf(logText) + logText.length() + 2, log.length() - 1)
+ .replace("\n", "")
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/HttpSettingsSpec.groovy b/src/test/groovy/org/prebid/server/functional/HttpSettingsSpec.groovy
new file mode 100644
index 00000000000..4425998ba87
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/HttpSettingsSpec.groovy
@@ -0,0 +1,164 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.model.db.StoredRequest
+import org.prebid.server.functional.model.mock.services.httpsettings.HttpAccountsResponse
+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.event.EventRequest
+import org.prebid.server.functional.model.request.setuid.SetuidRequest
+import org.prebid.server.functional.model.request.setuid.UidsCookie
+import org.prebid.server.functional.model.request.vtrack.VtrackRequest
+import org.prebid.server.functional.model.request.vtrack.xml.Vast
+import org.prebid.server.functional.service.PrebidServerException
+import org.prebid.server.functional.service.PrebidServerService
+import org.prebid.server.functional.testcontainers.Dependencies
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.testcontainers.scaffolding.HttpSettings
+import org.prebid.server.functional.util.PBSUtils
+import org.prebid.server.util.ResourceUtil
+import spock.lang.Shared
+
+@PBSTest
+class HttpSettingsSpec extends BaseSpec {
+// Check that PBS actually applied account config only possible by relying on side effects.
+
+ @Shared
+ HttpSettings httpSettings = new HttpSettings(Dependencies.networkServiceContainer, mapper)
+
+ @Shared
+ PrebidServerService prebidServerService = pbsServiceFactory.getService(pbsServiceFactory.httpSettings())
+
+ def "PBS should take account information from http data source on auction request"() {
+ given: "Get basic BidRequest with generic bidder and set gdpr = 1"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.regs.ext.gdpr = 1
+
+ and: "Prepare default account response with gdpr = 0"
+ def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(bidRequest?.site?.publisher?.id)
+ httpSettings.setResponse(bidRequest?.site?.publisher?.id, httpSettingsResponse)
+
+ when: "PBS processes auction request"
+ def response = prebidServerService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain basic fields"
+ assert response.id
+ assert response.seatbid?.size() == 1
+ assert response.seatbid.first().seat == "generic"
+ assert response.seatbid?.first()?.bid?.size() == 1
+
+ and: "There should be only one call to bidder"
+ assert bidder.getRequestCount(bidRequest.id) == 1
+
+ and: "There should be only one account request"
+ assert httpSettings.getRequestCount(bidRequest?.site?.publisher?.id) == 1
+ }
+
+ def "PBS should take account information from http data source on AMP request"() {
+ given: "Default AmpRequest"
+ def ampRequest = AmpRequest.defaultAmpRequest
+
+ and: "Get basic stored request and set gdpr = 1"
+ def ampStoredRequest = BidRequest.defaultBidRequest
+ ampStoredRequest.site.publisher.id = ampRequest.account
+ ampStoredRequest.regs.ext.gdpr = 1
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ and: "Prepare default account response with gdpr = 0"
+ def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(ampRequest.account.toString())
+ httpSettings.setResponse(ampRequest.account.toString(), httpSettingsResponse)
+
+ when: "PBS processes amp request"
+ def response = prebidServerService.sendAmpRequest(ampRequest)
+
+ then: "Response should contain httpcalls"
+ assert !response.debug?.httpcalls?.isEmpty()
+
+ and: "There should be only one account request"
+ assert httpSettings.getRequestCount(ampRequest.account.toString()) == 1
+
+ then: "Response should contain targeting"
+ assert !response.debug?.httpcalls?.isEmpty()
+ }
+
+ def "PBS should take account information from http data source on event request"() {
+ given: "Default EventRequest"
+ def eventRequest = EventRequest.defaultEventRequest
+
+ and: "Prepare default account response"
+ def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(eventRequest.accountId.toString())
+ httpSettings.setResponse(eventRequest.accountId.toString(), httpSettingsResponse)
+
+ when: "PBS processes event request"
+ def responseBody = prebidServerService.sendEventRequest(eventRequest)
+
+ then: "Event response should contain and corresponding content-type"
+ assert responseBody ==
+ ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png")
+
+ and: "There should be only one account request"
+ assert httpSettings.getRequestCount(eventRequest.accountId.toString()) == 1
+ }
+
+ def "PBS should take account information from http data source on setuid request"() {
+ given: "Get default SetuidRequest and set account, gdpr=1 "
+ def request = SetuidRequest.defaultSetuidRequest
+ request.gdpr = 1
+ request.account = PBSUtils.randomNumber.toString()
+ def uidsCookie = UidsCookie.defaultUidsCookie
+
+ and: "Prepare default account response"
+ def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(request.account)
+ httpSettings.setResponse(request.account, httpSettingsResponse)
+
+ when: "PBS processes setuid request"
+ def response = prebidServerService.sendSetUidRequest(request, uidsCookie)
+
+ then: "Response should contain uids cookie"
+ assert response.uidsCookie
+ assert !response.responseBody?.isEmpty()
+
+ and: "There should be only one account request"
+ assert httpSettings.getRequestCount(request.account) == 1
+ }
+
+ def "PBS should take account information from http data source on vtrack request"() {
+ given: "Default VtrackRequest"
+ String payload = PBSUtils.randomString
+ def request = VtrackRequest.getDefaultVtrackRequest(mapper.encodeXml(Vast.getDefaultVastModel(payload)))
+ def accountId = PBSUtils.randomNumber.toString()
+
+ and: "Prepare default account response"
+ def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(accountId)
+ httpSettings.setResponse(accountId, httpSettingsResponse)
+
+ when: "PBS processes vtrack request"
+ def response = prebidServerService.sendVtrackRequest(request, accountId)
+
+ then: "Response should contain uid"
+ assert response.responses[0]?.uuid
+
+ and: "There should be only one account request and pbc request"
+ assert httpSettings.getRequestCount(accountId.toString()) == 1
+ assert prebidCache.getXmlRequestCount(payload) == 1
+
+ and: "VastXml that was send to PrebidCache must contain event url"
+ def prebidCacheRequest = prebidCache.getXmlRecordedRequestsBody(payload)[0]
+ assert prebidCacheRequest.contains("/event?t=imp&b=${request.puts[0].bidid}&a=$accountId&bidder=${request.puts[0].bidder}")
+ }
+
+ def "PBS should return error if account settings isn't found"() {
+ given: "Default EventRequest"
+ def eventRequest = EventRequest.defaultEventRequest
+
+ when: "PBS processes event request"
+ prebidServerService.sendEventRequest(eventRequest)
+
+ then: "Request should fail with error"
+ def exception = thrown(PrebidServerException)
+ assert exception.statusCode == 401
+ assert exception.responseBody.contains("Account '$eventRequest.accountId' doesn't support events")
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/MetricsSpec.groovy b/src/test/groovy/org/prebid/server/functional/MetricsSpec.groovy
new file mode 100644
index 00000000000..339e06c5a91
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/MetricsSpec.groovy
@@ -0,0 +1,97 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.model.request.vtrack.VtrackRequest
+import org.prebid.server.functional.model.request.vtrack.xml.Vast
+import org.prebid.server.functional.model.response.auction.BidResponse
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.util.PBSUtils
+import spock.lang.PendingFeature
+
+// TODO: move metrics tests to the respective specifications as the metrics are a part of normal PBS operation
+// TODO: this won't work as is for banner type as we need to signal PBS to store bid in the cache
+@PBSTest
+class MetricsSpec extends BaseSpec {
+
+ def setup() {
+ // flushing PBS metrics by receiving collected metrics so that each new test works with a fresh state
+ defaultPbsService.sendCollectedMetricsRequest()
+ }
+
+ @PendingFeature
+ def "PBS should update prebid_cache.creative_size.xml metric when xml creative is received"() {
+ given: "Default VtrackRequest"
+ def accountId = PBSUtils.randomNumber.toString()
+ def creative = mapper.encodeXml(Vast.getDefaultVastModel(PBSUtils.randomString))
+ def request = VtrackRequest.getDefaultVtrackRequest(creative)
+
+ when: "PBS processes vtrack request"
+ defaultPbsService.sendVtrackRequest(request, accountId)
+
+ then: "prebid_cache.creative_size.xml metric should be updated"
+ def metrics = defaultPbsService.sendCollectedMetricsRequest()
+ def creativeSize = creative.bytes.length
+ assert metrics["prebid_cache.creative_size.xml"] == creativeSize
+ assert metrics["prebid_cache.requests.ok"] == 1
+
+ and: "account..prebid_cache.creative_size.xml should be updated"
+ assert metrics["account.${accountId}.prebid_cache.creative_size.xml" as String] == creativeSize
+ assert metrics["account.${accountId}.prebid_cache.requests.ok" as String] == 1
+ }
+
+ @PendingFeature
+ def "PBS should update prebid_cache.creative_size.json metric when json creative is received"() {
+ given: "Default BidRequest"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.enableCache()
+
+ and: "Default basic bid with banner creative"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ def adm = PBSUtils.randomString
+ bidResponse.seatbid[0].bid[0].adm = adm
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes amp request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ and: "PBS processes collected metrics request"
+ def metrics = defaultPbsService.sendCollectedMetricsRequest()
+
+ then: "prebid_cache.creative_size.json should be update"
+
+ def creativeSize = adm.bytes.length
+ assert metrics["prebid_cache.creative_size.json"] == creativeSize
+ assert metrics["prebid_cache.requests.ok"] == 1
+
+ and: "account..prebid_cache.creative_size.json should be update"
+ def accountId = bidRequest.site.publisher.id
+ assert metrics["account.${accountId}.prebid_cache.requests.ok" as String] == 1
+ }
+
+ def "PBS should increase request_time metric when auction was held"() {
+ given: "Current value of metric request_time"
+ def initialValue = getCurrentMetricValue("request_time")
+
+ and: "Default basic BidRequest with generic bidder"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ when: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ and: "PBS processes collected metrics request"
+ def response = defaultPbsService.sendCollectedMetricsRequest()
+
+ then: "Response should contain metrics"
+ assert response.size() > 0
+
+ and: "request_time metric should be increased"
+ assert response["request_time"] == initialValue + 1
+ }
+
+ private static int getCurrentMetricValue(String name) {
+ def response = defaultPbsService.sendCollectedMetricsRequest()
+ response[name] as int ?: 0
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/MultibidSpec.groovy b/src/test/groovy/org/prebid/server/functional/MultibidSpec.groovy
new file mode 100644
index 00000000000..5bdcfff6b31
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/MultibidSpec.groovy
@@ -0,0 +1,63 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.model.request.auction.MultiBid
+import org.prebid.server.functional.model.request.auction.Targeting
+import org.prebid.server.functional.model.response.auction.Bid
+import org.prebid.server.functional.model.response.auction.BidResponse
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.util.PBSUtils
+
+@PBSTest
+class MultibidSpec extends BaseSpec {
+
+ def "PBS should not return seatbid[].bid[].ext.prebid.targeting for non-winning bid in multi-bid response when includeBidderKeys = false"() {
+ given: "Default basic BidRequest with generic bidder with includeBidderKeys = false"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.targeting = new Targeting(includeBidderKeys: false)
+
+ and: "Set maxbids = 2 for default bidder"
+ def maxBids = 2
+ def multiBid = new MultiBid(bidder: "generic", maxBids: maxBids, targetBidderCodePrefix: PBSUtils.randomString)
+ bidRequest.ext.prebid.multibid = [multiBid]
+
+ and: "Default basic bid"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ def anotherBid = Bid.getDefaultBid(bidRequest.imp.first()).tap { price = bidResponse.seatbid.first().bid.first().price - 0.1 }
+ bidResponse.seatbid.first().bid.add(anotherBid)
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "PBS should not return targeting for non-winning bid"
+ assert !response.seatbid?.first()?.bid?.last()?.ext?.prebid?.targeting
+ }
+
+ def "PBS should return seatbid[].bid[].ext.prebid.targeting for non-winning bid in multi-bid response when includeBidderKeys = true"() {
+ given: "Default basic BidRequest with generic bidder with includeBidderKeys = true"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.ext.prebid.targeting = new Targeting(includeBidderKeys: true)
+
+ and: "Set maxbids = 2 for default bidder"
+ def maxBids = 2
+ def multiBid = new MultiBid(bidder: "generic", maxBids: maxBids, targetBidderCodePrefix: PBSUtils.randomString)
+ bidRequest.ext.prebid.multibid = [multiBid]
+
+ and: "Default basic bid"
+ def bidResponse = BidResponse.getDefaultBidResponse(bidRequest)
+ def anotherBid = Bid.getDefaultBid(bidRequest.imp.first()).tap { price = bidResponse.seatbid.first().bid.first().price - 0.1 }
+ bidResponse.seatbid.first().bid.add(anotherBid)
+
+ and: "Set bidder response"
+ bidder.setResponse(bidRequest.id, bidResponse)
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "PBS should return targeting for non-winning bid"
+ assert response.seatbid?.first()?.bid?.last()?.ext?.prebid?.targeting
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/SchainSpec.groovy b/src/test/groovy/org/prebid/server/functional/SchainSpec.groovy
new file mode 100644
index 00000000000..ab44917f56d
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/SchainSpec.groovy
@@ -0,0 +1,124 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.model.request.auction.PrebidSchain
+import org.prebid.server.functional.model.request.auction.Schain
+import org.prebid.server.functional.model.request.auction.SchainNode
+import org.prebid.server.functional.model.request.auction.Source
+import org.prebid.server.functional.model.request.auction.SourceExt
+import org.prebid.server.functional.service.PrebidServerService
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.util.PBSUtils
+import spock.lang.Shared
+
+@PBSTest
+class SchainSpec extends BaseSpec {
+
+ private static final GLOBAL_SCHAIN_NODE = new SchainNode().tap {
+ asi = "pbshostcompany.com"
+ sid = "00001"
+ hp = 1
+ rid = "BidRequest"
+ }
+
+ @Shared
+ PrebidServerService prebidServerService = pbsServiceFactory.getService(["auction.host-schain-node": mapper.encode(GLOBAL_SCHAIN_NODE)])
+
+ def "Global schain node should be appended when only ext.prebid.schains exists"() {
+ given: "Basic bid request"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ and: "Set default prebid schain to bidRequest"
+ def schain = defaultSchain
+ def prebidSchain = new PrebidSchain(bidders: ["generic"], schain: schain)
+ bidRequest.ext.prebid.schains = [prebidSchain]
+
+ when: "PBS processes auction request"
+ prebidServerService.sendAuctionRequest(bidRequest)
+
+ then: "Configured schain node should be appended to the end of the node array"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.source?.ext?.schain?.nodes == schain.nodes + GLOBAL_SCHAIN_NODE
+ }
+
+ def "Global schain node should be appended to the end of the node array when only source.ext.schain exists"() {
+ given: "Basic bid request"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ and: "Set default source schain to bidRequest"
+ def schain = defaultSchain
+ def sourceExt = new SourceExt(schain: schain)
+ bidRequest.source = new Source(ext: sourceExt)
+
+ when: "PBS processes auction request"
+ prebidServerService.sendAuctionRequest(bidRequest)
+
+ then: "Configured schain node should be appended to the end of the node array"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.source?.ext?.schain?.nodes == schain.nodes + GLOBAL_SCHAIN_NODE
+ }
+
+ def "Global schain node should be appended when both ext.prebid.schains and source.ext.schain exist"() {
+ given: "Basic bid request"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ and: "Set default prebid schain to bidRequest"
+ def schain = defaultSchain
+ def prebidSchain = new PrebidSchain(bidders: ["generic"], schain: schain)
+ bidRequest.ext.prebid.schains = [prebidSchain]
+
+ and: "Set default source schain to bidRequest"
+ def sourceSchain = defaultSchain
+ def sourceExt = new SourceExt(schain: sourceSchain)
+ bidRequest.source = new Source(ext: sourceExt)
+
+ when: "PBS processes auction request"
+ prebidServerService.sendAuctionRequest(bidRequest)
+
+ then: "Configured schain node should be appended to the end of the node array"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.source?.ext?.schain?.nodes == schain.nodes + GLOBAL_SCHAIN_NODE
+ }
+
+ def "Global schain node should be appended when ext.prebid.schains and source.ext.schain doesn't exist"() {
+ given: "Basic bid request"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ when: "PBS processes auction request"
+ prebidServerService.sendAuctionRequest(bidRequest)
+
+ then: "Configured schain node should be appended to the end of the node array"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.source?.ext?.schain?.nodes == [GLOBAL_SCHAIN_NODE]
+ }
+
+ def "Global schain node should be appended when ext.prebid.schains applied for unknown bidder"() {
+ given: "Basic bid request"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ and: "Set default prebid schain with unknown bidder to bidRequest"
+ def schain = defaultSchain
+ def prebidSchain = new PrebidSchain(bidders: ["appnexus"], schain: schain)
+ bidRequest.ext.prebid.schains = [prebidSchain]
+
+ when: "PBS processes auction request"
+ prebidServerService.sendAuctionRequest(bidRequest)
+
+ then: "Configured schain node should be appended to the end of the node array"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.source?.ext?.schain?.nodes == [GLOBAL_SCHAIN_NODE]
+ }
+
+ private static Schain getDefaultSchain() {
+ def node = new SchainNode().tap {
+ asi = PBSUtils.randomString
+ sid = PBSUtils.randomString
+ hp = PBSUtils.randomNumber
+ name = PBSUtils.randomString
+ domain = PBSUtils.randomString
+ }
+ new Schain(ver: "1.0", complete: 1, nodes: [node])
+ }
+}
+
+
diff --git a/src/test/groovy/org/prebid/server/functional/SmokeSpec.groovy b/src/test/groovy/org/prebid/server/functional/SmokeSpec.groovy
new file mode 100644
index 00000000000..15bc2e431f4
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/SmokeSpec.groovy
@@ -0,0 +1,207 @@
+package org.prebid.server.functional
+
+import org.prebid.server.functional.model.db.Account
+import org.prebid.server.functional.model.db.StoredRequest
+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.cookiesync.CookieSyncRequest
+import org.prebid.server.functional.model.request.event.EventRequest
+import org.prebid.server.functional.model.request.logging.httpinteraction.HttpInteractionRequest
+import org.prebid.server.functional.model.request.setuid.SetuidRequest
+import org.prebid.server.functional.model.request.setuid.UidsCookie
+import org.prebid.server.functional.model.request.vtrack.VtrackRequest
+import org.prebid.server.functional.model.request.vtrack.xml.Vast
+import org.prebid.server.functional.model.response.cookiesync.CookieSyncResponse
+import org.prebid.server.functional.testcontainers.PBSTest
+import org.prebid.server.functional.util.PBSUtils
+import org.prebid.server.util.ResourceUtil
+
+import static org.prebid.server.functional.model.bidder.BidderName.GENERIC
+import static org.prebid.server.functional.model.response.status.Status.OK
+
+@PBSTest
+class SmokeSpec extends BaseSpec {
+
+ def "PBS should return BidResponse when there are valid bids"() {
+ given: "Default basic BidRequest with generic bidder"
+ def bidRequest = BidRequest.defaultBidRequest
+
+ when: "PBS processes auction request"
+ def response = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain basic fields"
+ assert response.id == bidRequest.id
+ assert response.seatbid?.size() == 1
+ assert response.seatbid[0]?.seat == "generic"
+ assert response.seatbid[0]?.bid?.size() == 1
+ assert response.seatbid[0]?.bid[0]?.impid == bidRequest.imp[0].id
+
+ and: "Only declared bidders should be called"
+ def requestBidders = bidRequest.requestBidders
+ def responseBidders = response.ext?.debug?.bidders
+ assert responseBidders.keySet() == requestBidders.toSet()
+
+ and: "There should be only one call to bidder"
+ assert bidder.getRequestCount(bidRequest.id) == 1
+ }
+
+ def "PBS should return AMP response"() {
+ given: "Default AmpRequest"
+ def ampRequest = AmpRequest.defaultAmpRequest
+ def ampStoredRequest = BidRequest.defaultBidRequest
+ ampStoredRequest.site.publisher.id = ampRequest.account
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ when: "PBS processes amp request"
+ def response = defaultPbsService.sendAmpRequest(ampRequest)
+
+ then: "Response should contain targeting and httpcalls"
+ assert response.targeting
+ assert response.debug.httpcalls
+
+ and: "httpcalls should send request for bidders from storedRequest"
+ def storedRequestBidders = ampStoredRequest.requestBidders
+ def responseBidders = response.debug?.bidders
+ assert responseBidders.keySet() == storedRequestBidders.toSet()
+ }
+
+ def "Call PBS /cookie_sync without uids cookie should return element.usersync.url"() {
+ given: "Default CookieSyncRequest"
+ def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest
+
+ when: "PBS processes cookie sync request"
+ def response = defaultPbsService.sendCookieSyncRequest(cookieSyncRequest)
+
+ then: "Response should contain all bidders"
+ assert response.status == CookieSyncResponse.Status.NO_COOKIE
+ assert response.bidderStatus?.size() == cookieSyncRequest.bidders.size()
+ def bidderStatus = response.getBidderUsersync(GENERIC)
+ assert bidderStatus?.usersync?.url
+ assert bidderStatus?.usersync?.type
+ }
+
+ def "Call PBS /cookie_sync with valid uids cookie should return status OK"() {
+ given: "Default CookieSyncRequest"
+ def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest
+ def uidsCookie = UidsCookie.defaultUidsCookie
+
+ when: "PBS processes cookie sync request"
+ def response = defaultPbsService.sendCookieSyncRequest(cookieSyncRequest, uidsCookie)
+
+ then: "Response should contain have status 'OK'"
+ assert response.status == CookieSyncResponse.Status.OK
+
+ and: "Response should contain all bidders"
+ assert !response.getBidderUsersync(GENERIC)
+ }
+
+ def "PBS should set uids cookie"() {
+ given: "Default SetuidRequest"
+ def request = SetuidRequest.defaultSetuidRequest
+ def uidsCookie = UidsCookie.defaultUidsCookie
+
+ when: "PBS processes setuid request"
+ def response = defaultPbsService.sendSetUidRequest(request, uidsCookie)
+
+ then: "Response should contain uids cookie"
+ assert response.uidsCookie
+ assert !response.responseBody?.isEmpty()
+ }
+
+ def "PBS should get uids cookie"() {
+ given: "Default uids Cookie"
+ def uidsCookie = UidsCookie.defaultUidsCookie
+
+ when: "PBS processes getuid request"
+ def response = defaultPbsService.sendGetUidRequest(uidsCookie)
+
+ then: "Response should contain bidder uids"
+ assert response.buyeruids?.size() == uidsCookie.tempUIDs.size()
+ assert response.buyeruids.every { bidder, uid -> uidsCookie.tempUIDs[bidder].uid == uid }
+ }
+
+ def "PBS should return tracking pixel on event request"() {
+ given: "Default EventRequest"
+ def eventRequest = EventRequest.defaultEventRequest
+
+ and: "Account in the DB"
+ def account = new Account(uuid: eventRequest.accountId, eventsEnabled: true)
+ accountDao.save(account)
+
+ when: "PBS processes event request"
+ def responseBody = defaultPbsService.sendEventRequest(eventRequest)
+
+ then: "Event response should contain and corresponding content-type"
+ assert responseBody ==
+ ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png")
+ }
+
+ def "PBS should return PBC response on vtrack request"() {
+ given: "Default VtrackRequest"
+ def payload = PBSUtils.randomNumber.toString()
+ def request = VtrackRequest.getDefaultVtrackRequest(mapper.encodeXml(Vast.getDefaultVastModel(payload)))
+ def accountId = PBSUtils.randomNumber.toString()
+
+ when: "PBS processes vtrack request"
+ def response = defaultPbsService.sendVtrackRequest(request, accountId)
+
+ then: "Response should contain uid"
+ assert response.responses[0]?.uuid
+ }
+
+ def "status responds with 200 OK"() {
+ when: "PBS processes status request"
+ def response = defaultPbsService.sendStatusRequest()
+
+ then: "Response should contain status OK"
+ assert response.application?.status == OK
+ }
+
+ def "PBS should get info about active bidders"() {
+ when: "PBS processes bidders info request"
+ def response = defaultPbsService.sendInfoBiddersRequest()
+
+ then: "Response should contain bidders info"
+ assert !response.isEmpty()
+ }
+
+ def "PBS should get info about requested bidder"() {
+ when: "PBS processes bidders info request"
+ def response = defaultPbsService.sendBidderInfoRequest(GENERIC)
+
+ then: "Response should contain bidder info"
+ assert response.maintainer?.email == "maintainer@example.com"
+ assert response.capabilities?.app?.mediaTypes == ["banner", "video", "native", "audio"]
+ assert response.capabilities?.site?.mediaTypes == ["banner", "video", "native", "audio"]
+ }
+
+ def "PBS should get bidders params"() {
+ when: "PBS processes bidders params request"
+ def response = defaultPbsService.sendBiddersParamsRequest()
+
+ then: "Response should contain bidders params"
+ assert response.parameters.size() > 0
+ }
+
+ def "PBS should return currency rates"() {
+ when: "PBS processes bidders params request"
+ def response = defaultPbsService.sendCurrencyRatesRequest()
+
+ then: "Response should contain bidders params"
+ assert response.rates?.size() > 0
+ }
+
+ def "PBS should return empty body on httpinteraction request"() {
+ given: "Default httpInteractionRequest"
+ def request = HttpInteractionRequest.defaultHttpInteractionRequest
+
+ when: "PBS processes bidders params request"
+ def response = defaultPbsService.sendLoggingHttpInteractionRequest(request)
+
+ then: "Response should contain bidders params"
+ assert response.isEmpty()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/AccountStatus.groovy b/src/test/groovy/org/prebid/server/functional/model/AccountStatus.groovy
new file mode 100644
index 00000000000..67c61e52066
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/AccountStatus.groovy
@@ -0,0 +1,17 @@
+package org.prebid.server.functional.model
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum AccountStatus {
+
+ ACTIVE, INACTIVE
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+
+ static AccountStatus forValue(String value) {
+ values().find { it.value == value }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/ResponseModel.groovy b/src/test/groovy/org/prebid/server/functional/model/ResponseModel.groovy
new file mode 100644
index 00000000000..91a7f1fe9d0
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/ResponseModel.groovy
@@ -0,0 +1,6 @@
+package org.prebid.server.functional.model
+
+/**
+ * This marker interface should limit the possible values used by the MockServerClientWrapper.
+ */
+interface ResponseModel {}
diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/AppNexus.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/AppNexus.groovy
new file mode 100644
index 00000000000..63fb2dd386c
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/bidder/AppNexus.groovy
@@ -0,0 +1,23 @@
+package org.prebid.server.functional.model.bidder
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+import org.prebid.server.functional.util.PBSUtils
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy)
+class AppNexus implements BidderAdapter {
+
+ Integer placementId
+ String invCode
+ String trafficSourceCode
+
+ static AppNexus getDefault() {
+ new AppNexus().tap {
+ placementId = PBSUtils.randomNumber
+ invCode = PBSUtils.randomString
+ trafficSourceCode = PBSUtils.randomString
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderAdapter.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderAdapter.groovy
new file mode 100644
index 00000000000..078415f615e
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderAdapter.groovy
@@ -0,0 +1,8 @@
+package org.prebid.server.functional.model.bidder
+
+/**
+ * This marker interface should limit the list of available bidders
+ */
+interface BidderAdapter {
+
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy
new file mode 100644
index 00000000000..8e92787d6a2
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.bidder
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum BidderName {
+
+ GENERIC, RUBICON, APPNEXUS
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/Generic.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/Generic.groovy
new file mode 100644
index 00000000000..e2ddc71dc0c
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/bidder/Generic.groovy
@@ -0,0 +1,7 @@
+package org.prebid.server.functional.model.bidder
+
+class Generic implements BidderAdapter {
+
+ Integer firstParam
+ Integer secondParam
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/Rubicon.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/Rubicon.groovy
new file mode 100644
index 00000000000..9bc06937e7d
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/bidder/Rubicon.groovy
@@ -0,0 +1,18 @@
+package org.prebid.server.functional.model.bidder
+
+import org.prebid.server.functional.util.PBSUtils
+
+class Rubicon implements BidderAdapter {
+
+ Integer accountId
+ Integer siteId
+ Integer zoneId
+
+ static Rubicon getDefault() {
+ new Rubicon().tap {
+ accountId = PBSUtils.randomNumber
+ siteId = PBSUtils.randomNumber
+ zoneId = PBSUtils.randomNumber
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/bidderspecific/BidderImp.groovy b/src/test/groovy/org/prebid/server/functional/model/bidderspecific/BidderImp.groovy
new file mode 100644
index 00000000000..62799412692
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/bidderspecific/BidderImp.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.bidderspecific
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.request.auction.Imp
+
+@ToString(includeNames = true, ignoreNulls = true)
+class BidderImp extends Imp {
+
+ BidderImpExt ext
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/bidderspecific/BidderImpExt.groovy b/src/test/groovy/org/prebid/server/functional/model/bidderspecific/BidderImpExt.groovy
new file mode 100644
index 00000000000..354e5ac7e60
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/bidderspecific/BidderImpExt.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.model.bidderspecific
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.bidder.Generic
+import org.prebid.server.functional.model.request.auction.ImpExt
+
+@ToString(includeNames = true, ignoreNulls = true)
+class BidderImpExt extends ImpExt {
+
+ Generic bidder
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/bidderspecific/BidderRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/bidderspecific/BidderRequest.groovy
new file mode 100644
index 00000000000..6def4eb434b
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/bidderspecific/BidderRequest.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.bidderspecific
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.request.auction.BidRequest
+
+@ToString(includeNames = true, ignoreNulls = true)
+class BidderRequest extends BidRequest {
+
+ List imp
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountAnalyticsConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountAnalyticsConfig.groovy
new file mode 100644
index 00000000000..96dcf8dcf30
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountAnalyticsConfig.groovy
@@ -0,0 +1,12 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class AccountAnalyticsConfig {
+
+ Map auctionEvents
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy
new file mode 100644
index 00000000000..fcad48ccab9
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy
@@ -0,0 +1,19 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class AccountAuctionConfig {
+
+ String priceGranularity
+ Integer bannerCacheTtl
+ Integer videoCacheTtl
+ Integer truncateTargetAttr
+ String defaultIntegration
+ AccountBidValidationConfig bidValidations
+ AccountEventsConfig events
+ Boolean debugAllow
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountBidValidationConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountBidValidationConfig.groovy
new file mode 100644
index 00000000000..eb6cd4fe882
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountBidValidationConfig.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class AccountBidValidationConfig {
+
+ @JsonProperty("banner-creative-max-size")
+ BidValidationEnforcement bannerMaxSizeEnforcement
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountCcpaConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountCcpaConfig.groovy
new file mode 100644
index 00000000000..dc7d0e27d00
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountCcpaConfig.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class AccountCcpaConfig {
+
+ Boolean enabled
+ Map enabledForRequestType
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy
new file mode 100644
index 00000000000..48dda7885c3
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy
@@ -0,0 +1,21 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.ToString
+import org.prebid.server.functional.model.AccountStatus
+
+@EqualsAndHashCode
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class AccountConfig {
+
+ String id
+ AccountStatus status
+ AccountAuctionConfig auction
+ AccountPrivacyConfig privacy
+ AccountAnalyticsConfig analytics
+ AccountCookieSyncConfig cookieSync
+ AccountHooksConfiguration hooks
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountCookieSyncConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountCookieSyncConfig.groovy
new file mode 100644
index 00000000000..5c76a655244
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountCookieSyncConfig.groovy
@@ -0,0 +1,14 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class AccountCookieSyncConfig {
+
+ Integer defaultLimit
+ Integer maxLimit
+ Boolean defaultCoopSync
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountEventsConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountEventsConfig.groovy
new file mode 100644
index 00000000000..09632c75bef
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountEventsConfig.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.config
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class AccountEventsConfig {
+
+ Boolean enabled
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountGdprConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountGdprConfig.groovy
new file mode 100644
index 00000000000..3782cb82e7e
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountGdprConfig.groovy
@@ -0,0 +1,17 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class AccountGdprConfig {
+
+ Boolean enabled
+ Map enabledForRequestType
+ Map purposes
+ Map specialFeatures
+ PurposeOneTreatmentInterpretation purposeOneTreatmentInterpretation
+ List basicEnforcementVendors
+}
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
new file mode 100644
index 00000000000..2dc0336662e
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountHooksConfiguration.groovy
@@ -0,0 +1,12 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class AccountHooksConfiguration {
+
+ ExecutionPlan executionPlan
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy
new file mode 100644
index 00000000000..4cd33fd8a4d
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class AccountPrivacyConfig {
+
+ AccountGdprConfig gdpr
+ AccountCcpaConfig ccpa
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/BidValidationEnforcement.groovy b/src/test/groovy/org/prebid/server/functional/model/config/BidValidationEnforcement.groovy
new file mode 100644
index 00000000000..79dbeace7a4
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/BidValidationEnforcement.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum BidValidationEnforcement {
+
+ SKIP, ENFORCE, WARN
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/ChannelType.groovy b/src/test/groovy/org/prebid/server/functional/model/config/ChannelType.groovy
new file mode 100644
index 00000000000..f01abb7800a
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/ChannelType.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum ChannelType {
+
+ WEB, AMP, APP, VIDEO
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/Endpoint.groovy b/src/test/groovy/org/prebid/server/functional/model/config/Endpoint.groovy
new file mode 100644
index 00000000000..eee6d80d319
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/Endpoint.groovy
@@ -0,0 +1,33 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.annotation.JsonValue
+import groovy.transform.ToString
+
+@ToString
+enum Endpoint {
+
+ OPENRTB2_AUCTION("/openrtb2/auction"),
+ OPENRTB2_AMP("/openrtb2/amp"),
+ OPENRTB2_VIDEO("/openrtb2/video"),
+ COOKIE_SYNC("/cookie_sync"),
+ SETUID("/setuid"),
+ BIDDER_PARAMS("/bidders/params"),
+ EVENT("/event"),
+ GETUIDS("/getuids"),
+ INFO_BIDDERS("/info/bidders"),
+ OPTOUT("/optout"),
+ STATUS("/status"),
+ VTRACK("/vtrack")
+
+ @JsonValue
+ final String value
+
+ Endpoint(String value) {
+ this.value = value
+ }
+
+ @Override
+ String toString() {
+ value
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/EndpointExecutionPlan.groovy b/src/test/groovy/org/prebid/server/functional/model/config/EndpointExecutionPlan.groovy
new file mode 100644
index 00000000000..81ad1446af0
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/EndpointExecutionPlan.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.config
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class EndpointExecutionPlan {
+
+ Map stages
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/ExecutionGroup.groovy b/src/test/groovy/org/prebid/server/functional/model/config/ExecutionGroup.groovy
new file mode 100644
index 00000000000..45f15a29cd8
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/ExecutionGroup.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class ExecutionGroup {
+
+ Long timeout
+ List hookSequence
+}
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
new file mode 100644
index 00000000000..8fbc9c5ab6c
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/ExecutionPlan.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.config
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class ExecutionPlan {
+
+ Map endpoints
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/HookId.groovy b/src/test/groovy/org/prebid/server/functional/model/config/HookId.groovy
new file mode 100644
index 00000000000..765de8d9068
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/HookId.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class HookId {
+
+ String moduleCode
+ String hookImplCode
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/Purpose.groovy b/src/test/groovy/org/prebid/server/functional/model/config/Purpose.groovy
new file mode 100644
index 00000000000..b0c92abf621
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/Purpose.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum Purpose {
+
+ P1, P2, P3, P4, P5, P6, P7, P8, P9, P10
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/PurposeConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/PurposeConfig.groovy
new file mode 100644
index 00000000000..e944200980c
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/PurposeConfig.groovy
@@ -0,0 +1,14 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class PurposeConfig {
+
+ PurposeEnforcement enforcePurpose
+ Boolean enforceVendors
+ List vendorExceptions
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/PurposeEnforcement.groovy b/src/test/groovy/org/prebid/server/functional/model/config/PurposeEnforcement.groovy
new file mode 100644
index 00000000000..42664e52fe5
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/PurposeEnforcement.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum PurposeEnforcement {
+
+ NO, BASIC, FULL
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/PurposeOneTreatmentInterpretation.groovy b/src/test/groovy/org/prebid/server/functional/model/config/PurposeOneTreatmentInterpretation.groovy
new file mode 100644
index 00000000000..427b3b630fe
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/PurposeOneTreatmentInterpretation.groovy
@@ -0,0 +1,24 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.annotation.JsonValue
+import groovy.transform.ToString
+
+@ToString
+enum PurposeOneTreatmentInterpretation {
+
+ IGNORE("ignore"),
+ NO_ACCESS_ALLOWED("no-access-allowed"),
+ ACCESS_ALLOWED("access-allowed")
+
+ @JsonValue
+ final String value
+
+ PurposeOneTreatmentInterpretation(String value) {
+ this.value = value
+ }
+
+ @Override
+ String toString() {
+ value
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/SpecialFeature.groovy b/src/test/groovy/org/prebid/server/functional/model/config/SpecialFeature.groovy
new file mode 100644
index 00000000000..1afb0f2deed
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/SpecialFeature.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum SpecialFeature {
+
+ SF1, SF2
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/SpecialFeatureConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/SpecialFeatureConfig.groovy
new file mode 100644
index 00000000000..e8c9cf96b72
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/SpecialFeatureConfig.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
+class SpecialFeatureConfig {
+
+ Boolean enforce
+ List vendorExceptions
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/Stage.groovy b/src/test/groovy/org/prebid/server/functional/model/config/Stage.groovy
new file mode 100644
index 00000000000..44c6930d869
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/Stage.groovy
@@ -0,0 +1,28 @@
+package org.prebid.server.functional.model.config
+
+import com.fasterxml.jackson.annotation.JsonValue
+import groovy.transform.ToString
+
+@ToString
+enum Stage {
+
+ ENTRYPOINT("entrypoint"),
+ RAW_AUCTION_REQUEST("raw-auction-request"),
+ PROCESSED_AUCTION_REQUEST("processed-auction-request"),
+ BIDDER_REQUEST("bidder-request"),
+ RAW_BIDDER_RESPONSE("raw-bidder-response"),
+ PROCESSED_BIDDER_RESPONSE("processed-bidder-response"),
+ AUCTION_RESPONSE("auction-response")
+
+ @JsonValue
+ final String value
+
+ Stage(String value) {
+ this.value = value
+ }
+
+ @Override
+ String toString() {
+ value
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/config/StageExecutionPlan.groovy b/src/test/groovy/org/prebid/server/functional/model/config/StageExecutionPlan.groovy
new file mode 100644
index 00000000000..33023c58176
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/config/StageExecutionPlan.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.config
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class StageExecutionPlan {
+
+ List groups
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/db/Account.groovy b/src/test/groovy/org/prebid/server/functional/model/db/Account.groovy
new file mode 100644
index 00000000000..bb089280334
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/db/Account.groovy
@@ -0,0 +1,60 @@
+package org.prebid.server.functional.model.db
+
+import groovy.transform.ToString
+import javax.persistence.Column
+import javax.persistence.Convert
+import javax.persistence.Entity
+import javax.persistence.GeneratedValue
+import javax.persistence.Id
+import javax.persistence.Table
+import org.prebid.server.functional.model.AccountStatus
+import org.prebid.server.functional.model.config.AccountConfig
+import org.prebid.server.functional.model.db.typeconverter.AccountConfigTypeConverter
+import org.prebid.server.functional.model.db.typeconverter.AccountStatusTypeConverter
+
+import java.sql.Timestamp
+
+import static javax.persistence.GenerationType.IDENTITY
+
+@Entity
+@Table(name = "accounts_account")
+@ToString(includeNames = true)
+class Account {
+
+ @Id
+ @GeneratedValue(strategy = IDENTITY)
+ @Column(name = "id")
+ Integer id
+ @Column(name = "uuid")
+ String uuid
+ @Column(name = "price_granularity")
+ String priceGranularity
+ @Column(name = "banner_cache_ttl")
+ Integer bannerCacheTtl
+ @Column(name = "video_cache_ttl")
+ Integer videoCacheTtl
+ @Column(name = "events_enabled")
+ Boolean eventsEnabled
+ @Column(name = "tcf_config")
+ String tcfConfig
+ @Column(name = "truncate_target_attr")
+ Integer truncateTargetAttr
+ @Column(name = "default_integration")
+ String defaultIntegration
+ @Column(name = "analytics_config")
+ String analyticsConfig
+ @Column(name = "bid_validations")
+ String bidValidations
+ @Column(name = "status")
+ @Convert(converter = AccountStatusTypeConverter)
+ AccountStatus status
+ @Column(name = "config")
+ @Convert(converter = AccountConfigTypeConverter)
+ AccountConfig config
+ @Column(name = "updated_by")
+ Integer updatedBy
+ @Column(name = "updated_by_user")
+ String updatedByUser
+ @Column(name = "updated")
+ Timestamp updated
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/db/S2sConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/db/S2sConfig.groovy
new file mode 100644
index 00000000000..d767c5cf4f6
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/db/S2sConfig.groovy
@@ -0,0 +1,25 @@
+package org.prebid.server.functional.model.db
+
+import groovy.transform.ToString
+import javax.persistence.Column
+import javax.persistence.Entity
+import javax.persistence.GeneratedValue
+import javax.persistence.Id
+import javax.persistence.Table
+
+import static javax.persistence.GenerationType.IDENTITY
+
+@Entity
+@Table(name = "s2sconfig_config")
+@ToString(includeNames = true)
+class S2sConfig {
+
+ @Id
+ @GeneratedValue(strategy = IDENTITY)
+ @Column(name = "id")
+ Integer id
+ @Column(name = "uuid", nullable = false)
+ String uuid
+ @Column(name = "config")
+ String config
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/db/StoredImp.groovy b/src/test/groovy/org/prebid/server/functional/model/db/StoredImp.groovy
new file mode 100644
index 00000000000..4152bea362b
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/db/StoredImp.groovy
@@ -0,0 +1,29 @@
+package org.prebid.server.functional.model.db
+
+import groovy.transform.ToString
+import javax.persistence.Column
+import javax.persistence.Convert
+import javax.persistence.Entity
+import javax.persistence.GeneratedValue
+import javax.persistence.Id
+import javax.persistence.Table
+import org.prebid.server.functional.model.db.typeconverter.ImpConfigTypeConverter
+import org.prebid.server.functional.model.request.auction.Imp
+
+import static javax.persistence.GenerationType.IDENTITY
+
+@Entity
+@Table(name = "stored_imps")
+@ToString(includeNames = true)
+class StoredImp {
+
+ @Id
+ @GeneratedValue(strategy = IDENTITY)
+ @Column(name = "id")
+ Integer id
+ @Column(name = "uuid")
+ String uuid
+ @Column(name = "config")
+ @Convert(converter = ImpConfigTypeConverter)
+ Imp config
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/db/StoredRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/db/StoredRequest.groovy
new file mode 100644
index 00000000000..0b41da34e10
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/db/StoredRequest.groovy
@@ -0,0 +1,36 @@
+package org.prebid.server.functional.model.db
+
+import groovy.transform.ToString
+import javax.persistence.Column
+import javax.persistence.Convert
+import javax.persistence.Entity
+import javax.persistence.GeneratedValue
+import javax.persistence.Id
+import javax.persistence.Table
+import org.prebid.server.functional.model.db.typeconverter.StoredRequestConfigTypeConverter
+import org.prebid.server.functional.model.request.amp.AmpRequest
+import org.prebid.server.functional.model.request.auction.BidRequest
+
+import static javax.persistence.GenerationType.IDENTITY
+
+@Entity
+@Table(name = "stored_requests")
+@ToString(includeNames = true)
+class StoredRequest {
+
+ @Id
+ @GeneratedValue(strategy = IDENTITY)
+ @Column(name = "id")
+ Integer id
+ @Column(name = "accountId")
+ String accountId
+ @Column(name = "reqid")
+ String reqid
+ @Column(name = "requestData")
+ @Convert(converter = StoredRequestConfigTypeConverter)
+ BidRequest requestData
+
+ static StoredRequest getDbStoredRequest(AmpRequest ampRequest, BidRequest bidRequest) {
+ new StoredRequest(reqid: ampRequest.tagId, accountId: ampRequest.account, requestData: bidRequest)
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/db/StoredResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/db/StoredResponse.groovy
new file mode 100644
index 00000000000..2e75ddd9144
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/db/StoredResponse.groovy
@@ -0,0 +1,29 @@
+package org.prebid.server.functional.model.db
+
+import groovy.transform.ToString
+import javax.persistence.Column
+import javax.persistence.Convert
+import javax.persistence.Entity
+import javax.persistence.GeneratedValue
+import javax.persistence.Id
+import javax.persistence.Table
+import org.prebid.server.functional.model.db.typeconverter.StoredResponseConfigTypeConverter
+import org.prebid.server.functional.model.response.auction.BidResponse
+
+import static javax.persistence.GenerationType.IDENTITY
+
+@Entity
+@Table(name = "stored_responses")
+@ToString(includeNames = true)
+class StoredResponse {
+
+ @Id
+ @GeneratedValue(strategy = IDENTITY)
+ @Column(name = "id")
+ Integer id
+ @Column(name = "uuid")
+ String uuid
+ @Column(name = "config")
+ @Convert(converter = StoredResponseConfigTypeConverter)
+ BidResponse config
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/AccountConfigTypeConverter.groovy b/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/AccountConfigTypeConverter.groovy
new file mode 100644
index 00000000000..42a11b145b8
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/AccountConfigTypeConverter.groovy
@@ -0,0 +1,18 @@
+package org.prebid.server.functional.model.db.typeconverter
+
+import javax.persistence.AttributeConverter
+import org.prebid.server.functional.model.config.AccountConfig
+import org.prebid.server.functional.testcontainers.Dependencies
+
+class AccountConfigTypeConverter implements AttributeConverter {
+
+ @Override
+ String convertToDatabaseColumn(AccountConfig accountConfig) {
+ accountConfig ? Dependencies.objectMapperWrapper.encode(accountConfig) : null
+ }
+
+ @Override
+ AccountConfig convertToEntityAttribute(String value) {
+ value ? Dependencies.objectMapperWrapper.decode(value, AccountConfig) : null
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/AccountStatusTypeConverter.groovy b/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/AccountStatusTypeConverter.groovy
new file mode 100644
index 00000000000..005aae09d1c
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/AccountStatusTypeConverter.groovy
@@ -0,0 +1,17 @@
+package org.prebid.server.functional.model.db.typeconverter
+
+import javax.persistence.AttributeConverter
+import org.prebid.server.functional.model.AccountStatus
+
+class AccountStatusTypeConverter implements AttributeConverter {
+
+ @Override
+ String convertToDatabaseColumn(AccountStatus accountStatus) {
+ accountStatus
+ }
+
+ @Override
+ AccountStatus convertToEntityAttribute(String value) {
+ value ? AccountStatus.forValue(value) : null
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/ImpConfigTypeConverter.groovy b/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/ImpConfigTypeConverter.groovy
new file mode 100644
index 00000000000..4a61adcf038
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/ImpConfigTypeConverter.groovy
@@ -0,0 +1,18 @@
+package org.prebid.server.functional.model.db.typeconverter
+
+import javax.persistence.AttributeConverter
+import org.prebid.server.functional.model.request.auction.Imp
+import org.prebid.server.functional.testcontainers.Dependencies
+
+class ImpConfigTypeConverter implements AttributeConverter {
+
+ @Override
+ String convertToDatabaseColumn(Imp imp) {
+ imp ? Dependencies.objectMapperWrapper.encode(imp) : null
+ }
+
+ @Override
+ Imp convertToEntityAttribute(String value) {
+ value ? Dependencies.objectMapperWrapper.decode(value, Imp) : null
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/StoredRequestConfigTypeConverter.groovy b/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/StoredRequestConfigTypeConverter.groovy
new file mode 100644
index 00000000000..0201b2b0d4b
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/StoredRequestConfigTypeConverter.groovy
@@ -0,0 +1,18 @@
+package org.prebid.server.functional.model.db.typeconverter
+
+import javax.persistence.AttributeConverter
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.testcontainers.Dependencies
+
+class StoredRequestConfigTypeConverter implements AttributeConverter {
+
+ @Override
+ String convertToDatabaseColumn(BidRequest bidRequest) {
+ bidRequest ? Dependencies.objectMapperWrapper.encode(bidRequest) : null
+ }
+
+ @Override
+ BidRequest convertToEntityAttribute(String value) {
+ value ? Dependencies.objectMapperWrapper.decode(value, BidRequest) : null
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/StoredResponseConfigTypeConverter.groovy b/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/StoredResponseConfigTypeConverter.groovy
new file mode 100644
index 00000000000..21662fe7890
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/db/typeconverter/StoredResponseConfigTypeConverter.groovy
@@ -0,0 +1,18 @@
+package org.prebid.server.functional.model.db.typeconverter
+
+import javax.persistence.AttributeConverter
+import org.prebid.server.functional.model.response.auction.BidResponse
+import org.prebid.server.functional.testcontainers.Dependencies
+
+class StoredResponseConfigTypeConverter implements AttributeConverter {
+
+ @Override
+ String convertToDatabaseColumn(BidResponse bidResponse) {
+ bidResponse ? Dependencies.objectMapperWrapper.encode(bidResponse) : null
+ }
+
+ @Override
+ BidResponse convertToEntityAttribute(String value) {
+ value ? Dependencies.objectMapperWrapper.decode(value, BidResponse) : null
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/mock/services/httpsettings/HttpAccountsResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/mock/services/httpsettings/HttpAccountsResponse.groovy
new file mode 100644
index 00000000000..a30b6146fce
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/mock/services/httpsettings/HttpAccountsResponse.groovy
@@ -0,0 +1,25 @@
+package org.prebid.server.functional.model.mock.services.httpsettings
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.ResponseModel
+import org.prebid.server.functional.model.config.AccountAuctionConfig
+import org.prebid.server.functional.model.config.AccountConfig
+import org.prebid.server.functional.model.config.AccountEventsConfig
+import org.prebid.server.functional.model.config.AccountGdprConfig
+import org.prebid.server.functional.model.config.AccountPrivacyConfig
+
+@ToString(includeNames = true, ignoreNulls = true)
+class HttpAccountsResponse implements ResponseModel {
+
+ Map accounts
+
+ static HttpAccountsResponse getDefaultHttpAccountsResponse(String accountId) {
+ def account = new AccountConfig().tap {
+ id = accountId
+ auction = new AccountAuctionConfig(events: new AccountEventsConfig(enabled: true))
+ privacy = new AccountPrivacyConfig(gdpr: new AccountGdprConfig(enabled: false))
+ }
+
+ new HttpAccountsResponse(accounts: [(accountId): account])
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/request/PutObject.groovy b/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/request/PutObject.groovy
new file mode 100644
index 00000000000..a2930dc8ed5
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/request/PutObject.groovy
@@ -0,0 +1,28 @@
+package org.prebid.server.functional.model.mock.services.prebidcache.request
+
+import groovy.transform.ToString
+import org.prebid.server.functional.util.PBSUtils
+
+import static Type.XML
+import static org.prebid.server.functional.model.bidder.BidderName.GENERIC
+
+@ToString(includeNames = true, ignoreNulls = true)
+class PutObject {
+
+ Type type
+ String value
+ Integer ttlseconds
+ String bidid
+ String bidder
+ Long timestamp
+
+ static PutObject getDefaultPutObject(String creative, Type creativeType = XML) {
+ new PutObject().tap {
+ bidid = PBSUtils.randomNumber.toString()
+ bidder = GENERIC.value
+ type = creativeType
+ ttlseconds = 10
+ value = creative
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/request/Type.groovy b/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/request/Type.groovy
new file mode 100644
index 00000000000..f291977ec67
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/request/Type.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.mock.services.prebidcache.request
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum Type {
+
+ XML, JSON
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/response/CacheObject.groovy b/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/response/CacheObject.groovy
new file mode 100644
index 00000000000..592bae7f00a
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/response/CacheObject.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.mock.services.prebidcache.response
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = false)
+class CacheObject {
+
+ String uuid
+
+ static CacheObject getDefaultCacheObject() {
+ new CacheObject(uuid: UUID.randomUUID())
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/response/PrebidCacheResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/response/PrebidCacheResponse.groovy
new file mode 100644
index 00000000000..91205e130ff
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/mock/services/prebidcache/response/PrebidCacheResponse.groovy
@@ -0,0 +1,23 @@
+package org.prebid.server.functional.model.mock.services.prebidcache.response
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.ResponseModel
+
+@ToString(includeNames = true, ignoreNulls = true)
+class PrebidCacheResponse implements ResponseModel {
+
+ List responses
+
+ static PrebidCacheResponse getDefaultCacheResponse() {
+ def response = new PrebidCacheResponse()
+ response.addResponse(CacheObject.defaultCacheObject)
+ response
+ }
+
+ void addResponse(CacheObject cacheResponse) {
+ if (this.responses == null) {
+ this.responses = []
+ }
+ this.responses.add(cacheResponse)
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/mock/services/pubstack/EventType.groovy b/src/test/groovy/org/prebid/server/functional/model/mock/services/pubstack/EventType.groovy
new file mode 100644
index 00000000000..88195ca616e
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/mock/services/pubstack/EventType.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.mock.services.pubstack
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum EventType {
+
+ AUCTION, COOKIESYNC, AMP, SETUID, VIDEO
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/mock/services/pubstack/PubStackResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/mock/services/pubstack/PubStackResponse.groovy
new file mode 100644
index 00000000000..44f21b586e3
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/mock/services/pubstack/PubStackResponse.groovy
@@ -0,0 +1,24 @@
+package org.prebid.server.functional.model.mock.services.pubstack
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.ResponseModel
+
+@ToString(includeNames = true, ignoreNulls = true)
+class PubStackResponse implements ResponseModel {
+
+ String scopeId
+ String endpoint
+ Map features
+
+ static PubStackResponse getDefaultPubStackResponse(String scopeIdValue, String endpointValue) {
+ new PubStackResponse().tap {
+ scopeId = scopeIdValue
+ endpoint = endpointValue
+ features = allEventTypeEnabled
+ }
+ }
+
+ private static Map getAllEventTypeEnabled() {
+ EventType.values().collectEntries { [it, true] }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/Format.groovy b/src/test/groovy/org/prebid/server/functional/model/request/Format.groovy
new file mode 100644
index 00000000000..e9ca3213591
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/Format.groovy
@@ -0,0 +1,22 @@
+package org.prebid.server.functional.model.request
+
+import com.fasterxml.jackson.annotation.JsonValue
+import groovy.transform.ToString
+
+@ToString
+enum Format {
+
+ IMAGE("i"), BLANK("b")
+
+ @JsonValue
+ final String value
+
+ Format(String value) {
+ this.value = value
+ }
+
+ @Override
+ String toString() {
+ value
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy
new file mode 100644
index 00000000000..e7230cd0b81
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy
@@ -0,0 +1,37 @@
+package org.prebid.server.functional.model.request.amp
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+import org.prebid.server.functional.util.PBSUtils
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy)
+class AmpRequest {
+
+ String tagId
+ String debug
+ Integer ow
+ Integer oh
+ Integer w
+ Integer h
+ Long ms
+ Long timeout
+ String slot
+ String curl
+ Integer account
+ String gdprConsent
+ String targeting
+ Integer consentType
+ Boolean gdprApplies
+ String addtlConsent
+
+ static AmpRequest getDefaultAmpRequest() {
+ def request = new AmpRequest()
+ request.tagId = PBSUtils.randomString
+ request.curl = PBSUtils.randomString
+ request.account = PBSUtils.randomNumber
+ request.debug = "1"
+ request
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Amp.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Amp.groovy
new file mode 100644
index 00000000000..cbc381ce564
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Amp.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.request.amp.AmpRequest
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Amp {
+
+ AmpRequest data
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/App.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/App.groovy
new file mode 100644
index 00000000000..bff2f7e0cd9
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/App.groovy
@@ -0,0 +1,27 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+import org.prebid.server.functional.util.PBSUtils
+
+@ToString(includeNames = true, ignoreNulls = true)
+class App {
+
+ String id
+ String name
+ String bundle
+ String domain
+ String storeurl
+ List cat
+ List sectioncat
+ List pagecat
+ String ver
+ Integer privacypolicy
+ Integer paid
+ Publisher publisher
+ Content content
+ String keywords
+
+ static App getDefaultApp() {
+ new App(id: PBSUtils.randomString)
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Asset.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Asset.groovy
new file mode 100644
index 00000000000..a083b49ade3
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Asset.groovy
@@ -0,0 +1,14 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Asset {
+
+ Integer id
+ Integer required
+ AssetTitle title
+ AssetImage img
+ AssetVideo video
+ AssetData data
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetData.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetData.groovy
new file mode 100644
index 00000000000..f8a10822cda
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetData.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class AssetData {
+
+ Integer type
+ Integer len
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetImage.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetImage.groovy
new file mode 100644
index 00000000000..9406468df2d
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetImage.groovy
@@ -0,0 +1,14 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class AssetImage {
+
+ Integer type
+ Integer w
+ Integer wmin
+ Integer h
+ Integer hmin
+ List mimes
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetTitle.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetTitle.groovy
new file mode 100644
index 00000000000..63fc4d01223
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetTitle.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class AssetTitle {
+
+ Integer len
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetVideo.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetVideo.groovy
new file mode 100644
index 00000000000..65e43f9658b
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/AssetVideo.groovy
@@ -0,0 +1,12 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class AssetVideo {
+
+ List mimes
+ Integer minduration
+ Integer maxduration
+ List protocols
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Audio.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Audio.groovy
new file mode 100644
index 00000000000..e61ad3175f7
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Audio.groovy
@@ -0,0 +1,26 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Audio {
+
+ List mimes
+ Integer minduration
+ Integer maxduration
+ List protocols
+ Integer startdelay
+ Integer sequence
+ List battr
+ Integer maxextended
+ Integer minbitrate
+ Integer maxbitrate
+ List delivery
+ List companionad
+ List api
+ List companiontype
+ Integer maxseq
+ Integer feed
+ Integer stitched
+ Integer nvol
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Banner.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Banner.groovy
new file mode 100644
index 00000000000..b9d4faf3e16
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Banner.groovy
@@ -0,0 +1,33 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Banner {
+
+ List format
+ Integer w
+ Integer h
+ List btype
+ List battr
+ Integer pos
+ List mimes
+ Integer topframe
+ List expdir
+ List api
+ String id
+ Integer vcm
+
+ static Banner getDefaultBanner() {
+ new Banner().tap {
+ addFormat(Format.defaultFormat)
+ }
+ }
+
+ void addFormat(Format format) {
+ if (this.format == null) {
+ this.format = []
+ }
+ this.format.add(format)
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy
new file mode 100644
index 00000000000..07722f4f1ca
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy
@@ -0,0 +1,83 @@
+package org.prebid.server.functional.model.request.auction
+
+import com.fasterxml.jackson.annotation.JsonIgnore
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.ToString
+
+@EqualsAndHashCode
+@ToString(includeNames = true, ignoreNulls = true)
+class BidRequest {
+
+ String id
+ List imp
+ Site site
+ App app
+ Device device
+ User user
+ Integer test
+ Integer at
+ Long tmax
+ List wseat
+ List bseat
+ Integer allimps
+ List cur
+ List wlang
+ List bcat
+ List badv
+ List bapp
+ Source source
+ Regs regs
+ BidRequestExt ext
+
+ static BidRequest getDefaultBidRequest() {
+ new BidRequest().tap {
+ it.addImp(Imp.defaultImpression)
+ regs = Regs.defaultRegs
+ id = UUID.randomUUID()
+ tmax = 2500
+ site = Site.defaultSite
+ ext = new BidRequestExt(prebid: new Prebid(debug: 1))
+ }
+ }
+
+ static BidRequest getDefaultStoredRequest() {
+ getDefaultBidRequest().tap {
+ site = null
+ }
+ }
+
+ void addImp(Imp impression) {
+ if (imp == null) {
+ imp = []
+ }
+ imp.add(impression)
+ }
+
+ @JsonIgnore
+ List getRequestBidders() {
+ def bidderList = []
+ def bidder = imp[0]?.ext?.prebid?.bidder
+ if (bidder) {
+ bidderList = bidder.configuredBidders
+ }
+ bidderList
+ }
+
+ void enableCache() {
+ if (ext == null) {
+ ext = new BidRequestExt()
+ }
+ if (ext.prebid == null) {
+ ext.prebid = new Prebid()
+ }
+ if (ext.prebid.cache == null) {
+ ext.prebid.cache = new PrebidCache()
+ }
+ if (ext.prebid.cache.bids == null) {
+ ext.prebid.cache.bids = new PrebidCacheSettings()
+ }
+ if (ext.prebid.cache.vastXml == null) {
+ ext.prebid.cache.vastXml = new PrebidCacheSettings()
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequestExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequestExt.groovy
new file mode 100644
index 00000000000..460f1382ed4
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequestExt.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class BidRequestExt {
+
+ Prebid prebid
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Bidder.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Bidder.groovy
new file mode 100644
index 00000000000..8e550b5a30d
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Bidder.groovy
@@ -0,0 +1,29 @@
+package org.prebid.server.functional.model.request.auction
+
+import com.fasterxml.jackson.annotation.JsonIgnore
+import com.fasterxml.jackson.annotation.JsonProperty
+import groovy.transform.ToString
+import org.prebid.server.functional.model.bidder.AppNexus
+import org.prebid.server.functional.model.bidder.Generic
+import org.prebid.server.functional.model.bidder.Rubicon
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Bidder {
+
+ Generic generic
+ Rubicon rubicon
+ @JsonProperty("appnexus")
+ AppNexus appNexus
+
+ static Bidder getDefaultBidder() {
+ new Bidder().tap {
+ generic = new Generic()
+ }
+ }
+
+ @JsonIgnore
+ List getConfiguredBidders() {
+ this.class.declaredFields.findAll { !it.synthetic && this[it.name] != null }
+ .collect { it.name }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Channel.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Channel.groovy
new file mode 100644
index 00000000000..551d94a40c2
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Channel.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Channel {
+
+ String name
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Content.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Content.groovy
new file mode 100644
index 00000000000..7a493461712
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Content.groovy
@@ -0,0 +1,32 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Content {
+
+ String id
+ Integer episode
+ String title
+ String series
+ String season
+ String artist
+ String genre
+ String album
+ String isrc
+ Producer producer
+ String url
+ List cat
+ Integer prodq
+ Integer context
+ String contentrating
+ String userrating
+ Integer qagmediarating
+ String keywords
+ Integer livestream
+ Integer sourcerelationship
+ Integer len
+ String language
+ Integer embeddable
+ List data
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Data.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Data.groovy
new file mode 100644
index 00000000000..287d436d5ae
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Data.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Data {
+
+ String id
+ String name
+ List segment
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Deal.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Deal.groovy
new file mode 100644
index 00000000000..bb8b86ab594
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Deal.groovy
@@ -0,0 +1,14 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Deal {
+
+ String id
+ Float bidfloor
+ String bidfloorcur
+ Integer at
+ List wseat
+ List wadomain
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Device.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Device.groovy
new file mode 100644
index 00000000000..136d0584f53
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Device.groovy
@@ -0,0 +1,38 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Device {
+
+ String ua
+ Geo geo
+ Integer dnt
+ Integer lmt
+ String ip
+ String ipv6
+ Integer devicetype
+ String make
+ String model
+ String os
+ String osv
+ String hwv
+ Integer h
+ Integer w
+ Integer ppi
+ BigDecimal pxratio
+ Integer js
+ Integer geofetch
+ String flashver
+ String language
+ String carrier
+ String mccmnc
+ Integer connectiontype
+ String ifa
+ String didsha1
+ String didmd5
+ String dpidsha1
+ String dpidmd5
+ String macsha1
+ String macmd5
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Format.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Format.groovy
new file mode 100644
index 00000000000..0f8236481f5
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Format.groovy
@@ -0,0 +1,20 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Format {
+
+ Integer w
+ Integer h
+ Integer wratio
+ Integer hratio
+ Integer wmin
+
+ static Format getDefaultFormat() {
+ new Format().tap {
+ w = 300
+ h = 250
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Geo.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Geo.groovy
new file mode 100644
index 00000000000..09792638fed
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Geo.groovy
@@ -0,0 +1,21 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Geo {
+
+ Float lat
+ Float lon
+ Integer type
+ Integer accuracy
+ Integer lastfix
+ Integer ipservice
+ String country
+ String region
+ String regionfips104
+ String metro
+ String city
+ String zip
+ Integer utcoffset
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy
new file mode 100644
index 00000000000..1f2ef62dd68
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy
@@ -0,0 +1,43 @@
+package org.prebid.server.functional.model.request.auction
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.ToString
+
+@EqualsAndHashCode
+@ToString(includeNames = true, ignoreNulls = true)
+class Imp {
+
+ String id
+ Banner banner
+ List metric
+ Video video
+ Audio audio
+ @JsonProperty("native")
+ Native nativeObj
+ Pmp pmp
+ String displaymanager
+ String displaymanagerver
+ Integer instl
+ String tagid
+ BigDecimal bidfloor
+ String bidfloorcur
+ Integer clickbrowser
+ Integer secure
+ List iframebuster
+ Integer exp
+ ImpExt ext
+
+ static Imp getDefaultImpression() {
+ getDefaultImp().tap {
+ banner = Banner.getDefaultBanner()
+ }
+ }
+
+ private static Imp getDefaultImp() {
+ new Imp().tap {
+ id = UUID.randomUUID()
+ ext = ImpExt.defaultImpExt
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExt.groovy
new file mode 100644
index 00000000000..2d7c9f2c4e1
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExt.groovy
@@ -0,0 +1,28 @@
+package org.prebid.server.functional.model.request.auction
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import groovy.transform.ToString
+import org.prebid.server.functional.model.bidder.AppNexus
+import org.prebid.server.functional.model.bidder.Generic
+import org.prebid.server.functional.model.bidder.Rubicon
+
+@ToString(includeNames = true, ignoreNulls = true)
+class ImpExt {
+
+ ImpExtPrebid prebid
+
+ Generic generic
+
+ @Deprecated
+ Rubicon rubicon
+
+ @Deprecated
+ @JsonProperty("appnexus")
+ AppNexus appNexus
+
+ static ImpExt getDefaultImpExt() {
+ new ImpExt().tap {
+ prebid = ImpExtPrebid.defaultImpExtPrebid
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExtPrebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExtPrebid.groovy
new file mode 100644
index 00000000000..29200bd300b
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExtPrebid.groovy
@@ -0,0 +1,15 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class ImpExtPrebid {
+
+ Bidder bidder
+
+ static ImpExtPrebid getDefaultImpExtPrebid() {
+ new ImpExtPrebid().tap {
+ bidder = Bidder.defaultBidder
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Metric.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Metric.groovy
new file mode 100644
index 00000000000..c936d08973f
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Metric.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Metric {
+
+ String type
+ Float value
+ String vendor
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/MultiBid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/MultiBid.groovy
new file mode 100644
index 00000000000..22fd5451629
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/MultiBid.groovy
@@ -0,0 +1,15 @@
+package org.prebid.server.functional.model.request.auction
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = false)
+@JsonNaming(PropertyNamingStrategy.LowerCaseStrategy.class)
+class MultiBid {
+
+ String bidder
+ List bidders
+ Integer maxBids
+ String targetBidderCodePrefix
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Native.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Native.groovy
new file mode 100644
index 00000000000..ea6d50030ed
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Native.groovy
@@ -0,0 +1,12 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Native {
+
+ String request
+ String ver
+ List api
+ List battr
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Pbs.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Pbs.groovy
new file mode 100644
index 00000000000..cf7f9734bf3
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Pbs.groovy
@@ -0,0 +1,6 @@
+package org.prebid.server.functional.model.request.auction
+
+class Pbs {
+
+ String endpoint
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Pmp.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Pmp.groovy
new file mode 100644
index 00000000000..bd5f64fbe82
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Pmp.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Pmp {
+
+ Integer privateAuction
+ List deals
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy
new file mode 100644
index 00000000000..515e760b5cc
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy
@@ -0,0 +1,22 @@
+package org.prebid.server.functional.model.request.auction
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+import org.prebid.server.functional.model.bidder.BidderName
+
+@JsonNaming(PropertyNamingStrategy.LowerCaseStrategy.class)
+@ToString(includeNames = true, ignoreNulls = true)
+class Prebid {
+
+ Integer debug
+ Targeting targeting
+ PrebidCache cache
+ StoredRequest storedRequest
+ Amp amp
+ Channel channel
+ List schains
+ List multibid
+ Pbs pbs
+ Map> bidderParams
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidCache.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidCache.groovy
new file mode 100644
index 00000000000..d196c8ea775
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidCache.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.request.auction
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class PrebidCache {
+
+ Boolean winningOnly
+ PrebidCacheSettings bids
+ @JsonProperty("vastxml")
+ PrebidCacheSettings vastXml
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidCacheSettings.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidCacheSettings.groovy
new file mode 100644
index 00000000000..76f39880e71
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidCacheSettings.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class PrebidCacheSettings {
+
+ Integer ttlSeconds
+ Boolean returnCreative
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidSchain.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidSchain.groovy
new file mode 100644
index 00000000000..5032bac6de3
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidSchain.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = false)
+class PrebidSchain {
+
+ List bidders
+ Schain schain
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/PriceGranularity.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/PriceGranularity.groovy
new file mode 100644
index 00000000000..29f4472cab2
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/PriceGranularity.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class PriceGranularity {
+
+ Integer precision
+ List ranges
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Producer.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Producer.groovy
new file mode 100644
index 00000000000..3882b876dd4
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Producer.groovy
@@ -0,0 +1,12 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Producer {
+
+ String id
+ String name
+ List cat
+ String domain
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Publisher.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Publisher.groovy
new file mode 100644
index 00000000000..cd0295fe98c
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Publisher.groovy
@@ -0,0 +1,19 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+import org.prebid.server.functional.util.PBSUtils
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Publisher {
+
+ String id
+ String name
+ List cat
+ String domain
+
+ static Publisher getDefaultPublisher() {
+ new Publisher().tap {
+ id = PBSUtils.randomNumber.toString()
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Range.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Range.groovy
new file mode 100644
index 00000000000..f54034f9f14
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Range.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Range {
+
+ Integer max
+ BigDecimal increment
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Regs.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Regs.groovy
new file mode 100644
index 00000000000..fe7175ef8be
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Regs.groovy
@@ -0,0 +1,16 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Regs {
+
+ Integer coppa
+ RegsExt ext
+
+ static Regs getDefaultRegs() {
+ new Regs().tap {
+ ext = new RegsExt(gdpr: 0)
+ }
+ }
+}
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
new file mode 100644
index 00000000000..2647e3ac020
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.request.auction
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy)
+class RegsExt {
+
+ Integer gdpr
+ String usPrivacy
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Schain.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Schain.groovy
new file mode 100644
index 00000000000..8ae7f452c4b
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Schain.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = false)
+class Schain {
+
+ String ver
+ Integer complete
+ List nodes
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/SchainNode.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/SchainNode.groovy
new file mode 100644
index 00000000000..883543b7dd6
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/SchainNode.groovy
@@ -0,0 +1,16 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.ToString
+
+@EqualsAndHashCode
+@ToString(includeNames = true, ignoreNulls = false)
+class SchainNode {
+
+ String asi
+ String sid
+ Integer hp
+ String rid
+ String name
+ String domain
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Segment.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Segment.groovy
new file mode 100644
index 00000000000..c6f54e34c2f
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Segment.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = false)
+class Segment {
+
+ String id
+ String name
+ String value
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Site.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Site.groovy
new file mode 100644
index 00000000000..621ebb910a4
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Site.groovy
@@ -0,0 +1,31 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+import org.prebid.server.functional.util.PBSUtils
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Site {
+
+ String id
+ String name
+ String domain
+ List cat
+ List sectioncat
+ List pagecat
+ String page
+ String ref
+ String search
+ Integer mobile
+ Integer privacypolicy
+ Publisher publisher
+ Content content
+ String keywords
+ SiteExt ext
+
+ static Site getDefaultSite() {
+ new Site().tap {
+ page = PBSUtils.randomString
+ publisher = Publisher.defaultPublisher
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/SiteExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/SiteExt.groovy
new file mode 100644
index 00000000000..2a9f35a6d44
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/SiteExt.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class SiteExt {
+
+ Integer amp
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Source.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Source.groovy
new file mode 100644
index 00000000000..b3496024265
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Source.groovy
@@ -0,0 +1,12 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Source {
+
+ Integer fd
+ String tid
+ String pchain
+ SourceExt ext
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/SourceExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/SourceExt.groovy
new file mode 100644
index 00000000000..469130a0cc7
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/SourceExt.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = false)
+class SourceExt {
+
+ Schain schain
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredRequest.groovy
new file mode 100644
index 00000000000..d392a3ab817
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredRequest.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class StoredRequest {
+
+ String id
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Targeting.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Targeting.groovy
new file mode 100644
index 00000000000..b27c0e9c267
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Targeting.groovy
@@ -0,0 +1,15 @@
+package org.prebid.server.functional.model.request.auction
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.LowerCaseStrategy.class)
+class Targeting {
+
+ PriceGranularity priceGranularity
+ Boolean includeWinners
+ Boolean includeBidderKeys
+ Boolean preferdeals
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/User.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/User.groovy
new file mode 100644
index 00000000000..8df6e8039d6
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/User.groovy
@@ -0,0 +1,18 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class User {
+
+ String id
+ String buyeruid
+ Integer yob
+ String gender
+ String language
+ String keywords
+ String customdata
+ Geo geo
+ List data
+ UserExt ext
+}
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
new file mode 100644
index 00000000000..79b4b7b8c38
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class UserExt {
+
+ String consent
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Video.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Video.groovy
new file mode 100644
index 00000000000..2c604d3b7ad
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Video.groovy
@@ -0,0 +1,33 @@
+package org.prebid.server.functional.model.request.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Video {
+
+ List mimes
+ Integer minduration
+ Integer maxduration
+ List protocols
+ Integer w
+ Integer h
+ Integer startdelay
+ Integer placement
+ Integer linearity
+ Integer skip
+ Integer skipmin
+ Integer skipafter
+ Integer sequence
+ List battr
+ Integer maxextended
+ Integer minbitrate
+ Integer maxbitrate
+ Integer boxingallowed
+ List playbackmethod
+ Integer playbackend
+ List delivery
+ Integer pos
+ List companionad
+ List api
+ List companiontype
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/cookiesync/CookieSyncRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/cookiesync/CookieSyncRequest.groovy
new file mode 100644
index 00000000000..df3e4858fb6
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/cookiesync/CookieSyncRequest.groovy
@@ -0,0 +1,30 @@
+package org.prebid.server.functional.model.request.cookiesync
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import groovy.transform.ToString
+import org.prebid.server.functional.model.bidder.BidderName
+
+import static org.prebid.server.functional.model.bidder.BidderName.GENERIC
+
+@ToString(includeNames = true, ignoreNulls = true)
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy)
+class CookieSyncRequest {
+
+ List bidders
+ Integer gdpr
+ String gdprConsent
+ String usPrivacy
+ @JsonProperty("coopSync")
+ Boolean coopSync
+ Integer limit
+ String account
+
+ static CookieSyncRequest getDefaultCookieSyncRequest() {
+ def request = new CookieSyncRequest()
+ request.bidders = [GENERIC]
+ request.gdpr = 0
+ request
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/event/EventRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/event/EventRequest.groovy
new file mode 100644
index 00000000000..6ed94f08161
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/event/EventRequest.groovy
@@ -0,0 +1,38 @@
+package org.prebid.server.functional.model.request.event
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import groovy.transform.ToString
+import org.prebid.server.functional.model.request.Format
+import org.prebid.server.functional.util.PBSUtils
+
+import static org.prebid.server.functional.model.request.Format.IMAGE
+import static org.prebid.server.functional.model.request.event.EventType.WIN
+
+@ToString(includeNames = true, ignoreNulls = true)
+class EventRequest {
+
+ @JsonProperty("t")
+ EventType type
+ @JsonProperty("b")
+ String bidId
+ @JsonProperty("a")
+ Integer accountId
+ @JsonProperty("f")
+ Format format
+ String bidder
+ @JsonProperty("x")
+ Integer analytics
+ @JsonProperty("ts")
+ Long timestamp
+
+ static EventRequest getDefaultEventRequest() {
+ def request = new EventRequest()
+ request.type = WIN
+ request.bidId = PBSUtils.randomString
+ request.accountId = PBSUtils.randomNumber
+ request.format = IMAGE
+ request.bidder = "generic"
+ request.timestamp = System.currentTimeMillis()
+ request
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/event/EventType.groovy b/src/test/groovy/org/prebid/server/functional/model/request/event/EventType.groovy
new file mode 100644
index 00000000000..841c13e0843
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/event/EventType.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.request.event
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum EventType {
+
+ WIN, IMP
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/logging/httpinteraction/HttpInteractionRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/logging/httpinteraction/HttpInteractionRequest.groovy
new file mode 100644
index 00000000000..d4a4c3313cb
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/logging/httpinteraction/HttpInteractionRequest.groovy
@@ -0,0 +1,20 @@
+package org.prebid.server.functional.model.request.logging.httpinteraction
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.bidder.BidderName
+
+@ToString(includeNames = true, ignoreNulls = true)
+class HttpInteractionRequest {
+
+ String endpoint
+ String statusCode
+ String account
+ Integer limit
+ BidderName bidder
+
+ static HttpInteractionRequest getDefaultHttpInteractionRequest() {
+ def request = new HttpInteractionRequest()
+ request.limit = 1 // Using this number as there is no point in capturing more requests by default
+ request
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/setuid/SetuidRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/setuid/SetuidRequest.groovy
new file mode 100644
index 00000000000..cb6bccb8495
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/setuid/SetuidRequest.groovy
@@ -0,0 +1,28 @@
+package org.prebid.server.functional.model.request.setuid
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import groovy.transform.ToString
+import org.prebid.server.functional.model.bidder.BidderName
+import org.prebid.server.functional.model.request.Format
+
+import static org.prebid.server.functional.model.bidder.BidderName.GENERIC
+
+@ToString(includeNames = true, ignoreNulls = true)
+class SetuidRequest {
+
+ BidderName bidder
+ String uid
+ String gdpr
+ @JsonProperty("gdpr_consent")
+ String gdprConsent
+ @JsonProperty("f")
+ Format format
+ String account
+
+ static SetuidRequest getDefaultSetuidRequest() {
+ def request = new SetuidRequest()
+ request.bidder = GENERIC
+ request.gdpr = "0"
+ request
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy b/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy
new file mode 100644
index 00000000000..6cd6b83f689
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy
@@ -0,0 +1,14 @@
+package org.prebid.server.functional.model.request.setuid
+
+import com.fasterxml.jackson.annotation.JsonFormat
+import groovy.transform.ToString
+
+import java.time.ZonedDateTime
+
+@ToString(includeNames = true, ignoreNulls = true)
+class UidWithExpiry {
+
+ String uid
+ @JsonFormat(pattern = "yyyy-MM-dd'T'hh:mm:ss.SSS'Z'", timezone = "UTC")
+ ZonedDateTime expires
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidsCookie.groovy b/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidsCookie.groovy
new file mode 100644
index 00000000000..4897eb7b672
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidsCookie.groovy
@@ -0,0 +1,25 @@
+package org.prebid.server.functional.model.request.setuid
+
+import com.fasterxml.jackson.annotation.JsonFormat
+import groovy.transform.ToString
+
+import java.time.Clock
+import java.time.ZonedDateTime
+
+@ToString(includeNames = true, ignoreNulls = true)
+class UidsCookie {
+
+ Map uids
+ Map tempUIDs
+ Boolean optout
+ @JsonFormat(pattern = "yyyy-MM-dd'T'hh:mm:ss.SSS'Z'", timezone = "UTC")
+ ZonedDateTime bday
+
+ static UidsCookie getDefaultUidsCookie() {
+ def uidsCookie = new UidsCookie()
+ uidsCookie.uids = [generic: UUID.randomUUID().toString()]
+ uidsCookie.bday = ZonedDateTime.now(Clock.systemUTC())
+ uidsCookie.tempUIDs = ["generic": new UidWithExpiry(uid: UUID.randomUUID().toString(), expires: ZonedDateTime.now(Clock.systemUTC()).plusDays(2))]
+ uidsCookie
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/vtrack/VtrackRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/vtrack/VtrackRequest.groovy
new file mode 100644
index 00000000000..03f7b8e01ba
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/vtrack/VtrackRequest.groovy
@@ -0,0 +1,23 @@
+package org.prebid.server.functional.model.request.vtrack
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.mock.services.prebidcache.request.PutObject
+
+@ToString(includeNames = true, ignoreNulls = true)
+class VtrackRequest {
+
+ List puts
+
+ static VtrackRequest getDefaultVtrackRequest(String creative) {
+ def vtrack = new VtrackRequest()
+ vtrack.addPutObject(PutObject.getDefaultPutObject(creative))
+ vtrack
+ }
+
+ void addPutObject(PutObject putObject) {
+ if (this.puts == null) {
+ this.puts = []
+ }
+ this.puts.add(putObject)
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/vtrack/xml/Ad.groovy b/src/test/groovy/org/prebid/server/functional/model/request/vtrack/xml/Ad.groovy
new file mode 100644
index 00000000000..9eec43677fe
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/vtrack/xml/Ad.groovy
@@ -0,0 +1,15 @@
+package org.prebid.server.functional.model.request.vtrack.xml
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
+
+class Ad {
+
+ @JacksonXmlProperty(localName = "Wrapper")
+ Wrapper wrapper
+
+ static Ad getDefaultAd(String payload) {
+ new Ad().tap {
+ wrapper = Wrapper.getDefaultWrapper(payload)
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/vtrack/xml/Vast.groovy b/src/test/groovy/org/prebid/server/functional/model/request/vtrack/xml/Vast.groovy
new file mode 100644
index 00000000000..46043571e40
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/vtrack/xml/Vast.groovy
@@ -0,0 +1,20 @@
+package org.prebid.server.functional.model.request.vtrack.xml
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement
+
+@JacksonXmlRootElement(localName = "VAST")
+class Vast {
+
+ @JacksonXmlProperty(isAttribute = true)
+ String version
+ @JacksonXmlProperty(localName = "Ad")
+ Ad ad
+
+ static Vast getDefaultVastModel(String payload) {
+ new Vast().tap {
+ version = "3.0"
+ ad = Ad.getDefaultAd(payload)
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/vtrack/xml/Wrapper.groovy b/src/test/groovy/org/prebid/server/functional/model/request/vtrack/xml/Wrapper.groovy
new file mode 100644
index 00000000000..d3dff6da36a
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/request/vtrack/xml/Wrapper.groovy
@@ -0,0 +1,24 @@
+package org.prebid.server.functional.model.request.vtrack.xml
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
+
+class Wrapper {
+
+ @JacksonXmlProperty(localName = "AdSystem")
+ String adSystem
+ @JacksonXmlProperty(localName = "VASTAdTagURI")
+ String vastAdTagUri
+ @JacksonXmlProperty(localName = "Impression")
+ String impression
+ @JacksonXmlProperty(localName = "Creatives")
+ String creatives
+
+ static Wrapper getDefaultWrapper(String payload) {
+ new Wrapper().tap {
+ adSystem = "prebid.org wrapper"
+ vastAdTagUri = ""
+ impression = " "
+ creatives = ""
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/BidderError.groovy b/src/test/groovy/org/prebid/server/functional/model/response/BidderError.groovy
new file mode 100644
index 00000000000..28fe2a7ef95
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/BidderError.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.response
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class BidderError {
+
+ Integer code
+ String message
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/Debug.groovy b/src/test/groovy/org/prebid/server/functional/model/response/Debug.groovy
new file mode 100644
index 00000000000..925b20a0322
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/Debug.groovy
@@ -0,0 +1,17 @@
+package org.prebid.server.functional.model.response
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.request.auction.BidRequest
+import org.prebid.server.functional.model.response.auction.HttpCall
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Debug {
+
+ Map> httpcalls
+ BidRequest resolvedrequest
+
+ Map> getBidders() {
+ def result = httpcalls?.findAll { it.key != "cache" }
+ result ?: [:]
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/amp/AmpResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/amp/AmpResponse.groovy
new file mode 100644
index 00000000000..1acb00df6fb
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/amp/AmpResponse.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.model.response.amp
+
+import org.prebid.server.functional.model.response.BidderError
+import org.prebid.server.functional.model.response.Debug
+
+class AmpResponse {
+
+ Map targeting
+ Debug debug
+ Map> errors
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy
new file mode 100644
index 00000000000..ebcc645a512
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy
@@ -0,0 +1,48 @@
+package org.prebid.server.functional.model.response.auction
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.request.auction.Imp
+
+@ToString(includeNames = true, ignoreNulls = true)
+class Bid {
+
+ String id
+ String impid
+ BigDecimal price
+ String nurl
+ String burl
+ String lurl
+ String adm
+ String adid
+ List adomain
+ String bundle
+ String iurl
+ String cid
+ String crid
+ List cat
+ List attr
+ Integer api
+ Integer protocol
+ Integer qagmediarating
+ String language
+ String dealid
+ Integer w
+ Integer h
+ Integer wratio
+ Integer hratio
+ Integer exp
+ BidExt ext
+
+ static Bid getDefaultBid(Imp imp) {
+ getDefaultBid(imp.id)
+ }
+
+ static Bid getDefaultBid(String impId) {
+ new Bid().tap {
+ id = UUID.randomUUID()
+ impid = impId
+ price = 1.23
+ crid = 1
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExt.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExt.groovy
new file mode 100644
index 00000000000..a28236c2873
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidExt.groovy
@@ -0,0 +1,7 @@
+package org.prebid.server.functional.model.response.auction
+
+class BidExt {
+
+ Prebid prebid
+ BigDecimal origbidcpm
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidResponse.groovy
new file mode 100644
index 00000000000..2398e99d576
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidResponse.groovy
@@ -0,0 +1,35 @@
+package org.prebid.server.functional.model.response.auction
+
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.ToString
+import org.prebid.server.functional.model.ResponseModel
+import org.prebid.server.functional.model.request.auction.BidRequest
+
+@EqualsAndHashCode
+@ToString(includeNames = true, ignoreNulls = true)
+class BidResponse implements ResponseModel {
+
+ String id
+ List seatbid
+ String bidid
+ String cur
+ String customdata
+ Integer nbr
+ BidResponseExt ext
+
+ static BidResponse getDefaultBidResponse(BidRequest bidRequest) {
+ getDefaultBidResponse(bidRequest.id, bidRequest.imp*.id)
+ }
+
+ static BidResponse getDefaultBidResponse(String id, List impIds) {
+ def bidResponse = new BidResponse(id: id)
+ def bids = getDefaultBids(impIds)
+ def seatBid = new SeatBid(bid: bids)
+ bidResponse.seatbid = [seatBid]
+ bidResponse
+ }
+
+ static private List getDefaultBids(List impIds) {
+ impIds.collect { Bid.getDefaultBid(it) }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidResponseExt.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidResponseExt.groovy
new file mode 100644
index 00000000000..0363158d750
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidResponseExt.groovy
@@ -0,0 +1,17 @@
+package org.prebid.server.functional.model.response.auction
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.response.BidderError
+import org.prebid.server.functional.model.response.Debug
+
+@ToString(includeNames = true, ignoreNulls = true)
+class BidResponseExt {
+
+ Debug debug
+ Map> errors
+ Map responsetimemillis
+ Long tmaxrequest
+ Map usersync
+ BidResponsePrebid prebid
+ Map> warnings
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidResponsePrebid.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidResponsePrebid.groovy
new file mode 100644
index 00000000000..9f398367a84
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidResponsePrebid.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.response.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class BidResponsePrebid {
+
+ Long auctiontimestamp
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy
new file mode 100644
index 00000000000..d332b378277
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.response.auction
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum ErrorType {
+
+ GENERAL, GENERIC, RUBICON, APPNEXUS, PREBID
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/HttpCall.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/HttpCall.groovy
new file mode 100644
index 00000000000..0df682648f3
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/HttpCall.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.response.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class HttpCall {
+
+ String uri
+ String requestbody
+ Map> requestheaders
+ String responsebody
+ Integer status
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/MediaType.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/MediaType.groovy
new file mode 100644
index 00000000000..8af36d5ec69
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/MediaType.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.response.auction
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum MediaType {
+
+ BANNER, VIDEO
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Prebid.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Prebid.groovy
new file mode 100644
index 00000000000..143582391f9
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Prebid.groovy
@@ -0,0 +1,8 @@
+package org.prebid.server.functional.model.response.auction
+
+class Prebid {
+
+ MediaType type
+ Map targeting
+ String targetbiddercode
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/ResponseSyncData.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/ResponseSyncData.groovy
new file mode 100644
index 00000000000..788c1793577
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/ResponseSyncData.groovy
@@ -0,0 +1,22 @@
+package org.prebid.server.functional.model.response.auction
+
+import com.fasterxml.jackson.annotation.JsonValue
+import groovy.transform.ToString
+import org.prebid.server.functional.model.response.cookiesync.UserSync
+
+@ToString(includeNames = true, ignoreNulls = true)
+class ResponseSyncData {
+
+ CookieStatus status
+ List syncs
+
+ enum CookieStatus {
+
+ NONE, EXPIRED, AVAILABLE
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/SeatBid.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/SeatBid.groovy
new file mode 100644
index 00000000000..26f16db5443
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/SeatBid.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.model.response.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class SeatBid {
+
+ List bid
+ String seat
+ Integer group
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/WarningEntry.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/WarningEntry.groovy
new file mode 100644
index 00000000000..0e53914fdad
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/WarningEntry.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.response.auction
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class WarningEntry {
+
+ Integer code
+ String message
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/biddersparams/BidderParams.groovy b/src/test/groovy/org/prebid/server/functional/model/response/biddersparams/BidderParams.groovy
new file mode 100644
index 00000000000..140f25c410f
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/biddersparams/BidderParams.groovy
@@ -0,0 +1,20 @@
+package org.prebid.server.functional.model.response.biddersparams
+
+import com.fasterxml.jackson.annotation.JsonProperty
+
+class BidderParams {
+
+ @JsonProperty("\$schema")
+ String schema
+ String title
+ String description
+ String type
+ def properties
+ List oneOf
+ List required
+ def not
+ def anyOf
+ def appid
+ def placementid
+ def dependencies
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/biddersparams/BiddersParamsResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/biddersparams/BiddersParamsResponse.groovy
new file mode 100644
index 00000000000..d1608c1c7e9
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/biddersparams/BiddersParamsResponse.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.response.biddersparams
+
+import com.fasterxml.jackson.annotation.JsonAnySetter
+
+class BiddersParamsResponse {
+
+ @JsonAnySetter
+ Map parameters = [:]
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/biddersparams/OneOf.groovy b/src/test/groovy/org/prebid/server/functional/model/response/biddersparams/OneOf.groovy
new file mode 100644
index 00000000000..905a8766b19
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/biddersparams/OneOf.groovy
@@ -0,0 +1,7 @@
+package org.prebid.server.functional.model.response.biddersparams
+
+class OneOf {
+
+ List required
+ def oneOf
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/BidderUsersyncStatus.groovy b/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/BidderUsersyncStatus.groovy
new file mode 100644
index 00000000000..c2f12b18548
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/BidderUsersyncStatus.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.response.cookiesync
+
+import groovy.transform.ToString
+import org.prebid.server.functional.model.bidder.BidderName
+
+@ToString(includeNames = true, ignoreNulls = true)
+class BidderUsersyncStatus {
+
+ BidderName bidder
+ String error
+ Boolean no_cookie
+ UsersyncInfo usersync
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/CookieSyncResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/CookieSyncResponse.groovy
new file mode 100644
index 00000000000..c85fb573be9
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/CookieSyncResponse.groovy
@@ -0,0 +1,28 @@
+package org.prebid.server.functional.model.response.cookiesync
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.annotation.JsonValue
+import groovy.transform.ToString
+import org.prebid.server.functional.model.bidder.BidderName
+
+@ToString(includeNames = true, ignoreNulls = true)
+class CookieSyncResponse {
+
+ Status status
+ @JsonProperty("bidder_status")
+ List bidderStatus
+
+ BidderUsersyncStatus getBidderUsersync(BidderName bidderName) {
+ bidderStatus?.find { it.bidder == bidderName }
+ }
+
+ enum Status {
+
+ OK, NO_COOKIE
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/UserSync.groovy b/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/UserSync.groovy
new file mode 100644
index 00000000000..76822fe2e57
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/UserSync.groovy
@@ -0,0 +1,21 @@
+package org.prebid.server.functional.model.response.cookiesync
+
+import com.fasterxml.jackson.annotation.JsonValue
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class UserSync {
+
+ String url
+ UserSyncType type
+
+ enum UserSyncType {
+
+ IFRAME, PIXEL
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/UsersyncInfo.groovy b/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/UsersyncInfo.groovy
new file mode 100644
index 00000000000..766f121c172
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/cookiesync/UsersyncInfo.groovy
@@ -0,0 +1,22 @@
+package org.prebid.server.functional.model.response.cookiesync
+
+import com.fasterxml.jackson.annotation.JsonValue
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class UsersyncInfo {
+
+ String url
+ Type type
+ Boolean supportCORS
+
+ enum Type {
+
+ REDIRECT, IFRAME
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/currencyrates/CurrencyRatesResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/currencyrates/CurrencyRatesResponse.groovy
new file mode 100644
index 00000000000..7523253b340
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/currencyrates/CurrencyRatesResponse.groovy
@@ -0,0 +1,17 @@
+package org.prebid.server.functional.model.response.currencyrates
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class CurrencyRatesResponse {
+
+ Boolean active
+
+ String source
+
+ Long fetchingIntervalNs
+
+ String lastUpdated
+
+ Map> rates
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/getuids/GetuidResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/getuids/GetuidResponse.groovy
new file mode 100644
index 00000000000..d86ed5a76a3
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/getuids/GetuidResponse.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.response.getuids
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class GetuidResponse {
+
+ Map buyeruids
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/infobidders/BidderInfoResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/infobidders/BidderInfoResponse.groovy
new file mode 100644
index 00000000000..1edb7bc8006
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/infobidders/BidderInfoResponse.groovy
@@ -0,0 +1,18 @@
+package org.prebid.server.functional.model.response.infobidders
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class BidderInfoResponse {
+
+ BidderStatus status
+ Boolean usesHttps
+ MaintainerInfo maintainer
+ Map capabilities
+ String aliasOf
+
+ enum BidderStatus {
+
+ ACTIVE, DISABLED
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/infobidders/GdprInfo.groovy b/src/test/groovy/org/prebid/server/functional/model/response/infobidders/GdprInfo.groovy
new file mode 100644
index 00000000000..eaf76954026
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/infobidders/GdprInfo.groovy
@@ -0,0 +1,10 @@
+package org.prebid.server.functional.model.response.infobidders
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class GdprInfo {
+
+ Integer vendorId
+ Boolean enforced
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/infobidders/MaintainerInfo.groovy b/src/test/groovy/org/prebid/server/functional/model/response/infobidders/MaintainerInfo.groovy
new file mode 100644
index 00000000000..728558fc0e9
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/infobidders/MaintainerInfo.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.response.infobidders
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class MaintainerInfo {
+
+ String email
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/infobidders/PlatformInfo.groovy b/src/test/groovy/org/prebid/server/functional/model/response/infobidders/PlatformInfo.groovy
new file mode 100644
index 00000000000..604c966f1eb
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/infobidders/PlatformInfo.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.response.infobidders
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class PlatformInfo {
+
+ List mediaTypes
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy
new file mode 100644
index 00000000000..3bef4783280
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.model.response.setuid
+
+import groovy.transform.ToString
+import io.restassured.http.Cookie
+
+@ToString(includeNames = true, ignoreNulls = true)
+class SetuidResponse {
+
+ Cookie uidsCookie
+ String responseBody
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/status/ApplicationStatus.groovy b/src/test/groovy/org/prebid/server/functional/model/response/status/ApplicationStatus.groovy
new file mode 100644
index 00000000000..592a819e5cd
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/status/ApplicationStatus.groovy
@@ -0,0 +1,9 @@
+package org.prebid.server.functional.model.response.status
+
+import groovy.transform.ToString
+
+@ToString(includeNames = true, ignoreNulls = true)
+class ApplicationStatus {
+
+ Status status
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/status/Status.groovy b/src/test/groovy/org/prebid/server/functional/model/response/status/Status.groovy
new file mode 100644
index 00000000000..52ac841f6fe
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/status/Status.groovy
@@ -0,0 +1,13 @@
+package org.prebid.server.functional.model.response.status
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum Status {
+
+ OK
+
+ @JsonValue
+ String getValue() {
+ name().toLowerCase()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/response/status/StatusResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/status/StatusResponse.groovy
new file mode 100644
index 00000000000..177c80375b7
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/model/response/status/StatusResponse.groovy
@@ -0,0 +1,6 @@
+package org.prebid.server.functional.model.response.status
+
+class StatusResponse {
+
+ ApplicationStatus application
+}
diff --git a/src/test/groovy/org/prebid/server/functional/repository/EntityManagerUtil.groovy b/src/test/groovy/org/prebid/server/functional/repository/EntityManagerUtil.groovy
new file mode 100644
index 00000000000..a7712d6430f
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/repository/EntityManagerUtil.groovy
@@ -0,0 +1,52 @@
+package org.prebid.server.functional.repository
+
+import javax.persistence.EntityManager
+import org.hibernate.SessionFactory
+
+import java.util.function.Consumer
+import java.util.function.Function
+
+class EntityManagerUtil {
+
+ private SessionFactory sessionFactory
+
+ EntityManagerUtil(SessionFactory sessionFactory) {
+ this.sessionFactory = sessionFactory
+ }
+
+ void performWithinTx(Consumer entityManagerConsumer) {
+ EntityManager entityManager = sessionFactory.createEntityManager()
+ entityManager.transaction.begin()
+ try {
+ entityManagerConsumer.accept(entityManager)
+ entityManager.transaction.commit()
+ } catch (all) {
+ entityManager.transaction.rollback()
+ throw new DaoException("Error performing dao operation. Transaction is rolled back!", all)
+ } finally {
+ entityManager.close()
+ }
+ }
+
+ def T getResultWithinTx(Function entityManagerFunction) {
+ EntityManager entityManager = sessionFactory.createEntityManager()
+ entityManager.transaction.begin()
+ try {
+ T result = entityManagerFunction.apply(entityManager)
+ entityManager.transaction.commit()
+ return result
+ } catch (all) {
+ entityManager.transaction.rollback()
+ throw new DaoException("Error performing dao operation. Transaction is rolled back!", all)
+ } finally {
+ entityManager.close()
+ }
+ }
+
+ class DaoException extends RuntimeException {
+
+ DaoException(String message, Throwable cause) {
+ super(message, cause)
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/repository/HibernateRepositoryService.groovy b/src/test/groovy/org/prebid/server/functional/repository/HibernateRepositoryService.groovy
new file mode 100644
index 00000000000..298f3c48773
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/repository/HibernateRepositoryService.groovy
@@ -0,0 +1,69 @@
+package org.prebid.server.functional.repository
+
+import org.hibernate.SessionFactory
+import org.hibernate.cfg.Configuration
+import org.prebid.server.functional.model.db.Account
+import org.prebid.server.functional.model.db.S2sConfig
+import org.prebid.server.functional.model.db.StoredImp
+import org.prebid.server.functional.model.db.StoredRequest
+import org.prebid.server.functional.model.db.StoredResponse
+import org.prebid.server.functional.repository.dao.AccountDao
+import org.prebid.server.functional.repository.dao.ConfigDao
+import org.prebid.server.functional.repository.dao.StoredImpDao
+import org.prebid.server.functional.repository.dao.StoredRequestDao
+import org.prebid.server.functional.repository.dao.StoredResponseDao
+import org.testcontainers.containers.MySQLContainer
+
+class HibernateRepositoryService {
+
+ EntityManagerUtil entityManagerUtil
+ AccountDao accountDao
+ ConfigDao configDao
+ StoredImpDao storedImpDao
+ StoredRequestDao storedRequestDao
+ StoredResponseDao storedResponseDao
+
+ HibernateRepositoryService(MySQLContainer container) {
+ def jdbcUrl = container.jdbcUrl
+ def user = container.username
+ def pass = container.password
+ def driver = container.driverClassName
+ SessionFactory sessionFactory = configureHibernate(jdbcUrl, user, pass, driver)
+ entityManagerUtil = new EntityManagerUtil(sessionFactory)
+
+ accountDao = new AccountDao(entityManagerUtil)
+ configDao = new ConfigDao(entityManagerUtil)
+ storedImpDao = new StoredImpDao(entityManagerUtil)
+ storedRequestDao = new StoredRequestDao(entityManagerUtil)
+ storedResponseDao = new StoredResponseDao(entityManagerUtil)
+ }
+
+ private static SessionFactory configureHibernate(String jdbcUrl, String user, String pass, String driver) {
+ def properties = new Properties()
+ properties.setProperty("hibernate.connection.url", jdbcUrl)
+ properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect")
+ properties.setProperty("hibernate.connection.username", user)
+ properties.setProperty("hibernate.connection.password", pass)
+ properties.setProperty("hibernate.connection.driver_class", driver)
+ properties.setProperty("hibernate.show_sql", "false")
+ properties.setProperty("hibernate.format_sql", "false")
+
+ def configuration = new Configuration()
+ configuration.addAnnotatedClass(Account)
+ configuration.addAnnotatedClass(S2sConfig)
+ configuration.addAnnotatedClass(StoredImp)
+ configuration.addAnnotatedClass(StoredRequest)
+ configuration.addAnnotatedClass(StoredResponse)
+
+ SessionFactory sessionFactory = configuration.addProperties(properties).buildSessionFactory()
+ sessionFactory
+ }
+
+ void removeAllDatabaseData() {
+ accountDao.removeAll()
+ configDao.removeAll()
+ storedImpDao.removeAll()
+ storedRequestDao.removeAll()
+ storedResponseDao.removeAll()
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/repository/dao/AccountDao.groovy b/src/test/groovy/org/prebid/server/functional/repository/dao/AccountDao.groovy
new file mode 100644
index 00000000000..7a7bc2650a0
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/repository/dao/AccountDao.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.repository.dao
+
+import org.prebid.server.functional.model.db.Account
+import org.prebid.server.functional.repository.EntityManagerUtil
+
+class AccountDao extends EntityDao {
+
+ AccountDao(EntityManagerUtil entityManagerUtil) {
+ super(entityManagerUtil, Account)
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/repository/dao/ConfigDao.groovy b/src/test/groovy/org/prebid/server/functional/repository/dao/ConfigDao.groovy
new file mode 100644
index 00000000000..1d1055605ab
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/repository/dao/ConfigDao.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.repository.dao
+
+import org.prebid.server.functional.model.db.S2sConfig
+import org.prebid.server.functional.repository.EntityManagerUtil
+
+class ConfigDao extends EntityDao {
+
+ ConfigDao(EntityManagerUtil entityManagerUtil) {
+ super(entityManagerUtil, S2sConfig)
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/repository/dao/EntityDao.groovy b/src/test/groovy/org/prebid/server/functional/repository/dao/EntityDao.groovy
new file mode 100644
index 00000000000..39f61daf3cb
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/repository/dao/EntityDao.groovy
@@ -0,0 +1,45 @@
+package org.prebid.server.functional.repository.dao
+
+import org.prebid.server.functional.repository.EntityManagerUtil
+
+abstract class EntityDao {
+
+ EntityManagerUtil emUtil
+
+ Class entityClass
+ String entityClassName
+
+ EntityDao(EntityManagerUtil entityManagerUtil, Class entityClass) {
+ this.emUtil = entityManagerUtil
+ this.entityClass = entityClass
+ this.entityClassName = entityClass.simpleName
+ }
+
+ ENTITY update(ENTITY entity) {
+ emUtil.getResultWithinTx { it.merge(entity) }
+ }
+
+ ENTITY save(ENTITY entity) {
+ emUtil.performWithinTx { it.persist(entity) }
+ entity
+ }
+
+ ENTITY findById(ID id) {
+ emUtil.getResultWithinTx { it.find(entityClass, id) }
+ }
+
+ List findAll() {
+ emUtil.getResultWithinTx { it.createQuery("SELECT e FROM $entityClassName e").resultList }
+ }
+
+ void remove(ENTITY entity) {
+ emUtil.performWithinTx {
+ ENTITY managedEntity = it.merge(entity)
+ it.remove(managedEntity)
+ }
+ }
+
+ void removeAll() {
+ emUtil.performWithinTx { it.createQuery("DELETE FROM $entityClassName").executeUpdate() }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/repository/dao/StoredImpDao.groovy b/src/test/groovy/org/prebid/server/functional/repository/dao/StoredImpDao.groovy
new file mode 100644
index 00000000000..a382dc6d45b
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/repository/dao/StoredImpDao.groovy
@@ -0,0 +1,12 @@
+package org.prebid.server.functional.repository.dao
+
+import org.prebid.server.functional.model.db.StoredImp
+import org.prebid.server.functional.repository.EntityManagerUtil
+
+class StoredImpDao extends EntityDao {
+
+ StoredImpDao(EntityManagerUtil entityManagerUtil) {
+ super(entityManagerUtil, StoredImp)
+ }
+
+}
diff --git a/src/test/groovy/org/prebid/server/functional/repository/dao/StoredRequestDao.groovy b/src/test/groovy/org/prebid/server/functional/repository/dao/StoredRequestDao.groovy
new file mode 100644
index 00000000000..78a5e7aa40b
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/repository/dao/StoredRequestDao.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.repository.dao
+
+import org.prebid.server.functional.model.db.StoredRequest
+import org.prebid.server.functional.repository.EntityManagerUtil
+
+class StoredRequestDao extends EntityDao {
+
+ StoredRequestDao(EntityManagerUtil entityManagerUtil) {
+ super(entityManagerUtil, StoredRequest)
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/repository/dao/StoredResponseDao.groovy b/src/test/groovy/org/prebid/server/functional/repository/dao/StoredResponseDao.groovy
new file mode 100644
index 00000000000..bd9a784f97d
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/repository/dao/StoredResponseDao.groovy
@@ -0,0 +1,11 @@
+package org.prebid.server.functional.repository.dao
+
+import org.prebid.server.functional.model.db.StoredResponse
+import org.prebid.server.functional.repository.EntityManagerUtil
+
+class StoredResponseDao extends EntityDao {
+
+ StoredResponseDao(EntityManagerUtil entityManagerUtil) {
+ super(entityManagerUtil, StoredResponse)
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/service/PrebidServerException.groovy b/src/test/groovy/org/prebid/server/functional/service/PrebidServerException.groovy
new file mode 100644
index 00000000000..1f62ccf2936
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/service/PrebidServerException.groovy
@@ -0,0 +1,12 @@
+package org.prebid.server.functional.service
+
+class PrebidServerException extends Exception {
+
+ final int statusCode
+ final String responseBody
+
+ PrebidServerException(int statusCode, String message) {
+ this.statusCode = statusCode
+ this.responseBody = message
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy
new file mode 100644
index 00000000000..3a554d710ea
--- /dev/null
+++ b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy
@@ -0,0 +1,275 @@
+package org.prebid.server.functional.service
+
+import com.fasterxml.jackson.core.type.TypeReference
+import io.qameta.allure.Step
+import io.restassured.RestAssured
+import io.restassured.authentication.BasicAuthScheme
+import io.restassured.builder.RequestSpecBuilder
+import io.restassured.response.Response
+import io.restassured.specification.RequestSpecification
+import org.prebid.server.functional.model.bidder.BidderName
+import org.prebid.server.functional.model.mock.services.prebidcache.response.PrebidCacheResponse
+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.cookiesync.CookieSyncRequest
+import org.prebid.server.functional.model.request.event.EventRequest
+import org.prebid.server.functional.model.request.logging.httpinteraction.HttpInteractionRequest
+import org.prebid.server.functional.model.request.setuid.SetuidRequest
+import org.prebid.server.functional.model.request.setuid.UidsCookie
+import org.prebid.server.functional.model.request.vtrack.VtrackRequest
+import org.prebid.server.functional.model.response.amp.AmpResponse
+import org.prebid.server.functional.model.response.auction.BidResponse
+import org.prebid.server.functional.model.response.biddersparams.BiddersParamsResponse
+import org.prebid.server.functional.model.response.cookiesync.CookieSyncResponse
+import org.prebid.server.functional.model.response.currencyrates.CurrencyRatesResponse
+import org.prebid.server.functional.model.response.getuids.GetuidResponse
+import org.prebid.server.functional.model.response.infobidders.BidderInfoResponse
+import org.prebid.server.functional.model.response.setuid.SetuidResponse
+import org.prebid.server.functional.model.response.status.StatusResponse
+import org.prebid.server.functional.testcontainers.container.PrebidServerContainer
+import org.prebid.server.functional.util.ObjectMapperWrapper
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import java.time.Duration
+import java.time.Instant
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+
+import static io.restassured.RestAssured.given
+import static io.restassured.parsing.Parser.JSON
+import static java.time.ZoneOffset.UTC
+
+class PrebidServerService {
+
+ static final String AUCTION_ENDPOINT = "/openrtb2/auction"
+ static final String AMP_ENDPOINT = "/openrtb2/amp"
+ static final String COOKIE_SYNC_ENDPOINT = "/cookie_sync"
+ static final String SET_UID_ENDPOINT = "/setuid"
+ static final String GET_UIDS_ENDPOINT = "/getuids"
+ static final String EVENT_ENDPOINT = "/event"
+ static final String VTRACK_ENDPOINT = "/vtrack"
+ static final String STATUS_ENDPOINT = "/status"
+ static final String INFO_BIDDERS_ENDPOINT = "/info/bidders"
+ static final String BIDDERS_PARAMS_ENDPOINT = "/bidders/params"
+ static final String CURRENCY_RATES_ENDPOINT = "/currency/rates"
+ static final String HTTP_INTERACTION_ENDPOINT = "/logging/httpinteraction"
+ static final String COLLECTED_METRICS_ENDPOINT = "/collected-metrics"
+
+ private final PrebidServerContainer pbsContainer
+ private final ObjectMapperWrapper mapper
+ private final RequestSpecification requestSpecification
+ private final RequestSpecification adminRequestSpecification
+
+ private final Logger log = LoggerFactory.getLogger(PrebidServerService.class)
+
+ PrebidServerService(PrebidServerContainer pbsContainer, ObjectMapperWrapper mapper) {
+ def authenticationScheme = new BasicAuthScheme()
+ authenticationScheme.userName = pbsContainer.ADMIN_ENDPOINT_USERNAME
+ authenticationScheme.password = pbsContainer.ADMIN_ENDPOINT_PASSWORD
+ this.pbsContainer = pbsContainer
+ this.mapper = mapper
+ requestSpecification = new RequestSpecBuilder().setBaseUri(pbsContainer.rootUri)
+ .build()
+ adminRequestSpecification = new RequestSpecBuilder().setBaseUri(pbsContainer.adminRootUri)
+ .setAuth(authenticationScheme)
+ .build()
+ }
+
+ @Step("[POST] /openrtb2/auction")
+ BidResponse sendAuctionRequest(BidRequest bidRequest) {
+ def payload = mapper.encode(bidRequest)
+
+ def response = given(requestSpecification).body(payload)
+ .post(AUCTION_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.as(BidResponse)
+ }
+
+ @Step("[POST] /openrtb2/auction")
+ BidResponse sendAuctionRequest(BidRequest bidRequest, Map headers) {
+ def payload = mapper.encode(bidRequest)
+
+ def response = given(requestSpecification).headers(headers)
+ .body(payload)
+ .post(AUCTION_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.as(BidResponse)
+ }
+
+ @Step("[GET] /openrtb2/amp")
+ AmpResponse sendAmpRequest(AmpRequest ampRequest) {
+ def response = given(requestSpecification).queryParams(mapper.toMap(ampRequest))
+ .get(AMP_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.as(AmpResponse)
+ }
+
+ @Step("[POST] /cookie_sync without cookie")
+ CookieSyncResponse sendCookieSyncRequest(CookieSyncRequest request) {
+ def payload = mapper.encode(request)
+ def response = given(requestSpecification).body(payload)
+ .post(COOKIE_SYNC_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.as(CookieSyncResponse)
+ }
+
+ @Step("[POST] /cookie_sync with cookie")
+ CookieSyncResponse sendCookieSyncRequest(CookieSyncRequest request, UidsCookie uidsCookie) {
+ def uidsCookieAsJson = mapper.encode(uidsCookie)
+ def uidsCookieAsEncodedJson = Base64.urlEncoder.encodeToString(uidsCookieAsJson.bytes)
+
+ def payload = mapper.encode(request)
+ def response = given(requestSpecification).cookie("uids", uidsCookieAsEncodedJson)
+ .body(payload)
+ .post(COOKIE_SYNC_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.as(CookieSyncResponse)
+ }
+
+ @Step("[GET] /setuid")
+ SetuidResponse sendSetUidRequest(SetuidRequest request, UidsCookie uidsCookie) {
+ def uidsCookieAsJson = mapper.encode(uidsCookie)
+ def uidsCookieAsEncodedJson = Base64.urlEncoder.encodeToString(uidsCookieAsJson.bytes)
+ def response = given(requestSpecification).cookie("uids", uidsCookieAsEncodedJson)
+ .queryParams(mapper.toMap(request))
+ .get(SET_UID_ENDPOINT)
+
+ checkResponseStatusCode(response)
+
+ def setuidResponse = new SetuidResponse()
+ setuidResponse.uidsCookie = response.detailedCookie("uids")
+ setuidResponse.responseBody = response.asString()
+ setuidResponse
+ }
+
+ @Step("[GET] /getuids")
+ GetuidResponse sendGetUidRequest(UidsCookie uidsCookie) {
+ def uidsCookieAsJson = mapper.encode(uidsCookie)
+ def uidsCookieAsEncodedJson = Base64.urlEncoder.encodeToString(uidsCookieAsJson.bytes)
+
+ def response = given(requestSpecification).cookie("uids", uidsCookieAsEncodedJson)
+ .get(GET_UIDS_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.as(GetuidResponse)
+ }
+
+ @Step("[GET] /event")
+ byte[] sendEventRequest(EventRequest eventRequest) {
+ def response = given(requestSpecification).queryParams(mapper.toMap(eventRequest))
+ .get(EVENT_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.body.asByteArray()
+ }
+
+ @Step("[POST] /vtrack")
+ PrebidCacheResponse sendVtrackRequest(VtrackRequest request, String account) {
+ def response = given(requestSpecification).queryParam("a", account)
+ .body(request)
+ .post(VTRACK_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.as(PrebidCacheResponse)
+ }
+
+ @Step("[GET] /status")
+ StatusResponse sendStatusRequest() {
+ def response = given(requestSpecification).get(STATUS_ENDPOINT)
+
+ checkResponseStatusCode(response)
+
+ response.as(StatusResponse)
+ }
+
+ @Step("[GET] /info/bidders")
+ String sendInfoBiddersRequest(boolean enabledOnly = true) {
+ def response = given(requestSpecification).queryParam("enabledonly", enabledOnly)
+ .get(INFO_BIDDERS_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.body().asString()
+ }
+
+ @Step("[GET] /info/bidders/{bidderName}")
+ BidderInfoResponse sendBidderInfoRequest(BidderName bidderName) {
+
+ def response = given(requestSpecification).get("$INFO_BIDDERS_ENDPOINT/$bidderName.value")
+
+ checkResponseStatusCode(response)
+ response.as(BidderInfoResponse)
+ }
+
+ @Step("[GET] /bidders/params")
+ BiddersParamsResponse sendBiddersParamsRequest() {
+ RestAssured.defaultParser = JSON
+ def response = given(requestSpecification).get(BIDDERS_PARAMS_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.as(BiddersParamsResponse)
+ }
+
+ @Step("[GET] /currency/rates")
+ CurrencyRatesResponse sendCurrencyRatesRequest() {
+ def response = given(adminRequestSpecification).get(CURRENCY_RATES_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.as(CurrencyRatesResponse)
+ }
+
+ @Step("[GET] /logging/httpinteraction")
+ String sendLoggingHttpInteractionRequest(HttpInteractionRequest httpInteractionRequest) {
+ def response = given(adminRequestSpecification).queryParams(mapper.toMap(httpInteractionRequest))
+ .get(HTTP_INTERACTION_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ response.body().asString()
+ }
+
+ @Step("[GET] /collected-metrics")
+ Map sendCollectedMetricsRequest() {
+ def response = given(adminRequestSpecification).get(COLLECTED_METRICS_ENDPOINT)
+
+ checkResponseStatusCode(response)
+ mapper.decode(response.asString(), new TypeReference