diff --git a/.github/workflows/cross-repo-issue.yml b/.github/workflows/cross-repo-issue.yml index c2288da271a..a2aa9471ecf 100644 --- a/.github/workflows/cross-repo-issue.yml +++ b/.github/workflows/cross-repo-issue.yml @@ -23,9 +23,10 @@ jobs: github.event.pull_request.merged env: GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + PR_TITLE: ${{ github.event.pull_request.title }} run: | echo -e "A PR was merged over on PBS-Java\n\n- [https://github.com/prebid/prebid-server-java/pull/${{github.event.number}}](https://github.com/prebid/prebid-server-java/pull/${{github.event.number}})\n- timestamp: ${{ github.event.pull_request.merged_at}}" > msg export msg=$(cat msg) - gh issue create --repo prebid/prebid-server --title "Port PR from PBS-Java: ${{ github.event.pull_request.title }}" \ + gh issue create --repo prebid/prebid-server --title "Port PR from PBS-Java: $PR_TITLE" \ --body "$msg" \ --label auto diff --git a/.github/workflows/pr-java-ci.yml b/.github/workflows/pr-java-ci.yml index 3f68172bf32..1a198dac001 100644 --- a/.github/workflows/pr-java-ci.yml +++ b/.github/workflows/pr-java-ci.yml @@ -35,11 +35,9 @@ jobs: - name: Publish JUnit Report uses: mikepenz/action-junit-report@v5 + if: always() with: - report_paths: '**/target/surefire-reports/TEST-*.xml' check_name: 'JUnit Test Report' - include_passed: true - comment: true + report_paths: '**/target/surefire-reports/TEST-*.xml' fail_on_failure: true - updateComment: true - token: ${{ secrets.GITHUB_TOKEN }} + annotate_only: true diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index e5ecd041937..ae599096627 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT ../../extra/pom.xml diff --git a/extra/modules/confiant-ad-quality/pom.xml b/extra/modules/confiant-ad-quality/pom.xml index 62be75c4bd3..4059db432d5 100644 --- a/extra/modules/confiant-ad-quality/pom.xml +++ b/extra/modules/confiant-ad-quality/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT confiant-ad-quality diff --git a/extra/modules/fiftyone-devicedetection/pom.xml b/extra/modules/fiftyone-devicedetection/pom.xml index 04447c1e2e8..544033cca2b 100644 --- a/extra/modules/fiftyone-devicedetection/pom.xml +++ b/extra/modules/fiftyone-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT fiftyone-devicedetection diff --git a/extra/modules/greenbids-real-time-data/pom.xml b/extra/modules/greenbids-real-time-data/pom.xml index bcacf785cc8..61da977278d 100644 --- a/extra/modules/greenbids-real-time-data/pom.xml +++ b/extra/modules/greenbids-real-time-data/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT greenbids-real-time-data diff --git a/extra/modules/live-intent-omni-channel-identity/pom.xml b/extra/modules/live-intent-omni-channel-identity/pom.xml index 1e16b624733..f6782ff201d 100644 --- a/extra/modules/live-intent-omni-channel-identity/pom.xml +++ b/extra/modules/live-intent-omni-channel-identity/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT live-intent-omni-channel-identity diff --git a/extra/modules/optable-targeting/pom.xml b/extra/modules/optable-targeting/pom.xml index 0e6b0622299..03383e72ffe 100644 --- a/extra/modules/optable-targeting/pom.xml +++ b/extra/modules/optable-targeting/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT optable-targeting diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml index 32c26829d83..d904007722b 100644 --- a/extra/modules/ortb2-blocking/pom.xml +++ b/extra/modules/ortb2-blocking/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT ortb2-blocking diff --git a/extra/modules/pb-request-correction/pom.xml b/extra/modules/pb-request-correction/pom.xml index 0e6297e934b..b4dda3e3c35 100644 --- a/extra/modules/pb-request-correction/pom.xml +++ b/extra/modules/pb-request-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT pb-request-correction diff --git a/extra/modules/pb-response-correction/pom.xml b/extra/modules/pb-response-correction/pom.xml index 6fda39fe4ec..0770b766e5b 100644 --- a/extra/modules/pb-response-correction/pom.xml +++ b/extra/modules/pb-response-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT pb-response-correction diff --git a/extra/modules/pb-richmedia-filter/pom.xml b/extra/modules/pb-richmedia-filter/pom.xml index 27a17ea0009..6ddd11efcf7 100644 --- a/extra/modules/pb-richmedia-filter/pom.xml +++ b/extra/modules/pb-richmedia-filter/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT pb-richmedia-filter diff --git a/extra/modules/pb-rule-engine/pom.xml b/extra/modules/pb-rule-engine/pom.xml index a8b4caa752f..e23c6767817 100644 --- a/extra/modules/pb-rule-engine/pom.xml +++ b/extra/modules/pb-rule-engine/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT pb-rule-engine diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index 50eb19cdee5..6c4e70d62ab 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT ../../extra/pom.xml diff --git a/extra/modules/wurfl-devicedetection/pom.xml b/extra/modules/wurfl-devicedetection/pom.xml index 17a99a407cf..555ea37fdce 100644 --- a/extra/modules/wurfl-devicedetection/pom.xml +++ b/extra/modules/wurfl-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT wurfl-devicedetection diff --git a/extra/pom.xml b/extra/pom.xml index cbf3012ff8d..c41bd328043 100644 --- a/extra/pom.xml +++ b/extra/pom.xml @@ -4,7 +4,7 @@ org.prebid prebid-server-aggregator - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT pom @@ -47,7 +47,8 @@ 1.5.6 1.13 2.2.0 - 1.3.4 + + 1.2.3 0.16.0 2.0.10 3.2.4 diff --git a/pom.xml b/pom.xml index 1c198a3df57..0c79bae025d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.37.0-SNAPSHOT + 3.38.0-SNAPSHOT extra/pom.xml @@ -345,11 +345,22 @@ postgresql test + + org.testcontainers + influxdb + test + org.mock-server mockserver-client-java test + + org.influxdb + influxdb-java + 2.23 + test + 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 index 2dc5ff7c77b..b0a37977b4a 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy @@ -37,6 +37,8 @@ class AccountAuctionConfig { BidAdjustment bidAdjustments BidRounding bidRounding Integer impressionLimit + @JsonProperty("secondarybidders") + List secondaryBidders @JsonProperty("price_granularity") PriceGranularityType priceGranularitySnakeCase 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 index 80f504a05ec..04d6535f8ad 100644 --- 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 @@ -17,6 +17,7 @@ enum ErrorType { OPENX("openx"), AMX("amx"), AMX_UPPER_CASE("AMX"), + OPENX_ALIAS("openxalias"), @JsonValue final String value diff --git a/src/test/groovy/org/prebid/server/functional/model/response/influx/InfluxResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/influx/InfluxResponse.groovy new file mode 100644 index 00000000000..c2fc5b0c6c2 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/influx/InfluxResponse.groovy @@ -0,0 +1,6 @@ +package org.prebid.server.functional.model.response.influx + +class InfluxResponse { + + List results +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/influx/InfluxResult.groovy b/src/test/groovy/org/prebid/server/functional/model/response/influx/InfluxResult.groovy new file mode 100644 index 00000000000..ae6f6864334 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/influx/InfluxResult.groovy @@ -0,0 +1,10 @@ +package org.prebid.server.functional.model.response.influx + +import com.fasterxml.jackson.annotation.JsonProperty + +class InfluxResult { + + @JsonProperty("statement_id") + Integer statementId + List series +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/influx/Series.groovy b/src/test/groovy/org/prebid/server/functional/model/response/influx/Series.groovy new file mode 100644 index 00000000000..30b4cf86e61 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/influx/Series.groovy @@ -0,0 +1,9 @@ +package org.prebid.server.functional.model.response.influx + +class Series { + + String name + Tags tags + List columns + List> values +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/influx/Tags.groovy b/src/test/groovy/org/prebid/server/functional/model/response/influx/Tags.groovy new file mode 100644 index 00000000000..a88807ad8b4 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/influx/Tags.groovy @@ -0,0 +1,6 @@ +package org.prebid.server.functional.model.response.influx + +class Tags { + + String measurement +} diff --git a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy index 9e760b7173e..249d6fa3f13 100644 --- a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy +++ b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy @@ -25,6 +25,7 @@ import org.prebid.server.functional.model.response.cookiesync.CookieSyncResponse import org.prebid.server.functional.model.response.cookiesync.RawCookieSyncResponse 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.influx.InfluxResponse 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 @@ -42,6 +43,8 @@ import java.time.format.DateTimeFormatter import static io.restassured.RestAssured.given import static java.time.ZoneOffset.UTC +import static org.prebid.server.functional.testcontainers.Dependencies.influxdbContainer + class PrebidServerService implements ObjectMapperWrapper { @@ -59,10 +62,12 @@ class PrebidServerService implements ObjectMapperWrapper { static final String HTTP_INTERACTION_ENDPOINT = "/logging/httpinteraction" static final String COLLECTED_METRICS_ENDPOINT = "/collected-metrics" static final String PROMETHEUS_METRICS_ENDPOINT = "/metrics" + static final String INFLUX_DB_ENDPOINT = "/query" static final String UIDS_COOKIE_NAME = "uids" private final PrebidServerContainer pbsContainer private final RequestSpecification requestSpecification + private final RequestSpecification influxRequestSpecification private final RequestSpecification adminRequestSpecification private final RequestSpecification prometheusRequestSpecification @@ -75,6 +80,8 @@ class PrebidServerService implements ObjectMapperWrapper { this.pbsContainer = pbsContainer requestSpecification = new RequestSpecBuilder().setBaseUri(pbsContainer.rootUri) .build() + influxRequestSpecification = new RequestSpecBuilder().setBaseUri(pbsContainer.influxUri) + .build() adminRequestSpecification = buildAndGetRequestSpecification(pbsContainer.adminRootUri, authenticationScheme) prometheusRequestSpecification = buildAndGetRequestSpecification(pbsContainer.prometheusRootUri, authenticationScheme) } @@ -290,6 +297,16 @@ class PrebidServerService implements ObjectMapperWrapper { decode(response.asString(), new TypeReference>() {}) } + Map sendInfluxMetricsRequest() { + def response = given(influxRequestSpecification) + .queryParams(["db": influxdbContainer.getDatabase(), + "q" : "SELECT COUNT(count) FROM /.*/ WHERE count >= 1 GROUP BY \"measurement\""]) + .get(INFLUX_DB_ENDPOINT) + + checkResponseStatusCode(response) + collectInToMap(decode(response.getBody().asString(), InfluxResponse)) + } + String sendPrometheusMetricsRequest() { def response = given(prometheusRequestSpecification).get(PROMETHEUS_METRICS_ENDPOINT) @@ -431,6 +448,15 @@ class PrebidServerService implements ObjectMapperWrapper { } } + Boolean isContainMetricByValue(String value) { + try { + PBSUtils.waitUntil({ sendInfluxMetricsRequest()[value] != null }) + true + } catch (IllegalStateException ignored) { + false + } + } + private String getPbsLogsByValue(String value) { pbsContainer.logs.split("\n").find { it.contains(value) } } @@ -454,4 +480,10 @@ class PrebidServerService implements ObjectMapperWrapper { .setAuth(authScheme) .build() } + + private static Map collectInToMap(InfluxResponse responseBody) { + responseBody?.results?.first()?.series?.collectEntries { + [(it.name): it.values?.first()?.getAt(1) as Integer] + } ?: [:] + } } diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/Dependencies.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/Dependencies.groovy index 70c99a2a833..ab614e0ca5f 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/Dependencies.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/Dependencies.groovy @@ -2,6 +2,7 @@ package org.prebid.server.functional.testcontainers import org.prebid.server.functional.testcontainers.container.NetworkServiceContainer import org.prebid.server.functional.util.SystemProperties +import org.testcontainers.containers.InfluxDBContainer import org.testcontainers.containers.MySQLContainer import org.testcontainers.containers.Network import org.testcontainers.containers.localstack.LocalStackContainer @@ -34,6 +35,13 @@ class Dependencies { .withInitScript("org/prebid/server/functional/db_psql_schema.sql") .withNetwork(network) + static final InfluxDBContainer influxdbContainer = new InfluxDBContainer<>(DockerImageName.parse("influxdb:1.8.10")) + .withUsername("prebid") + .withPassword("prebid") + .withAuthEnabled(false) + .withDatabase("prebid") + .withNetwork(network) + static final NetworkServiceContainer networkServiceContainer = new NetworkServiceContainer(MOCKSERVER_VERSION) .withNetwork(network) @@ -44,13 +52,13 @@ class Dependencies { localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack:s3-latest")) .withNetwork(network) .withServices(S3) - Startables.deepStart([networkServiceContainer, mysqlContainer, localStackContainer]).join() + Startables.deepStart([networkServiceContainer, mysqlContainer, localStackContainer, influxdbContainer]).join() } } static void stop() { if (IS_LAUNCH_CONTAINERS) { - [networkServiceContainer, mysqlContainer, localStackContainer].parallelStream() + [networkServiceContainer, mysqlContainer, localStackContainer, influxdbContainer].parallelStream() .forEach({ it.stop() }) } } diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy index a63039f3416..02d627f141a 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.testcontainers +import org.testcontainers.containers.InfluxDBContainer import org.testcontainers.containers.MySQLContainer import org.testcontainers.containers.PostgreSQLContainer @@ -100,7 +101,6 @@ LIMIT 1 "settings.database.idle-connection-timeout": "300" ].asImmutable() } - static Map getPostgreSqlConfig(PostgreSQLContainer postgres = Dependencies.postgresqlContainer) { ["settings.database.type" : "postgres", "settings.database.host" : postgres.getNetworkAliases().get(0), @@ -145,7 +145,7 @@ LIMIT 1 "currency-converter.external-rates.refresh-period-ms" : "900000"] } - static Map getTargetingConfig() { + static Map getTargetingConfig() { ["settings.targeting.truncate-attr-chars": '255'] } diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/PbsServiceFactory.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/PbsServiceFactory.groovy index e0911a2b1ca..2e499815e39 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/PbsServiceFactory.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/PbsServiceFactory.groovy @@ -50,6 +50,9 @@ class PbsServiceFactory { static void removeContainer(Map config) { def container = containers.get(config) + if (container == null) { + throw new IllegalArgumentException("Unknown or invalid container config: " + config) + } container.stop() containers.remove(config) } diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy index 0daa6883acf..70509252af6 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy @@ -75,6 +75,10 @@ class PrebidServerContainer extends GenericContainer { getMappedPort(PROMETHEUS_PORT) } + String getInfluxUri() { + return "http://$host:$Dependencies.influxdbContainer.firstMappedPort" + } + String getRootUri() { return "http://$host:$port" } diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/Bidder.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/Bidder.groovy index 05d6fcfa3d7..99025431a76 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/Bidder.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/Bidder.groovy @@ -12,6 +12,7 @@ import org.prebid.server.functional.model.request.auction.Imp import org.prebid.server.functional.model.response.auction.BidResponse import org.testcontainers.containers.MockServerContainer +import static java.util.concurrent.TimeUnit.SECONDS import static org.mockserver.model.HttpRequest.request import static org.mockserver.model.HttpResponse.response import static org.mockserver.model.HttpStatusCode.OK_200 @@ -47,6 +48,13 @@ class Bidder extends NetworkScaffolding { : HttpResponse.notFoundResponse()} } + void setResponseWithDilay(Integer dilayTimeout = 5) { + mockServerClient.when(request().withPath(endpoint), Times.unlimited(), TimeToLive.unlimited(), -10) + .respond {request -> request.withPath(endpoint) + ? response().withDelay(SECONDS, dilayTimeout).withStatusCode(OK_200.code()).withBody(getBodyByRequest(request)) + : HttpResponse.notFoundResponse()} + } + List getBidderRequests(String bidRequestId) { getRecordedRequestsBody(bidRequestId).collect { decode(it, BidderRequest) } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/InfluxDBSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/InfluxDBSpec.groovy new file mode 100644 index 00000000000..4e0253c1f95 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/InfluxDBSpec.groovy @@ -0,0 +1,78 @@ +package org.prebid.server.functional.tests + +import org.prebid.server.functional.model.AccountStatus +import org.prebid.server.functional.model.config.AccountConfig +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.service.PrebidServerException +import org.prebid.server.functional.service.PrebidServerService + +import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED +import static org.prebid.server.functional.testcontainers.Dependencies.influxdbContainer + +class InfluxDBSpec extends BaseSpec { + + private static final String ACCOUNT_REJECTED_METRIC = "influx.metric.account.%s.requests.rejected.invalid-account" + + private static final Map PBS_CONFIG_WITH_INFLUX_AND_ENFORCE_VALIDATION_ACCOUNTANT = [ + "metrics.influxdb.enabled" : "true", + "metrics.influxdb.prefix" : "influx.metric.", + "metrics.influxdb.host" : influxdbContainer.getNetworkAliases().get(0), + "metrics.influxdb.port" : influxdbContainer.getExposedPorts().get(0) as String, + "metrics.influxdb.protocol" : "http", + "metrics.influxdb.database" : influxdbContainer.database as String, + "metrics.influxdb.auth" : "${influxdbContainer.username}:${influxdbContainer.password}" as String, + "metrics.influxdb.interval" : "1", + "metrics.influxdb.connectTimeout": "5000", + "metrics.influxdb.readTimeout" : "100", + "settings.enforce-valid-account": true as String] + + private static final PrebidServerService pbsServiceWithEnforceValidAccount + = pbsServiceFactory.getService(PBS_CONFIG_WITH_INFLUX_AND_ENFORCE_VALIDATION_ACCOUNTANT) + + def cleanupSpec() { + pbsServiceFactory.removeContainer(PBS_CONFIG_WITH_INFLUX_AND_ENFORCE_VALIDATION_ACCOUNTANT) + } + + def "PBS should reject request with error and metrics when inactive account"() { + given: "Default basic BidRequest with inactive account id" + def bidRequest = BidRequest.defaultBidRequest + + and: "Inactive account id" + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(status: AccountStatus.INACTIVE)) + accountDao.save(account) + + when: "PBS processes auction request" + pbsServiceWithEnforceValidAccount.sendAuctionRequest(bidRequest) + + then: "PBS should reject the entire auction" + def exception = thrown(PrebidServerException) + assert exception.statusCode == UNAUTHORIZED.code() + assert exception.responseBody == "Account $bidRequest.accountId is inactive" + + and: "PBS wait until get metric" + assert pbsServiceWithEnforceValidAccount.isContainMetricByValue(ACCOUNT_REJECTED_METRIC.formatted(bidRequest.accountId)) + + and: "PBS metrics populated correctly" + def influxMetricsRequest = pbsServiceWithEnforceValidAccount.sendInfluxMetricsRequest() + assert influxMetricsRequest[ACCOUNT_REJECTED_METRIC.formatted(bidRequest.accountId)] == 1 + } + + def "PBS shouldn't reject request with error and metrics when active account"() { + given: "Default basic BidRequest with inactive account id" + def bidRequest = BidRequest.defaultBidRequest + + and: "Inactive account id" + def account = new Account(uuid: bidRequest.accountId, config: new AccountConfig(status: AccountStatus.ACTIVE)) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsServiceWithEnforceValidAccount.sendAuctionRequest(bidRequest) + + then: "Bid response should contain seatBid" + assert response.seatbid.size() == 1 + + and: "PBs shouldn't emit metric" + assert !pbsServiceWithEnforceValidAccount.isContainMetricByValue(ACCOUNT_REJECTED_METRIC.formatted(bidRequest.accountId)) + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy new file mode 100644 index 00000000000..e50fa4d774a --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy @@ -0,0 +1,343 @@ +package org.prebid.server.functional.tests + +import org.prebid.server.functional.model.bidder.Openx +import org.prebid.server.functional.model.config.AccountAuctionConfig +import org.prebid.server.functional.model.config.AccountConfig +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.response.auction.ErrorType +import org.prebid.server.functional.service.PrebidServerService +import org.prebid.server.functional.testcontainers.scaffolding.Bidder +import spock.lang.Shared + +import static org.prebid.server.functional.model.bidder.BidderName.OPENX +import static org.prebid.server.functional.model.bidder.BidderName.GENERIC +import static org.prebid.server.functional.model.bidder.BidderName.OPENX_ALIAS +import static org.prebid.server.functional.model.bidder.BidderName.UNKNOWN +import static org.prebid.server.functional.model.response.auction.BidRejectionReason.ERROR_TIMED_OUT +import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer + +class SecondaryBidderSpec extends BaseSpec { + + private static final Map OPENX_CONFIG = [ + "adapters.openx.enabled" : "true", + "adapters.openx.endpoint": "$networkServiceContainer.rootUri/openx-auction".toString()] + + private static final Map OPENX_ALIAS_CONFIG = [ + "adapters.${OPENX.value}.aliases.${OPENX_ALIAS}.enabled" : "true", + "adapters.${OPENX.value}.aliases.${OPENX_ALIAS}.endpoint": "$networkServiceContainer.rootUri/openx-alias-auction".toString()] + + protected static final Bidder openXBidder = new Bidder(networkServiceContainer, "/openx-auction") + protected static final Bidder openXAliasBidder = new Bidder(networkServiceContainer, "/openx-alias-auction") + + @Shared + PrebidServerService pbsServiceWithOpenXAndIXBidder = pbsServiceFactory.getService(OPENX_CONFIG + OPENX_ALIAS_CONFIG) + + @Override + def cleanupSpec() { + openXBidder.setResponse() + openXAliasBidder.setResponse() + pbsServiceFactory.removeContainer(OPENX_CONFIG + OPENX_ALIAS_CONFIG) + } + + def "PBS should proceed as default when secondaryBidders not define in config"() { + given: "Default basic BidRequest with generic bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.ext.prebid.returnAllBidStatus = true + } + + and: "Account in the DB" + def accountConfig = AccountConfig.defaultAccountConfig.tap { + it.auction = new AccountAuctionConfig(secondaryBidders: null) + } + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + + then: "PBs should processed bidder request" + assert bidder.getBidderRequest(bidRequest.id) + + and: "PBS shouldn't contain errors, warnings and seat non bit" + assert !bidResponse.ext?.warnings + assert !bidResponse.ext?.errors + assert !bidResponse.ext.seatnonbid + } + + def "PBS should emit a warning when null in secondary bidders config"() { + given: "Default basic BidRequest with generic bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.ext.prebid.returnAllBidStatus = true + } + + and: "Account in the DB" + def accountConfig = AccountConfig.defaultAccountConfig.tap { + it.auction = new AccountAuctionConfig(secondaryBidders: [null]) + } + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + + then: "PBs should processed bidder request" + assert bidder.getBidderRequest(bidRequest.id) + + and: "PBS shouldn't contain errors, warnings and seat non bid" + assert !bidResponse.ext?.warnings + assert !bidResponse.ext?.errors + assert !bidResponse.ext.seatnonbid + } + + def "PBS shouldn't emit a warning when invalid bidder in secondary bidders config"() { + given: "Default basic BidRequest with generic bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.ext.prebid.returnAllBidStatus = true + } + + and: "Account in the DB" + def accountConfig = AccountConfig.defaultAccountConfig.tap { + it.auction = new AccountAuctionConfig(secondaryBidders: [UNKNOWN]) + } + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + + then: "PBs should processed bidder request" + def bidderRequests = bidder.getBidderRequests(bidRequest.id) + assert bidderRequests.size() == 1 + + and: "PBS shouldn't contain errors, warnings and seat non bid" + assert !bidResponse.ext?.warnings + assert !bidResponse.ext?.errors + assert !bidResponse.ext.seatnonbid + } + + def "PBS should thread all bidders as primary when all requested bidders in secondary bidders config"() { + given: "Default basic BidRequest with generic and openx bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.imp[0].ext.prebid.bidder.openx = Openx.defaultOpenx + it.ext.prebid.returnAllBidStatus = true + } + + and: "Account in the DB" + def accountConfig = AccountConfig.defaultAccountConfig.tap { + it.auction = new AccountAuctionConfig(secondaryBidders: [GENERIC, OPENX]) + } + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Set up openx response" + openXBidder.setResponse() + + when: "PBS processes auction request" + def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + + then: "PBs should processed generic request" + def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) + assert genericBidderRequests.size() == 1 + + and: "PBs should processed openx request" + def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id) + assert openXBidderRequests.size() == 1 + + and: "PBS shouldn't contain errors, warnings and seat non bid" + assert !bidResponse.ext?.warnings + assert !bidResponse.ext?.errors + assert !bidResponse.ext.seatnonbid + + cleanup: + openXBidder.reset() + } + + def "PBS shouldn't wait on non prioritize bidder when primary bidder respond"() { + given: "Default bid request with generic and openX bidders" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.imp[0].ext.prebid.bidder.openx = Openx.defaultOpenx + it.ext.prebid.returnAllBidStatus = true + } + + and: "Account in the DB" + def accountConfig = AccountConfig.defaultAccountConfig.tap { + it.auction = new AccountAuctionConfig(secondaryBidders: [OPENX]) + } + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Set up openx bidder response with delay" + openXBidder.setResponseWithDilay(5) + + when: "PBS processes auction request" + def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + + then: "PBs should processed bidder call" + def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) + def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id) + assert genericBidderRequests.size() == 1 + assert openXBidderRequests.size() == 1 + + and: "PBs response shouldn't contain response body from openX bidder" + assert !bidResponse?.ext?.debug?.httpcalls[OPENX]?.responseBody + + and: "PBS shouldn't contain error for openX due to timeout" + assert !bidResponse.ext?.errors + + and: "PBs should respond with warning for openx" + assert bidResponse.ext?.warnings[ErrorType.OPENX].message == ["secondary bidder timed out, auction proceeded"] + + and: "PBs should populate seatNonBid for openX bidder" + def seatNonBid = bidResponse.ext.seatnonbid[0] + assert seatNonBid.seat == OPENX + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == ERROR_TIMED_OUT + } + + def "PBS shouldn't treated alias bidder as secondary when root bidder code in secondary"() { + given: "Default bid request with generic and openX bidders" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.imp[0].ext.prebid.bidder.tap { + it.openx = Openx.defaultOpenx + it.openxAlias = Openx.defaultOpenx + } + it.ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX] + it.ext.prebid.returnAllBidStatus = true + } + + and: "Account in the DB" + def accountConfig = AccountConfig.defaultAccountConfig.tap { + it.auction = new AccountAuctionConfig(secondaryBidders: [OPENX]) + } + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Set up openx bidder response with delay" + openXBidder.setResponseWithDilay(5) + + and: "Set up openx alias bidder response" + openXAliasBidder.setResponse() + + when: "PBS processes auction request" + def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + + then: "PBs should processed bidder request" + def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) + def openXAliasBidderRequests = openXAliasBidder.getBidderRequests(bidRequest.id) + def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id) + assert genericBidderRequests.size() == 1 + assert openXBidderRequests.size() == 1 + assert openXAliasBidderRequests.size() == 1 + + and: "PBs repose shouldn't contain response body from openX bidder" + assert !bidResponse?.ext?.debug?.httpcalls[OPENX]?.responseBody + + and: "PBS shouldn't contain error for openX due to timeout" + assert !bidResponse.ext?.errors + + and: "PBs should respond with warning for openx" + assert bidResponse.ext?.warnings[ErrorType.OPENX].message == ["secondary bidder timed out, auction proceeded"] + + and: "PBs should populate seatNonBid" + def seatNonBid = bidResponse.ext.seatnonbid[0] + assert seatNonBid.seat == OPENX + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == ERROR_TIMED_OUT + + cleanup: "Reset mock" + openXBidder.reset() + openXAliasBidder.reset() + } + + def "PBS shouldn't wait on secondary bidder when alias bidder respond with dilay"() { + given: "Default bid request with generic and openX bidders" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.imp[0].ext.prebid.bidder.tap { + it.openx = Openx.defaultOpenx + it.openxAlias = Openx.defaultOpenx + } + it.ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX] + it.ext.prebid.returnAllBidStatus = true + } + + and: "Account in the DB" + def accountConfig = AccountConfig.defaultAccountConfig.tap { + it.auction = new AccountAuctionConfig(secondaryBidders: [OPENX_ALIAS]) + } + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Set up openx bidder response with delay" + openXAliasBidder.setResponseWithDilay(5) + + and: "Set up openx alias bidder response" + openXBidder.setResponse() + + when: "PBS processes auction request" + def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + + then: "PBs should processed bidder request" + def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) + def openXAliasBidderRequests = openXAliasBidder.getBidderRequests(bidRequest.id) + def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id) + assert genericBidderRequests.size() == 1 + assert openXBidderRequests.size() == 1 + assert openXAliasBidderRequests.size() == 1 + + and: "PBs repose shouldn't contain response body from openX bidder" + assert !bidResponse?.ext?.debug?.httpcalls[OPENX_ALIAS]?.responseBody + + and: "PBS should contain error for openX due to timeout" + assert !bidResponse.ext?.errors + + and: "PBs should respond with warning for openx alias" + assert bidResponse.ext?.warnings[ErrorType.OPENX_ALIAS].message == ["secondary bidder timed out, auction proceeded"] + + and: "PBs should populate seatNonBid" + def seatNonBid = bidResponse.ext.seatnonbid[0] + assert seatNonBid.seat == OPENX_ALIAS + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == ERROR_TIMED_OUT + + cleanup: "Reset mock" + openXBidder.reset() + openXAliasBidder.reset() + } + + def "PBS should pass auction as usual when secondary bidder respond first and primary with dilay"() { + given: "Default bid request with generic and openX bidders" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.imp[0].ext.prebid.bidder.openx = Openx.defaultOpenx + it.ext.prebid.returnAllBidStatus = true + } + + and: "Account in the DB" + def accountConfig = AccountConfig.defaultAccountConfig.tap { + it.auction = new AccountAuctionConfig(secondaryBidders: [GENERIC]) + } + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Set up openx bidder response with delay" + openXBidder.setResponseWithDilay(1) + + when: "PBS processes auction request" + def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + + then: "PBs should processed bidder request" + def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) + def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id) + assert genericBidderRequests.size() == 1 + assert openXBidderRequests.size() == 1 + + and: "PBS shouldn't contain errors, warnings and seat non bid" + assert !bidResponse.ext?.warnings + assert !bidResponse.ext?.errors + assert !bidResponse.ext.seatnonbid + + cleanup: "Reset mock" + openXBidder.reset() + bidder.reset() + } +}