diff --git a/src/main/java/org/prebid/server/bidder/algorix/AlgorixBidder.java b/src/main/java/org/prebid/server/bidder/algorix/AlgorixBidder.java index 9f8b4072de9..ff08575eade 100644 --- a/src/main/java/org/prebid/server/bidder/algorix/AlgorixBidder.java +++ b/src/main/java/org/prebid/server/bidder/algorix/AlgorixBidder.java @@ -1,16 +1,21 @@ package org.prebid.server.bidder.algorix; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Format; import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Video; +import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; import io.vertx.core.MultiMap; import io.vertx.core.http.HttpMethod; import org.apache.commons.collections4.CollectionUtils; import org.prebid.server.bidder.Bidder; +import org.prebid.server.bidder.algorix.model.AlgorixBidExt; +import org.prebid.server.bidder.algorix.model.AlgorixVideoExt; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; import org.prebid.server.bidder.model.HttpCall; @@ -20,6 +25,7 @@ import org.prebid.server.json.DecodeException; import org.prebid.server.json.JacksonMapper; import org.prebid.server.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebid; import org.prebid.server.proto.openrtb.ext.request.algorix.ExtImpAlgorix; import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.util.HttpUtil; @@ -36,7 +42,7 @@ */ public class AlgorixBidder implements Bidder { - private static final TypeReference> ALGORIX_EXT_TYPE_REFERENCE = + private static final TypeReference> ALGORIX_EXT_TYPE_REFERENCE = new TypeReference<>() { }; @@ -63,8 +69,9 @@ public Result>> makeHttpRequests(BidRequest request for (Imp imp : request.getImp()) { try { - extImpAlgorix = extImpAlgorix == null ? parseImpExt(imp) : extImpAlgorix; - updatedImps.add(updateImp(imp)); + final ExtPrebid impExt = parseImpExt(imp); + extImpAlgorix = extImpAlgorix == null ? impExt.getBidder() : extImpAlgorix; + updatedImps.add(updateImp(imp, impExt.getPrebid())); } catch (PreBidException error) { errors.add(BidderError.badInput(error.getMessage())); } @@ -86,39 +93,47 @@ public Result>> makeHttpRequests(BidRequest request errors); } - /** - * Parse Ext Imp - * - * @param imp BidRequest Imp - * @return Algorix Ext Imp - */ - private ExtImpAlgorix parseImpExt(Imp imp) { + private ExtPrebid parseImpExt(Imp imp) { try { - return mapper.mapper().convertValue(imp.getExt(), ALGORIX_EXT_TYPE_REFERENCE).getBidder(); + return mapper.mapper().convertValue(imp.getExt(), ALGORIX_EXT_TYPE_REFERENCE); } catch (IllegalArgumentException error) { throw new PreBidException(String.format("Impression Id=%s, has invalid Ext", imp.getId())); } } - /** - * Update Imp for transform banner Size - * - * @param imp imp - * @return new imp - */ - private static Imp updateImp(Imp imp) { + private Imp updateImp(Imp imp, ExtImpPrebid extImpPrebid) { if (imp.getBanner() != null) { - final Banner banner = imp.getBanner(); - if (!(isValidSizeValue(banner.getW()) && isValidSizeValue(banner.getH())) - && CollectionUtils.isNotEmpty(banner.getFormat())) { - final Format firstFormat = banner.getFormat().get(FIRST_INDEX); - imp = imp.toBuilder() - .banner(banner.toBuilder() - .w(firstFormat.getW()) - .h(firstFormat.getH()) - .build()) - .build(); - } + imp = updateBannerImp(imp); + } + if (imp.getVideo() != null) { + imp = updateVideoImp(imp, extImpPrebid); + } + return imp; + } + + private Imp updateBannerImp(Imp imp) { + final Banner banner = imp.getBanner(); + if (!(isValidSizeValue(banner.getW()) && isValidSizeValue(banner.getH())) + && CollectionUtils.isNotEmpty(banner.getFormat())) { + final Format firstFormat = banner.getFormat().get(FIRST_INDEX); + return imp.toBuilder() + .banner(banner.toBuilder() + .w(firstFormat.getW()) + .h(firstFormat.getH()) + .build()) + .build(); + } + return imp; + } + + private Imp updateVideoImp(Imp imp, ExtImpPrebid extImpPrebid) { + if (extImpPrebid != null && Objects.equals(extImpPrebid.getIsRewardedInventory(), 1)) { + final Video video = imp.getVideo(); + return imp.toBuilder() + .video(video.toBuilder() + .ext(mapper.mapper().valueToTree(AlgorixVideoExt.of(1))) + .build()) + .build(); } return imp; } @@ -133,13 +148,6 @@ private static boolean isValidSizeValue(Integer value) { return value != null && value > 0; } - /** - * get Region Info From Algorix Ext Imp - * Default For Global EP, APAC for apse EP, USE for use EP - * - * @param extImp Algorix Ext Imp - * @return Region String - */ private static String getRegionInfo(ExtImpAlgorix extImp) { if (Objects.isNull(extImp.getRegion())) { return "xyz"; @@ -154,13 +162,6 @@ private static String getRegionInfo(ExtImpAlgorix extImp) { } } - /** - * Replace url macro - * - * @param endpoint endpoint Url - * @param extImp Algorix Ext Imp - * @return target Url - */ private static String resolveUrl(String endpoint, ExtImpAlgorix extImp) { return endpoint .replace(URL_REGION_MACRO, getRegionInfo(extImp)) @@ -168,11 +169,6 @@ private static String resolveUrl(String endpoint, ExtImpAlgorix extImp) { .replace(URL_TOKEN_MACRO, extImp.getToken()); } - /** - * Add openrtb version header 2.5 - * - * @return headers - */ private static MultiMap resolveHeaders() { final MultiMap headers = HttpUtil.headers(); headers.add(HttpUtil.X_OPENRTB_VERSION_HEADER, "2.5"); @@ -196,19 +192,40 @@ private List extractBids(BidRequest bidRequest, BidResponse bidRespon return bidsFromResponse(bidRequest, bidResponse); } - private static List bidsFromResponse(BidRequest bidRequest, BidResponse 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, getBidType(bid, bidRequest.getImp()), bidResponse.getCur())) .collect(Collectors.toList()); } - private static BidType getBidType(String impId, List imps) { + private AlgorixBidExt parseAlgorixBidExt(Bid bid) { + try { + return mapper.mapper().treeToValue(bid.getExt(), AlgorixBidExt.class); + } catch (IllegalArgumentException | JsonProcessingException error) { + return null; + } + } + + private BidType getBidType(Bid bid, List imps) { + final AlgorixBidExt bidExt = parseAlgorixBidExt(bid); + if (Objects.nonNull(bidExt)) { + switch (bidExt.getMediaType()) { + case "banner": + return BidType.banner; + case "native": + return BidType.xNative; + case "video": + return BidType.video; + default: + break; + } + } for (Imp imp : imps) { - if (imp.getId().equals(impId)) { + if (imp.getId().equals(bid.getImpid())) { if (imp.getBanner() != null) { return BidType.banner; } else if (imp.getVideo() != null) { diff --git a/src/main/java/org/prebid/server/bidder/algorix/model/AlgorixBidExt.java b/src/main/java/org/prebid/server/bidder/algorix/model/AlgorixBidExt.java new file mode 100644 index 00000000000..efdd2fa48df --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/algorix/model/AlgorixBidExt.java @@ -0,0 +1,11 @@ +package org.prebid.server.bidder.algorix.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class AlgorixBidExt { + + @JsonProperty("mediaType") + String mediaType; +} diff --git a/src/main/java/org/prebid/server/bidder/algorix/model/AlgorixVideoExt.java b/src/main/java/org/prebid/server/bidder/algorix/model/AlgorixVideoExt.java new file mode 100644 index 00000000000..322a6ed19e3 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/algorix/model/AlgorixVideoExt.java @@ -0,0 +1,9 @@ +package org.prebid.server.bidder.algorix.model; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class AlgorixVideoExt { + + Integer rewarded; +} diff --git a/src/test/java/org/prebid/server/bidder/algorix/AlgorixBidderTest.java b/src/test/java/org/prebid/server/bidder/algorix/AlgorixBidderTest.java index 0ab7ec0a4e7..8b3b636e9dd 100644 --- a/src/test/java/org/prebid/server/bidder/algorix/AlgorixBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/algorix/AlgorixBidderTest.java @@ -13,6 +13,8 @@ import org.junit.Before; import org.junit.Test; import org.prebid.server.VertxTest; +import org.prebid.server.bidder.algorix.model.AlgorixBidExt; +import org.prebid.server.bidder.algorix.model.AlgorixVideoExt; import org.prebid.server.bidder.model.HttpRequest; import org.prebid.server.bidder.model.HttpCall; import org.prebid.server.bidder.model.HttpResponse; @@ -20,6 +22,7 @@ import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; import org.prebid.server.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebid; import org.prebid.server.proto.openrtb.ext.request.algorix.ExtImpAlgorix; import java.util.List; @@ -58,9 +61,7 @@ public void creationShouldFailOnInvalidEndPointUrl() { public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { // given final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder - .id("123") - .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + impBuilder -> impBuilder.id("123"), mapper.createArrayNode(), "APAC"); // when final Result>> result = algorixBidder.makeHttpRequests(bidRequest); @@ -72,11 +73,13 @@ public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { @Test public void makeHttpRequestsShouldReturnErrorOfEveryNotValidImp() { // given + final BidRequest bidRequest = BidRequest.builder() - .imp(asList(givenImp(impBuilder -> impBuilder + .imp(asList(Imp.builder() .id("123") - .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))), - givenImp(identity()))) + .banner(Banner.builder().build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()))).build(), + givenImp(identity(), null, "APAC"))) .build(); // when @@ -89,7 +92,7 @@ public void makeHttpRequestsShouldReturnErrorOfEveryNotValidImp() { @Test public void makeHttpRequestsShouldCreateCorrectURL() { // given - final BidRequest bidRequest = givenBidRequest(identity()); + final BidRequest bidRequest = givenBidRequest(identity(), null, "APAC"); // when final Result>> result = algorixBidder.makeHttpRequests(bidRequest); @@ -102,13 +105,29 @@ public void makeHttpRequestsShouldCreateCorrectURL() { .containsExactly("https://apac.xyz.svr-algorix.com/rtb/sa?sid=testSid&token=testToken"); } + @Test + public void makeHttpRequestsShouldCreateCorrectURLUSE() { + // given + final BidRequest bidRequest = givenBidRequest(identity(), null, "USE"); + + // when + final Result>> result = algorixBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .hasSize(1) + .extracting(HttpRequest::getUri) + .containsExactly("https://use.xyz.svr-algorix.com/rtb/sa?sid=testSid&token=testToken"); + } + @Test public void shouldSetBannerFormatWAndHValuesToBannerIfTheyAreNotPresentInBanner() { // given final Format bannerFormat = Format.builder().w(320).h(50).build(); final BidRequest bidRequest = givenBidRequest( impBuilder -> impBuilder.banner(Banner.builder() - .format(singletonList(bannerFormat)).build())); + .format(singletonList(bannerFormat)).build()), null, "APAC"); // when final Result>> result = algorixBidder.makeHttpRequests(bidRequest); @@ -123,6 +142,33 @@ public void shouldSetBannerFormatWAndHValuesToBannerIfTheyAreNotPresentInBanner( .containsOnly(tuple(320, 50)); } + @Test + public void shouldSetVideoRewardedToVideoIfTheySetIsRewardedTagInVideo() { + // given + final ExtImpPrebid extImpPrebid = ExtImpPrebid.builder().isRewardedInventory(1).build(); + final ExtImpAlgorix extImpAlgorix = ExtImpAlgorix + .of("testSid", "testToken", "testPlacementId", "testAppId", "APAC"); + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("123") + .video(Video.builder().build()) + .ext(mapper.valueToTree(ExtPrebid.of(extImpPrebid, extImpAlgorix))) + .build())) + .build(); + + // when + final Result>> result = algorixBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getVideo) + .extracting(Video::getExt) + .contains(mapper.valueToTree(AlgorixVideoExt.of(1))); + } + @Test public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { // given @@ -243,25 +289,94 @@ public void makeBidsShouldReturnNativeBidIfNativeIsPresentInRequestImp() throws .containsExactly(BidderBid.of(Bid.builder().impid("123").build(), xNative, "USD")); } + @Test + public void makeBidsShouldReturnBannerBidIfAlgorixBidExtOfBannerMediaType() throws JsonProcessingException { + // given + final HttpCall httpCall = givenHttpCall( + BidRequest.builder().build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> + bidBuilder.impid("123").ext(mapper.valueToTree(AlgorixBidExt.of("banner")))))); + + // when + final Result> result = algorixBidder.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsExactly(BidderBid.of(Bid.builder() + .impid("123") + .ext(mapper.valueToTree(AlgorixBidExt.of("banner"))) + .build(), banner, "USD")); + } + + @Test + public void makeBidsShouldReturnVideoBidIfAlgorixBidExtOfVideoMediaType() throws JsonProcessingException { + // given + final HttpCall httpCall = givenHttpCall( + BidRequest.builder().build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> + bidBuilder.impid("123").ext(mapper.valueToTree(AlgorixBidExt.of("video")))))); + + // when + final Result> result = algorixBidder.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsExactly(BidderBid.of(Bid.builder() + .impid("123") + .ext(mapper.valueToTree(AlgorixBidExt.of("video"))) + .build(), video, "USD")); + } + + @Test + public void makeBidsShouldReturnNativeBidIfAlgorixBidExtOfNativeMediaType() throws JsonProcessingException { + // given + final HttpCall httpCall = givenHttpCall( + BidRequest.builder().build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> + bidBuilder.impid("123").ext(mapper.valueToTree(AlgorixBidExt.of("native")))))); + + // when + final Result> result = algorixBidder.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsExactly(BidderBid.of(Bid.builder() + .impid("123") + .ext(mapper.valueToTree(AlgorixBidExt.of("native"))) + .build(), xNative, "USD")); + } + private static BidRequest givenBidRequest( Function bidRequestCustomizer, - Function impCustomizer) { + Function impCustomizer, + Object extImpPrebid, + String region) { return bidRequestCustomizer.apply(BidRequest.builder() - .imp(singletonList(givenImp(impCustomizer)))) + .imp(singletonList(givenImp(impCustomizer, extImpPrebid, region)))) .build(); } - private static BidRequest givenBidRequest(Function impCustomizer) { - return givenBidRequest(identity(), impCustomizer); + private static BidRequest givenBidRequest(Function impCustomizer, + Object extImpPrebid, + String region) { + return givenBidRequest(identity(), impCustomizer, extImpPrebid, region); } - private static Imp givenImp(Function impCustomizer) { + private static Imp givenImp(Function impCustomizer, + Object extImpPrebid, + String region) { return impCustomizer.apply(Imp.builder() .id("123") .banner(Banner.builder().id("banner_id").build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAlgorix.of("testSid", "testToken", "testPlacementId", "testAppId", "APAC"))))) + .ext(mapper.valueToTree(ExtPrebid.of(extImpPrebid, + ExtImpAlgorix.of("testSid", "testToken", "testPlacementId", "testAppId", region))))) .build(); } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-request.json index 6466e22c3aa..8ba4bc0cd10 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-request.json @@ -7,8 +7,21 @@ "w": 300, "h": 250 }, + "video": { + "w": 320, + "h": 480, + "mimes": [ + "video/mp4" + ], + "ext": { + "rewarded": 1 + } + }, "tagid": "123", "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, "bidder": { "sid": "testSid", "token": "testToken", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-request.json b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-request.json index 424724a77cf..3e1bf3e6d3f 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-request.json @@ -7,6 +7,13 @@ "w": 300, "h": 250 }, + "video": { + "w": 320, + "h": 480, + "mimes": [ + "video/mp4" + ] + }, "tagid": "123", "ext": { "algorix": { @@ -15,6 +22,9 @@ "placementId": "testPlacementId", "appId": "testAppId", "region": "APAC" + }, + "prebid": { + "is_rewarded_inventory": 1 } } }