diff --git a/src/main/java/org/prebid/server/bidder/grid/GridBidder.java b/src/main/java/org/prebid/server/bidder/grid/GridBidder.java index 22e6eb6b40d..6f5b0e93b35 100644 --- a/src/main/java/org/prebid/server/bidder/grid/GridBidder.java +++ b/src/main/java/org/prebid/server/bidder/grid/GridBidder.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Native; import com.iab.openrtb.request.Site; import com.iab.openrtb.request.User; import com.iab.openrtb.response.Bid; @@ -16,6 +17,7 @@ import org.prebid.server.bidder.grid.model.request.ExtImp; import org.prebid.server.bidder.grid.model.request.ExtImpGridData; import org.prebid.server.bidder.grid.model.request.ExtImpGridDataAdServer; +import org.prebid.server.bidder.grid.model.request.GridNative; import org.prebid.server.bidder.grid.model.request.Keywords; import org.prebid.server.bidder.grid.model.response.GridBidResponse; import org.prebid.server.bidder.grid.model.response.GridSeatBid; @@ -103,19 +105,48 @@ private static void validateImpExt(ExtImp extImp, String impId) { } private Imp modifyImp(Imp imp, ExtImp extImp) { + return imp.toBuilder() + .xNative(modifyNative(imp.getXNative())) + .ext(modifyImpExt(extImp)) + .build(); + } + + private ObjectNode modifyImpExt(ExtImp extImp) { final ExtImpGridData extImpData = extImp.getData(); final ExtImpGridDataAdServer adServer = extImpData != null ? extImpData.getAdServer() : null; final String adSlot = adServer != null ? adServer.getAdSlot() : null; if (StringUtils.isNotEmpty(adSlot)) { - final ExtImp modifiedExtImp = extImp.toBuilder() - .gpid(adSlot) - .build(); - return imp.toBuilder() - .ext(mapper.mapper().valueToTree(modifiedExtImp)) - .build(); + final ExtImp modifiedExtImp = extImp.toBuilder().gpid(adSlot).build(); + return mapper.mapper().valueToTree(modifiedExtImp); + } + + return mapper.mapper().valueToTree(extImp); + } + + private Native modifyNative(Native xNative) { + if (xNative == null) { + return null; + } + + final String nativeRequest = xNative.getRequest(); + final JsonNode requestNode = nodeFromString(nativeRequest); + + return GridNative.builder() + .requestNative((ObjectNode) requestNode) + .ver(xNative.getVer()) + .api(xNative.getApi()) + .battr(xNative.getBattr()) + .ext(xNative.getExt()) + .build(); + } + + public final JsonNode nodeFromString(String stringValue) { + try { + return StringUtils.isNotBlank(stringValue) ? mapper.mapper().readTree(stringValue) : null; + } catch (Exception e) { + return null; } - return imp; } private Keywords getKeywordsFromImpExt(JsonNode extImp) { @@ -218,7 +249,7 @@ private List bidsFromResponse(BidRequest bidRequest, GridBidResponse private BidderBid makeBidderBid(ObjectNode bidNode, List imps, String currency) { try { final Bid bid = mapper.mapper().treeToValue(bidNode, Bid.class); - final Bid modifiedBid = bid.toBuilder().ext(modifyBidExt(bidNode)).build(); + final Bid modifiedBid = bid.toBuilder().adm(modifyAdm(bidNode, bid)).ext(modifyBidExt(bidNode)).build(); return BidderBid.of(modifiedBid, resolveBidType(bidNode, bid.getImpid(), imps), currency); } catch (JsonProcessingException | IllegalArgumentException e) { @@ -226,6 +257,16 @@ private BidderBid makeBidderBid(ObjectNode bidNode, List imps, String curre } } + private String modifyAdm(ObjectNode bidNode, Bid bid) { + final JsonNode admNative = bidNode.at("/adm_native"); + final String bidAdm = bid.getAdm(); + if (admNative != null && !admNative.isEmpty() && StringUtils.isBlank(bidAdm)) { + return mapper.encodeToString(admNative); + } + + return bidAdm; + } + private ObjectNode modifyBidExt(ObjectNode gridBid) { final String demandSource = ObjectUtils.defaultIfNull(gridBid, MissingNode.getInstance()) .at("/ext/bidder/grid/demandSource") @@ -252,6 +293,8 @@ private static BidType resolveBidType(ObjectNode bidNode, String impId, List>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final ObjectNode expectedNativeRequest = mapper.createObjectNode(); + expectedNativeRequest.set("eventtrackers", mapper.createArrayNode()); + expectedNativeRequest.set("context", new IntNode(1)); + expectedNativeRequest.set("plcmttype", new IntNode(2)); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getXNative) + .containsExactly(GridNative.builder() + .requestNative(expectedNativeRequest) + .request(null) + .ver("1.2") + .build()); + } + @Test public void makeHttpRequestsShouldCorrectlyModifyRequestExt() throws IOException { // given @@ -289,7 +323,28 @@ public void makeBidsShouldReturnVideoBidIfNoBannerAndVideoIsPresent() throws Jso } @Test - public void makeBidsShouldReturnErrorIfImpHadNoBannerOrVideo() throws JsonProcessingException { + public void makeBidsShouldReturnNativeBidIfNoBannerAndNoVideoAndNativeIsPresent() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .xNative(Native.builder().build()) + .id("123").build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(givenBidNode()))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsExactly(BidderBid.of(Bid.builder().impid("123").build(), xNative, "USD")); + } + + @Test + public void makeBidsShouldReturnErrorIfImpHadNoBannerOrVideoOrNative() throws JsonProcessingException { // given final BidderCall httpCall = givenHttpCall( BidRequest.builder() @@ -381,6 +436,59 @@ public void makeBidsShouldModifyBidExtWithMetaIfDemandSourceIsPresentInBidExt() .containsExactly(expectedBidExt); } + @Test + public void makeBidsShouldNotReplacePresentAdmWithAdmNative() throws JsonProcessingException { + // given + final ObjectNode admNative = mapper.createObjectNode(); + admNative.put("admNativeProperty", "admNativeValue"); + + final ObjectNode bidNode = givenBidNode() + .put("adm", "someAdm") + .set("adm_native", admNative); + + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").video(Video.builder().build()).build())) + .build(), + mapper.writeValueAsString(givenBidResponse(bidNode))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getBid) + .extracting(Bid::getAdm) + .containsExactly("someAdm"); + } + + @Test + public void makeBidsShouldReplaceNotPresentAdmWithAdmNative() throws JsonProcessingException { + // given + final ObjectNode admNative = mapper.createObjectNode(); + admNative.put("admNativeProperty", "admNativeValue"); + + final ObjectNode bidNode = givenBidNode() + .set("adm_native", admNative); + + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").video(Video.builder().build()).build())) + .build(), + mapper.writeValueAsString(givenBidResponse(bidNode))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getBid) + .extracting(Bid::getAdm) + .containsExactly("{\"admNativeProperty\":\"admNativeValue\"}"); + } + private static GridBidResponse givenBidResponse( UnaryOperator gridBidResponse, ObjectNode objectNode) {