From 9e139602c95aaec7bc85cb8362de4cabc4e22e16 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Thu, 13 Mar 2025 20:31:02 +0200 Subject: [PATCH 1/8] Tests: Record a default core bid ranking --- .../model/config/AccountAuctionConfig.groovy | 2 + .../model/config/AuctionCacheConfig.groovy | 6 + .../model/config/AuctionRankingConfig.groovy | 6 + .../model/response/auction/Prebid.groovy | 1 + .../functional/tests/TargetingSpec.groovy | 360 ++++++++++++++++++ 5 files changed, 375 insertions(+) create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/AuctionCacheConfig.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.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 8dc9831e3fa..1bb4e59fdf4 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 @@ -31,6 +31,8 @@ class AccountAuctionConfig { PrivacySandbox privacySandbox @JsonProperty("bidadjustments") BidAdjustment bidAdjustments + AuctionCacheConfig cache + AuctionRankingConfig ranking @JsonProperty("price_granularity") PriceGranularityType priceGranularitySnakeCase diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AuctionCacheConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AuctionCacheConfig.groovy new file mode 100644 index 00000000000..186011735e2 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/AuctionCacheConfig.groovy @@ -0,0 +1,6 @@ +package org.prebid.server.functional.model.config + +class AuctionCacheConfig { + + Boolean enabled +} diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.groovy new file mode 100644 index 00000000000..77f73131c8d --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.groovy @@ -0,0 +1,6 @@ +package org.prebid.server.functional.model.config + +class AuctionRankingConfig { + + Boolean enabled +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Prebid.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Prebid.groovy index 35fa5b6a540..d8accbf82ac 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/Prebid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Prebid.groovy @@ -15,4 +15,5 @@ class Prebid { Meta meta Map passThrough Video storedRequestAttributes + Integer rank } diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index e24d22b4b8f..98f4f5d6851 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -4,6 +4,8 @@ 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 +import org.prebid.server.functional.model.config.AuctionCacheConfig +import org.prebid.server.functional.model.config.AuctionRankingConfig import org.prebid.server.functional.model.config.PriceGranularityType import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredRequest @@ -11,13 +13,19 @@ import org.prebid.server.functional.model.db.StoredResponse import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.AdServerTargeting import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.MultiBid import org.prebid.server.functional.model.request.auction.PrebidCache import org.prebid.server.functional.model.request.auction.PriceGranularity import org.prebid.server.functional.model.request.auction.Range +import org.prebid.server.functional.model.request.auction.StoredAuctionResponse import org.prebid.server.functional.model.request.auction.StoredBidResponse import org.prebid.server.functional.model.request.auction.Targeting import org.prebid.server.functional.model.response.auction.Bid +import org.prebid.server.functional.model.response.auction.BidExt +import org.prebid.server.functional.model.response.auction.BidMediaType import org.prebid.server.functional.model.response.auction.BidResponse +import org.prebid.server.functional.model.response.auction.Prebid +import org.prebid.server.functional.model.response.auction.SeatBid import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.util.PBSUtils @@ -40,6 +48,7 @@ class TargetingSpec extends BaseSpec { private static final String DEFAULT_TARGETING_PREFIX = "hb" private static final Integer TARGETING_PREFIX_LENGTH = 11 private static final Integer MAX_TRUNCATE_ATTR_CHARS = 255 + private static final Integer MAX_BIDS_RANKING = 2 private static final String HB_ENV_AMP = "amp" def "PBS should include targeting bidder specific keys when alwaysIncludeDeals is true and deal bid wins"() { @@ -1344,6 +1353,357 @@ class TargetingSpec extends BaseSpec { priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) } + def "PBS should add hb_uuid and hb_cache_id targeting values when account config for auction.cache enabled or default"() { + given: "Bid request with cache and targeting" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + enableCache() + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + } + + and: "Account in the DB" + def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Set video bidResponse" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid.first.bid.first.mediaType = BidMediaType.VIDEO + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS response targeting contains bidder specific keys" + def targetingKeyMap = response.seatbid?.first()?.bid?.first()?.ext?.prebid?.targeting + assert targetingKeyMap.containsKey('hb_cache_id') + assert targetingKeyMap.containsKey("hb_cache_id_${GENERIC}".toString()) + assert targetingKeyMap.containsKey('hb_uuid') + assert targetingKeyMap.containsKey("hb_uuid_${GENERIC}".toString()) + + where: + accountAuctionConfig << [ + new AccountAuctionConfig(), + new AccountAuctionConfig(cache: new AuctionCacheConfig()), + new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: null)), + new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: true)) + ] + } + + def "PBS shouldn't add hb_uuid and hb_cache_id targeting values when account config for auction.cache disabled"() { + given: "Bid request with cache and targeting" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + enableCache() + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + } + + and: "Account in the DB" + def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Set video bidResponse" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid.first.bid.first.mediaType = BidMediaType.VIDEO + } + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS response targeting contains bidder specific keys" + def targetingKeyMap = response.seatbid?.first()?.bid?.first()?.ext?.prebid?.targeting + assert !targetingKeyMap.containsKey('hb_cache_id') + assert !targetingKeyMap.containsKey("hb_cache_id_${GENERIC}".toString()) + assert !targetingKeyMap.containsKey('hb_uuid') + assert !targetingKeyMap.containsKey("hb_uuid_${GENERIC}".toString()) + + where: + accountAuctionConfig << [ + new AccountAuctionConfig(), + new AccountAuctionConfig(cache: new AuctionCacheConfig()), + new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: null)), + new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: true)) + ] + } + + def "PBS shouldn't add bid ranked when account config for auction.ranking disabled or default"() { + given: "Bid request with alwaysIncludeDeals = true" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + } + + and: "Account in the DB" + def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Bid response with 2 bids where deal bid has higher price" + def imp = bidRequest.imp.first + def bids = [Bid.getDefaultBid(imp), Bid.getDefaultBid(imp)] + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = bids + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS bids in response shouldn't contain ranks" + assert response?.seatbid?.bid?.ext?.prebid?.rank.flatten() == [null] * MAX_BIDS_RANKING + + where: + accountAuctionConfig << [ + null, + new AccountAuctionConfig(), + new AccountAuctionConfig(ranking: new AuctionRankingConfig()), + new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: null)), + new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: false)) + ] + } + + def "PBS should add bid ranked and rank by deals when auction.ranking and preferdeals are enabled"() { + given: "Bid request with alwaysIncludeDeals = true" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { + preferDeals = true + } + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + } + + and: "Account in the DB" + def accountAuctionConfig = new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: true)) + def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Bid response with 2 bids where deal bid has lower price" + def bidPrice = PBSUtils.randomPrice + def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.price = bidPrice + } + def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.dealid = PBSUtils.randomNumber + it.price = bidPrice - 1 + } + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = [bidBiggerPrice, bidBDeal] + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should rank bid with deal as top priority" + def bids = response.seatbid.first.bid + assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 2 + } + + def "PBS should add bid ranked and rank by price when auction.ranking is enabled and preferdeals disabled"() { + given: "Bid request with alwaysIncludeDeals = true" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { + preferDeals = false + } + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + } + + and: "Account in the DB" + def accountAuctionConfig = new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: true)) + def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Bid response with 2 bids where deal bid has lower price" + def bidPrice = PBSUtils.randomPrice + def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.price = bidPrice + } + def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.dealid = PBSUtils.randomNumber + it.price = bidPrice - 1 + } + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = [bidBiggerPrice, bidBDeal] + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should rank bid with higher price as top priority" + def bids = response.seatbid.first.bid + assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 2 + } + + def "PBS should ignore bid ranked from original response when auction.ranking enabled"() { + given: "Bid request with alwaysIncludeDeals = true" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + } + + and: "Account in the DB" + def accountAuctionConfig = new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: true)) + def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Bid response with 2 bids where deal bid has lower price" + def bidPrice = PBSUtils.randomPrice + def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.price = bidPrice + it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) + } + def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.dealid = PBSUtils.randomNumber + it.price = bidPrice - 1 + it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) + } + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = [bidBiggerPrice, bidBDeal] + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should rank bid with deal as top priority" + def bids = response.seatbid.first.bid + assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 2 + } + + def "PBS should ignore bid ranked from original response when auction.ranking default"() { + given: "Bid request with alwaysIncludeDeals = true" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + } + + and: "Account in the DB" + def accountConfig = new AccountConfig(status: ACTIVE, auction: new AccountAuctionConfig()) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Bid response with 2 bids where deal bid has lower price" + def bidPrice = PBSUtils.randomPrice + def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.price = bidPrice + it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) + } + def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.dealid = PBSUtils.randomNumber + it.price = bidPrice - 1 + it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) + } + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = [bidBiggerPrice, bidBDeal] + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS bids in response shouldn't contain ranks" + assert response?.seatbid?.bid?.ext?.prebid?.rank.flatten() == [null] * MAX_BIDS_RANKING + } + + def "PBS should ignore bid ranked from stored response when auction.ranking enabled"() { + given: "Bid request with alwaysIncludeDeals = true" + def storedResponseId = PBSUtils.randomNumber + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + ext.prebid.storedAuctionResponse = new StoredAuctionResponse(id: storedResponseId) + } + + and: "Account in the DB" + def accountAuctionConfig = new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: true)) + def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Stored response in DB" + def bidPrice = PBSUtils.randomPrice + def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.price = bidPrice + it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) + } + def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.dealid = PBSUtils.randomNumber + it.price = bidPrice - 1 + it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) + } + def storedResponse = new StoredResponse(responseId: storedResponseId, + storedAuctionResponse: new SeatBid(bid: [bidBiggerPrice, bidBDeal], seat: GENERIC)) + storedResponseDao.save(storedResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should rank bid with deal as top priority" + def bids = response.seatbid.first.bid + assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 2 + } + + def "PBS should ignore bid ranked from stored response when auction.ranking default"() { + given: "Bid request with alwaysIncludeDeals = true" + def storedResponseId = PBSUtils.randomNumber + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + ext.prebid.storedAuctionResponse = new StoredAuctionResponse(id: storedResponseId) + } + + and: "Account in the DB" + def accountConfig = new AccountConfig(status: ACTIVE, auction: new AccountAuctionConfig()) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Stored response in DB" + def bidPrice = PBSUtils.randomPrice + def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.price = bidPrice + it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) + } + def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.dealid = PBSUtils.randomNumber + it.price = bidPrice - 1 + it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) + } + def storedResponse = new StoredResponse(responseId: storedResponseId, + storedAuctionResponse: new SeatBid(bid: [bidBiggerPrice, bidBDeal], seat: GENERIC)) + storedResponseDao.save(storedResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS bids in response shouldn't contain ranks" + assert response?.seatbid?.bid?.ext?.prebid?.rank.flatten() == [null] * MAX_BIDS_RANKING + } + def createAccountWithPriceGranularity(String accountId, PriceGranularityType priceGranularity) { def accountAuctionConfig = new AccountAuctionConfig(priceGranularity: priceGranularity) def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) From fab21409c1a6e0702a9d6a784202d5a72678ab0c Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Mon, 24 Mar 2025 12:23:32 +0200 Subject: [PATCH 2/8] update tests --- .../server/functional/tests/CacheSpec.groovy | 58 +++++++++++++++++++ .../functional/tests/TargetingSpec.groovy | 11 +--- .../ResponseCorrectionSpec.groovy | 55 ++++++++++++++++++ 3 files changed, 115 insertions(+), 9 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy index e145e5a972f..aa771862a27 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy @@ -3,6 +3,7 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountEventsConfig +import org.prebid.server.functional.model.config.AuctionCacheConfig import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.request.auction.Asset import org.prebid.server.functional.model.request.auction.BidRequest @@ -14,6 +15,7 @@ import org.prebid.server.functional.model.response.auction.Adm import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils +import static org.prebid.server.functional.model.AccountStatus.ACTIVE import static org.prebid.server.functional.model.response.auction.MediaType.BANNER import static org.prebid.server.functional.model.response.auction.MediaType.VIDEO @@ -492,4 +494,60 @@ class CacheSpec extends BaseSpec { PBSUtils.getRandomCase(" inline ") | " ${PBSUtils.getRandomCase(" impression ")} $PBSUtils.randomNumber " " inline ${PBSUtils.getRandomString()} " | " ImpreSSion " } + + def "PBS should cache bids from auction when account config for auction.cache disabled"() { + given: "Default BidRequest with cache, targeting" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.enableCache() + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + } + + and: "Account in the DB" + def accountAuctionConfig = new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: false)) + def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should call PBC" + assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 1 + + and: "PBS call shouldn't include api-key" + assert !prebidCache.getRequestHeaders(bidRequest.imp[0].id)[PBS_API_HEADER] + } + + def "PBS should call still call vtrack endpoint when cache is disabled in account config"() { + given: "Current value of metric prebid_cache.requests.ok" + def initialValue = getCurrentMetricValue(defaultPbsService, CACHE_REQUEST_OK_GLOBAL_METRIC) + + and: "Account in the DB" + def accountId = PBSUtils.randomNumber.toString() + + def accountAuctionConfig = new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: false)) + def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) + def account = new Account(uuid: accountId, config: accountConfig) + accountDao.save(account) + + and: "Default VtrackRequest" + def creative = encodeXml(Vast.getDefaultVastModel(PBSUtils.randomString)) + def request = VtrackRequest.getDefaultVtrackRequest(creative) + + when: "PBS processes vtrack request" + def cacheResponse = defaultPbsService.sendVtrackRequest(request, accountId) + + then: "vtrack request should be called" + assert cacheResponse + + and: "prebid_cache.creative_size.xml metric should be updated" + def metrics = defaultPbsService.sendCollectedMetricsRequest() + def creativeSize = creative.bytes.length + assert metrics[CACHE_REQUEST_OK_GLOBAL_METRIC] == initialValue + 1 + assert metrics[XML_CREATIVE_SIZE_GLOBAL_METRIC] == creativeSize + + and: "account..prebid_cache.creative_size.xml should be updated" + assert metrics[CACHE_REQUEST_OK_ACCOUNT_METRIC.formatted(accountId)] == 1 + assert metrics[XML_CREATIVE_SIZE_ACCOUNT_METRIC.formatted(accountId)] == creativeSize + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index 98f4f5d6851..316d48c21a2 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -1398,6 +1398,7 @@ class TargetingSpec extends BaseSpec { } and: "Account in the DB" + def accountAuctionConfig = new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: false)) def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) @@ -1411,20 +1412,12 @@ class TargetingSpec extends BaseSpec { when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) - then: "PBS response targeting contains bidder specific keys" + then: "PBS response targeting shouldn't contains bidder specific keys" def targetingKeyMap = response.seatbid?.first()?.bid?.first()?.ext?.prebid?.targeting assert !targetingKeyMap.containsKey('hb_cache_id') assert !targetingKeyMap.containsKey("hb_cache_id_${GENERIC}".toString()) assert !targetingKeyMap.containsKey('hb_uuid') assert !targetingKeyMap.containsKey("hb_uuid_${GENERIC}".toString()) - - where: - accountAuctionConfig << [ - new AccountAuctionConfig(), - new AccountAuctionConfig(cache: new AuctionCacheConfig()), - new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: null)), - new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: true)) - ] } def "PBS shouldn't add bid ranked when account config for auction.ranking disabled or default"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy index b4cbdd3fb88..fdb7d7dd57f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy @@ -423,6 +423,61 @@ class ResponseCorrectionSpec extends ModuleBaseSpec { ] } + def "PBS should modify response when requested video impression respond with invalid adm VAST keyword and disabled cache config"() { + given: "Start up time" + def start = Instant.now() + + and: "Default bid request with APP and Video imp" + def bidRequest = getDefaultVideoRequest(APP) + + and: "Set bidder response" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid[0].setAdm(PBSUtils.getRandomCase(admValue)) + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Save account with enabled response correction module" + def accountWithResponseCorrectionModule = accountConfigWithResponseCorrectionModule(bidRequest).tap { + config.auction.cache.enabled = false + } + accountDao.save(accountWithResponseCorrectionModule) + + when: "PBS processes auction request" + def response = pbsServiceWithResponseCorrectionModule.sendAuctionRequest(bidRequest) + + then: "PBS should emit log" + def logsByTime = pbsServiceWithResponseCorrectionModule.getLogsByTime(start) + def bidId = bidResponse.seatbid[0].bid[0].id + def responseCorrection = getLogsByText(logsByTime, bidId) + assert responseCorrection.size() == 1 + assert responseCorrection.any { + it.contains("Bid $bidId of bidder generic: changing media type to banner" as String) + } + + and: "Response should contain seatBid" + assert response.seatbid.size() == 1 + + and: "Response should contain single seatBid with proper media type" + assert response.seatbid.bid.ext.prebid.type.flatten() == [BANNER] + + and: "Response should contain single seatBid with proper meta media type" + assert response.seatbid.bid.ext.prebid.meta.mediaType.flatten() == [VIDEO.value] + + and: "Response shouldn't contain errors" + assert !response.ext.errors + + and: "Response shouldn't contain warnings" + assert !response.ext.warnings + + where: + admValue << [ + "<${' ' * PBSUtils.getRandomNumber(0, OPTIMAL_MAX_LENGTH)}VAST${PBSUtils.randomString}", + "<${' ' * PBSUtils.getRandomNumber(0, OPTIMAL_MAX_LENGTH)}VAST", + "<${' ' * PBSUtils.getRandomNumber(0, OPTIMAL_MAX_LENGTH)}VAST>", + "<${PBSUtils.randomString}VAST${' ' * PBSUtils.getRandomNumber(1, OPTIMAL_MAX_LENGTH)}" + ] + } + def "PBS should modify response when requested #mediaType impression respond with adm VAST keyword"() { given: "Start up time" def start = Instant.now() From a69952887425b3d1d2dd52802474323d66447e27 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Wed, 28 May 2025 11:34:28 +0300 Subject: [PATCH 3/8] update after review --- .../model/config/AuctionCacheConfig.groovy | 3 + .../model/config/AuctionRankingConfig.groovy | 3 + .../server/functional/tests/CacheSpec.groovy | 1 - .../functional/tests/TargetingSpec.groovy | 90 ++++++------------- 4 files changed, 33 insertions(+), 64 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AuctionCacheConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AuctionCacheConfig.groovy index 186011735e2..bbc4c464c6c 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AuctionCacheConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AuctionCacheConfig.groovy @@ -1,5 +1,8 @@ package org.prebid.server.functional.model.config +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) class AuctionCacheConfig { Boolean enabled diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.groovy index 77f73131c8d..cd8421e8f07 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.groovy @@ -1,5 +1,8 @@ package org.prebid.server.functional.model.config +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) class AuctionRankingConfig { Boolean enabled diff --git a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy index aa771862a27..d8f0cd83ed3 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy @@ -524,7 +524,6 @@ class CacheSpec extends BaseSpec { and: "Account in the DB" def accountId = PBSUtils.randomNumber.toString() - def accountAuctionConfig = new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: false)) def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) def account = new Account(uuid: accountId, config: accountConfig) diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index 316d48c21a2..c016ef7aaff 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -1470,9 +1470,7 @@ class TargetingSpec extends BaseSpec { } and: "Account in the DB" - def accountAuctionConfig = new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: true)) - def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) - def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) accountDao.save(account) and: "Bid response with 2 bids where deal bid has lower price" @@ -1480,12 +1478,12 @@ class TargetingSpec extends BaseSpec { def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.price = bidPrice } - def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + def bidWithDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.dealid = PBSUtils.randomNumber it.price = bidPrice - 1 } def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid = [bidBiggerPrice, bidBDeal] + seatbid[0].bid = [bidBiggerPrice, bidWithDeal] } and: "Set bidder response" @@ -1496,7 +1494,7 @@ class TargetingSpec extends BaseSpec { then: "PBS should rank bid with deal as top priority" def bids = response.seatbid.first.bid - assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == bidWithDeal.id).ext.prebid.rank == 1 assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 2 } @@ -1511,9 +1509,7 @@ class TargetingSpec extends BaseSpec { } and: "Account in the DB" - def accountAuctionConfig = new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: true)) - def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) - def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) accountDao.save(account) and: "Bid response with 2 bids where deal bid has lower price" @@ -1544,15 +1540,15 @@ class TargetingSpec extends BaseSpec { def "PBS should ignore bid ranked from original response when auction.ranking enabled"() { given: "Bid request with alwaysIncludeDeals = true" def bidRequest = BidRequest.getDefaultVideoRequest().tap { - it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { + preferDeals = false + } it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache() } and: "Account in the DB" - def accountAuctionConfig = new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: true)) - def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) - def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) accountDao.save(account) and: "Bid response with 2 bids where deal bid has lower price" @@ -1576,64 +1572,26 @@ class TargetingSpec extends BaseSpec { when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) - then: "PBS should rank bid with deal as top priority" + then: "PBS should rank bid with higher price as top priority" def bids = response.seatbid.first.bid - assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 1 - assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 2 - } - - def "PBS should ignore bid ranked from original response when auction.ranking default"() { - given: "Bid request with alwaysIncludeDeals = true" - def bidRequest = BidRequest.getDefaultVideoRequest().tap { - it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) - it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] - enableCache() - } - - and: "Account in the DB" - def accountConfig = new AccountConfig(status: ACTIVE, auction: new AccountAuctionConfig()) - def account = new Account(uuid: bidRequest.accountId, config: accountConfig) - accountDao.save(account) - - and: "Bid response with 2 bids where deal bid has lower price" - def bidPrice = PBSUtils.randomPrice - def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { - it.price = bidPrice - it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) - } - def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { - it.dealid = PBSUtils.randomNumber - it.price = bidPrice - 1 - it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) - } - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid = [bidBiggerPrice, bidBDeal] - } - - and: "Set bidder response" - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) - - then: "PBS bids in response shouldn't contain ranks" - assert response?.seatbid?.bid?.ext?.prebid?.rank.flatten() == [null] * MAX_BIDS_RANKING + assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 2 } def "PBS should ignore bid ranked from stored response when auction.ranking enabled"() { given: "Bid request with alwaysIncludeDeals = true" def storedResponseId = PBSUtils.randomNumber def bidRequest = BidRequest.getDefaultVideoRequest().tap { - it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { + preferDeals = false + } it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache() ext.prebid.storedAuctionResponse = new StoredAuctionResponse(id: storedResponseId) } and: "Account in the DB" - def accountAuctionConfig = new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: true)) - def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) - def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) accountDao.save(account) and: "Stored response in DB" @@ -1654,10 +1612,10 @@ class TargetingSpec extends BaseSpec { when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) - then: "PBS should rank bid with deal as top priority" + then: "PBS should rank bid with higher price as top priority" def bids = response.seatbid.first.bid - assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 1 - assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 2 + assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 2 } def "PBS should ignore bid ranked from stored response when auction.ranking default"() { @@ -1697,10 +1655,16 @@ class TargetingSpec extends BaseSpec { assert response?.seatbid?.bid?.ext?.prebid?.rank.flatten() == [null] * MAX_BIDS_RANKING } - def createAccountWithPriceGranularity(String accountId, PriceGranularityType priceGranularity) { + Account createAccountWithPriceGranularity(String accountId, PriceGranularityType priceGranularity) { def accountAuctionConfig = new AccountAuctionConfig(priceGranularity: priceGranularity) def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) - return new Account(uuid: accountId, config: accountConfig) + new Account(uuid: accountId, config: accountConfig) + } + + Account getAccountConfigWithAuctionRanking(String accountId, Boolean auctionRankingEnablement = true) { + def accountAuctionConfig = new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: auctionRankingEnablement)) + def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) + new Account(uuid: accountId, config: accountConfig) } private static PrebidServerService getEnabledWinBidsPbsService() { From 34428bb5a60927fdbde4fe7dc5445ae7fb4cdcd3 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Wed, 4 Jun 2025 00:05:49 +0300 Subject: [PATCH 4/8] Update functional tests --- .../model/config/AccountAuctionConfig.groovy | 3 +- ...fig.groovy => AccountRankingConfig.groovy} | 2 +- .../model/config/AuctionRankingConfig.groovy | 9 - .../model/response/auction/Bid.groovy | 30 ++ .../server/functional/tests/CacheSpec.groovy | 57 ---- .../functional/tests/TargetingSpec.groovy | 268 ++++++++++++++---- .../ResponseCorrectionSpec.groovy | 55 ---- 7 files changed, 249 insertions(+), 175 deletions(-) rename src/test/groovy/org/prebid/server/functional/model/config/{AuctionCacheConfig.groovy => AccountRankingConfig.groovy} (84%) delete mode 100644 src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.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 1bb4e59fdf4..3865e5254cc 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 @@ -22,6 +22,7 @@ class AccountAuctionConfig { Boolean debugAllow AccountBidValidationConfig bidValidations AccountEventsConfig events + AccountRankingConfig ranking AccountPriceFloorsConfig priceFloors Targeting targeting PaaFormat paaformat @@ -31,8 +32,6 @@ class AccountAuctionConfig { PrivacySandbox privacySandbox @JsonProperty("bidadjustments") BidAdjustment bidAdjustments - AuctionCacheConfig cache - AuctionRankingConfig ranking @JsonProperty("price_granularity") PriceGranularityType priceGranularitySnakeCase diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AuctionCacheConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountRankingConfig.groovy similarity index 84% rename from src/test/groovy/org/prebid/server/functional/model/config/AuctionCacheConfig.groovy rename to src/test/groovy/org/prebid/server/functional/model/config/AccountRankingConfig.groovy index bbc4c464c6c..64103717127 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AuctionCacheConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountRankingConfig.groovy @@ -3,7 +3,7 @@ package org.prebid.server.functional.model.config import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -class AuctionCacheConfig { +class AccountRankingConfig { Boolean enabled } diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.groovy deleted file mode 100644 index cd8421e8f07..00000000000 --- a/src/test/groovy/org/prebid/server/functional/model/config/AuctionRankingConfig.groovy +++ /dev/null @@ -1,9 +0,0 @@ -package org.prebid.server.functional.model.config - -import groovy.transform.ToString - -@ToString(includeNames = true, ignoreNulls = true) -class AuctionRankingConfig { - - Boolean enabled -} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy index 127ed32bfd9..353f5516935 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy @@ -8,6 +8,8 @@ import org.prebid.server.functional.model.request.auction.Imp import org.prebid.server.functional.util.ObjectMapperWrapper import org.prebid.server.functional.util.PBSUtils +import static groovy.lang.Closure.DELEGATE_FIRST + @ToString(includeNames = true, ignoreNulls = true) @EqualsAndHashCode class Bid implements ObjectMapperWrapper { @@ -70,6 +72,23 @@ class Bid implements ObjectMapperWrapper { } } + static List getDefaultMultyTypesBids(Imp imp, @DelegatesTo(Bid) Closure commonInit = null) { + List bids = [] + if (imp.banner) bids << createBid(imp, BidMediaType.BANNER) { adm = null } + if (imp.video) bids << createBid(imp, BidMediaType.VIDEO) + if (imp.nativeObj) bids << createBid(imp, BidMediaType.NATIVE) + if (imp.audio) bids << createBid(imp, BidMediaType.AUDIO) { adm = null } + + if (commonInit) { + bids.each { bid -> + commonInit.delegate = bid + commonInit.resolveStrategy = DELEGATE_FIRST + commonInit() + } + } + bids + } + void setAdm(Object adm) { if (adm instanceof Adm) { this.adm = encode(adm) @@ -79,4 +98,15 @@ class Bid implements ObjectMapperWrapper { this.adm = null } } + + private static Bid createBid(Imp imp, BidMediaType type, @DelegatesTo(Bid) Closure init = null) { + def bid = getDefaultBid(imp) + bid.mediaType = type + if (init) { + init.delegate = bid + init.resolveStrategy = DELEGATE_FIRST + init() + } + bid + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy index d8f0cd83ed3..e145e5a972f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy @@ -3,7 +3,6 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountEventsConfig -import org.prebid.server.functional.model.config.AuctionCacheConfig import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.request.auction.Asset import org.prebid.server.functional.model.request.auction.BidRequest @@ -15,7 +14,6 @@ import org.prebid.server.functional.model.response.auction.Adm import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils -import static org.prebid.server.functional.model.AccountStatus.ACTIVE import static org.prebid.server.functional.model.response.auction.MediaType.BANNER import static org.prebid.server.functional.model.response.auction.MediaType.VIDEO @@ -494,59 +492,4 @@ class CacheSpec extends BaseSpec { PBSUtils.getRandomCase(" inline ") | " ${PBSUtils.getRandomCase(" impression ")} $PBSUtils.randomNumber " " inline ${PBSUtils.getRandomString()} " | " ImpreSSion " } - - def "PBS should cache bids from auction when account config for auction.cache disabled"() { - given: "Default BidRequest with cache, targeting" - def bidRequest = BidRequest.defaultBidRequest.tap { - it.enableCache() - it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) - } - - and: "Account in the DB" - def accountAuctionConfig = new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: false)) - def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) - def account = new Account(uuid: bidRequest.accountId, config: accountConfig) - accountDao.save(account) - - when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "PBS should call PBC" - assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 1 - - and: "PBS call shouldn't include api-key" - assert !prebidCache.getRequestHeaders(bidRequest.imp[0].id)[PBS_API_HEADER] - } - - def "PBS should call still call vtrack endpoint when cache is disabled in account config"() { - given: "Current value of metric prebid_cache.requests.ok" - def initialValue = getCurrentMetricValue(defaultPbsService, CACHE_REQUEST_OK_GLOBAL_METRIC) - - and: "Account in the DB" - def accountId = PBSUtils.randomNumber.toString() - def accountAuctionConfig = new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: false)) - def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) - def account = new Account(uuid: accountId, config: accountConfig) - accountDao.save(account) - - and: "Default VtrackRequest" - def creative = encodeXml(Vast.getDefaultVastModel(PBSUtils.randomString)) - def request = VtrackRequest.getDefaultVtrackRequest(creative) - - when: "PBS processes vtrack request" - def cacheResponse = defaultPbsService.sendVtrackRequest(request, accountId) - - then: "vtrack request should be called" - assert cacheResponse - - and: "prebid_cache.creative_size.xml metric should be updated" - def metrics = defaultPbsService.sendCollectedMetricsRequest() - def creativeSize = creative.bytes.length - assert metrics[CACHE_REQUEST_OK_GLOBAL_METRIC] == initialValue + 1 - assert metrics[XML_CREATIVE_SIZE_GLOBAL_METRIC] == creativeSize - - and: "account..prebid_cache.creative_size.xml should be updated" - assert metrics[CACHE_REQUEST_OK_ACCOUNT_METRIC.formatted(accountId)] == 1 - assert metrics[XML_CREATIVE_SIZE_ACCOUNT_METRIC.formatted(accountId)] == creativeSize - } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index c016ef7aaff..0129db84e32 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -4,16 +4,17 @@ 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 -import org.prebid.server.functional.model.config.AuctionCacheConfig -import org.prebid.server.functional.model.config.AuctionRankingConfig +import org.prebid.server.functional.model.config.AccountRankingConfig import org.prebid.server.functional.model.config.PriceGranularityType import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.db.StoredResponse import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.AdServerTargeting +import org.prebid.server.functional.model.request.auction.Banner import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.MultiBid +import org.prebid.server.functional.model.request.auction.Native import org.prebid.server.functional.model.request.auction.PrebidCache import org.prebid.server.functional.model.request.auction.PriceGranularity import org.prebid.server.functional.model.request.auction.Range @@ -22,7 +23,6 @@ import org.prebid.server.functional.model.request.auction.StoredBidResponse import org.prebid.server.functional.model.request.auction.Targeting import org.prebid.server.functional.model.response.auction.Bid import org.prebid.server.functional.model.response.auction.BidExt -import org.prebid.server.functional.model.response.auction.BidMediaType import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.model.response.auction.Prebid import org.prebid.server.functional.model.response.auction.SeatBid @@ -1353,11 +1353,12 @@ class TargetingSpec extends BaseSpec { priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) } - def "PBS should add hb_uuid and hb_cache_id targeting values when account config for auction.cache enabled or default"() { - given: "Bid request with cache and targeting" + def "PBS shouldn't add bid ranked for request without multiBid when account config for auction.ranking disabled or default"() { + given: "Bid request with alwaysIncludeDeals = true" def bidRequest = BidRequest.getDefaultVideoRequest().tap { - enableCache() it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() } and: "Account in the DB" @@ -1365,64 +1366,76 @@ class TargetingSpec extends BaseSpec { def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) - and: "Set video bidResponse" + and: "Bid response with 2 bids where deal bid has higher price" + def imp = bidRequest.imp.first + def bids = [Bid.getDefaultBid(imp), Bid.getDefaultBid(imp)] def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid.first.bid.first.mediaType = BidMediaType.VIDEO + seatbid[0].bid = bids } + + and: "Set bidder response" bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) - then: "PBS response targeting contains bidder specific keys" - def targetingKeyMap = response.seatbid?.first()?.bid?.first()?.ext?.prebid?.targeting - assert targetingKeyMap.containsKey('hb_cache_id') - assert targetingKeyMap.containsKey("hb_cache_id_${GENERIC}".toString()) - assert targetingKeyMap.containsKey('hb_uuid') - assert targetingKeyMap.containsKey("hb_uuid_${GENERIC}".toString()) + then: "PBS bids in response shouldn't contain ranks" + assert response?.seatbid?.bid?.ext?.prebid?.rank?.flatten() == [null] * MAX_BIDS_RANKING where: accountAuctionConfig << [ + null, new AccountAuctionConfig(), - new AccountAuctionConfig(cache: new AuctionCacheConfig()), - new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: null)), - new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: true)) + new AccountAuctionConfig(ranking: new AccountRankingConfig()), + new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: null)), + new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: false)) ] } - def "PBS shouldn't add hb_uuid and hb_cache_id targeting values when account config for auction.cache disabled"() { - given: "Bid request with cache and targeting" + def "PBS shouldn't add bid ranked for request with multiBid when account config for auction.ranking disabled or default"() { + given: "Bid request with alwaysIncludeDeals = true" def bidRequest = BidRequest.getDefaultVideoRequest().tap { - enableCache() it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() } and: "Account in the DB" - def accountAuctionConfig = new AccountAuctionConfig(cache: new AuctionCacheConfig(enabled: false)) def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) - and: "Set video bidResponse" + and: "Bid response with 2 bids where deal bid has higher price" + def imp = bidRequest.imp.first + def bids = [Bid.getDefaultBid(imp), Bid.getDefaultBid(imp)] def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid.first.bid.first.mediaType = BidMediaType.VIDEO + seatbid[0].bid = bids } + + and: "Set bidder response" bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) - then: "PBS response targeting shouldn't contains bidder specific keys" - def targetingKeyMap = response.seatbid?.first()?.bid?.first()?.ext?.prebid?.targeting - assert !targetingKeyMap.containsKey('hb_cache_id') - assert !targetingKeyMap.containsKey("hb_cache_id_${GENERIC}".toString()) - assert !targetingKeyMap.containsKey('hb_uuid') - assert !targetingKeyMap.containsKey("hb_uuid_${GENERIC}".toString()) + then: "PBS bids in response shouldn't contain ranks" + assert response?.seatbid?.bid?.ext?.prebid?.rank?.flatten() == [null] * MAX_BIDS_RANKING + + where: + accountAuctionConfig << [ + null, + new AccountAuctionConfig(), + new AccountAuctionConfig(ranking: new AccountRankingConfig()), + new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: null)), + new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: false)) + ] } - def "PBS shouldn't add bid ranked when account config for auction.ranking disabled or default"() { + def "PBS shouldn't add bid ranked for multiple media types request when account config for auction.ranking disabled or default"() { given: "Bid request with alwaysIncludeDeals = true" def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.imp.first.banner = Banner.getDefaultBanner() + it.imp.first.nativeObj = Native.getDefaultNative() it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache() @@ -1434,10 +1447,8 @@ class TargetingSpec extends BaseSpec { accountDao.save(account) and: "Bid response with 2 bids where deal bid has higher price" - def imp = bidRequest.imp.first - def bids = [Bid.getDefaultBid(imp), Bid.getDefaultBid(imp)] def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid = bids + it.seatbid.first.bid = Bid.getDefaultMultyTypesBids(bidRequest.imp.first) } and: "Set bidder response" @@ -1447,25 +1458,24 @@ class TargetingSpec extends BaseSpec { def response = defaultPbsService.sendAuctionRequest(bidRequest) then: "PBS bids in response shouldn't contain ranks" - assert response?.seatbid?.bid?.ext?.prebid?.rank.flatten() == [null] * MAX_BIDS_RANKING + assert response?.seatbid?.bid?.ext?.prebid?.rank?.flatten() == [null] * MAX_BIDS_RANKING where: accountAuctionConfig << [ null, new AccountAuctionConfig(), - new AccountAuctionConfig(ranking: new AuctionRankingConfig()), - new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: null)), - new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: false)) + new AccountAuctionConfig(ranking: new AccountRankingConfig()), + new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: null)), + new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: false)) ] } - def "PBS should add bid ranked and rank by deals when auction.ranking and preferdeals are enabled"() { + def "PBS should add bid ranked and rank by deals for request without multiBid when auction.ranking and preferDeals are enabled"() { given: "Bid request with alwaysIncludeDeals = true" def bidRequest = BidRequest.getDefaultVideoRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = true } - it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache() } @@ -1476,11 +1486,48 @@ class TargetingSpec extends BaseSpec { and: "Bid response with 2 bids where deal bid has lower price" def bidPrice = PBSUtils.randomPrice def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.price = bidPrice + 1 + } + def bidWithDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.dealid = PBSUtils.randomNumber it.price = bidPrice } + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = [bidBiggerPrice, bidWithDeal] + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should rank single bid" + assert response.seatbid.first.bid?.ext?.prebid?.rank?.flatten() == [1] + } + + def "PBS should add bid ranked and rank by deals for request with multiBid when auction.ranking and preferDeals are enabled"() { + given: "Bid request with alwaysIncludeDeals = true" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { + preferDeals = true + } + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + } + + and: "Account in the DB" + def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) + accountDao.save(account) + + and: "Bid response with 2 bids where deal bid has lower price" + def bidPrice = PBSUtils.randomPrice + def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.price = bidPrice + 1 + } def bidWithDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.dealid = PBSUtils.randomNumber - it.price = bidPrice - 1 + it.price = bidPrice } def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { seatbid[0].bid = [bidBiggerPrice, bidWithDeal] @@ -1498,11 +1545,13 @@ class TargetingSpec extends BaseSpec { assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 2 } - def "PBS should add bid ranked and rank by price when auction.ranking is enabled and preferdeals disabled"() { + def "PBS should add bid ranked and rank by deals for multiple media types request when auction.ranking and preferDeals are enabled"() { given: "Bid request with alwaysIncludeDeals = true" def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.imp.first.banner = Banner.getDefaultBanner() + it.imp.first.nativeObj = Native.getDefaultNative() it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { - preferDeals = false + preferDeals = true } it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache() @@ -1512,14 +1561,130 @@ class TargetingSpec extends BaseSpec { def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) accountDao.save(account) + and: "Bid response with 2 bids where deal bid has lower price" + def bidPrice = PBSUtils.randomPrice + def bidBiggerPrice = Bid.getDefaultMultyTypesBids(bidRequest.imp.first).first.tap { + it.price = bidPrice + 1 + } + def bidWithDeal = Bid.getDefaultMultyTypesBids(bidRequest.imp.first).last.tap { + it.dealid = PBSUtils.randomNumber + it.price = bidPrice + } + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = [bidBiggerPrice, bidWithDeal] + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should rank bid with deal as top priority" + def bids = response.seatbid.first.bid + assert bids.find(it -> it.id == bidWithDeal.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 2 + } + + def "PBS should add bid ranked and rank by price for request without multiBid when auction.ranking is enabled and preferDeals disabled"() { + given: "Bid request with alwaysIncludeDeals = true" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { + preferDeals = false + } + enableCache() + } + + and: "Account in the DB" + def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) + accountDao.save(account) + and: "Bid response with 2 bids where deal bid has lower price" def bidPrice = PBSUtils.randomPrice def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.price = bidPrice + 1 + } + def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.dealid = PBSUtils.randomNumber it.price = bidPrice } + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = [bidBiggerPrice, bidBDeal] + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should rank single bid" + assert response.seatbid.first.bid?.ext?.prebid?.rank?.flatten() == [1] + } + + def "PBS should add bid ranked and rank by price for request with multiBid when auction.ranking is enabled and preferDeals disabled"() { + given: "Bid request with alwaysIncludeDeals = true" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { + preferDeals = false + } + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + } + + and: "Account in the DB" + def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) + accountDao.save(account) + + and: "Bid response with 2 bids where deal bid has lower price" + def bidPrice = PBSUtils.randomPrice + def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.price = bidPrice + 1 + } def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.dealid = PBSUtils.randomNumber - it.price = bidPrice - 1 + it.price = bidPrice + } + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = [bidBiggerPrice, bidBDeal] + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should rank bid with higher price as top priority" + def bids = response.seatbid.first.bid + assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 2 + } + + def "PBS should add bid ranked and rank by price for multiple media types request when auction.ranking is enabled and preferDeals disabled"() { + given: "Bid request with alwaysIncludeDeals = true" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.imp.first.banner = Banner.getDefaultBanner() + it.imp.first.nativeObj = Native.getDefaultNative() + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { + preferDeals = false + } + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + } + + and: "Account in the DB" + def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) + accountDao.save(account) + + and: "Bid response with 2 bids where deal bid has lower price" + def bidPrice = PBSUtils.randomPrice + def bidBiggerPrice = Bid.getDefaultMultyTypesBids(bidRequest.imp.first).first.tap { + it.price = bidPrice + 1 + } + def bidBDeal = Bid.getDefaultMultyTypesBids(bidRequest.imp.first).last.tap { + it.dealid = PBSUtils.randomNumber + it.price = bidPrice } def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { seatbid[0].bid = [bidBiggerPrice, bidBDeal] @@ -1532,6 +1697,7 @@ class TargetingSpec extends BaseSpec { def response = defaultPbsService.sendAuctionRequest(bidRequest) then: "PBS should rank bid with higher price as top priority" + assert !response.ext.warnings def bids = response.seatbid.first.bid assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 1 assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 2 @@ -1554,12 +1720,12 @@ class TargetingSpec extends BaseSpec { and: "Bid response with 2 bids where deal bid has lower price" def bidPrice = PBSUtils.randomPrice def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { - it.price = bidPrice + it.price = bidPrice + 1 it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) } def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.dealid = PBSUtils.randomNumber - it.price = bidPrice - 1 + it.price = bidPrice it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) } def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { @@ -1597,12 +1763,12 @@ class TargetingSpec extends BaseSpec { and: "Stored response in DB" def bidPrice = PBSUtils.randomPrice def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { - it.price = bidPrice + it.price = bidPrice + 1 it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) } def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.dealid = PBSUtils.randomNumber - it.price = bidPrice - 1 + it.price = bidPrice it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) } def storedResponse = new StoredResponse(responseId: storedResponseId, @@ -1636,12 +1802,12 @@ class TargetingSpec extends BaseSpec { and: "Stored response in DB" def bidPrice = PBSUtils.randomPrice def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { - it.price = bidPrice + it.price = bidPrice + 1 it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) } def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.dealid = PBSUtils.randomNumber - it.price = bidPrice - 1 + it.price = bidPrice it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) } def storedResponse = new StoredResponse(responseId: storedResponseId, @@ -1652,7 +1818,7 @@ class TargetingSpec extends BaseSpec { def response = defaultPbsService.sendAuctionRequest(bidRequest) then: "PBS bids in response shouldn't contain ranks" - assert response?.seatbid?.bid?.ext?.prebid?.rank.flatten() == [null] * MAX_BIDS_RANKING + assert response?.seatbid?.bid?.ext?.prebid?.rank?.flatten() == [null] * MAX_BIDS_RANKING } Account createAccountWithPriceGranularity(String accountId, PriceGranularityType priceGranularity) { @@ -1662,7 +1828,7 @@ class TargetingSpec extends BaseSpec { } Account getAccountConfigWithAuctionRanking(String accountId, Boolean auctionRankingEnablement = true) { - def accountAuctionConfig = new AccountAuctionConfig(ranking: new AuctionRankingConfig(enabled: auctionRankingEnablement)) + def accountAuctionConfig = new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: auctionRankingEnablement)) def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) new Account(uuid: accountId, config: accountConfig) } diff --git a/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy index fdb7d7dd57f..b4cbdd3fb88 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/module/responsecorrenction/ResponseCorrectionSpec.groovy @@ -423,61 +423,6 @@ class ResponseCorrectionSpec extends ModuleBaseSpec { ] } - def "PBS should modify response when requested video impression respond with invalid adm VAST keyword and disabled cache config"() { - given: "Start up time" - def start = Instant.now() - - and: "Default bid request with APP and Video imp" - def bidRequest = getDefaultVideoRequest(APP) - - and: "Set bidder response" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid[0].setAdm(PBSUtils.getRandomCase(admValue)) - } - bidder.setResponse(bidRequest.id, bidResponse) - - and: "Save account with enabled response correction module" - def accountWithResponseCorrectionModule = accountConfigWithResponseCorrectionModule(bidRequest).tap { - config.auction.cache.enabled = false - } - accountDao.save(accountWithResponseCorrectionModule) - - when: "PBS processes auction request" - def response = pbsServiceWithResponseCorrectionModule.sendAuctionRequest(bidRequest) - - then: "PBS should emit log" - def logsByTime = pbsServiceWithResponseCorrectionModule.getLogsByTime(start) - def bidId = bidResponse.seatbid[0].bid[0].id - def responseCorrection = getLogsByText(logsByTime, bidId) - assert responseCorrection.size() == 1 - assert responseCorrection.any { - it.contains("Bid $bidId of bidder generic: changing media type to banner" as String) - } - - and: "Response should contain seatBid" - assert response.seatbid.size() == 1 - - and: "Response should contain single seatBid with proper media type" - assert response.seatbid.bid.ext.prebid.type.flatten() == [BANNER] - - and: "Response should contain single seatBid with proper meta media type" - assert response.seatbid.bid.ext.prebid.meta.mediaType.flatten() == [VIDEO.value] - - and: "Response shouldn't contain errors" - assert !response.ext.errors - - and: "Response shouldn't contain warnings" - assert !response.ext.warnings - - where: - admValue << [ - "<${' ' * PBSUtils.getRandomNumber(0, OPTIMAL_MAX_LENGTH)}VAST${PBSUtils.randomString}", - "<${' ' * PBSUtils.getRandomNumber(0, OPTIMAL_MAX_LENGTH)}VAST", - "<${' ' * PBSUtils.getRandomNumber(0, OPTIMAL_MAX_LENGTH)}VAST>", - "<${PBSUtils.randomString}VAST${' ' * PBSUtils.getRandomNumber(1, OPTIMAL_MAX_LENGTH)}" - ] - } - def "PBS should modify response when requested #mediaType impression respond with adm VAST keyword"() { given: "Start up time" def start = Instant.now() From 70010ce42f4b265ba623af2b63971b7f5190ec68 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Thu, 5 Jun 2025 00:23:41 +0300 Subject: [PATCH 5/8] Update functional tests --- .../functional/tests/TargetingSpec.groovy | 114 +++++++++++++++--- 1 file changed, 96 insertions(+), 18 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index 0129db84e32..9982b79aae9 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -7,23 +7,28 @@ import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountRankingConfig import org.prebid.server.functional.model.config.PriceGranularityType import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.db.StoredImp import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.db.StoredResponse import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.AdServerTargeting import org.prebid.server.functional.model.request.auction.Banner import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.Imp import org.prebid.server.functional.model.request.auction.MultiBid import org.prebid.server.functional.model.request.auction.Native import org.prebid.server.functional.model.request.auction.PrebidCache +import org.prebid.server.functional.model.request.auction.PrebidStoredRequest import org.prebid.server.functional.model.request.auction.PriceGranularity import org.prebid.server.functional.model.request.auction.Range import org.prebid.server.functional.model.request.auction.StoredAuctionResponse import org.prebid.server.functional.model.request.auction.StoredBidResponse import org.prebid.server.functional.model.request.auction.Targeting +import org.prebid.server.functional.model.request.auction.Video import org.prebid.server.functional.model.response.auction.Bid import org.prebid.server.functional.model.response.auction.BidExt import org.prebid.server.functional.model.response.auction.BidResponse +import org.prebid.server.functional.model.response.auction.MediaType import org.prebid.server.functional.model.response.auction.Prebid import org.prebid.server.functional.model.response.auction.SeatBid import org.prebid.server.functional.service.PrebidServerException @@ -1744,36 +1749,47 @@ class TargetingSpec extends BaseSpec { assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 2 } - def "PBS should ignore bid ranked from stored response when auction.ranking enabled"() { - given: "Bid request with alwaysIncludeDeals = true" - def storedResponseId = PBSUtils.randomNumber + def "PBS should add bid ranked and rank by price for request with stored imp when auction.ranking enabled"() { + given: "Bid request with alwaysIncludeDeals = false" + def storedRequestId = PBSUtils.randomNumber def bidRequest = BidRequest.getDefaultVideoRequest().tap { + imp.first.ext.prebid.storedRequest = new PrebidStoredRequest(id: storedRequestId) it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = false } it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache() - ext.prebid.storedAuctionResponse = new StoredAuctionResponse(id: storedResponseId) } and: "Account in the DB" def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) accountDao.save(account) - and: "Stored response in DB" + and: "Save storedImp into DB" + def impression = Imp.getDefaultImpression(MediaType.BANNER).tap { + id = storedRequestId + video = Video.getDefaultVideo() + } + def storedImp = StoredImp.getStoredImp(bidRequest.accountId, impression) + storedImpDao.save(storedImp) + + and: "Bid response with 2 bids where deal bid has lower price" def bidPrice = PBSUtils.randomPrice - def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + def bidBiggerPrice = Bid.getDefaultMultyTypesBids(impression).first.tap { it.price = bidPrice + 1 - it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) + impid = bidRequest.imp.id.first } - def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + def bidBDeal = Bid.getDefaultMultyTypesBids(impression).last.tap { + impid = bidRequest.imp.id.first it.dealid = PBSUtils.randomNumber it.price = bidPrice - it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) } - def storedResponse = new StoredResponse(responseId: storedResponseId, - storedAuctionResponse: new SeatBid(bid: [bidBiggerPrice, bidBDeal], seat: GENERIC)) - storedResponseDao.save(storedResponse) + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = [bidBiggerPrice, bidBDeal] + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) @@ -1784,7 +1800,55 @@ class TargetingSpec extends BaseSpec { assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 2 } - def "PBS should ignore bid ranked from stored response when auction.ranking default"() { + def "PBS shouldn't rank bids for request with stored imp when auction.ranking default"() { + given: "Bid request with alwaysIncludeDeals = true" + def storedRequestId = PBSUtils.randomNumber + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + imp.first.ext.prebid.storedRequest = new PrebidStoredRequest(id: storedRequestId) + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + } + + and: "Account in the DB" + def accountConfig = new AccountConfig(status: ACTIVE, auction: new AccountAuctionConfig()) + def account = new Account(uuid: bidRequest.accountId, config: accountConfig) + accountDao.save(account) + + and: "Save storedImp into DB" + def impression = Imp.getDefaultImpression(MediaType.BANNER).tap { + id = storedRequestId + video = Video.getDefaultVideo() + } + def storedImp = StoredImp.getStoredImp(bidRequest.accountId, impression) + storedImpDao.save(storedImp) + + and: "Bid response with 2 bids where deal bid has lower price" + def bidPrice = PBSUtils.randomPrice + def bidBiggerPrice = Bid.getDefaultMultyTypesBids(impression).first.tap { + impid = bidRequest.imp.id.first + it.price = bidPrice + 1 + } + def bidBDeal = Bid.getDefaultMultyTypesBids(impression).last.tap { + impid = bidRequest.imp.id.first + it.dealid = PBSUtils.randomNumber + it.price = bidPrice + } + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = [bidBiggerPrice, bidBDeal] + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS bids in response shouldn't contain ranks" + assert response?.seatbid?.bid?.ext?.prebid?.rank?.flatten() == [null] * MAX_BIDS_RANKING + } + + def "PBS should copy bid ranked from stored response when auction.ranking #auction"() { given: "Bid request with alwaysIncludeDeals = true" def storedResponseId = PBSUtils.randomNumber def bidRequest = BidRequest.getDefaultVideoRequest().tap { @@ -1795,20 +1859,22 @@ class TargetingSpec extends BaseSpec { } and: "Account in the DB" - def accountConfig = new AccountConfig(status: ACTIVE, auction: new AccountAuctionConfig()) + def accountConfig = new AccountConfig(status: ACTIVE, auction: auction) def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) and: "Stored response in DB" def bidPrice = PBSUtils.randomPrice + def bidBiggerPriceRanking = PBSUtils.randomNumber def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.price = bidPrice + 1 - it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) + it.ext = new BidExt(prebid: new Prebid(rank: bidBiggerPriceRanking)) } + def bidBDealRanking = PBSUtils.randomNumber def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.dealid = PBSUtils.randomNumber it.price = bidPrice - it.ext = new BidExt(prebid: new Prebid(rank: PBSUtils.randomNumber)) + it.ext = new BidExt(prebid: new Prebid(rank: bidBDealRanking)) } def storedResponse = new StoredResponse(responseId: storedResponseId, storedAuctionResponse: new SeatBid(bid: [bidBiggerPrice, bidBDeal], seat: GENERIC)) @@ -1817,8 +1883,20 @@ class TargetingSpec extends BaseSpec { when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) - then: "PBS bids in response shouldn't contain ranks" - assert response?.seatbid?.bid?.ext?.prebid?.rank?.flatten() == [null] * MAX_BIDS_RANKING + then: "PBS should copy bid ranked from stored response" + def bids = response.seatbid.first.bid + assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == bidBiggerPriceRanking + assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == bidBDealRanking + + where: + auction << [ + null, + new AccountAuctionConfig(), + new AccountAuctionConfig(ranking: new AccountRankingConfig()), + new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: null)), + new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: false)), + new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: true)) + ] } Account createAccountWithPriceGranularity(String accountId, PriceGranularityType priceGranularity) { From d08b483386fc9cbc14c48c159e091e628178844f Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Thu, 5 Jun 2025 15:45:06 +0300 Subject: [PATCH 6/8] update after review --- .../model/response/auction/Bid.groovy | 2 +- .../functional/tests/TargetingSpec.groovy | 177 ++++++------------ 2 files changed, 58 insertions(+), 121 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy index 353f5516935..18bc588d1d3 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Bid.groovy @@ -72,7 +72,7 @@ class Bid implements ObjectMapperWrapper { } } - static List getDefaultMultyTypesBids(Imp imp, @DelegatesTo(Bid) Closure commonInit = null) { + static List getDefaultMultiTypesBids(Imp imp, @DelegatesTo(Bid) Closure commonInit = null) { List bids = [] if (imp.banner) bids << createBid(imp, BidMediaType.BANNER) { adm = null } if (imp.video) bids << createBid(imp, BidMediaType.VIDEO) diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index 9982b79aae9..dc354764e2f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -27,6 +27,7 @@ import org.prebid.server.functional.model.request.auction.Targeting import org.prebid.server.functional.model.request.auction.Video import org.prebid.server.functional.model.response.auction.Bid import org.prebid.server.functional.model.response.auction.BidExt +import org.prebid.server.functional.model.response.auction.BidMediaType import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.model.response.auction.MediaType import org.prebid.server.functional.model.response.auction.Prebid @@ -43,6 +44,7 @@ import static org.prebid.server.functional.model.AccountStatus.ACTIVE import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.config.PriceGranularityType.UNKNOWN import static org.prebid.server.functional.model.response.auction.ErrorType.TARGETING +import static org.prebid.server.functional.model.response.auction.MediaType.VIDEO import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer class TargetingSpec extends BaseSpec { @@ -53,7 +55,7 @@ class TargetingSpec extends BaseSpec { private static final String DEFAULT_TARGETING_PREFIX = "hb" private static final Integer TARGETING_PREFIX_LENGTH = 11 private static final Integer MAX_TRUNCATE_ATTR_CHARS = 255 - private static final Integer MAX_BIDS_RANKING = 2 + private static final Integer MAX_BIDS_RANKING = 3 private static final String HB_ENV_AMP = "amp" def "PBS should include targeting bidder specific keys when alwaysIncludeDeals is true and deal bid wins"() { @@ -1180,7 +1182,7 @@ class TargetingSpec extends BaseSpec { assert bidderRequest?.ext?.prebid?.targeting?.priceGranularity == requestPriceGranularity where: - priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) + priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) } def "PBS amp should prioritize price granularity from original request over account config"() { @@ -1210,7 +1212,7 @@ class TargetingSpec extends BaseSpec { assert bidderRequest?.ext?.prebid?.targeting?.priceGranularity == requestPriceGranularity where: - priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) + priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) } def "PBS auction should include price granularity from account config when original request doesn't contain price granularity"() { @@ -1231,7 +1233,7 @@ class TargetingSpec extends BaseSpec { assert bidderRequest?.ext?.prebid?.targeting?.priceGranularity == PriceGranularity.getDefault(priceGranularity) where: - priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) + priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) } def "PBS auction should include price granularity from account config with different name case when original request doesn't contain price granularity"() { @@ -1252,7 +1254,7 @@ class TargetingSpec extends BaseSpec { assert bidderRequest?.ext?.prebid?.targeting?.priceGranularity == PriceGranularity.getDefault(priceGranularity) where: - priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) + priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) } def "PBS auction should include price granularity from default account config when original request doesn't contain price granularity"() { @@ -1355,10 +1357,10 @@ class TargetingSpec extends BaseSpec { assert bidderRequest?.ext?.prebid?.targeting?.priceGranularity == PriceGranularity.getDefault(priceGranularity) where: - priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) + priceGranularity << (PriceGranularityType.values() - UNKNOWN as List) } - def "PBS shouldn't add bid ranked for request without multiBid when account config for auction.ranking disabled or default"() { + def "PBS shouldn't add bid ranked for request when account config for auction.ranking disabled or default"() { given: "Bid request with alwaysIncludeDeals = true" def bidRequest = BidRequest.getDefaultVideoRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) @@ -1371,48 +1373,9 @@ class TargetingSpec extends BaseSpec { def account = new Account(uuid: bidRequest.accountId, config: accountConfig) accountDao.save(account) - and: "Bid response with 2 bids where deal bid has higher price" - def imp = bidRequest.imp.first - def bids = [Bid.getDefaultBid(imp), Bid.getDefaultBid(imp)] - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid = bids - } - - and: "Set bidder response" - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) - - then: "PBS bids in response shouldn't contain ranks" - assert response?.seatbid?.bid?.ext?.prebid?.rank?.flatten() == [null] * MAX_BIDS_RANKING - - where: - accountAuctionConfig << [ - null, - new AccountAuctionConfig(), - new AccountAuctionConfig(ranking: new AccountRankingConfig()), - new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: null)), - new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: false)) - ] - } - - def "PBS shouldn't add bid ranked for request with multiBid when account config for auction.ranking disabled or default"() { - given: "Bid request with alwaysIncludeDeals = true" - def bidRequest = BidRequest.getDefaultVideoRequest().tap { - it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) - it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] - enableCache() - } - - and: "Account in the DB" - def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) - def account = new Account(uuid: bidRequest.accountId, config: accountConfig) - accountDao.save(account) - - and: "Bid response with 2 bids where deal bid has higher price" + and: "Bid response with 3 bids where deal bid has higher price" def imp = bidRequest.imp.first - def bids = [Bid.getDefaultBid(imp), Bid.getDefaultBid(imp)] + def bids = [Bid.getDefaultBid(imp), Bid.getDefaultBid(imp), Bid.getDefaultBid(imp)] def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { seatbid[0].bid = bids } @@ -1436,45 +1399,6 @@ class TargetingSpec extends BaseSpec { ] } - def "PBS shouldn't add bid ranked for multiple media types request when account config for auction.ranking disabled or default"() { - given: "Bid request with alwaysIncludeDeals = true" - def bidRequest = BidRequest.getDefaultVideoRequest().tap { - it.imp.first.banner = Banner.getDefaultBanner() - it.imp.first.nativeObj = Native.getDefaultNative() - it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) - it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] - enableCache() - } - - and: "Account in the DB" - def accountConfig = new AccountConfig(status: ACTIVE, auction: accountAuctionConfig) - def account = new Account(uuid: bidRequest.accountId, config: accountConfig) - accountDao.save(account) - - and: "Bid response with 2 bids where deal bid has higher price" - def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - it.seatbid.first.bid = Bid.getDefaultMultyTypesBids(bidRequest.imp.first) - } - - and: "Set bidder response" - bidder.setResponse(bidRequest.id, bidResponse) - - when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) - - then: "PBS bids in response shouldn't contain ranks" - assert response?.seatbid?.bid?.ext?.prebid?.rank?.flatten() == [null] * MAX_BIDS_RANKING - - where: - accountAuctionConfig << [ - null, - new AccountAuctionConfig(), - new AccountAuctionConfig(ranking: new AccountRankingConfig()), - new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: null)), - new AccountAuctionConfig(ranking: new AccountRankingConfig(enabled: false)) - ] - } - def "PBS should add bid ranked and rank by deals for request without multiBid when auction.ranking and preferDeals are enabled"() { given: "Bid request with alwaysIncludeDeals = true" def bidRequest = BidRequest.getDefaultVideoRequest().tap { @@ -1508,34 +1432,44 @@ class TargetingSpec extends BaseSpec { def response = defaultPbsService.sendAuctionRequest(bidRequest) then: "PBS should rank single bid" - assert response.seatbid.first.bid?.ext?.prebid?.rank?.flatten() == [1] + verifyAll(response.seatbid.first.bid) { + it.id == [bidWithDeal.id] + it.price == [bidWithDeal.price] + it.ext.prebid.rank == [1] + } } - def "PBS should add bid ranked and rank by deals for request with multiBid when auction.ranking and preferDeals are enabled"() { - given: "Bid request with alwaysIncludeDeals = true" + def "PBS should assign bid ranks for each imp separately when request has multiple imps and multiBid is configured"() { + given: "Bid request with multiple imps" def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.imp.first.nativeObj = Native.getDefaultNative() + imp.add(Imp.getDefaultImpression(VIDEO)) it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { - preferDeals = true + preferDeals = requestPreferDeals } it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache() } - and: "Account in the DB" + and: "Account in DB" def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) accountDao.save(account) - and: "Bid response with 2 bids where deal bid has lower price" + and: "Bid response with multiple bids" def bidPrice = PBSUtils.randomPrice - def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { - it.price = bidPrice + 1 + def bidLowerPrice = Bid.getDefaultBid(bidRequest.imp.first).tap { + price = bidPrice + mediaType = BidMediaType.NATIVE } - def bidWithDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { - it.dealid = PBSUtils.randomNumber - it.price = bidPrice + def bidHigherPrice = Bid.getDefaultBid(bidRequest.imp.first).tap { + price = bidPrice + 1 + } + def bidWithDeal = Bid.getDefaultBid(bidRequest.imp.last).tap { + dealid = PBSUtils.randomNumber + price = bidPrice } def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid = [bidBiggerPrice, bidWithDeal] + seatbid[0].bid = [bidLowerPrice, bidHigherPrice, bidWithDeal] } and: "Set bidder response" @@ -1544,10 +1478,18 @@ class TargetingSpec extends BaseSpec { when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) - then: "PBS should rank bid with deal as top priority" + then: "PBS should rank bids for first imp" def bids = response.seatbid.first.bid - assert bids.find(it -> it.id == bidWithDeal.id).ext.prebid.rank == 1 - assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 2 + def firstImpBidders = bids.findAll { it.impid == bidRequest.imp.id.first() } + assert firstImpBidders.find { it.id == bidHigherPrice.id }.ext.prebid.rank == 1 + assert firstImpBidders.find { it.id == bidLowerPrice.id }.ext.prebid.rank == 2 + + and: "should separately rank bids for second imp" + def secondImpBidders = bids.findAll { it.impid == bidRequest.imp.id.last() } + assert secondImpBidders*.ext.prebid.rank == [1] + + where: + requestPreferDeals << [null, false, true] } def "PBS should add bid ranked and rank by deals for multiple media types request when auction.ranking and preferDeals are enabled"() { @@ -1568,10 +1510,10 @@ class TargetingSpec extends BaseSpec { and: "Bid response with 2 bids where deal bid has lower price" def bidPrice = PBSUtils.randomPrice - def bidBiggerPrice = Bid.getDefaultMultyTypesBids(bidRequest.imp.first).first.tap { + def bidBiggerPrice = Bid.getDefaultMultiTypesBids(bidRequest.imp.first).first.tap { it.price = bidPrice + 1 } - def bidWithDeal = Bid.getDefaultMultyTypesBids(bidRequest.imp.first).last.tap { + def bidWithDeal = Bid.getDefaultMultiTypesBids(bidRequest.imp.first).last.tap { it.dealid = PBSUtils.randomNumber it.price = bidPrice } @@ -1624,7 +1566,11 @@ class TargetingSpec extends BaseSpec { def response = defaultPbsService.sendAuctionRequest(bidRequest) then: "PBS should rank single bid" - assert response.seatbid.first.bid?.ext?.prebid?.rank?.flatten() == [1] + verifyAll(response.seatbid.first.bid) { + it.id == [bidBiggerPrice.id] + it.price == [bidBiggerPrice.price] + it.ext.prebid.rank == [1] + } } def "PBS should add bid ranked and rank by price for request with multiBid when auction.ranking is enabled and preferDeals disabled"() { @@ -1684,10 +1630,10 @@ class TargetingSpec extends BaseSpec { and: "Bid response with 2 bids where deal bid has lower price" def bidPrice = PBSUtils.randomPrice - def bidBiggerPrice = Bid.getDefaultMultyTypesBids(bidRequest.imp.first).first.tap { + def bidBiggerPrice = Bid.getDefaultMultiTypesBids(bidRequest.imp.first).first.tap { it.price = bidPrice + 1 } - def bidBDeal = Bid.getDefaultMultyTypesBids(bidRequest.imp.first).last.tap { + def bidBDeal = Bid.getDefaultMultiTypesBids(bidRequest.imp.first).last.tap { it.dealid = PBSUtils.randomNumber it.price = bidPrice } @@ -1775,11 +1721,11 @@ class TargetingSpec extends BaseSpec { and: "Bid response with 2 bids where deal bid has lower price" def bidPrice = PBSUtils.randomPrice - def bidBiggerPrice = Bid.getDefaultMultyTypesBids(impression).first.tap { + def bidBiggerPrice = Bid.getDefaultMultiTypesBids(impression).first.tap { it.price = bidPrice + 1 impid = bidRequest.imp.id.first } - def bidBDeal = Bid.getDefaultMultyTypesBids(impression).last.tap { + def bidBDeal = Bid.getDefaultMultiTypesBids(impression).last.tap { impid = bidRequest.imp.id.first it.dealid = PBSUtils.randomNumber it.price = bidPrice @@ -1819,23 +1765,14 @@ class TargetingSpec extends BaseSpec { def impression = Imp.getDefaultImpression(MediaType.BANNER).tap { id = storedRequestId video = Video.getDefaultVideo() + nativeObj = Native.getDefaultNative() } def storedImp = StoredImp.getStoredImp(bidRequest.accountId, impression) storedImpDao.save(storedImp) and: "Bid response with 2 bids where deal bid has lower price" - def bidPrice = PBSUtils.randomPrice - def bidBiggerPrice = Bid.getDefaultMultyTypesBids(impression).first.tap { - impid = bidRequest.imp.id.first - it.price = bidPrice + 1 - } - def bidBDeal = Bid.getDefaultMultyTypesBids(impression).last.tap { - impid = bidRequest.imp.id.first - it.dealid = PBSUtils.randomNumber - it.price = bidPrice - } def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid = [bidBiggerPrice, bidBDeal] + seatbid[0].bid = Bid.getDefaultMultiTypesBids(impression) { impid = bidRequest.imp.id.first } } and: "Set bidder response" From 308b8284c756ea8d9e94e832ceccec01866c301c Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Fri, 6 Jun 2025 00:32:34 +0300 Subject: [PATCH 7/8] update after review --- .../functional/tests/TargetingSpec.groovy | 224 +++++++++++------- 1 file changed, 140 insertions(+), 84 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index dc354764e2f..d5eaf9a2467 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -29,6 +29,7 @@ import org.prebid.server.functional.model.response.auction.Bid import org.prebid.server.functional.model.response.auction.BidExt import org.prebid.server.functional.model.response.auction.BidMediaType import org.prebid.server.functional.model.response.auction.BidResponse +import org.prebid.server.functional.model.response.auction.ErrorType import org.prebid.server.functional.model.response.auction.MediaType import org.prebid.server.functional.model.response.auction.Prebid import org.prebid.server.functional.model.response.auction.SeatBid @@ -42,6 +43,8 @@ import java.nio.charset.StandardCharsets import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST import static org.prebid.server.functional.model.AccountStatus.ACTIVE import static org.prebid.server.functional.model.bidder.BidderName.GENERIC +import static org.prebid.server.functional.model.bidder.BidderName.OPENX +import static org.prebid.server.functional.model.bidder.BidderName.WILDCARD import static org.prebid.server.functional.model.config.PriceGranularityType.UNKNOWN import static org.prebid.server.functional.model.response.auction.ErrorType.TARGETING import static org.prebid.server.functional.model.response.auction.MediaType.VIDEO @@ -1361,7 +1364,7 @@ class TargetingSpec extends BaseSpec { } def "PBS shouldn't add bid ranked for request when account config for auction.ranking disabled or default"() { - given: "Bid request with alwaysIncludeDeals = true" + given: "Bid request with enabled preferDeals" def bidRequest = BidRequest.getDefaultVideoRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] @@ -1399,8 +1402,8 @@ class TargetingSpec extends BaseSpec { ] } - def "PBS should add bid ranked and rank by deals for request without multiBid when auction.ranking and preferDeals are enabled"() { - given: "Bid request with alwaysIncludeDeals = true" + def "PBS should add bid ranked and rank by deals for default request when auction.ranking and preferDeals are enabled"() { + given: "Bid request with enabled preferDeals" def bidRequest = BidRequest.getDefaultVideoRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = true @@ -1439,37 +1442,30 @@ class TargetingSpec extends BaseSpec { } } - def "PBS should assign bid ranks for each imp separately when request has multiple imps and multiBid is configured"() { - given: "Bid request with multiple imps" + def "PBS should add bid ranked and rank by price for default request when auction.ranking is enabled and preferDeals disabled"() { + given: "Bid request with disabled preferDeals" def bidRequest = BidRequest.getDefaultVideoRequest().tap { - it.imp.first.nativeObj = Native.getDefaultNative() - imp.add(Imp.getDefaultImpression(VIDEO)) it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { - preferDeals = requestPreferDeals + preferDeals = false } - it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache() } - and: "Account in DB" + and: "Account in the DB" def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) accountDao.save(account) - and: "Bid response with multiple bids" + and: "Bid response with 2 bids where deal bid has lower price" def bidPrice = PBSUtils.randomPrice - def bidLowerPrice = Bid.getDefaultBid(bidRequest.imp.first).tap { - price = bidPrice - mediaType = BidMediaType.NATIVE - } - def bidHigherPrice = Bid.getDefaultBid(bidRequest.imp.first).tap { - price = bidPrice + 1 + def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.price = bidPrice + 1 } - def bidWithDeal = Bid.getDefaultBid(bidRequest.imp.last).tap { - dealid = PBSUtils.randomNumber - price = bidPrice + def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + it.dealid = PBSUtils.randomNumber + it.price = bidPrice } def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid = [bidLowerPrice, bidHigherPrice, bidWithDeal] + seatbid[0].bid = [bidBiggerPrice, bidBDeal] } and: "Set bidder response" @@ -1478,27 +1474,19 @@ class TargetingSpec extends BaseSpec { when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) - then: "PBS should rank bids for first imp" - def bids = response.seatbid.first.bid - def firstImpBidders = bids.findAll { it.impid == bidRequest.imp.id.first() } - assert firstImpBidders.find { it.id == bidHigherPrice.id }.ext.prebid.rank == 1 - assert firstImpBidders.find { it.id == bidLowerPrice.id }.ext.prebid.rank == 2 - - and: "should separately rank bids for second imp" - def secondImpBidders = bids.findAll { it.impid == bidRequest.imp.id.last() } - assert secondImpBidders*.ext.prebid.rank == [1] - - where: - requestPreferDeals << [null, false, true] + then: "PBS should rank single bid" + verifyAll(response.seatbid.first.bid) { + it.id == [bidBiggerPrice.id] + it.price == [bidBiggerPrice.price] + it.ext.prebid.rank == [1] + } } - def "PBS should add bid ranked and rank by deals for multiple media types request when auction.ranking and preferDeals are enabled"() { - given: "Bid request with alwaysIncludeDeals = true" + def "PBS should add bid ranked and rank by price for request with multiBid when auction.ranking is enabled and preferDeals disabled"() { + given: "Bid request with disabled preferDeals" def bidRequest = BidRequest.getDefaultVideoRequest().tap { - it.imp.first.banner = Banner.getDefaultBanner() - it.imp.first.nativeObj = Native.getDefaultNative() it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { - preferDeals = true + preferDeals = false } it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache() @@ -1510,15 +1498,15 @@ class TargetingSpec extends BaseSpec { and: "Bid response with 2 bids where deal bid has lower price" def bidPrice = PBSUtils.randomPrice - def bidBiggerPrice = Bid.getDefaultMultiTypesBids(bidRequest.imp.first).first.tap { + def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.price = bidPrice + 1 } - def bidWithDeal = Bid.getDefaultMultiTypesBids(bidRequest.imp.first).last.tap { + def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.dealid = PBSUtils.randomNumber it.price = bidPrice } def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid = [bidBiggerPrice, bidWithDeal] + seatbid[0].bid = [bidBiggerPrice, bidBDeal] } and: "Set bidder response" @@ -1527,18 +1515,21 @@ class TargetingSpec extends BaseSpec { when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) - then: "PBS should rank bid with deal as top priority" + then: "PBS should rank bid with higher price as top priority" def bids = response.seatbid.first.bid - assert bids.find(it -> it.id == bidWithDeal.id).ext.prebid.rank == 1 - assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 2 + assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 2 } - def "PBS should add bid ranked and rank by price for request without multiBid when auction.ranking is enabled and preferDeals disabled"() { - given: "Bid request with alwaysIncludeDeals = true" + def "PBS should add bid ranked and rank by price for multiple media types request when auction.ranking is enabled and preferDeals disabled"() { + given: "Bid request with disabled preferDeals" def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.imp.first.banner = Banner.getDefaultBanner() + it.imp.first.nativeObj = Native.getDefaultNative() it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = false } + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache() } @@ -1548,10 +1539,10 @@ class TargetingSpec extends BaseSpec { and: "Bid response with 2 bids where deal bid has lower price" def bidPrice = PBSUtils.randomPrice - def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { + def bidBiggerPrice = Bid.getDefaultMultiTypesBids(bidRequest.imp.first).first.tap { it.price = bidPrice + 1 } - def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + def bidBDeal = Bid.getDefaultMultiTypesBids(bidRequest.imp.first).last.tap { it.dealid = PBSUtils.randomNumber it.price = bidPrice } @@ -1565,16 +1556,15 @@ class TargetingSpec extends BaseSpec { when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) - then: "PBS should rank single bid" - verifyAll(response.seatbid.first.bid) { - it.id == [bidBiggerPrice.id] - it.price == [bidBiggerPrice.price] - it.ext.prebid.rank == [1] - } + then: "PBS should rank bid with higher price as top priority" + assert !response.ext.warnings + def bids = response.seatbid.first.bid + assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 2 } - def "PBS should add bid ranked and rank by price for request with multiBid when auction.ranking is enabled and preferDeals disabled"() { - given: "Bid request with alwaysIncludeDeals = true" + def "PBS should properly rank bids when request with multibid contains some invalid bid"() { + given: "Bid request with disabled preferDeals" def bidRequest = BidRequest.getDefaultVideoRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = false @@ -1587,17 +1577,22 @@ class TargetingSpec extends BaseSpec { def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) accountDao.save(account) - and: "Bid response with 2 bids where deal bid has lower price" + and: "Bid response with multiple bids" def bidPrice = PBSUtils.randomPrice - def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { - it.price = bidPrice + 1 + def higherPriceBid = Bid.getDefaultBid(bidRequest.imp.first).tap { + price = bidPrice + 2 } - def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { - it.dealid = PBSUtils.randomNumber - it.price = bidPrice + + def middlePriceBid = Bid.getDefaultBid(bidRequest.imp.first).tap { + price = bidPrice + 1 + adm = null + } + + def lowerPriceBid = Bid.getDefaultBid(bidRequest.imp.first).tap { + price = bidPrice } def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid = [bidBiggerPrice, bidBDeal] + seatbid[0].bid = [lowerPriceBid, middlePriceBid, higherPriceBid] } and: "Set bidder response" @@ -1608,37 +1603,91 @@ class TargetingSpec extends BaseSpec { then: "PBS should rank bid with higher price as top priority" def bids = response.seatbid.first.bid - assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 1 - assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 2 + assert bids.find(it -> it.id == higherPriceBid.id).ext.prebid.rank == 1 + assert bids.find(it -> it.id == lowerPriceBid.id).ext.prebid.rank == 2 + + and: "PBS should contain error for invalid bid" + assert response.ext.errors[ErrorType.GENERIC]?.message?.any { it.contains(middlePriceBid.id) } } - def "PBS should add bid ranked and rank by price for multiple media types request when auction.ranking is enabled and preferDeals disabled"() { - given: "Bid request with alwaysIncludeDeals = true" + def "PBS should assign bid ranks across all seatbids combined when the request contains imps with multiple bidders"() { + given: "PBS config with openX bidder" + def pbsConfig = ["adapters.openx.enabled" : "true", + "adapters.openx.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + def prebidServerService = pbsServiceFactory.getService(pbsConfig) + + and: "Bid request with multiple bidders" def bidRequest = BidRequest.getDefaultVideoRequest().tap { - it.imp.first.banner = Banner.getDefaultBanner() - it.imp.first.nativeObj = Native.getDefaultNative() + imp[0].ext.prebid.bidder.openx = Openx.defaultOpenx it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { - preferDeals = false + preferDeals = true } - it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + it.ext.prebid.multibid = [new MultiBid(bidder: WILDCARD, maxBids: MAX_BIDS_RANKING)] enableCache() } - and: "Account in the DB" + and: "Account in DB" def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) accountDao.save(account) - and: "Bid response with 2 bids where deal bid has lower price" + and: "Bid response with multiple bids" def bidPrice = PBSUtils.randomPrice - def bidBiggerPrice = Bid.getDefaultMultiTypesBids(bidRequest.imp.first).first.tap { + def genericBid = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.price = bidPrice + 1 } - def bidBDeal = Bid.getDefaultMultiTypesBids(bidRequest.imp.first).last.tap { + def openxBid = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.dealid = PBSUtils.randomNumber it.price = bidPrice } def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid = [bidBiggerPrice, bidBDeal] + it.seatbid = [new SeatBid(bid: [genericBid], seat: GENERIC), + new SeatBid(bid: [openxBid], seat: OPENX)] + } + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = prebidServerService.sendAuctionRequest(bidRequest) + + then: "PBS should rank OpenX bid higher than Generic bid" + assert response.seatbid.findAll { it.seat == OPENX }.bid.ext.prebid.rank.flatten() == [1] + assert response.seatbid.findAll { it.seat == GENERIC }.bid.ext.prebid.rank.flatten() == [2] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS should assign bid ranks for each imp separately when request has multiple imps and multiBid is configured"() { + given: "Bid request with multiple imps" + def bidRequest = BidRequest.getDefaultVideoRequest().tap { + it.imp.first.nativeObj = Native.getDefaultNative() + imp.add(Imp.getDefaultImpression(VIDEO)) + it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { + preferDeals = requestPreferDeals + } + it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] + enableCache() + } + + and: "Account in DB" + def account = getAccountConfigWithAuctionRanking(bidRequest.accountId) + accountDao.save(account) + + and: "Bid response with multiple bids" + def bidPrice = PBSUtils.randomPrice + def bidLowerPrice = Bid.getDefaultBid(bidRequest.imp.first).tap { + price = bidPrice + mediaType = BidMediaType.NATIVE + } + def bidHigherPrice = Bid.getDefaultBid(bidRequest.imp.first).tap { + price = bidPrice + 1 + } + def bidWithDeal = Bid.getDefaultBid(bidRequest.imp.last).tap { + dealid = PBSUtils.randomNumber + price = bidPrice + } + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid = [bidLowerPrice, bidHigherPrice, bidWithDeal] } and: "Set bidder response" @@ -1647,15 +1696,22 @@ class TargetingSpec extends BaseSpec { when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequest(bidRequest) - then: "PBS should rank bid with higher price as top priority" - assert !response.ext.warnings + then: "PBS should rank bids for first imp" def bids = response.seatbid.first.bid - assert bids.find(it -> it.id == bidBiggerPrice.id).ext.prebid.rank == 1 - assert bids.find(it -> it.id == bidBDeal.id).ext.prebid.rank == 2 + def firstImpBidders = bids.findAll { it.impid == bidRequest.imp.id.first() } + assert firstImpBidders.find { it.id == bidHigherPrice.id }.ext.prebid.rank == 1 + assert firstImpBidders.find { it.id == bidLowerPrice.id }.ext.prebid.rank == 2 + + and: "should separately rank bids for second imp" + def secondImpBidders = bids.findAll { it.impid == bidRequest.imp.id.last() } + assert secondImpBidders*.ext.prebid.rank == [1] + + where: + requestPreferDeals << [null, false, true] } def "PBS should ignore bid ranked from original response when auction.ranking enabled"() { - given: "Bid request with alwaysIncludeDeals = true" + given: "Bid request with disabled preferDeals" def bidRequest = BidRequest.getDefaultVideoRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = false @@ -1696,7 +1752,7 @@ class TargetingSpec extends BaseSpec { } def "PBS should add bid ranked and rank by price for request with stored imp when auction.ranking enabled"() { - given: "Bid request with alwaysIncludeDeals = false" + given: "Bid request with disabled preferDeals" def storedRequestId = PBSUtils.randomNumber def bidRequest = BidRequest.getDefaultVideoRequest().tap { imp.first.ext.prebid.storedRequest = new PrebidStoredRequest(id: storedRequestId) @@ -1747,7 +1803,7 @@ class TargetingSpec extends BaseSpec { } def "PBS shouldn't rank bids for request with stored imp when auction.ranking default"() { - given: "Bid request with alwaysIncludeDeals = true" + given: "Bid request with enabled preferDeals" def storedRequestId = PBSUtils.randomNumber def bidRequest = BidRequest.getDefaultVideoRequest().tap { imp.first.ext.prebid.storedRequest = new PrebidStoredRequest(id: storedRequestId) @@ -1786,7 +1842,7 @@ class TargetingSpec extends BaseSpec { } def "PBS should copy bid ranked from stored response when auction.ranking #auction"() { - given: "Bid request with alwaysIncludeDeals = true" + given: "Bid request with enabled preferDeals" def storedResponseId = PBSUtils.randomNumber def bidRequest = BidRequest.getDefaultVideoRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) From 7c4d9e18b8d2123e5ba1d3a09a78680937a9e088 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Mon, 9 Jun 2025 16:37:34 +0300 Subject: [PATCH 8/8] update after review --- .../functional/tests/TargetingSpec.groovy | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index d5eaf9a2467..081c578ecfa 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -12,7 +12,6 @@ import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.db.StoredResponse import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.AdServerTargeting -import org.prebid.server.functional.model.request.auction.Banner import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Imp import org.prebid.server.functional.model.request.auction.MultiBid @@ -60,6 +59,8 @@ class TargetingSpec extends BaseSpec { private static final Integer MAX_TRUNCATE_ATTR_CHARS = 255 private static final Integer MAX_BIDS_RANKING = 3 private static final String HB_ENV_AMP = "amp" + private static final Integer MAIN_RANK = 1 + private static final Integer SUBORDINATE_RANK = 2 def "PBS should include targeting bidder specific keys when alwaysIncludeDeals is true and deal bid wins"() { given: "Bid request with alwaysIncludeDeals = true" @@ -1320,7 +1321,6 @@ class TargetingSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) storedRequestDao.save(storedRequest) - and: "Account in the DB" def account = createAccountWithPriceGranularity(ampRequest.account, PBSUtils.getRandomEnum(PriceGranularityType)) accountDao.save(account) @@ -1365,7 +1365,7 @@ class TargetingSpec extends BaseSpec { def "PBS shouldn't add bid ranked for request when account config for auction.ranking disabled or default"() { given: "Bid request with enabled preferDeals" - def bidRequest = BidRequest.getDefaultVideoRequest().tap { + def bidRequest = BidRequest.getDefaultBidRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache() @@ -1404,7 +1404,7 @@ class TargetingSpec extends BaseSpec { def "PBS should add bid ranked and rank by deals for default request when auction.ranking and preferDeals are enabled"() { given: "Bid request with enabled preferDeals" - def bidRequest = BidRequest.getDefaultVideoRequest().tap { + def bidRequest = BidRequest.getDefaultBidRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = true } @@ -1438,13 +1438,13 @@ class TargetingSpec extends BaseSpec { verifyAll(response.seatbid.first.bid) { it.id == [bidWithDeal.id] it.price == [bidWithDeal.price] - it.ext.prebid.rank == [1] + it.ext.prebid.rank == [MAIN_RANK] } } def "PBS should add bid ranked and rank by price for default request when auction.ranking is enabled and preferDeals disabled"() { given: "Bid request with disabled preferDeals" - def bidRequest = BidRequest.getDefaultVideoRequest().tap { + def bidRequest = BidRequest.getDefaultBidRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = false } @@ -1460,12 +1460,12 @@ class TargetingSpec extends BaseSpec { def bidBiggerPrice = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.price = bidPrice + 1 } - def bidBDeal = Bid.getDefaultBid(bidRequest.imp[0]).tap { + def bidWithDealId = Bid.getDefaultBid(bidRequest.imp[0]).tap { it.dealid = PBSUtils.randomNumber it.price = bidPrice } def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { - seatbid[0].bid = [bidBiggerPrice, bidBDeal] + seatbid[0].bid = [bidBiggerPrice, bidWithDealId] } and: "Set bidder response" @@ -1478,13 +1478,13 @@ class TargetingSpec extends BaseSpec { verifyAll(response.seatbid.first.bid) { it.id == [bidBiggerPrice.id] it.price == [bidBiggerPrice.price] - it.ext.prebid.rank == [1] + it.ext.prebid.rank == [MAIN_RANK] } } def "PBS should add bid ranked and rank by price for request with multiBid when auction.ranking is enabled and preferDeals disabled"() { given: "Bid request with disabled preferDeals" - def bidRequest = BidRequest.getDefaultVideoRequest().tap { + def bidRequest = BidRequest.getDefaultBidRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = false } @@ -1523,8 +1523,8 @@ class TargetingSpec extends BaseSpec { def "PBS should add bid ranked and rank by price for multiple media types request when auction.ranking is enabled and preferDeals disabled"() { given: "Bid request with disabled preferDeals" - def bidRequest = BidRequest.getDefaultVideoRequest().tap { - it.imp.first.banner = Banner.getDefaultBanner() + def bidRequest = BidRequest.getDefaultBidRequest().tap { + it.imp.first.video = Video.getDefaultVideo() it.imp.first.nativeObj = Native.getDefaultNative() it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = false @@ -1607,17 +1607,18 @@ class TargetingSpec extends BaseSpec { assert bids.find(it -> it.id == lowerPriceBid.id).ext.prebid.rank == 2 and: "PBS should contain error for invalid bid" - assert response.ext.errors[ErrorType.GENERIC]?.message?.any { it.contains(middlePriceBid.id) } + response.ext.errors[ErrorType.GENERIC]?.message == + ["BidId `${middlePriceBid.id}` validation messages: Error: Bid \"${middlePriceBid.id}\" with video type missing adm and nurl"] } def "PBS should assign bid ranks across all seatbids combined when the request contains imps with multiple bidders"() { given: "PBS config with openX bidder" def pbsConfig = ["adapters.openx.enabled" : "true", - "adapters.openx.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + "adapters.openx.endpoint": "$networkServiceContainer.rootUri/auction".toString()] def prebidServerService = pbsServiceFactory.getService(pbsConfig) and: "Bid request with multiple bidders" - def bidRequest = BidRequest.getDefaultVideoRequest().tap { + def bidRequest = BidRequest.getDefaultBidRequest().tap { imp[0].ext.prebid.bidder.openx = Openx.defaultOpenx it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = true @@ -1650,8 +1651,8 @@ class TargetingSpec extends BaseSpec { def response = prebidServerService.sendAuctionRequest(bidRequest) then: "PBS should rank OpenX bid higher than Generic bid" - assert response.seatbid.findAll { it.seat == OPENX }.bid.ext.prebid.rank.flatten() == [1] - assert response.seatbid.findAll { it.seat == GENERIC }.bid.ext.prebid.rank.flatten() == [2] + assert response.seatbid.findAll { it.seat == OPENX }.bid.ext.prebid.rank.flatten() == [MAIN_RANK] + assert response.seatbid.findAll { it.seat == GENERIC }.bid.ext.prebid.rank.flatten() == [SUBORDINATE_RANK] cleanup: "Stop and remove pbs container" pbsServiceFactory.removeContainer(pbsConfig) @@ -1659,7 +1660,7 @@ class TargetingSpec extends BaseSpec { def "PBS should assign bid ranks for each imp separately when request has multiple imps and multiBid is configured"() { given: "Bid request with multiple imps" - def bidRequest = BidRequest.getDefaultVideoRequest().tap { + def bidRequest = BidRequest.getDefaultBidRequest().tap { it.imp.first.nativeObj = Native.getDefaultNative() imp.add(Imp.getDefaultImpression(VIDEO)) it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { @@ -1704,7 +1705,7 @@ class TargetingSpec extends BaseSpec { and: "should separately rank bids for second imp" def secondImpBidders = bids.findAll { it.impid == bidRequest.imp.id.last() } - assert secondImpBidders*.ext.prebid.rank == [1] + assert secondImpBidders*.ext.prebid.rank == [MAIN_RANK] where: requestPreferDeals << [null, false, true] @@ -1712,7 +1713,7 @@ class TargetingSpec extends BaseSpec { def "PBS should ignore bid ranked from original response when auction.ranking enabled"() { given: "Bid request with disabled preferDeals" - def bidRequest = BidRequest.getDefaultVideoRequest().tap { + def bidRequest = BidRequest.getDefaultBidRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = false } @@ -1754,7 +1755,7 @@ class TargetingSpec extends BaseSpec { def "PBS should add bid ranked and rank by price for request with stored imp when auction.ranking enabled"() { given: "Bid request with disabled preferDeals" def storedRequestId = PBSUtils.randomNumber - def bidRequest = BidRequest.getDefaultVideoRequest().tap { + def bidRequest = BidRequest.getDefaultBidRequest().tap { imp.first.ext.prebid.storedRequest = new PrebidStoredRequest(id: storedRequestId) it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true).tap { preferDeals = false @@ -1805,7 +1806,7 @@ class TargetingSpec extends BaseSpec { def "PBS shouldn't rank bids for request with stored imp when auction.ranking default"() { given: "Bid request with enabled preferDeals" def storedRequestId = PBSUtils.randomNumber - def bidRequest = BidRequest.getDefaultVideoRequest().tap { + def bidRequest = BidRequest.getDefaultBidRequest().tap { imp.first.ext.prebid.storedRequest = new PrebidStoredRequest(id: storedRequestId) it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] @@ -1844,7 +1845,7 @@ class TargetingSpec extends BaseSpec { def "PBS should copy bid ranked from stored response when auction.ranking #auction"() { given: "Bid request with enabled preferDeals" def storedResponseId = PBSUtils.randomNumber - def bidRequest = BidRequest.getDefaultVideoRequest().tap { + def bidRequest = BidRequest.getDefaultBidRequest().tap { it.ext.prebid.targeting = Targeting.createWithAllValuesSetTo(true) it.ext.prebid.multibid = [new MultiBid(bidder: GENERIC, maxBids: MAX_BIDS_RANKING)] enableCache()