Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.prebid.server.functional.model.request.auction

import com.fasterxml.jackson.annotation.JsonValue
import org.prebid.server.functional.util.PBSUtils

enum AuctionEnvironment {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import org.prebid.server.functional.model.config.AccountConfig
import org.prebid.server.functional.model.config.AlternateBidderCodes
import org.prebid.server.functional.model.config.BidderConfig
import org.prebid.server.functional.model.db.Account
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also pls add smoke case for stored request, response, imp request

import org.prebid.server.functional.model.db.StoredImp
import org.prebid.server.functional.model.db.StoredResponse
import org.prebid.server.functional.model.request.auction.Amx
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.PrebidStoredRequest
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.BidExt
import org.prebid.server.functional.model.response.auction.BidResponse
Expand Down Expand Up @@ -269,7 +274,7 @@ class AlternateBidderCodeSpec extends BaseSpec {
assert response.ext.seatnonbid.size() == 1

def seatNonBid = response.ext.seatnonbid[0]
assert seatNonBid.seat == AMX.value
assert seatNonBid.seat == bidderName.value
assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id
assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL

Expand Down Expand Up @@ -324,7 +329,7 @@ class AlternateBidderCodeSpec extends BaseSpec {
assert response.ext.seatnonbid.size() == 1

def seatNonBid = response.ext.seatnonbid[0]
assert seatNonBid.seat == ALIAS.value
assert seatNonBid.seat == bidderName.value
assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id
assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL

Expand Down Expand Up @@ -782,7 +787,7 @@ class AlternateBidderCodeSpec extends BaseSpec {
assert response.ext.seatnonbid.size() == 1

def seatNonBid = response.ext.seatnonbid[0]
assert seatNonBid.seat == AMX.value
assert seatNonBid.seat == UNKNOWN.value
assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id
assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL

Expand Down Expand Up @@ -838,7 +843,7 @@ class AlternateBidderCodeSpec extends BaseSpec {
assert response.ext.seatnonbid.size() == 1

def seatNonBid = response.ext.seatnonbid[0]
assert seatNonBid.seat == AMX.value
assert seatNonBid.seat == UNKNOWN.value
assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id
assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL

Expand Down Expand Up @@ -1182,7 +1187,7 @@ class AlternateBidderCodeSpec extends BaseSpec {
assert response.ext.seatnonbid.size() == 1

def seatNonBid = response.ext.seatnonbid[0]
assert seatNonBid.seat == AMX.value
assert seatNonBid.seat == requestedAllowedBidderCode.value
assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id
assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL

Expand Down Expand Up @@ -1237,7 +1242,7 @@ class AlternateBidderCodeSpec extends BaseSpec {
assert response.ext.seatnonbid.size() == 1

def seatNonBid = response.ext.seatnonbid[0]
assert seatNonBid.seat == AMX.value
assert seatNonBid.seat == requestedAllowedBidderCode.value
assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id
assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL

Expand Down Expand Up @@ -1298,7 +1303,7 @@ class AlternateBidderCodeSpec extends BaseSpec {
assert response.ext.seatnonbid.size() == 1

def seatNonBid = response.ext.seatnonbid[0]
assert seatNonBid.seat == AMX.value
assert seatNonBid.seat == allowedBidderCodes.value
assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id
assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_GENERAL

Expand Down Expand Up @@ -1572,6 +1577,228 @@ class AlternateBidderCodeSpec extends BaseSpec {
pbsServiceFactory.removeContainer(pbsConfig)
}

def "PBS should populate two seat bid when different bidder response with same seat"() {
given: "Default bid request with amx and generic bidder"
def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap {
imp[0].ext.prebid.bidder.generic = new Generic()
ext.prebid.alternateBidderCodes.bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])]
}

and: "Bid response with bidder code"
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap {
it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC)
}
bidder.setResponse(bidRequest.id, bidResponse)

and: "Flash metrics"
flushMetrics(pbsServiceWithAmxBidder)

when: "PBS processes auction request"
def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest)

then: "Bid response should contain seat"
assert response.seatbid.seat.sort() == [GENERIC, GENERIC].sort()

and: "Response should contain adapter code"
assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten().sort() == [AMX, GENERIC].sort()

and: "Response should contain bidder generic targeting"
def targeting = response.seatbid.bid.ext.prebid.targeting.flatten().collectEntries()
assert targeting["hb_pb_${GENERIC}"]
assert targeting["hb_size_${GENERIC}"]
assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value

and: "Response should contain repose millis with corresponding bidder"
assert response.ext.responsetimemillis.containsKey(GENERIC.value)

and: "Response shouldn't contain repose millis with amx bidder"
assert !response.ext.responsetimemillis.containsKey(AMX.value)

and: "Bidder request should be valid"
assert bidder.getBidderRequests(bidRequest.id)

and: "Response shouldn't contain warnings and error and seatNonBid"
assert !response.ext?.warnings
assert !response.ext?.errors
assert !response.ext?.seatnonbid

and: "Response shouldn't contain demand source"
assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource

and: "PBS shouldn't emit validation metrics"
def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest()
assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)]
assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(GENERIC)]
}

def "PBS should return two seat when same bidder response with different bidder code"() {
given: "Default bid request with amx and generic bidder"
def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap {
imp.add(Imp.getDefaultImpression())
imp[1].ext.prebid.bidder.amx = new Amx()
imp[1].ext.prebid.bidder.generic = null
ext.prebid.alternateBidderCodes.bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC, AMX])]
}

and: "Bid response with bidder code"
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap {
it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC)
it.seatbid[0].bid[1].ext = new BidExt(bidderCode: AMX)
}
bidder.setResponse(bidRequest.id, bidResponse)

and: "Flash metrics"
flushMetrics(pbsServiceWithAmxBidder)

when: "PBS processes auction request"
def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest)

then: "Bid response should contain seat"
assert response.seatbid.seat.sort() == [GENERIC, AMX].sort()

and: "Response should contain adapter code"
assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX, AMX]

and: "Response should contain bidder amx targeting"
def targeting = response.seatbid.bid.ext.prebid.targeting.flatten().collectEntries()
assert targeting["hb_pb_${AMX}"]
assert targeting["hb_size_${AMX}"]
assert targeting["hb_bidder_${AMX}"] == AMX.value

and: 'Response targeting should contain generic'
assert targeting["hb_pb_${GENERIC}"]
assert targeting["hb_size_${GENERIC}"]
assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value

and: "Response should contain repose millis with corresponding bidder"
assert response.ext.responsetimemillis.containsKey(GENERIC.value)
assert response.ext.responsetimemillis.containsKey(AMX.value)

and: "Bidder request should be valid"
assert bidder.getBidderRequests(bidRequest.id)

and: "Response shouldn't contain warnings and error and seatNonBid"
assert !response.ext?.warnings
assert !response.ext?.errors
assert !response.ext?.seatnonbid

and: "Response shouldn't contain demand source"
assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource

and: "PBS shouldn't emit validation metrics"
def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest()
assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)]
assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(GENERIC)]
}

def "PBS should populate seat bid from stored bid response when stored bid response and alternate bidder code specified"() {
given: "Default bid request with amx bidder"
def storedResponseId = PBSUtils.randomNumber
def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap {
imp[0].ext.prebid.storedBidResponse = [new StoredBidResponse(id: storedResponseId, bidder: AMX)]
ext.prebid.alternateBidderCodes.bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])]
}

and: "Stored bid response in DB"
def storedBidResponse = BidResponse.getDefaultBidResponse(bidRequest)
def storedResponse = new StoredResponse(responseId: storedResponseId, storedBidResponse: storedBidResponse)
storedResponseDao.save(storedResponse)

and: "Bid response with bidder code"
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap {
it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC)
}
bidder.setResponse(bidRequest.id, bidResponse)

and: "Flash metrics"
flushMetrics(pbsServiceWithAmxBidder)

when: "PBS processes auction request"
def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest)

then: "Bid response should contain seat"
assert response.seatbid.seat == [AMX]

and: "Response should contain adapter code"
assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX]

and: "Response should contain bidder generic targeting"
def targeting = response.seatbid.bid.ext.prebid.targeting.flatten().collectEntries()
assert targeting["hb_pb_${AMX}"]
assert targeting["hb_size_${AMX}"]
assert targeting["hb_bidder_${AMX}"] == AMX.value

and: "Response should contain repose millis with corresponding bidder"
assert response.ext.responsetimemillis.containsKey(AMX.value)

and: "Bidder request shouldn't be called due to storedBidResponse"
assert !bidder.getBidderRequests(bidRequest.id)

and: "Response shouldn't contain warnings and error and seatNonBid"
assert !response.ext?.warnings
assert !response.ext?.errors
assert !response.ext?.seatnonbid

and: "Response shouldn't contain demand source"
assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource

and: "PBS shouldn't emit validation metrics"
def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest()
assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)]
}

def "PBS auction allow bidder code when imp stored request and allowed bidder code present"() {
given: "Default bid request"
def bidRequest = getBidRequestWithAmxBidderAndAlternateBidderCode().tap {
imp[0].ext.prebid.storedRequest = new PrebidStoredRequest(id: PBSUtils.randomString)
ext.prebid.alternateBidderCodes.bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])]
}

and: "Save storedImp into DB"
def storedImp = StoredImp.getStoredImp(bidRequest)
storedImpDao.save(storedImp)

and: "Bid response with bidder code"
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap {
it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC)
}
bidder.setResponse(bidRequest.id, bidResponse)

when: "Requesting PBS auction"
def response = pbsServiceWithAmxBidder.sendAuctionRequest(bidRequest)

then: "Bid response should contain seat"
assert response.seatbid.seat.sort() == [GENERIC].sort()

and: "Response should contain adapter code"
assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [AMX]

and: "Response should contain bidder generic targeting"
def targeting = response.seatbid.bid.ext.prebid.targeting.flatten().collectEntries()
assert targeting["hb_pb_${GENERIC}"]
assert targeting["hb_size_${GENERIC}"]
assert targeting["hb_bidder_${GENERIC}"] == GENERIC.value

and: "Response should contain repose millis with corresponding bidder"
assert response.ext.responsetimemillis.containsKey(GENERIC.value)

and: "Bidder request should be called"
assert bidder.getBidderRequests(bidRequest.id)

and: "Response shouldn't contain warnings and error and seatNonBid"
assert !response.ext?.warnings
assert !response.ext?.errors
assert !response.ext?.seatnonbid

and: "Response shouldn't contain demand source"
assert !response.seatbid.first.bid.first.ext.prebid.meta.demandSource

and: "PBS shouldn't emit validation metrics"
def metrics = pbsServiceWithAmxBidder.sendCollectedMetricsRequest()
assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(GENERIC)]
assert !metrics[ADAPTER_RESPONSE_VALIDATION_METRICS.formatted(AMX)]
}

private static Account getAccountWithAlternateBidderCode(BidRequest bidRequest) {
new Account().tap {
it.uuid = bidRequest.accountId
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.prebid.server.functional.tests

import org.prebid.server.functional.model.Currency
import org.prebid.server.functional.model.bidder.Generic
import org.prebid.server.functional.model.config.AccountAuctionConfig
import org.prebid.server.functional.model.config.AccountConfig
import org.prebid.server.functional.model.config.AlternateBidderCodes
Expand Down Expand Up @@ -31,6 +32,7 @@ import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST
import static org.prebid.server.functional.model.Currency.EUR
import static org.prebid.server.functional.model.Currency.GBP
import static org.prebid.server.functional.model.Currency.USD
import static org.prebid.server.functional.model.bidder.BidderName.ALIAS
import static org.prebid.server.functional.model.bidder.BidderName.AMX
import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS
import static org.prebid.server.functional.model.bidder.BidderName.GENERIC
Expand Down Expand Up @@ -1152,6 +1154,45 @@ class BidAdjustmentSpec extends BaseSpec {
bidAdjustmentFactor << [0.9, 1.1]
}

def "PBS should prefer bid price adjustment based on media type and alternate bidder code when request has per-media-type bid adjustment factors with soft alias"() {
given: "Default bid request with bid adjustment"
def bidRequest = BidRequest.getDefaultBidRequest(SITE).tap {
ext.prebid.aliases = [(ALIAS.value): AMX]
imp[0].ext.prebid.bidder.generic = null
imp[0].ext.prebid.bidder.amx = null
imp[0].ext.prebid.bidder.alias = new Generic()
ext.prebid.tap {
bidAdjustmentFactors = new BidAdjustmentFactors().tap {
adjustments = [(GENERIC): randomDecimal]
mediaTypes = [(BANNER): [(GENERIC): bidAdjustmentFactor]]
}
alternateBidderCodes = new AlternateBidderCodes().tap {
enabled = true
bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [GENERIC])]
}
}
}

and: "Bid response with bidder code"
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap {
it.seatbid[0].bid[0].ext = new BidExt(bidderCode: GENERIC)
}
bidder.setResponse(bidRequest.id, bidResponse)

when: "PBS processes auction request"
def response = pbsService.sendAuctionRequest(bidRequest)

then: "Final bid price should be adjusted"
assert response?.seatbid?.first?.bid?.first?.price == bidResponse.seatbid.first.bid.first.price *
bidAdjustmentFactor

and: "Response should contain repose millis with corresponding bidder"
assert response.ext.responsetimemillis.containsKey(GENERIC.value)

where:
bidAdjustmentFactor << [0.9, 1.1]
}

private static Map<String, String> getExternalCurrencyConverterConfig() {
["auction.ad-server-currency" : DEFAULT_CURRENCY as String,
"currency-converter.external-rates.enabled" : "true",
Expand Down
Loading