Skip to content

Commit

Permalink
Core: Add storedAuctionResponse on imp level. (prebid#3461)
Browse files Browse the repository at this point in the history
  • Loading branch information
CTMBNara authored and sergseven committed Dec 23, 2024
1 parent c84a5d9 commit 3385670
Show file tree
Hide file tree
Showing 9 changed files with 459 additions and 200 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
Expand Down Expand Up @@ -191,10 +192,11 @@ Future<BidResponse> createOnSkippedAuction(AuctionContext auctionContext, List<S
.tmaxrequest(bidRequest.getTmax())
.build();

final List<String> cur = bidRequest.getCur();
final BidResponse bidResponse = BidResponse.builder()
.id(bidRequest.getId())
.cur(Stream.ofNullable(bidRequest.getCur()).flatMap(Collection::stream).findFirst().orElse(null))
.seatbid(Optional.ofNullable(seatBids).orElse(Collections.emptyList()))
.cur(CollectionUtils.isNotEmpty(cur) ? cur.getFirst() : null)
.seatbid(ListUtils.emptyIfNull(seatBids))
.ext(extBidResponse)
.build();

Expand Down
388 changes: 220 additions & 168 deletions src/main/java/org/prebid/server/auction/StoredResponseProcessor.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ public class ExtStoredAuctionResponse {

@JsonProperty("seatbidarr")
List<SeatBid> seatBids;

@JsonProperty("seatbidobj")
SeatBid seatBid;
}
5 changes: 3 additions & 2 deletions src/main/java/org/prebid/server/validation/ImpValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,9 @@ private void validateImpExtPrebidStoredResponses(ExtImpPrebid extPrebid,
+ " is not supported at the imp level");
}

if (extStoredAuctionResponse.getId() == null) {
throw new ValidationException("request.imp[%d].ext.prebid.storedauctionresponse.id should be defined",
if (extStoredAuctionResponse.getId() == null && extStoredAuctionResponse.getSeatBid() == null) {
throw new ValidationException(
"request.imp[%d].ext.prebid.storedauctionresponse.{id or seatbidobj} should be defined",
impIndex);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ class StoredAuctionResponse {
String id
@JsonProperty("seatbidarr")
List<SeatBid> seatBids
@JsonProperty("seatbidobj")
SeatBid seatBidObject
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package org.prebid.server.functional.tests

import org.prebid.server.functional.model.db.StoredResponse
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.StoredAuctionResponse
import org.prebid.server.functional.model.request.auction.StoredBidResponse
import org.prebid.server.functional.model.response.auction.Bid
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.SeatBid
import org.prebid.server.functional.service.PrebidServerException
import org.prebid.server.functional.util.PBSUtils
import spock.lang.PendingFeature

Expand Down Expand Up @@ -249,4 +252,127 @@ class StoredResponseSpec extends BaseSpec {
and: "PBS not send request to bidder"
assert bidder.getRequestCount(bidRequest.id) == 0
}

def "PBS should set seatBid in response from single imp.ext.prebid.storedBidResponse.seatbidobj when it is defined"() {
given: "Default basic BidRequest with stored response"
def bidRequest = BidRequest.defaultBidRequest
def storedAuctionResponse = SeatBid.getStoredResponse(bidRequest)
bidRequest.imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse(seatBidObject: storedAuctionResponse)

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

then: "Response should contain same stored auction response as requested"
assert convertToComparableSeatBid(response.seatbid) == [storedAuctionResponse]

and: "PBS not send request to bidder"
assert bidder.getRequestCount(bidRequest.id) == 0
}

def "PBS should throw error when imp.ext.prebid.storedBidResponse.seatbidobj is with empty seatbid"() {
given: "Default basic BidRequest with empty stored response"
def bidRequest = BidRequest.defaultBidRequest
bidRequest.imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse(seatBidObject: new SeatBid())

when: "PBS processes auction request"
defaultPbsService.sendAuctionRequest(bidRequest)

then: "PBS throws an exception"
def exception = thrown(PrebidServerException)
assert exception.statusCode == 400
assert exception.responseBody == 'Invalid request format: Seat can\'t be empty in stored response seatBid'

and: "PBS not send request to bidder"
assert bidder.getRequestCount(bidRequest.id) == 0
}

def "PBS should throw error when imp.ext.prebid.storedBidResponse.seatbidobj is with empty bids"() {
given: "Default basic BidRequest with empty bids for stored response"
def bidRequest = BidRequest.defaultBidRequest
bidRequest.imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse(seatBidObject: new SeatBid(bid: [], seat: GENERIC))

when: "PBS processes auction request"
defaultPbsService.sendAuctionRequest(bidRequest)

then: "PBS throws an exception"
def exception = thrown(PrebidServerException)
assert exception.statusCode == 400
assert exception.responseBody == 'Invalid request format: There must be at least one bid in stored response seatBid'

and: "PBS not send request to bidder"
assert bidder.getRequestCount(bidRequest.id) == 0
}

def "PBS should prefer seatbidobj over storedAuctionResponse.id from imp when both are present"() {
given: "Default basic BidRequest with stored response"
def bidRequest = BidRequest.defaultBidRequest
def storedAuctionResponse = SeatBid.getStoredResponse(bidRequest)
bidRequest.imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse().tap {
id = PBSUtils.randomString
seatBidObject = storedAuctionResponse
}

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

then: "Response should contain same stored auction response as requested"
assert convertToComparableSeatBid(response.seatbid) == [storedAuctionResponse]

and: "PBS not send request to bidder"
assert bidder.getRequestCount(bidRequest.id) == 0
}

def "PBS should set seatBids in response from multiple imp.ext.prebid.storedBidResponse.seatbidobj when it is defined"() {
given: "BidRequest with multiple imps"
def bidRequest = BidRequest.defaultBidRequest.tap {
imp = [impWithSeatBidObject, impWithSeatBidObject]
}

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

then: "Response should contain same stored auction response bids as requested"
assert convertToComparableSeatBid(response.seatbid).bid.flatten().sort() ==
bidRequest.imp.ext.prebid.storedAuctionResponse.seatBidObject.bid.flatten().sort()

and: "PBS not send request to bidder"
assert bidder.getRequestCount(bidRequest.id) == 0
}

def "PBS should prefer seatbidarr from request over seatbidobj from imp when both are present"() {
given: "Default basic BidRequest with stored response"
def bidRequest = BidRequest.defaultBidRequest
def storedAuctionResponse = SeatBid.getStoredResponse(bidRequest)
bidRequest.tap{
imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse().tap {
seatBidObject = SeatBid.getStoredResponse(bidRequest)
}
ext.prebid.storedAuctionResponse = new StoredAuctionResponse(seatBids: [storedAuctionResponse])
}

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

then: "Response should contain same stored auction response as requested"
assert response.seatbid == [storedAuctionResponse]

and: "PBS not send request to bidder"
assert bidder.getRequestCount(bidRequest.id) == 0
}

private static final Imp getImpWithSeatBidObject() {
def imp = Imp.defaultImpression
def bids = Bid.getDefaultBids([imp])
def seatBid = new SeatBid(bid: bids, seat: GENERIC)
imp.tap {
ext.prebid.storedAuctionResponse = new StoredAuctionResponse(seatBidObject: seatBid)
}
}

private static final List<SeatBid> convertToComparableSeatBid(List<SeatBid> seatBid) {
seatBid*.tap {
it.bid*.ext = null
it.group = null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public void skipAuctionShouldReturnFailedFutureWhenStoredResponseSeatBidAndIdAre
final AuctionContext auctionContext = AuctionContext.builder()
.bidRequest(BidRequest.builder()
.ext(ExtRequest.of(ExtRequestPrebid.builder()
.storedAuctionResponse(ExtStoredAuctionResponse.of(null, null))
.storedAuctionResponse(ExtStoredAuctionResponse.of(null, null, null))
.build()))
.build())
.build();
Expand All @@ -147,7 +147,7 @@ public void skipAuctionShouldReturnFailedFutureWhenStoredResponseSeatBidAndIdAre
public void skipAuctionShouldReturnBidResponseWithSeatBidsFromStoredAuctionResponse() {
// given
final List<SeatBid> givenSeatBids = givenSeatBids("bidId1", "bidId2");
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of("id", givenSeatBids);
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of("id", givenSeatBids, null);
final AuctionContext givenAuctionContext = AuctionContext.builder()
.debugWarnings(new ArrayList<>())
.bidRequest(BidRequest.builder()
Expand Down Expand Up @@ -179,7 +179,8 @@ public void skipAuctionShouldReturnBidResponseWithSeatBidsFromStoredAuctionRespo
@Test
public void skipAuctionShouldReturnEmptySeatBidsWhenSeatBidIsNull() {
// given
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of("id", singletonList(null));
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of(
"id", singletonList(null), null);
final AuctionContext givenAuctionContext = AuctionContext.builder()
.debugWarnings(new ArrayList<>())
.bidRequest(BidRequest.builder()
Expand Down Expand Up @@ -214,7 +215,7 @@ public void skipAuctionShouldReturnEmptySeatBidsWhenSeatBidIsNull() {
public void skipAuctionShouldReturnEmptySeatBidsWhenSeatIsEmpty() {
// given
final List<SeatBid> givenSeatBids = singletonList(SeatBid.builder().seat("").build());
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of("id", givenSeatBids);
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of("id", givenSeatBids, null);
final AuctionContext givenAuctionContext = AuctionContext.builder()
.debugWarnings(new ArrayList<>())
.bidRequest(BidRequest.builder()
Expand Down Expand Up @@ -249,7 +250,7 @@ public void skipAuctionShouldReturnEmptySeatBidsWhenSeatIsEmpty() {
public void skipAuctionShouldReturnEmptySeatBidsWhenBidsAreEmpty() {
// given
final List<SeatBid> givenSeatBids = singletonList(SeatBid.builder().seat("seat").bid(emptyList()).build());
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of("id", givenSeatBids);
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of("id", givenSeatBids, null);
final AuctionContext givenAuctionContext = AuctionContext.builder()
.debugWarnings(new ArrayList<>())
.bidRequest(BidRequest.builder()
Expand Down Expand Up @@ -283,7 +284,7 @@ public void skipAuctionShouldReturnEmptySeatBidsWhenBidsAreEmpty() {
@Test
public void skipAuctionShouldReturnBidResponseWithEmptySeatBidsWhenNoValueAvailableById() {
// given
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of("id", null);
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of("id", null, null);
final AuctionContext givenAuctionContext = AuctionContext.builder()
.debugWarnings(new ArrayList<>())
.timeoutContext(TimeoutContext.of(1000L, timeout, 0))
Expand Down Expand Up @@ -320,7 +321,7 @@ public void skipAuctionShouldReturnBidResponseWithEmptySeatBidsWhenNoValueAvaila
public void skipAuctionShouldReturnBidResponseWithStoredSeatBidsByProvidedId() {
// given
final List<SeatBid> givenSeatBids = givenSeatBids("bidId1", "bidId2");
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of("id", null);
final ExtStoredAuctionResponse givenStoredResponse = ExtStoredAuctionResponse.of("id", null, null);
final AuctionContext givenAuctionContext = AuctionContext.builder()
.debugWarnings(new ArrayList<>())
.timeoutContext(TimeoutContext.of(1000L, timeout, 0))
Expand Down
Loading

0 comments on commit 3385670

Please sign in to comment.