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,16 +1,18 @@
package org.prebid.server.functional.model.request.auction

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.annotation.JsonNaming
import groovy.transform.ToString

@JsonNaming(PropertyNamingStrategies.LowerCaseStrategy)
@ToString(includeNames = true, ignoreNulls = true)
class BidAdjustmentRule {

@JsonProperty('*')
Map<String, List<AdjustmentRule>> wildcardBidder
Map<String, List<AdjustmentRule>> generic
Map<String, List<AdjustmentRule>> alias
@JsonProperty("ALIAS")
Map<String, List<AdjustmentRule>> aliasUpperCase
@JsonProperty("AlIaS")
Map<String, List<AdjustmentRule>> aliasCamelCase
Map<String, List<AdjustmentRule>> amx
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,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.ACUITYADS
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
Expand Down Expand Up @@ -79,6 +80,11 @@ class BidAdjustmentSpec extends BaseSpec {
"adapters.amx.endpoint": "$networkServiceContainer.rootUri/auction".toString()]
private static final PrebidServerService pbsService = pbsServiceFactory.getService(externalCurrencyConverterConfig + AMX_CONFIG)

@Override
def cleanupSpec() {
pbsServiceFactory.removeContainer(externalCurrencyConverterConfig + AMX_CONFIG)
}

def "PBS should adjust bid price for matching bidder when request has per-bidder bid adjustment factors"() {
given: "Default bid request with bid adjustment"
def bidRequest = BidRequest.getDefaultBidRequest(SITE).tap {
Expand Down Expand Up @@ -1193,6 +1199,325 @@ class BidAdjustmentSpec extends BaseSpec {
bidAdjustmentFactor << [0.9, 1.1]
}

def "PBS shouldn't adjust bid price when bid adjustment rule doesn't match with bidder code"() {
given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode"
def exactRulePrice = PBSUtils.randomPrice
def currency = USD
def adjustmentRule = new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency)
def bidAdjustmentRule = new BidAdjustmentRule((bidAdjustmentRuleBidder): [(WILDCARD): [adjustmentRule]])
def bidRequest = BidRequest.defaultBidRequest.tap {
cur = [currency]
imp[0].ext.prebid.bidder.generic = null
imp[0].ext.prebid.bidder.amx = new Amx()
ext.prebid.tap {
bidAdjustments = new BidAdjustment(mediaType: [(BANNER): bidAdjustmentRule])
alternateBidderCodes = new AlternateBidderCodes().tap {
enabled = true
bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])]
}
}
}

and: "Default bid response with price and bidder code"
def originalPrice = PBSUtils.randomPrice
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap {
cur = currency
seatbid.first.bid.first.price = originalPrice
seatbid.first.bid.first.ext = new BidExt(bidderCode: ACUITYADS)
}
bidder.setResponse(bidRequest.id, bidResponse)

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

then: "Final bid price should be adjusted according to exact rule"
assert response.seatbid.first.bid.first.price == originalPrice
assert response.cur == bidResponse.cur

and: "Original bid price and currency should be presented in bid.ext"
verifyAll(response.seatbid.first.bid.first.ext) {
origbidcpm == originalPrice
origbidcur == bidResponse.cur
}

and: "Bidder request should contain currency from request"
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
assert bidderRequest.cur == [currency]

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

and: "Response should contain seatbid.seat"
assert response.seatbid[0].seat == ACUITYADS

where:
bidAdjustmentRuleBidder << ["alias", "aliasUpperCase", "aliasCamelCase"]
}

def "PBS should adjust bid price when two bid adjustment rules are compatible"() {
given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode"
def exactRulePrice = PBSUtils.randomPrice
def currency = USD
def dealId = PBSUtils.randomString
def adjustmentRule = new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency)
def firstBidAdjustmentRule = new BidAdjustmentRule(amx: [(dealId): [adjustmentRule]])
def secondBidAdjustmentRule = new BidAdjustmentRule(amx: [(WILDCARD): [adjustmentRule]])
def bidRequest = BidRequest.defaultBidRequest.tap {
cur = [currency]
imp[0].ext.prebid.bidder.generic = null
imp[0].ext.prebid.bidder.amx = new Amx()
ext.prebid.tap {
bidAdjustments = new BidAdjustment(mediaType: [(BANNER): firstBidAdjustmentRule,
(ANY) : secondBidAdjustmentRule])
alternateBidderCodes = new AlternateBidderCodes().tap {
enabled = true
bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])]
}
}
}

and: "Default bid response with price and bidder code and dealId"
def originalPrice = PBSUtils.randomPrice
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap {
cur = currency
seatbid.first.bid.first.price = originalPrice
seatbid.first.bid.first.ext = new BidExt(bidderCode: AMX)
seatbid.first.bid.first.dealid = dealId
}
bidder.setResponse(bidRequest.id, bidResponse)

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

then: "Final bid price should be adjusted according to exact rule"
assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, exactRulePrice, STATIC)
assert response.cur == bidResponse.cur

and: "Original bid price and currency should be presented in bid.ext"
verifyAll(response.seatbid.first.bid.first.ext) {
origbidcpm == originalPrice
origbidcur == bidResponse.cur
}

and: "Bidder request should contain currency from request"
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
assert bidderRequest.cur == [currency]

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

and: "Response should contain seatbid.seat"
assert response.seatbid[0].seat == AMX
}

def "PBS should adjust bid price when bid adjustment bidder and bidder code different"() {
given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode"
def exactRulePrice = PBSUtils.randomPrice
def currency = USD
def adjustmentRule = new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency)
def bidAdjustmentRule = new BidAdjustmentRule((bidAdjustmentRuleBidder): [(WILDCARD): [adjustmentRule]])
def bidRequest = BidRequest.defaultBidRequest.tap {
cur = [currency]
imp[0].ext.prebid.bidder.generic = null
imp[0].ext.prebid.bidder.amx = new Amx()
ext.prebid.tap {
bidAdjustments = new BidAdjustment(mediaType: [(BANNER): bidAdjustmentRule])
alternateBidderCodes = new AlternateBidderCodes().tap {
enabled = true
bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])]
}
}
}

and: "Default bid response with price and bidder code"
def originalPrice = PBSUtils.randomPrice
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap {
cur = currency
seatbid.first.bid.first.price = originalPrice
seatbid.first.bid.first.ext = new BidExt(bidderCode: ALIAS)
}
bidder.setResponse(bidRequest.id, bidResponse)

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

then: "Final bid price should be adjusted according to exact rule"
assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, exactRulePrice, STATIC)
assert response.cur == bidResponse.cur

and: "Original bid price and currency should be presented in bid.ext"
verifyAll(response.seatbid.first.bid.first.ext) {
origbidcpm == originalPrice
origbidcur == bidResponse.cur
}

and: "Bidder request should contain currency from request"
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
assert bidderRequest.cur == [currency]

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

and: "Response should contain seatbid.seat"
assert response.seatbid[0].seat == ALIAS

where:
bidAdjustmentRuleBidder << ["alias", "aliasUpperCase", "aliasCamelCase"]
}

def "PBS should adjust bid price when bid adjustment bidder and bidder code same as requested"() {
given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode"
def exactRulePrice = PBSUtils.randomPrice
def currency = USD
def exactRule = new BidAdjustmentRule(amx: [(WILDCARD): [new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency)]])
def bidRequest = BidRequest.defaultBidRequest.tap {
cur = [currency]
imp[0].ext.prebid.bidder.generic = null
imp[0].ext.prebid.bidder.amx = new Amx()
ext.prebid.tap {
bidAdjustments = new BidAdjustment(mediaType: [(BANNER): exactRule])
alternateBidderCodes = new AlternateBidderCodes().tap {
enabled = true
bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])]
}
}
}

and: "Default bid response with price and bidder code"
def originalPrice = PBSUtils.randomPrice
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap {
cur = currency
seatbid.first.bid.first.price = originalPrice
seatbid.first.bid.first.ext = new BidExt(bidderCode: AMX)
}
bidder.setResponse(bidRequest.id, bidResponse)

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

then: "Final bid price should be adjusted according to exact rule"
assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, exactRulePrice, STATIC)
assert response.cur == bidResponse.cur

and: "Original bid price and currency should be presented in bid.ext"
verifyAll(response.seatbid.first.bid.first.ext) {
origbidcpm == originalPrice
origbidcur == bidResponse.cur
}

and: "Bidder request should contain currency from request"
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
assert bidderRequest.cur == [currency]

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

and: "Response should contain seatbid.seat"
assert response.seatbid[0].seat == AMX
}

def "PBS should adjust bid price when bid adjustment bidder is the same as bidder code"() {
given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode"
def exactRulePrice = PBSUtils.randomPrice
def currency = USD
def exactRule = new BidAdjustmentRule(alias: [(WILDCARD): [new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency)]])
def bidRequest = BidRequest.defaultBidRequest.tap {
cur = [currency]
imp[0].ext.prebid.bidder.generic = null
imp[0].ext.prebid.bidder.amx = new Amx()
ext.prebid.tap {
bidAdjustments = new BidAdjustment(mediaType: [(BANNER): exactRule])
alternateBidderCodes = new AlternateBidderCodes().tap {
enabled = true
bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])]
}
}
}

and: "Default bid response with price and bidder code"
def originalPrice = PBSUtils.randomPrice
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap {
cur = currency
seatbid.first.bid.first.price = originalPrice
seatbid.first.bid.first.ext = new BidExt(bidderCode: ALIAS)
}
bidder.setResponse(bidRequest.id, bidResponse)

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

then: "Final bid price should be adjusted according to exact rule"
assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, exactRulePrice, STATIC)
assert response.cur == bidResponse.cur

and: "Original bid price and currency should be presented in bid.ext"
verifyAll(response.seatbid.first.bid.first.ext) {
origbidcpm == originalPrice
origbidcur == bidResponse.cur
}

and: "Bidder request should contain currency from request"
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
assert bidderRequest.cur == [currency]

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

and: "Response should contain seatbid.seat"
assert response.seatbid[0].seat == ALIAS
}

def "PBS should adjust bid price when bid adjustment wildcard bidder and bidder code specified"() {
given: "Bid request with ext.prebid.bidAdjustments and ext.prebid.alternateBidderCode"
def exactRulePrice = PBSUtils.randomPrice
def currency = USD
def exactRule = new BidAdjustmentRule(wildcardBidder: [(WILDCARD): [new AdjustmentRule(adjustmentType: STATIC, value: exactRulePrice, currency: currency)]])
def bidRequest = BidRequest.defaultBidRequest.tap {
cur = [currency]
imp[0].ext.prebid.bidder.generic = null
imp[0].ext.prebid.bidder.amx = new Amx()
ext.prebid.tap {
bidAdjustments = new BidAdjustment(mediaType: [(BANNER): exactRule])
alternateBidderCodes = new AlternateBidderCodes().tap {
enabled = true
bidders = [(AMX): new BidderConfig(enabled: true, allowedBidderCodes: [AMX])]
}
}
}

and: "Default bid response with price and bidder code"
def originalPrice = PBSUtils.randomPrice
def bidResponse = BidResponse.getDefaultBidResponse(bidRequest, AMX).tap {
cur = currency
seatbid.first.bid.first.price = originalPrice
seatbid.first.bid.first.ext = new BidExt(bidderCode: ALIAS)
}
bidder.setResponse(bidRequest.id, bidResponse)

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

then: "Final bid price should be adjusted according to exact rule"
assert response.seatbid.first.bid.first.price == getAdjustedPrice(originalPrice, exactRulePrice, STATIC)
assert response.cur == bidResponse.cur

and: "Original bid price and currency should be presented in bid.ext"
verifyAll(response.seatbid.first.bid.first.ext) {
origbidcpm == originalPrice
origbidcur == bidResponse.cur
}

and: "Bidder request should contain currency from request"
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
assert bidderRequest.cur == [currency]

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

and: "Response should contain seatbid.seat"
assert response.seatbid[0].seat == ALIAS
}

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