From 932446c978617e1f80f8adcf06a5f8c9114794dc Mon Sep 17 00:00:00 2001 From: markiian Date: Thu, 25 Dec 2025 00:14:27 +0200 Subject: [PATCH 1/9] Add functional tests for Secondary Bidder faute --- .../model/config/AccountAuctionConfig.groovy | 2 + .../model/response/auction/ErrorType.groovy | 1 + .../testcontainers/scaffolding/Bidder.groovy | 8 + .../tests/SecondaryBidderSpec.groovy | 338 ++++++++++++++++++ 4 files changed, 349 insertions(+) create mode 100644 src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy 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/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/SecondaryBidderSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy new file mode 100644 index 00000000000..1da90a2c1f5 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy @@ -0,0 +1,338 @@ +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.RESPONSE_REJECTED_ADVERTISER_BLOCKED +import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer + +class SecondaryBidderSpec extends BaseSpec { + + private static final Map OPENX_CONFIG = [ + "adapters.${OPENX.value}.enabled" : "true", + "adapters.${OPENX.value}.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() { + 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 + + 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 + + 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 should emit a warning when invalid bidder in secondary bidders config"() { + given: "Default basic BidRequest with generic bidder" + def bidRequest = BidRequest.defaultBidRequest + + 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() == 3 + + and: "PBS shouldn't contain errors, warnings and seat non bid" + assert !bidResponse.ext?.warnings + assert !bidResponse.ext?.errors + assert !bidResponse.ext.seatnonbid + } + + //todo: If every bidder in the auction is flagged as secondary, + // then the feature is ignored, and all bidders are considered 'primary'. + + def "PBS should thread all bidders as primary when all requested bidders in secondary bidders config"() { + given: "Default basic BidRequest with generic bidder" + def bidRequest = BidRequest.defaultBidRequest + + 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) + + 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() == 2 + + and: "PBS shouldn't contain errors, warnings and seat non bid" + assert !bidResponse.ext?.warnings + assert !bidResponse.ext?.errors + assert !bidResponse.ext.seatnonbid + } + + //todo: If a bidder is defined as secondary by the account-level config, + // PBS should not wait for that bidder to respond. i.e. + // when the last primary bidder responds, the auction is over and any secondary bidder that hasn't returned is considered timed out. + + 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.tap { + it.openx = Openx.defaultOpenx + } + } + + 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 request" + def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) + def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id) + assert genericBidderRequests.size() == 1 + assert openXBidderRequests.size() == 1 + + and: "PBs repose shouldn't contain response body from openX bidder" + assert !bidResponse?.ext?.debug?.httpcalls[OPENX]?.responseBody + + and: "PBS should contain error for openX due to timeout" + assert bidResponse.ext?.errors[ErrorType.OPENX] + + 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 == RESPONSE_REJECTED_ADVERTISER_BLOCKED + } + + //todo: Aliases are treated separately. i.e. + // just because a biddercode is defined as secondary does not mean any aliases or root biddercodes are also secondary. + + 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 + } + ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX] + } + + 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 should contain error for openX due to timeout" + assert bidResponse.ext?.errors[ErrorType.OPENX] + + and: "PBs should respond with warning for openx" + assert bidResponse.ext?.warnings[ErrorType.OPENX].message == ["Secondary bidder openx 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 == RESPONSE_REJECTED_ADVERTISER_BLOCKED + + 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 + } + ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX] + } + + 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[ErrorType.OPENX_ALIAS] + + and: "PBs should respond with warning for openx" + assert bidResponse.ext?.warnings[ErrorType.OPENX_ALIAS].message == ["Secondary bidder opnex_alias 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 == RESPONSE_REJECTED_ADVERTISER_BLOCKED + + cleanup: "Reset mock" + openXBidder.reset() + openXAliasBidder.reset() + } + + //todo: what if primary bidder will respond slowest than secondary bidder, usual flow of action? + + 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.tap { + it.openx = Openx.defaultOpenx + } + } + + 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() + } +} + + From 09a3cc0f7e44d4e7b7a5fed5a48a01fe33638b6a Mon Sep 17 00:00:00 2001 From: markiian Date: Wed, 31 Dec 2025 17:21:19 +0200 Subject: [PATCH 2/9] Fix NPE when removing invalid config. --- .../server/functional/testcontainers/PbsServiceFactory.groovy | 3 +++ 1 file changed, 3 insertions(+) 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) } From 311d45dce76c7ac8eb2303dc93fce770d1011b07 Mon Sep 17 00:00:00 2001 From: markiian Date: Wed, 7 Jan 2026 16:03:10 +0200 Subject: [PATCH 3/9] Minor update --- .../tests/SecondaryBidderSpec.groovy | 115 ++++++++++-------- 1 file changed, 61 insertions(+), 54 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy index 1da90a2c1f5..8da22d174a9 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy @@ -1,6 +1,5 @@ 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 @@ -15,15 +14,14 @@ 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.RESPONSE_REJECTED_ADVERTISER_BLOCKED +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.value}.enabled" : "true", - "adapters.${OPENX.value}.endpoint": "$networkServiceContainer.rootUri/openx-auction".toString()] + "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", @@ -37,12 +35,16 @@ class SecondaryBidderSpec extends BaseSpec { @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 + def bidRequest = BidRequest.defaultBidRequest.tap { + it.ext.prebid.returnAllBidStatus = true + } and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { @@ -65,7 +67,9 @@ class SecondaryBidderSpec extends BaseSpec { def "PBS should emit a warning when null in secondary bidders config"() { given: "Default basic BidRequest with generic bidder" - def bidRequest = BidRequest.defaultBidRequest + def bidRequest = BidRequest.defaultBidRequest.tap { + it.ext.prebid.returnAllBidStatus = true + } and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { @@ -86,9 +90,11 @@ class SecondaryBidderSpec extends BaseSpec { assert !bidResponse.ext.seatnonbid } - def "PBS should emit a warning when invalid bidder in secondary bidders config"() { + 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 + def bidRequest = BidRequest.defaultBidRequest.tap { + it.ext.prebid.returnAllBidStatus = true + } and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { @@ -102,7 +108,7 @@ class SecondaryBidderSpec extends BaseSpec { then: "PBs should processed bidder request" def bidderRequests = bidder.getBidderRequests(bidRequest.id) - assert bidderRequests.size() == 3 + assert bidderRequests.size() == 1 and: "PBS shouldn't contain errors, warnings and seat non bid" assert !bidResponse.ext?.warnings @@ -110,12 +116,12 @@ class SecondaryBidderSpec extends BaseSpec { assert !bidResponse.ext.seatnonbid } - //todo: If every bidder in the auction is flagged as secondary, - // then the feature is ignored, and all bidders are considered 'primary'. - def "PBS should thread all bidders as primary when all requested bidders in secondary bidders config"() { - given: "Default basic BidRequest with generic bidder" - def bidRequest = BidRequest.defaultBidRequest + 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 { @@ -124,34 +130,39 @@ class SecondaryBidderSpec extends BaseSpec { 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 bidder request" - def bidderRequests = bidder.getBidderRequests(bidRequest.id) - assert bidderRequests.size() == 2 + 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 - } - //todo: If a bidder is defined as secondary by the account-level config, - // PBS should not wait for that bidder to respond. i.e. - // when the last primary bidder responds, the auction is over and any secondary bidder that hasn't returned is considered timed out. + 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.tap { - it.openx = Openx.defaultOpenx - } + 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]) + it.auction = new AccountAuctionConfig(secondaryBidders: [OPENX]) } def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) @@ -162,31 +173,28 @@ class SecondaryBidderSpec extends BaseSpec { when: "PBS processes auction request" def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) - then: "PBs should processed bidder request" + 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 repose shouldn't contain response body from openX bidder" + and: "PBs response shouldn't contain response body from openX bidder" assert !bidResponse?.ext?.debug?.httpcalls[OPENX]?.responseBody - and: "PBS should contain error for openX due to timeout" - assert bidResponse.ext?.errors[ErrorType.OPENX] + 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"] + assert bidResponse.ext?.warnings[ErrorType.OPENX].message == ["secondary bidder timed out, auction proceeded"] - and: "PBs should populate seatNonBid" + 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 == RESPONSE_REJECTED_ADVERTISER_BLOCKED + assert seatNonBid.nonBid[0].statusCode == ERROR_TIMED_OUT } - //todo: Aliases are treated separately. i.e. - // just because a biddercode is defined as secondary does not mean any aliases or root biddercodes are also secondary. - 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 { @@ -194,12 +202,13 @@ class SecondaryBidderSpec extends BaseSpec { it.openx = Openx.defaultOpenx it.openxAlias = Openx.defaultOpenx } - ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX] + 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]) + it.auction = new AccountAuctionConfig(secondaryBidders: [OPENX]) } def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) @@ -224,17 +233,17 @@ class SecondaryBidderSpec extends BaseSpec { and: "PBs repose shouldn't contain response body from openX bidder" assert !bidResponse?.ext?.debug?.httpcalls[OPENX]?.responseBody - and: "PBS should contain error for openX due to timeout" - assert bidResponse.ext?.errors[ErrorType.OPENX] + 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 openx timed out, auction proceeded"] + 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 == RESPONSE_REJECTED_ADVERTISER_BLOCKED + assert seatNonBid.nonBid[0].statusCode == ERROR_TIMED_OUT cleanup: "Reset mock" openXBidder.reset() @@ -248,12 +257,13 @@ class SecondaryBidderSpec extends BaseSpec { it.openx = Openx.defaultOpenx it.openxAlias = Openx.defaultOpenx } - ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX] + 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]) + it.auction = new AccountAuctionConfig(secondaryBidders: [OPENX_ALIAS]) } def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) @@ -279,35 +289,32 @@ class SecondaryBidderSpec extends BaseSpec { assert !bidResponse?.ext?.debug?.httpcalls[OPENX_ALIAS]?.responseBody and: "PBS should contain error for openX due to timeout" - assert bidResponse.ext?.errors[ErrorType.OPENX_ALIAS] + assert !bidResponse.ext?.errors - and: "PBs should respond with warning for openx" - assert bidResponse.ext?.warnings[ErrorType.OPENX_ALIAS].message == ["Secondary bidder opnex_alias timed out, auction proceeded"] + 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 == RESPONSE_REJECTED_ADVERTISER_BLOCKED + assert seatNonBid.nonBid[0].statusCode == ERROR_TIMED_OUT cleanup: "Reset mock" openXBidder.reset() openXAliasBidder.reset() } - //todo: what if primary bidder will respond slowest than secondary bidder, usual flow of action? - 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.tap { - it.openx = Openx.defaultOpenx - } + 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]) + it.auction = new AccountAuctionConfig(secondaryBidders: [GENERIC]) } def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) From 65678b4e582009487b0af7f5457d99fe54cfb7a6 Mon Sep 17 00:00:00 2001 From: markiian Date: Wed, 7 Jan 2026 16:34:01 +0200 Subject: [PATCH 4/9] Minor updates --- .../prebid/server/functional/tests/SecondaryBidderSpec.groovy | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy index 8da22d174a9..e50fa4d774a 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy @@ -341,5 +341,3 @@ class SecondaryBidderSpec extends BaseSpec { bidder.reset() } } - - From 6f178767066c8c44f0810b5f23b7c2668b08d8c3 Mon Sep 17 00:00:00 2001 From: markiian Date: Thu, 29 Jan 2026 16:53:35 +0200 Subject: [PATCH 5/9] Update after review --- .../model/request/auction/BidRequest.groovy | 12 +++ .../testcontainers/scaffolding/Bidder.groovy | 2 +- .../tests/SecondaryBidderSpec.groovy | 89 ++++++++++--------- 3 files changed, 59 insertions(+), 44 deletions(-) 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 index 26e9ddc2057..ccbe970252f 100644 --- 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 @@ -170,4 +170,16 @@ class BidRequest { ext.prebid.events = new Events() } } + + void enabledReturnAllBidStatus() { + if (!ext) { + ext = new BidRequestExt() + } + if (!ext.prebid) { + ext.prebid = new Prebid() + } + if (!ext.prebid.returnAllBidStatus) { + ext.prebid.returnAllBidStatus = true + } + } } 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 99025431a76..d65d692c66a 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 @@ -48,7 +48,7 @@ class Bidder extends NetworkScaffolding { : HttpResponse.notFoundResponse()} } - void setResponseWithDilay(Integer dilayTimeout = 5) { + void setResponseWithDelay(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)) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy index e50fa4d774a..659fbb1858a 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy @@ -20,30 +20,31 @@ import static org.prebid.server.functional.testcontainers.Dependencies.getNetwor class SecondaryBidderSpec extends BaseSpec { private static final Map OPENX_CONFIG = [ - "adapters.openx.enabled" : "true", - "adapters.openx.endpoint": "$networkServiceContainer.rootUri/openx-auction".toString()] + "adapters.${OPENX.value}.enabled" : "true", + "adapters.${OPENX.value}.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") + private static final String WARNING_TIME_OUT_MESSAGE = "secondary bidder timed out, auction proceeded" + private static final Integer RESPONSE_DELAY_SECONDS = 5 + private static final Bidder openXBidder = new Bidder(networkServiceContainer, "/openx-auction") + private static final Bidder openXAliasBidder = new Bidder(networkServiceContainer, "/openx-alias-auction") @Shared - PrebidServerService pbsServiceWithOpenXAndIXBidder = pbsServiceFactory.getService(OPENX_CONFIG + OPENX_ALIAS_CONFIG) + PrebidServerService pbsServiceWithOpenXBidder = pbsServiceFactory.getService(OPENX_CONFIG + OPENX_ALIAS_CONFIG) @Override def cleanupSpec() { - openXBidder.setResponse() - openXAliasBidder.setResponse() + openXBidder.reset() + openXAliasBidder.reset() 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 + enabledReturnAllBidStatus() } and: "Account in the DB" @@ -54,7 +55,7 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) when: "PBS processes auction request" - def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) then: "PBs should processed bidder request" assert bidder.getBidderRequest(bidRequest.id) @@ -65,10 +66,10 @@ class SecondaryBidderSpec extends BaseSpec { assert !bidResponse.ext.seatnonbid } - def "PBS should emit a warning when null in secondary bidders config"() { + def "PBS shouldn't emit a warning when empty secondary bidders config"() { given: "Default basic BidRequest with generic bidder" def bidRequest = BidRequest.defaultBidRequest.tap { - it.ext.prebid.returnAllBidStatus = true + enabledReturnAllBidStatus() } and: "Account in the DB" @@ -79,7 +80,7 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) when: "PBS processes auction request" - def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) then: "PBs should processed bidder request" assert bidder.getBidderRequest(bidRequest.id) @@ -87,13 +88,13 @@ class SecondaryBidderSpec extends BaseSpec { and: "PBS shouldn't contain errors, warnings and seat non bid" assert !bidResponse.ext?.warnings assert !bidResponse.ext?.errors - assert !bidResponse.ext.seatnonbid + 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 + enabledReturnAllBidStatus() } and: "Account in the DB" @@ -104,7 +105,7 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) when: "PBS processes auction request" - def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) then: "PBs should processed bidder request" def bidderRequests = bidder.getBidderRequests(bidRequest.id) @@ -113,14 +114,14 @@ class SecondaryBidderSpec extends BaseSpec { and: "PBS shouldn't contain errors, warnings and seat non bid" assert !bidResponse.ext?.warnings assert !bidResponse.ext?.errors - assert !bidResponse.ext.seatnonbid + 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 + enabledReturnAllBidStatus() } and: "Account in the DB" @@ -134,7 +135,7 @@ class SecondaryBidderSpec extends BaseSpec { openXBidder.setResponse() when: "PBS processes auction request" - def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) then: "PBs should processed generic request" def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) @@ -147,7 +148,7 @@ class SecondaryBidderSpec extends BaseSpec { and: "PBS shouldn't contain errors, warnings and seat non bid" assert !bidResponse.ext?.warnings assert !bidResponse.ext?.errors - assert !bidResponse.ext.seatnonbid + assert !bidResponse.ext?.seatnonbid cleanup: openXBidder.reset() @@ -157,7 +158,7 @@ class SecondaryBidderSpec extends BaseSpec { 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 + enabledReturnAllBidStatus() } and: "Account in the DB" @@ -168,10 +169,10 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) and: "Set up openx bidder response with delay" - openXBidder.setResponseWithDilay(5) + openXBidder.setResponseWithDelay(RESPONSE_DELAY_SECONDS) when: "PBS processes auction request" - def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) then: "PBs should processed bidder call" def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) @@ -186,7 +187,7 @@ class SecondaryBidderSpec extends BaseSpec { 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"] + assert bidResponse.ext?.warnings[ErrorType.OPENX].message == [WARNING_TIME_OUT_MESSAGE] and: "PBs should populate seatNonBid for openX bidder" def seatNonBid = bidResponse.ext.seatnonbid[0] @@ -203,7 +204,7 @@ class SecondaryBidderSpec extends BaseSpec { it.openxAlias = Openx.defaultOpenx } it.ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX] - it.ext.prebid.returnAllBidStatus = true + enabledReturnAllBidStatus() } and: "Account in the DB" @@ -214,13 +215,13 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) and: "Set up openx bidder response with delay" - openXBidder.setResponseWithDilay(5) + openXBidder.setResponseWithDelay(RESPONSE_DELAY_SECONDS) and: "Set up openx alias bidder response" openXAliasBidder.setResponse() when: "PBS processes auction request" - def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) then: "PBs should processed bidder request" def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) @@ -230,6 +231,9 @@ class SecondaryBidderSpec extends BaseSpec { assert openXBidderRequests.size() == 1 assert openXAliasBidderRequests.size() == 1 + and: "PBs response should contain openX alias and generic" + assert bidResponse.seatbid.seat == [OPENX_ALIAS, GENERIC] + and: "PBs repose shouldn't contain response body from openX bidder" assert !bidResponse?.ext?.debug?.httpcalls[OPENX]?.responseBody @@ -237,7 +241,7 @@ class SecondaryBidderSpec extends BaseSpec { 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"] + assert bidResponse.ext?.warnings[ErrorType.OPENX].message == [WARNING_TIME_OUT_MESSAGE] and: "PBs should populate seatNonBid" def seatNonBid = bidResponse.ext.seatnonbid[0] @@ -250,7 +254,7 @@ class SecondaryBidderSpec extends BaseSpec { openXAliasBidder.reset() } - def "PBS shouldn't wait on secondary bidder when alias bidder respond with dilay"() { + def "PBS shouldn't wait on secondary bidder when alias bidder respond with delay"() { given: "Default bid request with generic and openX bidders" def bidRequest = BidRequest.defaultBidRequest.tap { it.imp[0].ext.prebid.bidder.tap { @@ -258,7 +262,7 @@ class SecondaryBidderSpec extends BaseSpec { it.openxAlias = Openx.defaultOpenx } it.ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX] - it.ext.prebid.returnAllBidStatus = true + enabledReturnAllBidStatus() } and: "Account in the DB" @@ -269,13 +273,13 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) and: "Set up openx bidder response with delay" - openXAliasBidder.setResponseWithDilay(5) + openXAliasBidder.setResponseWithDelay(RESPONSE_DELAY_SECONDS) and: "Set up openx alias bidder response" openXBidder.setResponse() when: "PBS processes auction request" - def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) then: "PBs should processed bidder request" def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) @@ -292,7 +296,7 @@ class SecondaryBidderSpec extends BaseSpec { 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"] + assert bidResponse.ext?.warnings[ErrorType.OPENX_ALIAS].message == [WARNING_TIME_OUT_MESSAGE] and: "PBs should populate seatNonBid" def seatNonBid = bidResponse.ext.seatnonbid[0] @@ -301,15 +305,14 @@ class SecondaryBidderSpec extends BaseSpec { 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"() { + def "PBS should pass auction as usual when secondary bidder respond first and primary with delay"() { 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 + enabledReturnAllBidStatus() } and: "Account in the DB" @@ -320,10 +323,11 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) and: "Set up openx bidder response with delay" - openXBidder.setResponseWithDilay(1) + openXBidder.reset() + openXBidder.setResponseWithDelay(2) when: "PBS processes auction request" - def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest) + def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) then: "PBs should processed bidder request" def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) @@ -331,13 +335,12 @@ class SecondaryBidderSpec extends BaseSpec { assert genericBidderRequests.size() == 1 assert openXBidderRequests.size() == 1 + and: "PBs response should contain openX alias and generic" + assert bidResponse.seatbid.seat.sort() == [OPENX, GENERIC].sort() + 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() + assert !bidResponse.ext?.seatnonbid } } From b74ed137d3320ae9f9d0b52f406d5d7b83204052 Mon Sep 17 00:00:00 2001 From: markiian Date: Thu, 29 Jan 2026 21:29:17 +0200 Subject: [PATCH 6/9] Last updated --- .../tests/SecondaryBidderSpec.groovy | 79 +++++++++++++------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy index 659fbb1858a..20df7599d4a 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.tests +import org.prebid.server.functional.model.bidder.Generic import org.prebid.server.functional.model.bidder.Openx import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig @@ -10,9 +11,9 @@ 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.ALIAS 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 @@ -23,22 +24,22 @@ class SecondaryBidderSpec extends BaseSpec { "adapters.${OPENX.value}.enabled" : "true", "adapters.${OPENX.value}.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()] + private static final Map GENERIC_ALIAS_CONFIG = [ + "adapters.${GENERIC.value}.aliases.${ALIAS}.enabled" : "true", + "adapters.${GENERIC.value}.aliases.${ALIAS}.endpoint": "$networkServiceContainer.rootUri/generic-alias-auction".toString()] private static final String WARNING_TIME_OUT_MESSAGE = "secondary bidder timed out, auction proceeded" private static final Integer RESPONSE_DELAY_SECONDS = 5 private static final Bidder openXBidder = new Bidder(networkServiceContainer, "/openx-auction") - private static final Bidder openXAliasBidder = new Bidder(networkServiceContainer, "/openx-alias-auction") + private static final Bidder genericAliasBidder = new Bidder(networkServiceContainer, "/generic-alias-auction") @Shared - PrebidServerService pbsServiceWithOpenXBidder = pbsServiceFactory.getService(OPENX_CONFIG + OPENX_ALIAS_CONFIG) + PrebidServerService pbsServiceWithOpenXBidder = pbsServiceFactory.getService(OPENX_CONFIG + GENERIC_ALIAS_CONFIG) @Override def cleanupSpec() { openXBidder.reset() - openXAliasBidder.reset() - pbsServiceFactory.removeContainer(OPENX_CONFIG + OPENX_ALIAS_CONFIG) + genericAliasBidder.reset() + pbsServiceFactory.removeContainer(OPENX_CONFIG + GENERIC_ALIAS_CONFIG) } def "PBS should proceed as default when secondaryBidders not define in config"() { @@ -201,9 +202,9 @@ class SecondaryBidderSpec extends BaseSpec { def bidRequest = BidRequest.defaultBidRequest.tap { it.imp[0].ext.prebid.bidder.tap { it.openx = Openx.defaultOpenx - it.openxAlias = Openx.defaultOpenx + it.alias = new Generic() } - it.ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX] + it.ext.prebid.aliases = [(ALIAS.value): OPENX] enabledReturnAllBidStatus() } @@ -218,21 +219,21 @@ class SecondaryBidderSpec extends BaseSpec { openXBidder.setResponseWithDelay(RESPONSE_DELAY_SECONDS) and: "Set up openx alias bidder response" - openXAliasBidder.setResponse() + genericAliasBidder.setResponse() when: "PBS processes auction request" def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) then: "PBs should processed bidder request" def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) - def openXAliasBidderRequests = openXAliasBidder.getBidderRequests(bidRequest.id) + def openXAliasBidderRequests = genericAliasBidder.getBidderRequests(bidRequest.id) def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id) assert genericBidderRequests.size() == 1 assert openXBidderRequests.size() == 1 assert openXAliasBidderRequests.size() == 1 and: "PBs response should contain openX alias and generic" - assert bidResponse.seatbid.seat == [OPENX_ALIAS, GENERIC] + assert bidResponse.seatbid.seat == [ALIAS, GENERIC] and: "PBs repose shouldn't contain response body from openX bidder" assert !bidResponse?.ext?.debug?.httpcalls[OPENX]?.responseBody @@ -251,7 +252,7 @@ class SecondaryBidderSpec extends BaseSpec { cleanup: "Reset mock" openXBidder.reset() - openXAliasBidder.reset() + genericAliasBidder.reset() } def "PBS shouldn't wait on secondary bidder when alias bidder respond with delay"() { @@ -259,21 +260,21 @@ class SecondaryBidderSpec extends BaseSpec { def bidRequest = BidRequest.defaultBidRequest.tap { it.imp[0].ext.prebid.bidder.tap { it.openx = Openx.defaultOpenx - it.openxAlias = Openx.defaultOpenx + it.alias = new Generic() } - it.ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX] + it.ext.prebid.aliases = [(ALIAS.value): OPENX] enabledReturnAllBidStatus() } and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { - it.auction = new AccountAuctionConfig(secondaryBidders: [OPENX_ALIAS]) + it.auction = new AccountAuctionConfig(secondaryBidders: [ALIAS]) } def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) and: "Set up openx bidder response with delay" - openXAliasBidder.setResponseWithDelay(RESPONSE_DELAY_SECONDS) + genericAliasBidder.setResponseWithDelay(RESPONSE_DELAY_SECONDS) and: "Set up openx alias bidder response" openXBidder.setResponse() @@ -283,29 +284,29 @@ class SecondaryBidderSpec extends BaseSpec { then: "PBs should processed bidder request" def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) - def openXAliasBidderRequests = openXAliasBidder.getBidderRequests(bidRequest.id) + def openXAliasBidderRequests = genericAliasBidder.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 + assert !bidResponse?.ext?.debug?.httpcalls[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 == [WARNING_TIME_OUT_MESSAGE] + assert bidResponse.ext?.warnings[ErrorType.ALIAS].message == [WARNING_TIME_OUT_MESSAGE] and: "PBs should populate seatNonBid" def seatNonBid = bidResponse.ext.seatnonbid[0] - assert seatNonBid.seat == OPENX_ALIAS + assert seatNonBid.seat == ALIAS assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id assert seatNonBid.nonBid[0].statusCode == ERROR_TIMED_OUT cleanup: "Reset mock" - openXAliasBidder.reset() + genericAliasBidder.reset() } def "PBS should pass auction as usual when secondary bidder respond first and primary with delay"() { @@ -343,4 +344,36 @@ class SecondaryBidderSpec extends BaseSpec { assert !bidResponse.ext?.errors assert !bidResponse.ext?.seatnonbid } + + def "PBS should pass auction as usual when secondary bidder respond all primary bidders"() { + given: "Default bid request with generic and openX bidders" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.imp[0].ext.prebid.bidder.openx = Openx.defaultOpenx + enabledReturnAllBidStatus() + } + + 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) + + when: "PBS processes auction request" + def bidResponse = pbsServiceWithOpenXBidder.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 response should contain openX alias and generic" + assert bidResponse.seatbid.seat.sort() == [OPENX, GENERIC].sort() + + and: "PBS shouldn't contain errors, warnings and seat non bid" + assert !bidResponse.ext?.warnings + assert !bidResponse.ext?.errors + assert !bidResponse.ext?.seatnonbid + } } From 1bfa809ad51955094a3e149c01033fb8ebacd3c3 Mon Sep 17 00:00:00 2001 From: markiian Date: Thu, 19 Feb 2026 14:03:35 +0200 Subject: [PATCH 7/9] Update after review --- .../testcontainers/scaffolding/Bidder.groovy | 5 +- .../tests/SecondaryBidderSpec.groovy | 157 ++++++------------ 2 files changed, 56 insertions(+), 106 deletions(-) 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 d65d692c66a..3fb31e61b11 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.MILLISECONDS import static java.util.concurrent.TimeUnit.SECONDS import static org.mockserver.model.HttpRequest.request import static org.mockserver.model.HttpResponse.response @@ -48,10 +49,10 @@ class Bidder extends NetworkScaffolding { : HttpResponse.notFoundResponse()} } - void setResponseWithDelay(Integer dilayTimeout = 5) { + void setResponseWithDelay(Long dilayTimeoutMillisecond = 5000) { 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)) + ? response().withDelay(MILLISECONDS, dilayTimeoutMillisecond).withStatusCode(OK_200.code()).withBody(getBodyByRequest(request)) : HttpResponse.notFoundResponse()} } diff --git a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy index 20df7599d4a..a05ce046c1d 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.tests +import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.bidder.Generic import org.prebid.server.functional.model.bidder.Openx import org.prebid.server.functional.model.config.AccountAuctionConfig @@ -20,54 +21,33 @@ import static org.prebid.server.functional.testcontainers.Dependencies.getNetwor class SecondaryBidderSpec extends BaseSpec { + private static final String OPENX_AUCTION_ENDPOINT = "/openx-auction" + private static final String GENERIC_ALIAS_AUCTION_ENDPOINT = "/generic-alias-auction" private static final Map OPENX_CONFIG = [ "adapters.${OPENX.value}.enabled" : "true", - "adapters.${OPENX.value}.endpoint": "$networkServiceContainer.rootUri/openx-auction".toString()] - + "adapters.${OPENX.value}.endpoint": "$networkServiceContainer.rootUri$OPENX_AUCTION_ENDPOINT".toString()] private static final Map GENERIC_ALIAS_CONFIG = [ "adapters.${GENERIC.value}.aliases.${ALIAS}.enabled" : "true", - "adapters.${GENERIC.value}.aliases.${ALIAS}.endpoint": "$networkServiceContainer.rootUri/generic-alias-auction".toString()] + "adapters.${GENERIC.value}.aliases.${ALIAS}.endpoint": "$networkServiceContainer.rootUri$GENERIC_ALIAS_AUCTION_ENDPOINT".toString()] private static final String WARNING_TIME_OUT_MESSAGE = "secondary bidder timed out, auction proceeded" - private static final Integer RESPONSE_DELAY_SECONDS = 5 - private static final Bidder openXBidder = new Bidder(networkServiceContainer, "/openx-auction") - private static final Bidder genericAliasBidder = new Bidder(networkServiceContainer, "/generic-alias-auction") + private static final Long RESPONSE_DELAY_MILLISECONDS = 5000 + private static final Bidder openXBidder = new Bidder(networkServiceContainer, OPENX_AUCTION_ENDPOINT) + private static final Bidder genericAliasBidder = new Bidder(networkServiceContainer, GENERIC_ALIAS_AUCTION_ENDPOINT) @Shared PrebidServerService pbsServiceWithOpenXBidder = pbsServiceFactory.getService(OPENX_CONFIG + GENERIC_ALIAS_CONFIG) @Override def cleanupSpec() { - openXBidder.reset() - genericAliasBidder.reset() pbsServiceFactory.removeContainer(OPENX_CONFIG + GENERIC_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 { - enabledReturnAllBidStatus() - } - - 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 = pbsServiceWithOpenXBidder.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 cleanup() { + openXBidder.reset() + genericAliasBidder.reset() } - def "PBS shouldn't emit a warning when empty secondary bidders config"() { + def "PBS shouldn't emit a warning when secondary bidders config #secondaryBidder"() { given: "Default basic BidRequest with generic bidder" def bidRequest = BidRequest.defaultBidRequest.tap { enabledReturnAllBidStatus() @@ -75,7 +55,7 @@ class SecondaryBidderSpec extends BaseSpec { and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { - it.auction = new AccountAuctionConfig(secondaryBidders: [null]) + it.auction = new AccountAuctionConfig(secondaryBidders: [secondaryBidder]) } def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) @@ -84,46 +64,20 @@ class SecondaryBidderSpec extends BaseSpec { def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) then: "PBs should processed bidder request" - assert bidder.getBidderRequest(bidRequest.id) + assert bidder.getBidderRequests(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 { - enabledReturnAllBidStatus() - } - - 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 = pbsServiceWithOpenXBidder.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 + where: + secondaryBidder << [null, UNKNOWN] } 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 - enabledReturnAllBidStatus() - } + def bidRequest = getEnrichedBidRequest([OPENX]) and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { @@ -150,17 +104,11 @@ class SecondaryBidderSpec extends BaseSpec { 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 - enabledReturnAllBidStatus() - } + def bidRequest = getEnrichedBidRequest([OPENX]) and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { @@ -170,7 +118,7 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) and: "Set up openx bidder response with delay" - openXBidder.setResponseWithDelay(RESPONSE_DELAY_SECONDS) + openXBidder.setResponseWithDelay(RESPONSE_DELAY_MILLISECONDS) when: "PBS processes auction request" def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) @@ -199,13 +147,8 @@ class SecondaryBidderSpec extends BaseSpec { 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.alias = new Generic() - } + def bidRequest = getEnrichedBidRequest([OPENX, ALIAS]).tap { it.ext.prebid.aliases = [(ALIAS.value): OPENX] - enabledReturnAllBidStatus() } and: "Account in the DB" @@ -216,7 +159,7 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) and: "Set up openx bidder response with delay" - openXBidder.setResponseWithDelay(RESPONSE_DELAY_SECONDS) + openXBidder.setResponseWithDelay(RESPONSE_DELAY_MILLISECONDS) and: "Set up openx alias bidder response" genericAliasBidder.setResponse() @@ -233,10 +176,15 @@ class SecondaryBidderSpec extends BaseSpec { assert openXAliasBidderRequests.size() == 1 and: "PBs response should contain openX alias and generic" - assert bidResponse.seatbid.seat == [ALIAS, GENERIC] + assert bidResponse.seatbid.seat.sort() == [ALIAS, GENERIC].sort() + + and: "PBs repose should contain response body from generic and alias bidder" + def httpCalls = bidResponse?.ext?.debug?.httpcalls + assert httpCalls[GENERIC.value]?.responseBody + assert httpCalls[ALIAS.value]?.responseBody and: "PBs repose shouldn't contain response body from openX bidder" - assert !bidResponse?.ext?.debug?.httpcalls[OPENX]?.responseBody + assert !httpCalls[OPENX.value]?.responseBody and: "PBS shouldn't contain error for openX due to timeout" assert !bidResponse.ext?.errors @@ -249,21 +197,12 @@ class SecondaryBidderSpec extends BaseSpec { 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() - genericAliasBidder.reset() } def "PBS shouldn't wait on secondary bidder when alias bidder respond with delay"() { 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.alias = new Generic() - } + def bidRequest = getEnrichedBidRequest([OPENX, ALIAS]).tap { it.ext.prebid.aliases = [(ALIAS.value): OPENX] - enabledReturnAllBidStatus() } and: "Account in the DB" @@ -274,7 +213,7 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) and: "Set up openx bidder response with delay" - genericAliasBidder.setResponseWithDelay(RESPONSE_DELAY_SECONDS) + genericAliasBidder.setResponseWithDelay(RESPONSE_DELAY_MILLISECONDS) and: "Set up openx alias bidder response" openXBidder.setResponse() @@ -304,17 +243,11 @@ class SecondaryBidderSpec extends BaseSpec { assert seatNonBid.seat == ALIAS assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id assert seatNonBid.nonBid[0].statusCode == ERROR_TIMED_OUT - - cleanup: "Reset mock" - genericAliasBidder.reset() } def "PBS should pass auction as usual when secondary bidder respond first and primary with delay"() { given: "Default bid request with generic and openX bidders" - def bidRequest = BidRequest.defaultBidRequest.tap { - it.imp[0].ext.prebid.bidder.openx = Openx.defaultOpenx - enabledReturnAllBidStatus() - } + def bidRequest = getEnrichedBidRequest([OPENX]) and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { @@ -324,8 +257,8 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) and: "Set up openx bidder response with delay" - openXBidder.reset() - openXBidder.setResponseWithDelay(2) + def almostMaxDelayMilliseconds = bidRequest.tmax - 200 + openXBidder.setResponseWithDelay(almostMaxDelayMilliseconds) when: "PBS processes auction request" def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) @@ -336,6 +269,10 @@ class SecondaryBidderSpec extends BaseSpec { assert genericBidderRequests.size() == 1 assert openXBidderRequests.size() == 1 + and: "Bid response should container time of opnex response" + def toleranceTimeoutMilliseconds = 50 + assert bidResponse.ext.responsetimemillis[OPENX.value] in almostMaxDelayMilliseconds..almostMaxDelayMilliseconds + toleranceTimeoutMilliseconds + and: "PBs response should contain openX alias and generic" assert bidResponse.seatbid.seat.sort() == [OPENX, GENERIC].sort() @@ -347,10 +284,7 @@ class SecondaryBidderSpec extends BaseSpec { def "PBS should pass auction as usual when secondary bidder respond all primary bidders"() { given: "Default bid request with generic and openX bidders" - def bidRequest = BidRequest.defaultBidRequest.tap { - it.imp[0].ext.prebid.bidder.openx = Openx.defaultOpenx - enabledReturnAllBidStatus() - } + def bidRequest = getEnrichedBidRequest([OPENX]) and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { @@ -359,6 +293,9 @@ class SecondaryBidderSpec extends BaseSpec { def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) + and: "OpenX set up response body" + openXBidder.setResponse() + when: "PBS processes auction request" def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) @@ -376,4 +313,16 @@ class SecondaryBidderSpec extends BaseSpec { assert !bidResponse.ext?.errors assert !bidResponse.ext?.seatnonbid } + + private static BidRequest getEnrichedBidRequest(List bidderNames) { + BidRequest.defaultBidRequest.tap { + if (bidderNames.contains(OPENX)) { + it.imp[0]?.ext?.prebid?.bidder?.openx = Openx.defaultOpenx + } + if (bidderNames.contains(ALIAS)) { + it.imp[0]?.ext?.prebid?.bidder?.alias = new Generic() + } + enabledReturnAllBidStatus() + } + } } From c574307ddd39303d5d1ad8060a855cda1c0aac3b Mon Sep 17 00:00:00 2001 From: markiian Date: Mon, 9 Mar 2026 20:44:10 +0200 Subject: [PATCH 8/9] Update after review --- .../tests/SecondaryBidderSpec.groovy | 38 +------------------ 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy index a05ce046c1d..6cdc4afb77b 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy @@ -178,7 +178,7 @@ class SecondaryBidderSpec extends BaseSpec { and: "PBs response should contain openX alias and generic" assert bidResponse.seatbid.seat.sort() == [ALIAS, GENERIC].sort() - and: "PBs repose should contain response body from generic and alias bidder" + and: "PBs response should contain response body from generic and alias bidder" def httpCalls = bidResponse?.ext?.debug?.httpcalls assert httpCalls[GENERIC.value]?.responseBody assert httpCalls[ALIAS.value]?.responseBody @@ -269,42 +269,6 @@ class SecondaryBidderSpec extends BaseSpec { assert genericBidderRequests.size() == 1 assert openXBidderRequests.size() == 1 - and: "Bid response should container time of opnex response" - def toleranceTimeoutMilliseconds = 50 - assert bidResponse.ext.responsetimemillis[OPENX.value] in almostMaxDelayMilliseconds..almostMaxDelayMilliseconds + toleranceTimeoutMilliseconds - - and: "PBs response should contain openX alias and generic" - assert bidResponse.seatbid.seat.sort() == [OPENX, GENERIC].sort() - - 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 pass auction as usual when secondary bidder respond all primary bidders"() { - given: "Default bid request with generic and openX bidders" - def bidRequest = getEnrichedBidRequest([OPENX]) - - 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: "OpenX set up response body" - openXBidder.setResponse() - - when: "PBS processes auction request" - def bidResponse = pbsServiceWithOpenXBidder.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 response should contain openX alias and generic" assert bidResponse.seatbid.seat.sort() == [OPENX, GENERIC].sort() From db28367dc64b3e0f6e000257b51f0f8f27f42f25 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Thu, 19 Mar 2026 19:40:48 +0200 Subject: [PATCH 9/9] minor update --- .../tests/SecondaryBidderSpec.groovy | 72 +++++++++---------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy index 6cdc4afb77b..e95b22b8bb0 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SecondaryBidderSpec.groovy @@ -10,6 +10,7 @@ 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 org.prebid.server.functional.util.PBSUtils import spock.lang.Shared import static org.prebid.server.functional.model.bidder.BidderName.ALIAS @@ -47,7 +48,7 @@ class SecondaryBidderSpec extends BaseSpec { genericAliasBidder.reset() } - def "PBS shouldn't emit a warning when secondary bidders config #secondaryBidder"() { + def "PBS shouldn't emit warning when secondary bidders config set to #secondaryBidder"() { given: "Default basic BidRequest with generic bidder" def bidRequest = BidRequest.defaultBidRequest.tap { enabledReturnAllBidStatus() @@ -63,7 +64,7 @@ class SecondaryBidderSpec extends BaseSpec { when: "PBS processes auction request" def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) - then: "PBs should processed bidder request" + then: "PBs should process bidder request" assert bidder.getBidderRequests(bidRequest.id) and: "PBS shouldn't contain errors, warnings and seat non bid" @@ -75,9 +76,9 @@ class SecondaryBidderSpec extends BaseSpec { secondaryBidder << [null, UNKNOWN] } - def "PBS should thread all bidders as primary when all requested bidders in secondary bidders config"() { + def "PBS should treat all bidders as primary when all requested bidders in secondary bidders config"() { given: "Default basic BidRequest with generic and openx bidder" - def bidRequest = getEnrichedBidRequest([OPENX]) + def bidRequest = getEnrichedBidRequest([GENERIC, OPENX]) and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { @@ -106,9 +107,9 @@ class SecondaryBidderSpec extends BaseSpec { assert !bidResponse.ext?.seatnonbid } - def "PBS shouldn't wait on non prioritize bidder when primary bidder respond"() { + def "PBS shouldn't wait on non-prioritized bidder when primary bidder responds"() { given: "Default bid request with generic and openX bidders" - def bidRequest = getEnrichedBidRequest([OPENX]) + def bidRequest = getEnrichedBidRequest([GENERIC, OPENX]) and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { @@ -124,13 +125,11 @@ class SecondaryBidderSpec extends BaseSpec { def bidResponse = pbsServiceWithOpenXBidder.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 + assert bidder.getBidderRequests(bidRequest.id) + assert openXBidder.getBidderRequest(bidRequest.id) and: "PBs response shouldn't contain response body from openX bidder" - assert !bidResponse?.ext?.debug?.httpcalls[OPENX]?.responseBody + assert !bidResponse?.ext?.debug?.httpcalls[OPENX.value]?.responseBody and: "PBS shouldn't contain error for openX due to timeout" assert !bidResponse.ext?.errors @@ -145,7 +144,7 @@ class SecondaryBidderSpec extends BaseSpec { assert seatNonBid.nonBid[0].statusCode == ERROR_TIMED_OUT } - def "PBS shouldn't treated alias bidder as secondary when root bidder code in secondary"() { + def "PBS shouldn't treat alias bidder as secondary when root bidder code in secondary"() { given: "Default bid request with generic and openX bidders" def bidRequest = getEnrichedBidRequest([OPENX, ALIAS]).tap { it.ext.prebid.aliases = [(ALIAS.value): OPENX] @@ -167,13 +166,10 @@ class SecondaryBidderSpec extends BaseSpec { when: "PBS processes auction request" def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) - then: "PBs should processed bidder request" - def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) - def openXAliasBidderRequests = genericAliasBidder.getBidderRequests(bidRequest.id) - def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id) - assert genericBidderRequests.size() == 1 - assert openXBidderRequests.size() == 1 - assert openXAliasBidderRequests.size() == 1 + then: "PBS should process bidder request" + assert bidder.getBidderRequest(bidRequest.id) + assert genericAliasBidder.getBidderRequest(bidRequest.id) + assert openXBidder.getBidderRequest(bidRequest.id) and: "PBs response should contain openX alias and generic" assert bidResponse.seatbid.seat.sort() == [ALIAS, GENERIC].sort() @@ -183,7 +179,7 @@ class SecondaryBidderSpec extends BaseSpec { assert httpCalls[GENERIC.value]?.responseBody assert httpCalls[ALIAS.value]?.responseBody - and: "PBs repose shouldn't contain response body from openX bidder" + and: "PBS response shouldn't contain response body from openX bidder" assert !httpCalls[OPENX.value]?.responseBody and: "PBS shouldn't contain error for openX due to timeout" @@ -221,16 +217,13 @@ class SecondaryBidderSpec extends BaseSpec { when: "PBS processes auction request" def bidResponse = pbsServiceWithOpenXBidder.sendAuctionRequest(bidRequest) - then: "PBs should processed bidder request" - def genericBidderRequests = bidder.getBidderRequests(bidRequest.id) - def openXAliasBidderRequests = genericAliasBidder.getBidderRequests(bidRequest.id) - def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id) - assert genericBidderRequests.size() == 1 - assert openXBidderRequests.size() == 1 - assert openXAliasBidderRequests.size() == 1 + then: "PBs should process bidder request" + assert bidder.getBidderRequest(bidRequest.id) + assert genericAliasBidder.getBidderRequest(bidRequest.id) + assert openXBidder.getBidderRequest(bidRequest.id) and: "PBs repose shouldn't contain response body from openX bidder" - assert !bidResponse?.ext?.debug?.httpcalls[ALIAS]?.responseBody + assert !bidResponse?.ext?.debug?.httpcalls[ALIAS.value]?.responseBody and: "PBS should contain error for openX due to timeout" assert !bidResponse.ext?.errors @@ -245,9 +238,9 @@ class SecondaryBidderSpec extends BaseSpec { assert seatNonBid.nonBid[0].statusCode == ERROR_TIMED_OUT } - def "PBS should pass auction as usual when secondary bidder respond first and primary with delay"() { + def "PBS should pass auction as usual when primary bidder responds after secondary"() { given: "Default bid request with generic and openX bidders" - def bidRequest = getEnrichedBidRequest([OPENX]) + def bidRequest = getEnrichedBidRequest([GENERIC, OPENX]) and: "Account in the DB" def accountConfig = AccountConfig.defaultAccountConfig.tap { @@ -257,20 +250,18 @@ class SecondaryBidderSpec extends BaseSpec { accountDao.save(account) and: "Set up openx bidder response with delay" - def almostMaxDelayMilliseconds = bidRequest.tmax - 200 - openXBidder.setResponseWithDelay(almostMaxDelayMilliseconds) + def openXRandomDelay = bidRequest.tmax - PBSUtils.getRandomNumber(100, 500) + openXBidder.setResponseWithDelay(openXRandomDelay) when: "PBS processes auction request" def bidResponse = pbsServiceWithOpenXBidder.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 + then: "PBs should process bidder request" + assert bidder.getBidderRequest(bidRequest.id) + assert openXBidder.getBidderRequest(bidRequest.id) - and: "PBs response should contain openX alias and generic" - assert bidResponse.seatbid.seat.sort() == [OPENX, GENERIC].sort() + and: "PBs response should contain generic and openX bidders" + assert bidResponse.seatbid.seat.sort() == [GENERIC, OPENX].sort() and: "PBS shouldn't contain errors, warnings and seat non bid" assert !bidResponse.ext?.warnings @@ -280,6 +271,9 @@ class SecondaryBidderSpec extends BaseSpec { private static BidRequest getEnrichedBidRequest(List bidderNames) { BidRequest.defaultBidRequest.tap { + if (bidderNames.contains(GENERIC)) { + it.imp[0]?.ext?.prebid?.bidder?.generic = new Generic() + } if (bidderNames.contains(OPENX)) { it.imp[0]?.ext?.prebid?.bidder?.openx = Openx.defaultOpenx }