diff --git a/src/main/java/org/prebid/server/bidder/iqzone/IqzoneBidder.java b/src/main/java/org/prebid/server/bidder/iqzone/IqzoneBidder.java index 7785cd476b8..c6f473bc52a 100644 --- a/src/main/java/org/prebid/server/bidder/iqzone/IqzoneBidder.java +++ b/src/main/java/org/prebid/server/bidder/iqzone/IqzoneBidder.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; +import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; import org.apache.commons.collections4.CollectionUtils; @@ -49,32 +50,43 @@ public Result>> makeHttpRequests(BidRequest request final List> httpRequests = new ArrayList<>(); for (Imp imp : request.getImp()) { + final ExtImpIqzone extImpIqzone; try { - final ExtImpIqzone extImpIqzone = parseImpExt(imp); - final Imp modifiedImp = modifyImp(imp, extImpIqzone); - - httpRequests.add(makeHttpRequest(request, modifiedImp)); + extImpIqzone = parseImpExt(imp); } catch (IllegalArgumentException e) { return Result.withError(BidderError.badInput(e.getMessage())); } + + final Imp modifiedImp = modifyImp(imp, extImpIqzone); + httpRequests.add(makeHttpRequest(request, modifiedImp)); } return Result.withValues(httpRequests); } private ExtImpIqzone parseImpExt(Imp imp) { - return mapper.mapper().convertValue(imp.getExt(), IQZONE_EXT_TYPE_REFERENCE).getBidder(); + try { + return mapper.mapper().convertValue(imp.getExt(), IQZONE_EXT_TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException(e.getMessage()); + } } private Imp modifyImp(Imp imp, ExtImpIqzone impExt) { final String placementId = impExt.getPlacementId(); - final ObjectNode modifiedImpExtBidder = mapper.mapper().createObjectNode(); + final String endpointId = impExt.getEndpointId(); + + final boolean isPlacementIdEmpty = StringUtils.isEmpty(placementId); + if (isPlacementIdEmpty && StringUtils.isEmpty(endpointId)) { + return imp; + } - if (StringUtils.isNotEmpty(placementId)) { + final ObjectNode modifiedImpExtBidder = mapper.mapper().createObjectNode(); + if (!isPlacementIdEmpty) { modifiedImpExtBidder.set("placementId", TextNode.valueOf(placementId)); modifiedImpExtBidder.set("type", TextNode.valueOf("publisher")); } else { - modifiedImpExtBidder.set("endpointId", TextNode.valueOf(impExt.getEndpointId())); + modifiedImpExtBidder.set("endpointId", TextNode.valueOf(endpointId)); modifiedImpExtBidder.set("type", TextNode.valueOf("network")); } @@ -84,8 +96,7 @@ private Imp modifyImp(Imp imp, ExtImpIqzone impExt) { } private HttpRequest makeHttpRequest(BidRequest request, Imp imp) { - final BidRequest outgoingRequest = request.toBuilder().imp(List.of(imp)).build(); - + final BidRequest outgoingRequest = request.toBuilder().imp(Collections.singletonList(imp)).build(); return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper); } @@ -93,45 +104,39 @@ private HttpRequest makeHttpRequest(BidRequest request, Imp imp) { public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - return Result.of(extractBids(httpCall.getRequest().getPayload(), bidResponse), Collections.emptyList()); + return Result.withValues(extractBids(bidResponse)); } catch (DecodeException | PreBidException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private List extractBids(BidRequest bidRequest, BidResponse bidResponse) { + private List extractBids(BidResponse bidResponse) { if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { return Collections.emptyList(); } - return bidsFromResponse(bidRequest, bidResponse); - } - - private List bidsFromResponse(BidRequest bidRequest, BidResponse bidResponse) { return bidResponse.getSeatbid().stream() .filter(Objects::nonNull) .map(SeatBid::getBid) .filter(Objects::nonNull) .flatMap(Collection::stream) - .map(bid -> BidderBid.of(bid, getBidType(bid.getImpid(), bidRequest.getImp()), bidResponse.getCur())) + .filter(Objects::nonNull) + .map(bid -> BidderBid.of(bid, getBidMediaType(bid), bidResponse.getCur())) .toList(); } - private static BidType getBidType(String impId, List imps) { - for (Imp imp : imps) { - if (imp.getId().equals(impId)) { - if (imp.getBanner() != null) { - return BidType.banner; - } - if (imp.getVideo() != null) { - return BidType.video; - } - if (imp.getXNative() != null) { - return BidType.xNative; - } - throw new PreBidException("Unknown impression type for ID: \"%s\"".formatted(impId)); - } + private static BidType getBidMediaType(Bid bid) { + final Integer markupType = bid.getMtype(); + if (markupType == null) { + throw new PreBidException("Missing MType for bid: " + bid.getId()); } - throw new PreBidException("Failed to find impression for ID: \"%s\"".formatted(impId)); + + return switch (markupType) { + case 1 -> BidType.banner; + case 2 -> BidType.video; + case 4 -> BidType.xNative; + default -> throw new PreBidException( + "Unable to fetch mediaType " + bid.getMtype() + " in multi-format: " + bid.getImpid()); + }; } } diff --git a/src/test/java/org/prebid/server/bidder/iqzone/IqzoneBidderTest.java b/src/test/java/org/prebid/server/bidder/iqzone/IqzoneBidderTest.java index 94e31912408..5cb1f7f43fa 100644 --- a/src/test/java/org/prebid/server/bidder/iqzone/IqzoneBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/iqzone/IqzoneBidderTest.java @@ -3,11 +3,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.TextNode; -import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; -import com.iab.openrtb.request.Native; -import com.iab.openrtb.request.Video; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; @@ -116,6 +113,26 @@ public void makeHttpRequestsShouldModifyImpExtWithEndpointIdAndTypeIfEndpointIdP .set("bidder", mapper.valueToTree(expectedImpExtBidder))); } + @Test + public void makeHttpRequestsShouldNotModifyImpExt() { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> + impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpIqzone.of(null, null))))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .extracting(ext -> ext.get("bidder")) + .map(JsonNode::isEmpty) + .containsExactly(true); + } + @Test public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { // given @@ -163,9 +180,8 @@ public void makeBidsShouldReturnEmptyResponseIfBidResponseSeatBidIsNull() throws @Test public void makeBidsShouldCorrectlyProceedWithVideo() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidRequest(impBuilder -> impBuilder - .id("someId").video(Video.builder().build())), - mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("someId")))); + final BidderCall httpCall = givenHttpCall(givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.mtype(2)))); // when final Result> result = target.makeBids(httpCall, null); @@ -179,9 +195,8 @@ public void makeBidsShouldCorrectlyProceedWithVideo() throws JsonProcessingExcep @Test public void makeBidsShouldCorrectlyProceedWithNative() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidRequest(impBuilder -> impBuilder - .id("someId").xNative(Native.builder().build())), - mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("someId")))); + final BidderCall httpCall = givenHttpCall(givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.mtype(4)))); // when final Result> result = target.makeBids(httpCall, null); @@ -195,9 +210,8 @@ public void makeBidsShouldCorrectlyProceedWithNative() throws JsonProcessingExce @Test public void makeBidsShouldCorrectlyProceedWithBanner() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidRequest(impBuilder -> impBuilder - .id("someId").banner(Banner.builder().build())), - mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("someId")))); + final BidderCall httpCall = givenHttpCall(givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.mtype(1)))); // when final Result> result = target.makeBids(httpCall, null); @@ -211,9 +225,8 @@ public void makeBidsShouldCorrectlyProceedWithBanner() throws JsonProcessingExce @Test public void makeBidsShouldReturnErrorIfImpIdDoesNotMatchImpIdInBid() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidRequest(impBuilder -> impBuilder - .id("someIdThatIsDifferentFromIDInBid").xNative(Native.builder().build())), - mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("someId")))); + final BidderCall httpCall = givenHttpCall(givenBidRequest(identity()), + mapper.writeValueAsString(givenBidResponse(identity()))); // when final Result> result = target.makeBids(httpCall, null); @@ -222,17 +235,16 @@ public void makeBidsShouldReturnErrorIfImpIdDoesNotMatchImpIdInBid() throws Json assertThat(result.getErrors()).hasSize(1) .allSatisfy(error -> { assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); - assertThat(error.getMessage()).startsWith("Failed to find impression for ID:"); + assertThat(error.getMessage()).startsWith("Missing MType for bid: null"); }); assertThat(result.getValue()).isEmpty(); } @Test - public void makeBidsShouldReturnErrorWhenMissingType() throws JsonProcessingException { + public void makeBidsShouldReturnErrorWhenMissingMType() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall( - givenBidRequest(impBuilder -> impBuilder.id("someId")), - mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("someId")))); + final BidderCall httpCall = givenHttpCall(givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.mtype(null)))); // when final Result> result = target.makeBids(httpCall, null); @@ -241,7 +253,7 @@ public void makeBidsShouldReturnErrorWhenMissingType() throws JsonProcessingExce assertThat(result.getErrors()).hasSize(1) .allSatisfy(error -> { assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); - assertThat(error.getMessage()).startsWith("Unknown impression type for ID"); + assertThat(error.getMessage()).startsWith("Missing MType for bid: null"); }); } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-auction-iqzone-response.json b/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-auction-iqzone-response.json index ec5eadf42d0..4a6e48aac57 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-auction-iqzone-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-auction-iqzone-response.json @@ -8,6 +8,7 @@ "impid": "imp_id", "price": 3.33, "adm": "adm001", + "mtype": 1, "adid": "adid001", "cid": "cid001", "crid": "crid001", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-iqzone-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-iqzone-bid-response.json index 31a6f4419e3..26af1edc5a3 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-iqzone-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/iqzone/test-iqzone-bid-response.json @@ -7,6 +7,7 @@ "id": "bid_id", "impid": "imp_id", "price": 3.33, + "mtype": 1, "adid": "adid001", "crid": "crid001", "cid": "cid001",