Skip to content

Commit 932446c

Browse files
committed
Add functional tests for Secondary Bidder faute
1 parent 01acd95 commit 932446c

File tree

4 files changed

+349
-0
lines changed

4 files changed

+349
-0
lines changed

src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class AccountAuctionConfig {
3737
BidAdjustment bidAdjustments
3838
BidRounding bidRounding
3939
Integer impressionLimit
40+
@JsonProperty("secondarybidders")
41+
List<BidderName> secondaryBidders
4042

4143
@JsonProperty("price_granularity")
4244
PriceGranularityType priceGranularitySnakeCase

src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ enum ErrorType {
1717
OPENX("openx"),
1818
AMX("amx"),
1919
AMX_UPPER_CASE("AMX"),
20+
OPENX_ALIAS("openxalias"),
2021

2122
@JsonValue
2223
final String value

src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/Bidder.groovy

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.prebid.server.functional.model.request.auction.Imp
1212
import org.prebid.server.functional.model.response.auction.BidResponse
1313
import org.testcontainers.containers.MockServerContainer
1414

15+
import static java.util.concurrent.TimeUnit.SECONDS
1516
import static org.mockserver.model.HttpRequest.request
1617
import static org.mockserver.model.HttpResponse.response
1718
import static org.mockserver.model.HttpStatusCode.OK_200
@@ -47,6 +48,13 @@ class Bidder extends NetworkScaffolding {
4748
: HttpResponse.notFoundResponse()}
4849
}
4950

51+
void setResponseWithDilay(Integer dilayTimeout = 5) {
52+
mockServerClient.when(request().withPath(endpoint), Times.unlimited(), TimeToLive.unlimited(), -10)
53+
.respond {request -> request.withPath(endpoint)
54+
? response().withDelay(SECONDS, dilayTimeout).withStatusCode(OK_200.code()).withBody(getBodyByRequest(request))
55+
: HttpResponse.notFoundResponse()}
56+
}
57+
5058
List<BidderRequest> getBidderRequests(String bidRequestId) {
5159
getRecordedRequestsBody(bidRequestId).collect { decode(it, BidderRequest) }
5260
}
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
package org.prebid.server.functional.tests
2+
3+
4+
import org.prebid.server.functional.model.bidder.Openx
5+
import org.prebid.server.functional.model.config.AccountAuctionConfig
6+
import org.prebid.server.functional.model.config.AccountConfig
7+
import org.prebid.server.functional.model.db.Account
8+
import org.prebid.server.functional.model.request.auction.BidRequest
9+
import org.prebid.server.functional.model.response.auction.ErrorType
10+
import org.prebid.server.functional.service.PrebidServerService
11+
import org.prebid.server.functional.testcontainers.scaffolding.Bidder
12+
import spock.lang.Shared
13+
14+
import static org.prebid.server.functional.model.bidder.BidderName.OPENX
15+
import static org.prebid.server.functional.model.bidder.BidderName.GENERIC
16+
import static org.prebid.server.functional.model.bidder.BidderName.OPENX_ALIAS
17+
import static org.prebid.server.functional.model.bidder.BidderName.UNKNOWN
18+
19+
import static org.prebid.server.functional.model.response.auction.BidRejectionReason.RESPONSE_REJECTED_ADVERTISER_BLOCKED
20+
import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer
21+
22+
class SecondaryBidderSpec extends BaseSpec {
23+
24+
private static final Map<String, String> OPENX_CONFIG = [
25+
"adapters.${OPENX.value}.enabled" : "true",
26+
"adapters.${OPENX.value}.endpoint": "$networkServiceContainer.rootUri/openx-auction".toString()]
27+
28+
private static final Map<String, String> OPENX_ALIAS_CONFIG = [
29+
"adapters.${OPENX.value}.aliases.${OPENX_ALIAS}.enabled" : "true",
30+
"adapters.${OPENX.value}.aliases.${OPENX_ALIAS}.endpoint": "$networkServiceContainer.rootUri/openx-alias-auction".toString()]
31+
32+
protected static final Bidder openXBidder = new Bidder(networkServiceContainer, "/openx-auction")
33+
protected static final Bidder openXAliasBidder = new Bidder(networkServiceContainer, "/openx-alias-auction")
34+
35+
@Shared
36+
PrebidServerService pbsServiceWithOpenXAndIXBidder = pbsServiceFactory.getService(OPENX_CONFIG + OPENX_ALIAS_CONFIG)
37+
38+
@Override
39+
def cleanupSpec() {
40+
pbsServiceFactory.removeContainer(OPENX_CONFIG + OPENX_ALIAS_CONFIG)
41+
}
42+
43+
def "PBS should proceed as default when secondaryBidders not define in config"() {
44+
given: "Default basic BidRequest with generic bidder"
45+
def bidRequest = BidRequest.defaultBidRequest
46+
47+
and: "Account in the DB"
48+
def accountConfig = AccountConfig.defaultAccountConfig.tap {
49+
it.auction = new AccountAuctionConfig(secondaryBidders: null)
50+
}
51+
def account = new Account(uuid: bidRequest.accountId, config: accountConfig)
52+
accountDao.save(account)
53+
54+
when: "PBS processes auction request"
55+
def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest)
56+
57+
then: "PBs should processed bidder request"
58+
assert bidder.getBidderRequest(bidRequest.id)
59+
60+
and: "PBS shouldn't contain errors, warnings and seat non bit"
61+
assert !bidResponse.ext?.warnings
62+
assert !bidResponse.ext?.errors
63+
assert !bidResponse.ext.seatnonbid
64+
}
65+
66+
def "PBS should emit a warning when null in secondary bidders config"() {
67+
given: "Default basic BidRequest with generic bidder"
68+
def bidRequest = BidRequest.defaultBidRequest
69+
70+
and: "Account in the DB"
71+
def accountConfig = AccountConfig.defaultAccountConfig.tap {
72+
it.auction = new AccountAuctionConfig(secondaryBidders: [null])
73+
}
74+
def account = new Account(uuid: bidRequest.accountId, config: accountConfig)
75+
accountDao.save(account)
76+
77+
when: "PBS processes auction request"
78+
def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest)
79+
80+
then: "PBs should processed bidder request"
81+
assert bidder.getBidderRequest(bidRequest.id)
82+
83+
and: "PBS shouldn't contain errors, warnings and seat non bid"
84+
assert !bidResponse.ext?.warnings
85+
assert !bidResponse.ext?.errors
86+
assert !bidResponse.ext.seatnonbid
87+
}
88+
89+
def "PBS should emit a warning when invalid bidder in secondary bidders config"() {
90+
given: "Default basic BidRequest with generic bidder"
91+
def bidRequest = BidRequest.defaultBidRequest
92+
93+
and: "Account in the DB"
94+
def accountConfig = AccountConfig.defaultAccountConfig.tap {
95+
it.auction = new AccountAuctionConfig(secondaryBidders: [UNKNOWN])
96+
}
97+
def account = new Account(uuid: bidRequest.accountId, config: accountConfig)
98+
accountDao.save(account)
99+
100+
when: "PBS processes auction request"
101+
def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest)
102+
103+
then: "PBs should processed bidder request"
104+
def bidderRequests = bidder.getBidderRequests(bidRequest.id)
105+
assert bidderRequests.size() == 3
106+
107+
and: "PBS shouldn't contain errors, warnings and seat non bid"
108+
assert !bidResponse.ext?.warnings
109+
assert !bidResponse.ext?.errors
110+
assert !bidResponse.ext.seatnonbid
111+
}
112+
113+
//todo: If every bidder in the auction is flagged as secondary,
114+
// then the feature is ignored, and all bidders are considered 'primary'.
115+
116+
def "PBS should thread all bidders as primary when all requested bidders in secondary bidders config"() {
117+
given: "Default basic BidRequest with generic bidder"
118+
def bidRequest = BidRequest.defaultBidRequest
119+
120+
and: "Account in the DB"
121+
def accountConfig = AccountConfig.defaultAccountConfig.tap {
122+
it.auction = new AccountAuctionConfig(secondaryBidders: [GENERIC, OPENX])
123+
}
124+
def account = new Account(uuid: bidRequest.accountId, config: accountConfig)
125+
accountDao.save(account)
126+
127+
when: "PBS processes auction request"
128+
def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest)
129+
130+
then: "PBs should processed bidder request"
131+
def bidderRequests = bidder.getBidderRequests(bidRequest.id)
132+
assert bidderRequests.size() == 2
133+
134+
and: "PBS shouldn't contain errors, warnings and seat non bid"
135+
assert !bidResponse.ext?.warnings
136+
assert !bidResponse.ext?.errors
137+
assert !bidResponse.ext.seatnonbid
138+
}
139+
140+
//todo: If a bidder is defined as secondary by the account-level config,
141+
// PBS should not wait for that bidder to respond. i.e.
142+
// when the last primary bidder responds, the auction is over and any secondary bidder that hasn't returned is considered timed out.
143+
144+
def "PBS shouldn't wait on non prioritize bidder when primary bidder respond"() {
145+
given: "Default bid request with generic and openX bidders"
146+
def bidRequest = BidRequest.defaultBidRequest.tap {
147+
it.imp[0].ext.prebid.bidder.tap {
148+
it.openx = Openx.defaultOpenx
149+
}
150+
}
151+
152+
and: "Account in the DB"
153+
def accountConfig = AccountConfig.defaultAccountConfig.tap {
154+
// it.auction = new AccountAuctionConfig(secondaryBidders: [OPENX])
155+
}
156+
def account = new Account(uuid: bidRequest.accountId, config: accountConfig)
157+
accountDao.save(account)
158+
159+
and: "Set up openx bidder response with delay"
160+
openXBidder.setResponseWithDilay(5)
161+
162+
when: "PBS processes auction request"
163+
def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest)
164+
165+
then: "PBs should processed bidder request"
166+
def genericBidderRequests = bidder.getBidderRequests(bidRequest.id)
167+
def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id)
168+
assert genericBidderRequests.size() == 1
169+
assert openXBidderRequests.size() == 1
170+
171+
and: "PBs repose shouldn't contain response body from openX bidder"
172+
assert !bidResponse?.ext?.debug?.httpcalls[OPENX]?.responseBody
173+
174+
and: "PBS should contain error for openX due to timeout"
175+
assert bidResponse.ext?.errors[ErrorType.OPENX]
176+
177+
and: "PBs should respond with warning for openx"
178+
assert bidResponse.ext?.warnings[ErrorType.OPENX].message == ["Secondary bidder timed out, auction proceeded"]
179+
180+
and: "PBs should populate seatNonBid"
181+
def seatNonBid = bidResponse.ext.seatnonbid[0]
182+
assert seatNonBid.seat == OPENX
183+
assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id
184+
assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_ADVERTISER_BLOCKED
185+
}
186+
187+
//todo: Aliases are treated separately. i.e.
188+
// just because a biddercode is defined as secondary does not mean any aliases or root biddercodes are also secondary.
189+
190+
def "PBS shouldn't treated alias bidder as secondary when root bidder code in secondary"() {
191+
given: "Default bid request with generic and openX bidders"
192+
def bidRequest = BidRequest.defaultBidRequest.tap {
193+
it.imp[0].ext.prebid.bidder.tap {
194+
it.openx = Openx.defaultOpenx
195+
it.openxAlias = Openx.defaultOpenx
196+
}
197+
ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX]
198+
}
199+
200+
and: "Account in the DB"
201+
def accountConfig = AccountConfig.defaultAccountConfig.tap {
202+
// it.auction = new AccountAuctionConfig(secondaryBidders: [OPENX])
203+
}
204+
def account = new Account(uuid: bidRequest.accountId, config: accountConfig)
205+
accountDao.save(account)
206+
207+
and: "Set up openx bidder response with delay"
208+
openXBidder.setResponseWithDilay(5)
209+
210+
and: "Set up openx alias bidder response"
211+
openXAliasBidder.setResponse()
212+
213+
when: "PBS processes auction request"
214+
def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest)
215+
216+
then: "PBs should processed bidder request"
217+
def genericBidderRequests = bidder.getBidderRequests(bidRequest.id)
218+
def openXAliasBidderRequests = openXAliasBidder.getBidderRequests(bidRequest.id)
219+
def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id)
220+
assert genericBidderRequests.size() == 1
221+
assert openXBidderRequests.size() == 1
222+
assert openXAliasBidderRequests.size() == 1
223+
224+
and: "PBs repose shouldn't contain response body from openX bidder"
225+
assert !bidResponse?.ext?.debug?.httpcalls[OPENX]?.responseBody
226+
227+
and: "PBS should contain error for openX due to timeout"
228+
assert bidResponse.ext?.errors[ErrorType.OPENX]
229+
230+
and: "PBs should respond with warning for openx"
231+
assert bidResponse.ext?.warnings[ErrorType.OPENX].message == ["Secondary bidder openx timed out, auction proceeded"]
232+
233+
and: "PBs should populate seatNonBid"
234+
def seatNonBid = bidResponse.ext.seatnonbid[0]
235+
assert seatNonBid.seat == OPENX
236+
assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id
237+
assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_ADVERTISER_BLOCKED
238+
239+
cleanup: "Reset mock"
240+
openXBidder.reset()
241+
openXAliasBidder.reset()
242+
}
243+
244+
def "PBS shouldn't wait on secondary bidder when alias bidder respond with dilay"() {
245+
given: "Default bid request with generic and openX bidders"
246+
def bidRequest = BidRequest.defaultBidRequest.tap {
247+
it.imp[0].ext.prebid.bidder.tap {
248+
it.openx = Openx.defaultOpenx
249+
it.openxAlias = Openx.defaultOpenx
250+
}
251+
ext.prebid.aliases = [(OPENX_ALIAS.value): OPENX]
252+
}
253+
254+
and: "Account in the DB"
255+
def accountConfig = AccountConfig.defaultAccountConfig.tap {
256+
// it.auction = new AccountAuctionConfig(secondaryBidders: [OPENX_ALIAS])
257+
}
258+
def account = new Account(uuid: bidRequest.accountId, config: accountConfig)
259+
accountDao.save(account)
260+
261+
and: "Set up openx bidder response with delay"
262+
openXAliasBidder.setResponseWithDilay(5)
263+
264+
and: "Set up openx alias bidder response"
265+
openXBidder.setResponse()
266+
267+
when: "PBS processes auction request"
268+
def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest)
269+
270+
then: "PBs should processed bidder request"
271+
def genericBidderRequests = bidder.getBidderRequests(bidRequest.id)
272+
def openXAliasBidderRequests = openXAliasBidder.getBidderRequests(bidRequest.id)
273+
def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id)
274+
assert genericBidderRequests.size() == 1
275+
assert openXBidderRequests.size() == 1
276+
assert openXAliasBidderRequests.size() == 1
277+
278+
and: "PBs repose shouldn't contain response body from openX bidder"
279+
assert !bidResponse?.ext?.debug?.httpcalls[OPENX_ALIAS]?.responseBody
280+
281+
and: "PBS should contain error for openX due to timeout"
282+
assert bidResponse.ext?.errors[ErrorType.OPENX_ALIAS]
283+
284+
and: "PBs should respond with warning for openx"
285+
assert bidResponse.ext?.warnings[ErrorType.OPENX_ALIAS].message == ["Secondary bidder opnex_alias timed out, auction proceeded"]
286+
287+
and: "PBs should populate seatNonBid"
288+
def seatNonBid = bidResponse.ext.seatnonbid[0]
289+
assert seatNonBid.seat == OPENX_ALIAS
290+
assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id
291+
assert seatNonBid.nonBid[0].statusCode == RESPONSE_REJECTED_ADVERTISER_BLOCKED
292+
293+
cleanup: "Reset mock"
294+
openXBidder.reset()
295+
openXAliasBidder.reset()
296+
}
297+
298+
//todo: what if primary bidder will respond slowest than secondary bidder, usual flow of action?
299+
300+
def "PBS should pass auction as usual when secondary bidder respond first and primary with dilay"() {
301+
given: "Default bid request with generic and openX bidders"
302+
def bidRequest = BidRequest.defaultBidRequest.tap {
303+
it.imp[0].ext.prebid.bidder.tap {
304+
it.openx = Openx.defaultOpenx
305+
}
306+
}
307+
308+
and: "Account in the DB"
309+
def accountConfig = AccountConfig.defaultAccountConfig.tap {
310+
// it.auction = new AccountAuctionConfig(secondaryBidders: [GENERIC])
311+
}
312+
def account = new Account(uuid: bidRequest.accountId, config: accountConfig)
313+
accountDao.save(account)
314+
315+
and: "Set up openx bidder response with delay"
316+
openXBidder.setResponseWithDilay(1)
317+
318+
when: "PBS processes auction request"
319+
def bidResponse = pbsServiceWithOpenXAndIXBidder.sendAuctionRequest(bidRequest)
320+
321+
then: "PBs should processed bidder request"
322+
def genericBidderRequests = bidder.getBidderRequests(bidRequest.id)
323+
def openXBidderRequests = openXBidder.getBidderRequests(bidRequest.id)
324+
assert genericBidderRequests.size() == 1
325+
assert openXBidderRequests.size() == 1
326+
327+
and: "PBS shouldn't contain errors, warnings and seat non bid"
328+
assert !bidResponse.ext?.warnings
329+
assert !bidResponse.ext?.errors
330+
assert !bidResponse.ext.seatnonbid
331+
332+
cleanup: "Reset mock"
333+
openXBidder.reset()
334+
bidder.reset()
335+
}
336+
}
337+
338+

0 commit comments

Comments
 (0)