From 5d01ed7ae2e751b1ff5448eebe641fa5a660cff6 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 2 Aug 2021 16:55:43 +0300 Subject: [PATCH 01/29] Work in progress --- .../server/bidder/smaato/SmaatoBidder.java | 269 ++++++++++++------ 1 file changed, 177 insertions(+), 92 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index a917460490e..dafa6fb7719 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -2,14 +2,14 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.iab.openrtb.request.App; +import com.fasterxml.jackson.databind.node.TextNode; 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.Publisher; import com.iab.openrtb.request.Site; import com.iab.openrtb.request.User; +import com.iab.openrtb.request.Video; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; @@ -18,6 +18,8 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.checkerframework.checker.units.qual.A; +import org.prebid.server.auction.model.Endpoint; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; @@ -38,6 +40,8 @@ import org.prebid.server.json.JacksonMapper; import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidPbs; import org.prebid.server.proto.openrtb.ext.request.ExtSite; import org.prebid.server.proto.openrtb.ext.request.ExtUser; import org.prebid.server.proto.openrtb.ext.request.smaato.ExtImpSmaato; @@ -45,6 +49,7 @@ import org.prebid.server.util.HttpUtil; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -61,7 +66,7 @@ public class SmaatoBidder implements Bidder { new TypeReference>() { }; - private static final String CLIENT_VERSION = "prebid_server_0.2"; + private static final String CLIENT_VERSION = "prebid_server_0.4"; private static final String SMT_ADTYPE_HEADER = "X-SMT-ADTYPE"; private static final String SMT_AD_TYPE_IMG = "Img"; private static final String SMT_ADTYPE_RICHMEDIA = "Richmedia"; @@ -77,44 +82,147 @@ public SmaatoBidder(String endpointUrl, JacksonMapper mapper) { @Override public Result>> makeHttpRequests(BidRequest request) { + final BidRequest enrichedRequest; + try { + enrichedRequest = enrichRequestWithCommonProperties(request); + } catch (PreBidException e) { + return Result.withError(BidderError.badInput(e.getMessage())); + } + final List errors = new ArrayList<>(); - final List imps = new ArrayList<>(); - - String firstPublisherId = null; - for (Imp imp : request.getImp()) { - try { - final ExtImpSmaato extImpSmaato = parseImpExt(imp); - firstPublisherId = firstPublisherId == null ? extImpSmaato.getPublisherId() : firstPublisherId; - final Imp modifiedImp = modifyImp(imp, extImpSmaato.getAdspaceId()); - imps.add(modifiedImp); - } catch (PreBidException e) { - errors.add(BidderError.badInput(e.getMessage())); - } + if (isVideoRequest(request)) { + return Result.of(constructPodRequests(enrichedRequest, errors), errors); } + return Result.of(constructIndividualRequests(enrichedRequest, errors), errors); + } - final BidRequest outgoingRequest; + private BidRequest enrichRequestWithCommonProperties(BidRequest bidRequest) { + final ExtRequest requestExt = ExtRequest.empty(); + requestExt.addProperty("client", TextNode.valueOf(CLIENT_VERSION)); + + return bidRequest.toBuilder() + .user(modifyUser(bidRequest.getUser())) + .site(modifySite(bidRequest.getSite())) + .ext(requestExt) + .build(); + } + + private User modifyUser(User user) { + final ExtUser userExt = user != null ? user.getExt() : null; + if (userExt == null) { + return user; + } + + final ObjectNode extDataNode = userExt.getData(); + if (extDataNode == null || extDataNode.isEmpty()) { + return user; + } + + final SmaatoUserExtData smaatoUserExtData = convertExt(extDataNode, SmaatoUserExtData.class); + final User.UserBuilder userBuilder = user.toBuilder(); + + final String gender = smaatoUserExtData.getGender(); + if (StringUtils.isNotBlank(gender)) { + userBuilder.gender(gender); + } + + final Integer yob = smaatoUserExtData.getYob(); + if (yob != null && yob != 0) { + userBuilder.yob(yob); + } + + final String keywords = smaatoUserExtData.getKeywords(); + if (StringUtils.isNotBlank(keywords)) { + userBuilder.keywords(keywords); + } + + return userBuilder + .ext(userExt.toBuilder().data(null).build()) + .build(); + } + + private Site modifySite(Site site) { + if (site == null) { + return null; + } + + final ExtSite siteExt = site.getExt(); + if (siteExt != null) { + final SmaatoSiteExtData data = convertExt(siteExt.getData(), SmaatoSiteExtData.class); + final String keywords = data != null ? data.getKeywords() : null; + return Site.builder().keywords(keywords).ext(null).build(); + } + + return site; + } + + private T convertExt(ObjectNode ext, Class className) { try { - outgoingRequest = request.toBuilder() - .imp(imps) - .site(modifySite(request.getSite(), firstPublisherId)) - .app(modifyApp(request.getApp(), firstPublisherId)) - .user(modifyUser(request.getUser())) - .ext(mapper.fillExtension(ExtRequest.empty(), SmaatoBidRequestExt.of(CLIENT_VERSION))) - .build(); - } catch (PreBidException e) { - errors.add(BidderError.badInput(e.getMessage())); - return Result.withErrors(errors); + return mapper.mapper().convertValue(ext, className); + } catch (IllegalArgumentException e) { + throw new PreBidException(String.format("Cannot decode extension: %s", e.getMessage()), e); } + } - return Result.of(Collections.singletonList( - HttpRequest.builder() - .method(HttpMethod.POST) - .uri(endpointUrl) - .headers(HttpUtil.headers()) - .payload(outgoingRequest) - .body(mapper.encode(outgoingRequest)) - .build()), - errors); + private List> constructPodRequests(BidRequest bidRequest, List errors) { + final List validImps = new ArrayList<>(); + for (Imp imp : bidRequest.getImp()) { + if (imp.getVideo() == null) { + errors.add(BidderError.badInput("Invalid MediaType. Smaato only supports Video for AdPod.")); + continue; + } + validImps.add(imp); + } + + return validImps.stream() + .collect(Collectors.groupingBy(SmaatoBidder::extractPod, Collectors.toList())) + .values().stream() + .map(imps -> constructHttpRequest(bidRequest, imps)) + .collect(Collectors.toList()); + } + + private static String extractPod(Imp imp) { + return imp.getId().split("_")[0]; + } + + private List> constructIndividualRequests(BidRequest bidRequest, List errors) { + final BidRequest enrichedBidRequest = enrichIndividualRequest(bidRequest); + final List> requests = new ArrayList<>(); + + for (Imp imp : bidRequest.getImp()) { + final Banner banner = imp.getBanner(); + final Video video = imp.getVideo(); + if (video == null && banner == null) { + errors.add(BidderError.badInput("Invalid MediaType. Smaato only supports Banner and Video.")); + continue; + } + + if (video != null) { + final Imp videoImp = imp.toBuilder().video(null).build(); + requests.add(constructHttpRequest(enrichedBidRequest, Collections.singletonList(videoImp))); + } + if (banner != null) { + final Imp videoImp = imp.toBuilder().banner(null).build(); + requests.add(constructHttpRequest(enrichedBidRequest, Collections.singletonList(videoImp))); + } + } + + return requests; + } + + private BidRequest enrichIndividualRequest(BidRequest bidRequest) { + return null; + } + + private HttpRequest constructHttpRequest(BidRequest bidRequest, List imps) { + final BidRequest outgoingRequest = bidRequest.toBuilder().imp(imps).build(); + return HttpRequest.builder() + .uri(endpointUrl) + .method(HttpMethod.POST) + .headers(HttpUtil.headers()) + .body(mapper.encode(outgoingRequest)) + .payload(outgoingRequest) + .build(); } private ExtImpSmaato parseImpExt(Imp imp) { @@ -150,70 +258,47 @@ private static Banner modifyBanner(Banner banner) { return banner.toBuilder().w(firstFormat.getW()).h(firstFormat.getH()).build(); } - private Site modifySite(Site site, String firstPublisherId) { - if (site == null) { - return null; - } - - final Site.SiteBuilder siteBuilder = site.toBuilder() - .publisher(Publisher.builder().id(firstPublisherId).build()); + private static boolean isVideoRequest(BidRequest bidRequest) { + final ExtRequest requestExt = bidRequest.getExt(); + final ExtRequestPrebid prebid = requestExt != null ? requestExt.getPrebid() : null; + final ExtRequestPrebidPbs pbs = prebid != null ? prebid.getPbs() : null; + final String endpointName = pbs != null ? pbs.getEndpoint() : null; - final ExtSite siteExt = site.getExt(); - if (siteExt != null) { - final SmaatoSiteExtData data = convertExt(siteExt.getData(), SmaatoSiteExtData.class); - final String keywords = data != null ? data.getKeywords() : null; - siteBuilder.keywords(keywords).ext(null); - } - - return siteBuilder.build(); + return StringUtils.equals(endpointName, Endpoint.openrtb2_video.value()); } - private App modifyApp(App app, String publishedId) { - return app != null - ? app.toBuilder().publisher(Publisher.builder().id(publishedId).build()).build() - : null; - } - - private User modifyUser(User user) { - if (user == null) { - return null; - } - - final ExtUser userExt = user.getExt(); - final ObjectNode extDataNode = userExt != null ? userExt.getData() : null; - if (extDataNode == null || extDataNode.isEmpty()) { - return user; - } - - final SmaatoUserExtData smaatoUserExtData = convertExt(extDataNode, SmaatoUserExtData.class); - final User.UserBuilder userBuilder = user.toBuilder(); - - final String gender = smaatoUserExtData.getGender(); - if (StringUtils.isNotBlank(gender)) { - userBuilder.gender(gender); - } - - final Integer yob = smaatoUserExtData.getYob(); - if (yob != null && yob != 0) { - userBuilder.yob(yob); - } - - final String keywords = smaatoUserExtData.getKeywords(); - if (StringUtils.isNotBlank(keywords)) { - userBuilder.keywords(keywords); + private Result>> constructResult(BidRequest bidRequest, + List imps, + String firstPublisherId, + List errors) { + final BidRequest outgoingRequest; + try { + outgoingRequest = modifyBidRequest(bidRequest, imps, firstPublisherId); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + return Result.withErrors(errors); } - return userBuilder - .ext(userExt.toBuilder().data(null).build()) - .build(); + return Result.of( + Collections.singletonList( + HttpRequest.builder() + .method(HttpMethod.POST) + .uri(endpointUrl) + .headers(HttpUtil.headers()) + .payload(outgoingRequest) + .body(mapper.encode(outgoingRequest)) + .build()), + errors); } - private T convertExt(ObjectNode ext, Class className) { - try { - return mapper.mapper().convertValue(ext, className); - } catch (IllegalArgumentException e) { - throw new PreBidException(String.format("Cannot decode extension: %s", e.getMessage()), e); - } + private BidRequest modifyBidRequest(BidRequest bidRequest, List imps, String firstPublisherId) { + return bidRequest.toBuilder() + .imp(imps) + .site(modifySite(bidRequest.getSite(), firstPublisherId)) + .app(modifyApp(bidRequest.getApp(), firstPublisherId)) + .user(modifyUser(bidRequest.getUser())) + .ext(mapper.fillExtension(ExtRequest.empty(), SmaatoBidRequestExt.of(CLIENT_VERSION))) + .build(); } @Override From 48ffb75584e2418581d6a8245698220a2d0ee8a0 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 2 Aug 2021 18:45:13 +0300 Subject: [PATCH 02/29] Work in progress --- .../server/bidder/smaato/SmaatoBidder.java | 92 ++++++++++++++++--- 1 file changed, 77 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index dafa6fb7719..c99535ca8c7 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -1,12 +1,15 @@ package org.prebid.server.bidder.smaato; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; +import com.iab.openrtb.request.App; 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.Publisher; import com.iab.openrtb.request.Site; import com.iab.openrtb.request.User; import com.iab.openrtb.request.Video; @@ -18,7 +21,6 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; -import org.checkerframework.checker.units.qual.A; import org.prebid.server.auction.model.Endpoint; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.model.BidderBid; @@ -49,13 +51,13 @@ import org.prebid.server.util.HttpUtil; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Smaato {@link Bidder} implementation. @@ -177,7 +179,7 @@ private List> constructPodRequests(BidRequest bidRequest return validImps.stream() .collect(Collectors.groupingBy(SmaatoBidder::extractPod, Collectors.toList())) .values().stream() - .map(imps -> constructHttpRequest(bidRequest, imps)) + .map(imps -> constructHttpRequest(preparePodRequest(bidRequest, imps))) .collect(Collectors.toList()); } @@ -185,9 +187,15 @@ private static String extractPod(Imp imp) { return imp.getId().split("_")[0]; } + private BidRequest preparePodRequest(BidRequest bidRequest, List imps) { + return enrichWithPublisherId(bidRequest.toBuilder(), bidRequest, imps.get(0)) + .imp(modifyImpForAdBreak(imps)) + .build(); + } + private List> constructIndividualRequests(BidRequest bidRequest, List errors) { - final BidRequest enrichedBidRequest = enrichIndividualRequest(bidRequest); - final List> requests = new ArrayList<>(); + final List videoImps = new ArrayList<>(); + final List bannerImps = new ArrayList<>(); for (Imp imp : bidRequest.getImp()) { final Banner banner = imp.getBanner(); @@ -198,30 +206,84 @@ private List> constructIndividualRequests(BidRequest bid } if (video != null) { - final Imp videoImp = imp.toBuilder().video(null).build(); - requests.add(constructHttpRequest(enrichedBidRequest, Collections.singletonList(videoImp))); + videoImps.add(imp.toBuilder().video(null).build()); } if (banner != null) { - final Imp videoImp = imp.toBuilder().banner(null).build(); - requests.add(constructHttpRequest(enrichedBidRequest, Collections.singletonList(videoImp))); + bannerImps.add(imp.toBuilder().banner(null).build()); } } - return requests; + return Stream.of(videoImps, bannerImps) + .flatMap(List::stream) + .map(imp -> prepareIndividualRequest(bidRequest, imp)) + .map(this::constructHttpRequest) + .collect(Collectors.toList()); + } + + private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp) { + return enrichWithPublisherId(bidRequest.toBuilder(), bidRequest, imp) + .imp(Collections.singletonList(modifyImpForAdspace(imp))) + .build(); + } + + private BidRequest.BidRequestBuilder enrichWithPublisherId(BidRequest.BidRequestBuilder bidRequestBuilder, + BidRequest bidRequest, + Imp imp) { + final Publisher publisher = getPublisher(imp); + final Site site = bidRequest.getSite(); + final App app = bidRequest.getApp(); + if (site != null) { + bidRequestBuilder.site(site.toBuilder().publisher(publisher).build()); + } else if (app != null) { + bidRequestBuilder.app(app.toBuilder().publisher(publisher).build()); + } else { + throw new PreBidException("Missing Site/App."); + } + + return bidRequestBuilder; } - private BidRequest enrichIndividualRequest(BidRequest bidRequest) { + private Publisher getPublisher(Imp imp) { + final JsonNode publisherIdNode = imp.getExt().path("bidder").path("publisherId"); + if (publisherIdNode == null || !publisherIdNode.isTextual()) { + throw new PreBidException("Missing publisherId parameter."); + } + + return Publisher.builder().id(publisherIdNode.asText()).build(); + } + + private Imp modifyImpForAdspace(Imp imp) { + final JsonNode adSpaceIdNode = imp.getExt().path("bidder").path("adspaceId"); + if (adSpaceIdNode == null || !adSpaceIdNode.isTextual()) { + throw new PreBidException("Missing publisherId parameter."); + } + + final Imp.ImpBuilder impBuilder = imp.toBuilder() + .tagid(adSpaceIdNode.asText()) + .ext(null); + + final Banner banner = imp.getBanner(); + if (banner != null) { + return impBuilder.banner(modifyBanner(banner)).build(); + } else if (imp.getVideo() != null) { + return impBuilder.build(); + } + + return imp; + } + + private List modifyImpForAdBreak(List imps) { return null; } - private HttpRequest constructHttpRequest(BidRequest bidRequest, List imps) { - final BidRequest outgoingRequest = bidRequest.toBuilder().imp(imps).build(); + + private HttpRequest constructHttpRequest(BidRequest bidRequest) { return HttpRequest.builder() .uri(endpointUrl) .method(HttpMethod.POST) .headers(HttpUtil.headers()) - .body(mapper.encode(outgoingRequest)) - .payload(outgoingRequest) + .body(mapper.encode(bidRequest)) + .payload(bidRequest) .build(); } From adf8e6f1bc60417526a7f274bfcbc6ce44ea3d16 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 12:01:29 +0300 Subject: [PATCH 03/29] Work in progress --- .../server/bidder/smaato/SmaatoBidder.java | 111 +++++++++--------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index c99535ca8c7..a0a6ecd4fb4 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -57,7 +57,7 @@ import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.stream.IntStream; /** * Smaato {@link Bidder} implementation. @@ -179,7 +179,8 @@ private List> constructPodRequests(BidRequest bidRequest return validImps.stream() .collect(Collectors.groupingBy(SmaatoBidder::extractPod, Collectors.toList())) .values().stream() - .map(imps -> constructHttpRequest(preparePodRequest(bidRequest, imps))) + .map(imps -> constructHttpRequest(preparePodRequest(bidRequest, imps, errors))) + .filter(Objects::nonNull) .collect(Collectors.toList()); } @@ -187,15 +188,19 @@ private static String extractPod(Imp imp) { return imp.getId().split("_")[0]; } - private BidRequest preparePodRequest(BidRequest bidRequest, List imps) { - return enrichWithPublisherId(bidRequest.toBuilder(), bidRequest, imps.get(0)) - .imp(modifyImpForAdBreak(imps)) - .build(); + private BidRequest preparePodRequest(BidRequest bidRequest, List imps, List errors) { + try { + return enrichRequestWithPublisherId(bidRequest.toBuilder(), bidRequest, imps.get(0)) + .imp(imps.stream().map(imp -> modifyImpsForAdBreak(imp)).collect(Collectors.toList())) + .build(); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + return null; + } } private List> constructIndividualRequests(BidRequest bidRequest, List errors) { - final List videoImps = new ArrayList<>(); - final List bannerImps = new ArrayList<>(); + final List splitImps = new ArrayList<>(); for (Imp imp : bidRequest.getImp()) { final Banner banner = imp.getBanner(); @@ -206,30 +211,40 @@ private List> constructIndividualRequests(BidRequest bid } if (video != null) { - videoImps.add(imp.toBuilder().video(null).build()); + splitImps.add(imp.toBuilder().video(null).build()); } if (banner != null) { - bannerImps.add(imp.toBuilder().banner(null).build()); + splitImps.add(imp.toBuilder().banner(null).build()); } } - return Stream.of(videoImps, bannerImps) - .flatMap(List::stream) - .map(imp -> prepareIndividualRequest(bidRequest, imp)) + return splitImps.stream() + .map(imp -> prepareIndividualRequest(bidRequest, imp, errors)) + .filter(Objects::nonNull) .map(this::constructHttpRequest) .collect(Collectors.toList()); } - private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp) { - return enrichWithPublisherId(bidRequest.toBuilder(), bidRequest, imp) - .imp(Collections.singletonList(modifyImpForAdspace(imp))) - .build(); + private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp, List errors) { + try { + return enrichRequestWithPublisherId(bidRequest.toBuilder(), bidRequest, imp) + .imp(Collections.singletonList(modifyImpForAdspace(imp))) + .build(); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + return null; + } } - private BidRequest.BidRequestBuilder enrichWithPublisherId(BidRequest.BidRequestBuilder bidRequestBuilder, - BidRequest bidRequest, - Imp imp) { - final Publisher publisher = getPublisher(imp); + private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.BidRequestBuilder bidRequestBuilder, + BidRequest bidRequest, + Imp imp) { + final JsonNode publisherIdNode = imp.getExt().path("bidder").path("publisherId"); + if (publisherIdNode == null || !publisherIdNode.isTextual()) { + throw new PreBidException("Missing publisherId parameter."); + } + final Publisher publisher = Publisher.builder().id(publisherIdNode.asText()).build(); + final Site site = bidRequest.getSite(); final App app = bidRequest.getApp(); if (site != null) { @@ -243,15 +258,6 @@ private BidRequest.BidRequestBuilder enrichWithPublisherId(BidRequest.BidRequest return bidRequestBuilder; } - private Publisher getPublisher(Imp imp) { - final JsonNode publisherIdNode = imp.getExt().path("bidder").path("publisherId"); - if (publisherIdNode == null || !publisherIdNode.isTextual()) { - throw new PreBidException("Missing publisherId parameter."); - } - - return Publisher.builder().id(publisherIdNode.asText()).build(); - } - private Imp modifyImpForAdspace(Imp imp) { final JsonNode adSpaceIdNode = imp.getExt().path("bidder").path("adspaceId"); if (adSpaceIdNode == null || !adSpaceIdNode.isTextual()) { @@ -272,10 +278,30 @@ private Imp modifyImpForAdspace(Imp imp) { return imp; } - private List modifyImpForAdBreak(List imps) { - return null; + private List modifyImpsForAdBreak(List imps) { + final JsonNode adBreakIdNode = imps.get(0).getExt().path("bidder").path("adbreakId"); + if (adBreakIdNode == null || !adBreakIdNode.isTextual()) { + throw new PreBidException("Missing adbreakId parameter."); + } + final String adBreakId = adBreakIdNode.asText(); + + return IntStream.range(0, imps.size()) + .mapToObj(idx -> modifyImpForAdBreak(imps.get(idx), idx, adBreakId)) + .collect(Collectors.toList()); } + private Imp modifyImpForAdBreak(Imp imp, Integer sequence, String tagId) { + final Video modifiedVideo = imp.getVideo().toBuilder() + .sequence(sequence) + .ext(mapper.mapper().createObjectNode().put("context", "adpod")) + .build(); + + return imp.toBuilder() + .tagid(tagId) + .video(modifiedVideo) + .ext(null) + .build(); + } private HttpRequest constructHttpRequest(BidRequest bidRequest) { return HttpRequest.builder() @@ -287,27 +313,6 @@ private HttpRequest constructHttpRequest(BidRequest bidRequest) { .build(); } - private ExtImpSmaato parseImpExt(Imp imp) { - try { - return mapper.mapper().convertValue(imp.getExt(), SMAATO_EXT_TYPE_REFERENCE).getBidder(); - } catch (IllegalArgumentException e) { - throw new PreBidException(e.getMessage(), e); - } - } - - private static Imp modifyImp(Imp imp, String adspaceId) { - final Imp.ImpBuilder impBuilder = imp.toBuilder(); - if (imp.getBanner() != null) { - return impBuilder.banner(modifyBanner(imp.getBanner())).tagid(adspaceId).ext(null).build(); - } - - if (imp.getVideo() != null) { - return impBuilder.tagid(adspaceId).ext(null).build(); - } - throw new PreBidException(String.format( - "invalid MediaType. SMAATO only supports Banner and Video. Ignoring ImpID=%s", imp.getId())); - } - private static Banner modifyBanner(Banner banner) { if (banner.getW() != null && banner.getH() != null) { return banner; From e6a72672be12bdcaf957630a67e0cac88c057965 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 12:12:52 +0300 Subject: [PATCH 04/29] Work in progress --- .../server/bidder/smaato/SmaatoBidder.java | 50 ++----------------- 1 file changed, 5 insertions(+), 45 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index a0a6ecd4fb4..e6e9ceb4711 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -99,13 +99,10 @@ public Result>> makeHttpRequests(BidRequest request } private BidRequest enrichRequestWithCommonProperties(BidRequest bidRequest) { - final ExtRequest requestExt = ExtRequest.empty(); - requestExt.addProperty("client", TextNode.valueOf(CLIENT_VERSION)); - return bidRequest.toBuilder() .user(modifyUser(bidRequest.getUser())) .site(modifySite(bidRequest.getSite())) - .ext(requestExt) + .ext(mapper.fillExtension(ExtRequest.empty(), SmaatoBidRequestExt.of(CLIENT_VERSION))) .build(); } @@ -179,8 +176,9 @@ private List> constructPodRequests(BidRequest bidRequest return validImps.stream() .collect(Collectors.groupingBy(SmaatoBidder::extractPod, Collectors.toList())) .values().stream() - .map(imps -> constructHttpRequest(preparePodRequest(bidRequest, imps, errors))) + .map(imps -> preparePodRequest(bidRequest, imps, errors)) .filter(Objects::nonNull) + .map(this::constructHttpRequest) .collect(Collectors.toList()); } @@ -191,7 +189,7 @@ private static String extractPod(Imp imp) { private BidRequest preparePodRequest(BidRequest bidRequest, List imps, List errors) { try { return enrichRequestWithPublisherId(bidRequest.toBuilder(), bidRequest, imps.get(0)) - .imp(imps.stream().map(imp -> modifyImpsForAdBreak(imp)).collect(Collectors.toList())) + .imp(modifyImpsForAdBreak(imps)) .build(); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); @@ -263,7 +261,6 @@ private Imp modifyImpForAdspace(Imp imp) { if (adSpaceIdNode == null || !adSpaceIdNode.isTextual()) { throw new PreBidException("Missing publisherId parameter."); } - final Imp.ImpBuilder impBuilder = imp.toBuilder() .tagid(adSpaceIdNode.asText()) .ext(null); @@ -274,7 +271,6 @@ private Imp modifyImpForAdspace(Imp imp) { } else if (imp.getVideo() != null) { return impBuilder.build(); } - return imp; } @@ -283,10 +279,9 @@ private List modifyImpsForAdBreak(List imps) { if (adBreakIdNode == null || !adBreakIdNode.isTextual()) { throw new PreBidException("Missing adbreakId parameter."); } - final String adBreakId = adBreakIdNode.asText(); return IntStream.range(0, imps.size()) - .mapToObj(idx -> modifyImpForAdBreak(imps.get(idx), idx, adBreakId)) + .mapToObj(idx -> modifyImpForAdBreak(imps.get(idx), idx, adBreakIdNode.asText())) .collect(Collectors.toList()); } @@ -295,7 +290,6 @@ private Imp modifyImpForAdBreak(Imp imp, Integer sequence, String tagId) { .sequence(sequence) .ext(mapper.mapper().createObjectNode().put("context", "adpod")) .build(); - return imp.toBuilder() .tagid(tagId) .video(modifiedVideo) @@ -334,40 +328,6 @@ private static boolean isVideoRequest(BidRequest bidRequest) { return StringUtils.equals(endpointName, Endpoint.openrtb2_video.value()); } - private Result>> constructResult(BidRequest bidRequest, - List imps, - String firstPublisherId, - List errors) { - final BidRequest outgoingRequest; - try { - outgoingRequest = modifyBidRequest(bidRequest, imps, firstPublisherId); - } catch (PreBidException e) { - errors.add(BidderError.badInput(e.getMessage())); - return Result.withErrors(errors); - } - - return Result.of( - Collections.singletonList( - HttpRequest.builder() - .method(HttpMethod.POST) - .uri(endpointUrl) - .headers(HttpUtil.headers()) - .payload(outgoingRequest) - .body(mapper.encode(outgoingRequest)) - .build()), - errors); - } - - private BidRequest modifyBidRequest(BidRequest bidRequest, List imps, String firstPublisherId) { - return bidRequest.toBuilder() - .imp(imps) - .site(modifySite(bidRequest.getSite(), firstPublisherId)) - .app(modifyApp(bidRequest.getApp(), firstPublisherId)) - .user(modifyUser(bidRequest.getUser())) - .ext(mapper.fillExtension(ExtRequest.empty(), SmaatoBidRequestExt.of(CLIENT_VERSION))) - .build(); - } - @Override public Result> makeBids(HttpCall httpCall, BidRequest bidRequest) { try { From 155ca80437d45b3165bffa2a02780802274762a2 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 12:44:24 +0300 Subject: [PATCH 05/29] Work in progress --- .../server/bidder/smaato/SmaatoBidder.java | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index e6e9ceb4711..c5dbaec3242 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; @@ -226,7 +225,7 @@ private List> constructIndividualRequests(BidRequest bid private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp, List errors) { try { return enrichRequestWithPublisherId(bidRequest.toBuilder(), bidRequest, imp) - .imp(Collections.singletonList(modifyImpForAdspace(imp))) + .imp(Collections.singletonList(modifyImpForAdSpace(imp))) .build(); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); @@ -237,11 +236,9 @@ private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp, List private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.BidRequestBuilder bidRequestBuilder, BidRequest bidRequest, Imp imp) { - final JsonNode publisherIdNode = imp.getExt().path("bidder").path("publisherId"); - if (publisherIdNode == null || !publisherIdNode.isTextual()) { - throw new PreBidException("Missing publisherId parameter."); - } - final Publisher publisher = Publisher.builder().id(publisherIdNode.asText()).build(); + final Publisher publisher = Publisher.builder() + .id(getTextualPropertyFromImpExtBidder(imp.getExt(), "publisherId")) + .build(); final Site site = bidRequest.getSite(); final App app = bidRequest.getApp(); @@ -256,13 +253,9 @@ private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.Bid return bidRequestBuilder; } - private Imp modifyImpForAdspace(Imp imp) { - final JsonNode adSpaceIdNode = imp.getExt().path("bidder").path("adspaceId"); - if (adSpaceIdNode == null || !adSpaceIdNode.isTextual()) { - throw new PreBidException("Missing publisherId parameter."); - } + private Imp modifyImpForAdSpace(Imp imp) { final Imp.ImpBuilder impBuilder = imp.toBuilder() - .tagid(adSpaceIdNode.asText()) + .tagid(getTextualPropertyFromImpExtBidder(imp.getExt(), "adSpaceId")) .ext(null); final Banner banner = imp.getBanner(); @@ -275,13 +268,9 @@ private Imp modifyImpForAdspace(Imp imp) { } private List modifyImpsForAdBreak(List imps) { - final JsonNode adBreakIdNode = imps.get(0).getExt().path("bidder").path("adbreakId"); - if (adBreakIdNode == null || !adBreakIdNode.isTextual()) { - throw new PreBidException("Missing adbreakId parameter."); - } - + final String adBreakId = getTextualPropertyFromImpExtBidder(imps.get(0).getExt(), "adBreakId"); return IntStream.range(0, imps.size()) - .mapToObj(idx -> modifyImpForAdBreak(imps.get(idx), idx, adBreakIdNode.asText())) + .mapToObj(idx -> modifyImpForAdBreak(imps.get(idx), idx + 1, adBreakId)) .collect(Collectors.toList()); } @@ -297,6 +286,18 @@ private Imp modifyImpForAdBreak(Imp imp, Integer sequence, String tagId) { .build(); } + private static String getTextualPropertyFromImpExtBidder(JsonNode extNode, String propertyName) { + final JsonNode bidderNode = extNode.path("bidder"); + final JsonNode propertyNode = bidderNode != null && bidderNode.isObject() + ? extNode.path("bidder").path("String") + : null; + if (propertyNode == null || !propertyNode.isTextual()) { + throw new PreBidException(String.format("Missing %s parameter.", propertyName)); + } + + return propertyNode.asText(); + } + private HttpRequest constructHttpRequest(BidRequest bidRequest) { return HttpRequest.builder() .uri(endpointUrl) From 69d97bcd429660639bef84d34b8c30eab14b7896 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 15:15:08 +0300 Subject: [PATCH 06/29] Work in progress --- .../server/bidder/smaato/SmaatoBidder.java | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index c5dbaec3242..a85e1ebf293 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -1,6 +1,5 @@ package org.prebid.server.bidder.smaato; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.App; @@ -39,13 +38,11 @@ import org.prebid.server.exception.PreBidException; 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.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidPbs; import org.prebid.server.proto.openrtb.ext.request.ExtSite; import org.prebid.server.proto.openrtb.ext.request.ExtUser; -import org.prebid.server.proto.openrtb.ext.request.smaato.ExtImpSmaato; import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.util.HttpUtil; @@ -58,21 +55,17 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -/** - * Smaato {@link Bidder} implementation. - */ public class SmaatoBidder implements Bidder { - private static final TypeReference> SMAATO_EXT_TYPE_REFERENCE = - new TypeReference>() { - }; - private static final String CLIENT_VERSION = "prebid_server_0.4"; private static final String SMT_ADTYPE_HEADER = "X-SMT-ADTYPE"; + private static final String SMT_EXPIRES_HEADER = "X-Smt-Expires"; private static final String SMT_AD_TYPE_IMG = "Img"; private static final String SMT_ADTYPE_RICHMEDIA = "Richmedia"; private static final String SMT_ADTYPE_VIDEO = "Video"; + private static final int DEFAULT_TTL = 300; + private final String endpointUrl; private final JacksonMapper mapper; @@ -162,6 +155,15 @@ private T convertExt(ObjectNode ext, Class className) { } } + private static boolean isVideoRequest(BidRequest bidRequest) { + final ExtRequest requestExt = bidRequest.getExt(); + final ExtRequestPrebid prebid = requestExt != null ? requestExt.getPrebid() : null; + final ExtRequestPrebidPbs pbs = prebid != null ? prebid.getPbs() : null; + final String endpointName = pbs != null ? pbs.getEndpoint() : null; + + return StringUtils.equals(endpointName, Endpoint.openrtb2_video.value()); + } + private List> constructPodRequests(BidRequest bidRequest, List errors) { final List validImps = new ArrayList<>(); for (Imp imp : bidRequest.getImp()) { @@ -267,6 +269,18 @@ private Imp modifyImpForAdSpace(Imp imp) { return imp; } + private static Banner modifyBanner(Banner banner) { + if (banner.getW() != null && banner.getH() != null) { + return banner; + } + final List format = banner.getFormat(); + if (CollectionUtils.isEmpty(format)) { + throw new PreBidException(String.format("No sizes provided for Banner %s", format)); + } + final Format firstFormat = format.get(0); + return banner.toBuilder().w(firstFormat.getW()).h(firstFormat.getH()).build(); + } + private List modifyImpsForAdBreak(List imps) { final String adBreakId = getTextualPropertyFromImpExtBidder(imps.get(0).getExt(), "adBreakId"); return IntStream.range(0, imps.size()) @@ -308,27 +322,6 @@ private HttpRequest constructHttpRequest(BidRequest bidRequest) { .build(); } - private static Banner modifyBanner(Banner banner) { - if (banner.getW() != null && banner.getH() != null) { - return banner; - } - final List format = banner.getFormat(); - if (CollectionUtils.isEmpty(format)) { - throw new PreBidException(String.format("No sizes provided for Banner %s", format)); - } - final Format firstFormat = format.get(0); - return banner.toBuilder().w(firstFormat.getW()).h(firstFormat.getH()).build(); - } - - private static boolean isVideoRequest(BidRequest bidRequest) { - final ExtRequest requestExt = bidRequest.getExt(); - final ExtRequestPrebid prebid = requestExt != null ? requestExt.getPrebid() : null; - final ExtRequestPrebidPbs pbs = prebid != null ? prebid.getPbs() : null; - final String endpointName = pbs != null ? pbs.getEndpoint() : null; - - return StringUtils.equals(endpointName, Endpoint.openrtb2_video.value()); - } - @Override public Result> makeBids(HttpCall httpCall, BidRequest bidRequest) { try { @@ -363,10 +356,23 @@ private BidderBid bidderBid(Bid bid, String currency, MultiMap headers) { } final String markupType = getAdMarkupType(headers, bidAdm); - final Bid updateBid = bid.toBuilder().adm(renderAdMarkup(markupType, bidAdm)).build(); + final Bid updateBid = bid.toBuilder() + .adm(renderAdMarkup(markupType, bidAdm)) + .exp(getTtl(headers)) + .build(); return BidderBid.of(updateBid, getBidType(markupType), currency); } + private static int getTtl(MultiMap headers) { + try { + final long expiresAtMillis = Long.parseLong(headers.get(SMT_EXPIRES_HEADER)); + final long currentTimeMillis = System.currentTimeMillis(); + return (int) Math.max((expiresAtMillis - currentTimeMillis) / 1000, 0); + } catch (NumberFormatException e) { + return DEFAULT_TTL; + } + } + private static String getAdMarkupType(MultiMap headers, String adm) { final String adMarkupType = headers.get(SMT_ADTYPE_HEADER); if (StringUtils.isNotBlank(adMarkupType)) { From 3e4ad6f3458a1abd3620132ed95b6f9946126073 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 15:51:00 +0300 Subject: [PATCH 07/29] Work in progress --- src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index a85e1ebf293..d08ee93a225 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -303,7 +303,7 @@ private Imp modifyImpForAdBreak(Imp imp, Integer sequence, String tagId) { private static String getTextualPropertyFromImpExtBidder(JsonNode extNode, String propertyName) { final JsonNode bidderNode = extNode.path("bidder"); final JsonNode propertyNode = bidderNode != null && bidderNode.isObject() - ? extNode.path("bidder").path("String") + ? bidderNode.path(propertyName) : null; if (propertyNode == null || !propertyNode.isTextual()) { throw new PreBidException(String.format("Missing %s parameter.", propertyName)); From a26ced2bf027f65fa0457c74aee6bf9ad7b7b5b4 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 15:55:32 +0300 Subject: [PATCH 08/29] Work in progress --- .../java/org/prebid/server/bidder/smaato/SmaatoBidder.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index d08ee93a225..dd92ebb8d8f 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -301,11 +301,8 @@ private Imp modifyImpForAdBreak(Imp imp, Integer sequence, String tagId) { } private static String getTextualPropertyFromImpExtBidder(JsonNode extNode, String propertyName) { - final JsonNode bidderNode = extNode.path("bidder"); - final JsonNode propertyNode = bidderNode != null && bidderNode.isObject() - ? bidderNode.path(propertyName) - : null; - if (propertyNode == null || !propertyNode.isTextual()) { + final JsonNode propertyNode = extNode.path("bidder").path(propertyName); + if (!propertyNode.isTextual()) { throw new PreBidException(String.format("Missing %s parameter.", propertyName)); } From c4bc66d62277cb73c6392aaeedf06a66758c184c Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 16:02:20 +0300 Subject: [PATCH 09/29] Work in progress --- .../java/org/prebid/server/bidder/smaato/SmaatoBidder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index dd92ebb8d8f..bd0904ecd25 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -257,7 +257,7 @@ private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.Bid private Imp modifyImpForAdSpace(Imp imp) { final Imp.ImpBuilder impBuilder = imp.toBuilder() - .tagid(getTextualPropertyFromImpExtBidder(imp.getExt(), "adSpaceId")) + .tagid(getTextualPropertyFromImpExtBidder(imp.getExt(), "adspaceId")) .ext(null); final Banner banner = imp.getBanner(); @@ -282,7 +282,7 @@ private static Banner modifyBanner(Banner banner) { } private List modifyImpsForAdBreak(List imps) { - final String adBreakId = getTextualPropertyFromImpExtBidder(imps.get(0).getExt(), "adBreakId"); + final String adBreakId = getTextualPropertyFromImpExtBidder(imps.get(0).getExt(), "adreakId"); return IntStream.range(0, imps.size()) .mapToObj(idx -> modifyImpForAdBreak(imps.get(idx), idx + 1, adBreakId)) .collect(Collectors.toList()); From 91a694e4af7785779b8c00a2d0623c3eb7cf6c8b Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 16:11:58 +0300 Subject: [PATCH 10/29] Work in progress --- .../java/org/prebid/server/bidder/smaato/SmaatoBidder.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index bd0904ecd25..4ba81dee40d 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -245,14 +245,12 @@ private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.Bid final Site site = bidRequest.getSite(); final App app = bidRequest.getApp(); if (site != null) { - bidRequestBuilder.site(site.toBuilder().publisher(publisher).build()); + return bidRequestBuilder.site(site.toBuilder().publisher(publisher).build()); } else if (app != null) { - bidRequestBuilder.app(app.toBuilder().publisher(publisher).build()); + return bidRequestBuilder.app(app.toBuilder().publisher(publisher).build()); } else { throw new PreBidException("Missing Site/App."); } - - return bidRequestBuilder; } private Imp modifyImpForAdSpace(Imp imp) { From 142e5473d96cecdacb6343cbe3f4ad69f0af799c Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 16:17:24 +0300 Subject: [PATCH 11/29] Work in progress --- src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index 4ba81dee40d..1db377711af 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -280,7 +280,7 @@ private static Banner modifyBanner(Banner banner) { } private List modifyImpsForAdBreak(List imps) { - final String adBreakId = getTextualPropertyFromImpExtBidder(imps.get(0).getExt(), "adreakId"); + final String adBreakId = getTextualPropertyFromImpExtBidder(imps.get(0).getExt(), "adbreakId"); return IntStream.range(0, imps.size()) .mapToObj(idx -> modifyImpForAdBreak(imps.get(idx), idx + 1, adBreakId)) .collect(Collectors.toList()); From c457cc0bf2645872560d5d137795ab892dcc2480 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 17:26:40 +0300 Subject: [PATCH 12/29] Work in progress --- .../server/bidder/smaato/SmaatoBidder.java | 69 +++++++++++-------- .../ext/request/smaato/ExtImpSmaato.java | 3 + .../bidder/smaato/SmaatoBidderTest.java | 49 ++++++------- 3 files changed, 66 insertions(+), 55 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index 1db377711af..8e66b02cb5e 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -1,7 +1,8 @@ package org.prebid.server.bidder.smaato; -import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; @@ -38,11 +39,13 @@ import org.prebid.server.exception.PreBidException; 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.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidPbs; import org.prebid.server.proto.openrtb.ext.request.ExtSite; import org.prebid.server.proto.openrtb.ext.request.ExtUser; +import org.prebid.server.proto.openrtb.ext.request.smaato.ExtImpSmaato; import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.util.HttpUtil; @@ -57,6 +60,9 @@ public class SmaatoBidder implements Bidder { + private static final TypeReference> SMAATO_EXT_TYPE_REFERENCE = + new TypeReference>() { + }; private static final String CLIENT_VERSION = "prebid_server_0.4"; private static final String SMT_ADTYPE_HEADER = "X-SMT-ADTYPE"; private static final String SMT_EXPIRES_HEADER = "X-Smt-Expires"; @@ -189,10 +195,16 @@ private static String extractPod(Imp imp) { private BidRequest preparePodRequest(BidRequest bidRequest, List imps, List errors) { try { - return enrichRequestWithPublisherId(bidRequest.toBuilder(), bidRequest, imps.get(0)) - .imp(modifyImpsForAdBreak(imps)) + final ExtImpSmaato extImpSmaato = mapper.mapper().convertValue(imps.get(0).getExt(), + SMAATO_EXT_TYPE_REFERENCE).getBidder(); + + final String publisherId = getIfNotNullOrThrow(extImpSmaato, ExtImpSmaato::getPublisherId, "publisherId"); + final String adBreakId = getIfNotNullOrThrow(extImpSmaato, ExtImpSmaato::getAdbreakId, "adbreakId"); + + return enrichRequestWithPublisherId(bidRequest.toBuilder(), bidRequest, publisherId) + .imp(modifyImpsForAdBreak(imps, adBreakId)) .build(); - } catch (PreBidException e) { + } catch (PreBidException | IllegalArgumentException e) { errors.add(BidderError.badInput(e.getMessage())); return null; } @@ -210,10 +222,10 @@ private List> constructIndividualRequests(BidRequest bid } if (video != null) { - splitImps.add(imp.toBuilder().video(null).build()); + splitImps.add(imp.toBuilder().banner(null).build()); } if (banner != null) { - splitImps.add(imp.toBuilder().banner(null).build()); + splitImps.add(imp.toBuilder().video(null).build()); } } @@ -226,10 +238,16 @@ private List> constructIndividualRequests(BidRequest bid private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp, List errors) { try { - return enrichRequestWithPublisherId(bidRequest.toBuilder(), bidRequest, imp) - .imp(Collections.singletonList(modifyImpForAdSpace(imp))) + final ExtImpSmaato extImpSmaato = mapper.mapper().convertValue(imp.getExt(), + SMAATO_EXT_TYPE_REFERENCE).getBidder(); + + final String publisherId = getIfNotNullOrThrow(extImpSmaato, ExtImpSmaato::getPublisherId, "publisherId"); + final String adSpaceId = getIfNotNullOrThrow(extImpSmaato, ExtImpSmaato::getAdspaceId, "adspaceId"); + + return enrichRequestWithPublisherId(bidRequest.toBuilder(), bidRequest, publisherId) + .imp(Collections.singletonList(modifyImpForAdSpace(imp, adSpaceId))) .build(); - } catch (PreBidException e) { + } catch (PreBidException | IllegalArgumentException e) { errors.add(BidderError.badInput(e.getMessage())); return null; } @@ -237,9 +255,9 @@ private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp, List private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.BidRequestBuilder bidRequestBuilder, BidRequest bidRequest, - Imp imp) { + String publisherId) { final Publisher publisher = Publisher.builder() - .id(getTextualPropertyFromImpExtBidder(imp.getExt(), "publisherId")) + .id(publisherId) .build(); final Site site = bidRequest.getSite(); @@ -248,14 +266,13 @@ private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.Bid return bidRequestBuilder.site(site.toBuilder().publisher(publisher).build()); } else if (app != null) { return bidRequestBuilder.app(app.toBuilder().publisher(publisher).build()); - } else { - throw new PreBidException("Missing Site/App."); } + throw new PreBidException("Missing Site/App."); } - private Imp modifyImpForAdSpace(Imp imp) { + private Imp modifyImpForAdSpace(Imp imp, String adSpaceId) { final Imp.ImpBuilder impBuilder = imp.toBuilder() - .tagid(getTextualPropertyFromImpExtBidder(imp.getExt(), "adspaceId")) + .tagid(adSpaceId) .ext(null); final Banner banner = imp.getBanner(); @@ -279,8 +296,7 @@ private static Banner modifyBanner(Banner banner) { return banner.toBuilder().w(firstFormat.getW()).h(firstFormat.getH()).build(); } - private List modifyImpsForAdBreak(List imps) { - final String adBreakId = getTextualPropertyFromImpExtBidder(imps.get(0).getExt(), "adbreakId"); + private List modifyImpsForAdBreak(List imps, String adBreakId) { return IntStream.range(0, imps.size()) .mapToObj(idx -> modifyImpForAdBreak(imps.get(idx), idx + 1, adBreakId)) .collect(Collectors.toList()); @@ -289,7 +305,7 @@ private List modifyImpsForAdBreak(List imps) { private Imp modifyImpForAdBreak(Imp imp, Integer sequence, String tagId) { final Video modifiedVideo = imp.getVideo().toBuilder() .sequence(sequence) - .ext(mapper.mapper().createObjectNode().put("context", "adpod")) + .ext(mapper.mapper().createObjectNode().set("context", TextNode.valueOf("adpod"))) .build(); return imp.toBuilder() .tagid(tagId) @@ -298,15 +314,6 @@ private Imp modifyImpForAdBreak(Imp imp, Integer sequence, String tagId) { .build(); } - private static String getTextualPropertyFromImpExtBidder(JsonNode extNode, String propertyName) { - final JsonNode propertyNode = extNode.path("bidder").path(propertyName); - if (!propertyNode.isTextual()) { - throw new PreBidException(String.format("Missing %s parameter.", propertyName)); - } - - return propertyNode.asText(); - } - private HttpRequest constructHttpRequest(BidRequest bidRequest) { return HttpRequest.builder() .uri(endpointUrl) @@ -471,6 +478,14 @@ private static BidType getBidType(String markupType) { } } + private static R getIfNotNullOrThrow(T target, Function getter, String propertyName) { + final R result = getIfNotNull(target, getter); + if (result == null) { + throw new PreBidException(String.format("Missing %s property.", propertyName)); + } + return result; + } + private static R getIfNotNull(T target, Function getter) { return target != null ? getter.apply(target) : null; } diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/smaato/ExtImpSmaato.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/smaato/ExtImpSmaato.java index d976f85fc52..507b7e9b658 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/smaato/ExtImpSmaato.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/smaato/ExtImpSmaato.java @@ -16,4 +16,7 @@ public class ExtImpSmaato { @JsonProperty("adspaceId") String adspaceId; + + @JsonProperty("adbreakId") + String adbreakId; } diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index 69dbf69cd7f..7511b05d47a 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -1,6 +1,7 @@ package org.prebid.server.bidder.smaato; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; @@ -88,22 +89,6 @@ public void makeHttpRequestsShouldReturnErrorIfBannerOrVideoImpIsEmpty() { .badInput("invalid MediaType. SMAATO only supports Banner and Video. Ignoring ImpID=impid")); } - @Test - public void makeHttpRequestsShouldReturnErrorIfBannerSizeIsEmpty() { - // given - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder.id("impid").banner(Banner.builder().build())); - - // when - final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).hasSize(1); - assertThat(result.getErrors()).hasSize(1) - .containsOnly(BidderError - .badInput("No sizes provided for Banner null")); - } - @Test public void makeHttpRequestsShouldNotChangeBannerWidthAndHeightIfPresent() { // given @@ -161,7 +146,8 @@ public void makeHttpRequestsShouldModifyRequestSite() { .id("banner_id").format(asList(Format.builder().w(300).h(500).build(), Format.builder().w(450).h(150).build())).build()) .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpSmaato.of("publisherId", "adspaceId")))).build())) + ExtImpSmaato.of("publisherId", "adspaceId", "adbreakId")))) + .build())) .site(Site.builder() .ext(ExtSite.of(null, mapper.valueToTree(SmaatoSiteExtData.of("keywords")))) .build()) @@ -188,8 +174,11 @@ public void makeHttpRequestsShouldModifyRequestSite() { @Test public void makeHttpRequestsShouldModifyRequestAppPublisherId() { // given - final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> - bidRequestBuilder.app(App.builder().build()), identity()); + final BidRequest bidRequest = givenBidRequest( + bidRequestBuilder -> bidRequestBuilder.app(App.builder().build()).site(null), + impBuilder -> impBuilder.ext( + mapper.createObjectNode().set("bidder", mapper.createObjectNode().set( + "publisherId", TextNode.valueOf("publisherId"))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -215,7 +204,8 @@ public void makeHttpRequestsShouldModifyRequestUser() { .build(), Format.builder().w(450).h(150).build())).build()) .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpSmaato.of("publisherId", "adspaceId")))).build())) + ExtImpSmaato.of("publisherId", "adspaceId", "adbreakId")))) + .build())) .user(User.builder() .ext(ExtUser.builder() .data(mapper.valueToTree(SmaatoUserExtData.of("keywords", "gender", 1))) @@ -485,7 +475,9 @@ private static BidRequest givenBidRequest( Function impCustomizer) { return bidRequestCustomizer.apply(BidRequest.builder() - .imp(singletonList(givenImp(impCustomizer)))) + .site(Site.builder().build()) + .app(App.builder().build()) + .imp(singletonList(givenImp(impCustomizer)))) .build(); } @@ -495,13 +487,14 @@ private static BidRequest givenBidRequest(Function impCustomizer) { return impCustomizer.apply(Imp.builder() - .id("123") - .banner(Banner.builder() - .id("banner_id") - .w(300) - .h(500) - .build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of("publisherId", "adspaceId"))))) + .id("123") + .banner(Banner.builder() + .id("banner_id") + .w(300) + .h(500) + .build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpSmaato.of("publisherId", "adspaceId", "adbreakId"))))) .build(); } From 6fa16a81c93521a154c9fb9bee101c9e4ec0eae2 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 17:30:02 +0300 Subject: [PATCH 13/29] Work in progress --- .../java/org/prebid/server/bidder/smaato/SmaatoBidder.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index 8e66b02cb5e..63208981539 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -162,10 +162,9 @@ private T convertExt(ObjectNode ext, Class className) { } private static boolean isVideoRequest(BidRequest bidRequest) { - final ExtRequest requestExt = bidRequest.getExt(); - final ExtRequestPrebid prebid = requestExt != null ? requestExt.getPrebid() : null; - final ExtRequestPrebidPbs pbs = prebid != null ? prebid.getPbs() : null; - final String endpointName = pbs != null ? pbs.getEndpoint() : null; + final ExtRequestPrebid prebid = getIfNotNull(bidRequest.getExt(), ExtRequest::getPrebid); + final ExtRequestPrebidPbs pbs = getIfNotNull(prebid, ExtRequestPrebid::getPbs); + final String endpointName = getIfNotNull(pbs, ExtRequestPrebidPbs::getEndpoint); return StringUtils.equals(endpointName, Endpoint.openrtb2_video.value()); } From 329dbd5dfb786fdf37670a74607b403807de9fcf Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 3 Aug 2021 17:30:35 +0300 Subject: [PATCH 14/29] Work in progress --- src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index 63208981539..cac53aa66e8 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -64,7 +64,7 @@ public class SmaatoBidder implements Bidder { new TypeReference>() { }; private static final String CLIENT_VERSION = "prebid_server_0.4"; - private static final String SMT_ADTYPE_HEADER = "X-SMT-ADTYPE"; + private static final String SMT_ADTYPE_HEADER = "X-Smt-Adtype"; private static final String SMT_EXPIRES_HEADER = "X-Smt-Expires"; private static final String SMT_AD_TYPE_IMG = "Img"; private static final String SMT_ADTYPE_RICHMEDIA = "Richmedia"; From ee4527f69bc6b57b9f83e46cffeac30a9866b920 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Wed, 4 Aug 2021 13:25:43 +0300 Subject: [PATCH 15/29] Finished bidder --- .../server/bidder/smaato/SmaatoBidder.java | 84 ++++++++++++------- .../bidder/smaato/proto/SmaatoBidExt.java | 12 +++ 2 files changed, 68 insertions(+), 28 deletions(-) create mode 100644 src/main/java/org/prebid/server/bidder/smaato/proto/SmaatoBidExt.java diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index cac53aa66e8..9f1870c0c37 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -27,6 +27,7 @@ import org.prebid.server.bidder.model.HttpCall; import org.prebid.server.bidder.model.HttpRequest; import org.prebid.server.bidder.model.Result; +import org.prebid.server.bidder.smaato.proto.SmaatoBidExt; import org.prebid.server.bidder.smaato.proto.SmaatoBidRequestExt; import org.prebid.server.bidder.smaato.proto.SmaatoImage; import org.prebid.server.bidder.smaato.proto.SmaatoImageAd; @@ -47,6 +48,8 @@ import org.prebid.server.proto.openrtb.ext.request.ExtUser; import org.prebid.server.proto.openrtb.ext.request.smaato.ExtImpSmaato; import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; +import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo; import org.prebid.server.util.HttpUtil; import java.util.ArrayList; @@ -239,7 +242,6 @@ private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp, List try { final ExtImpSmaato extImpSmaato = mapper.mapper().convertValue(imp.getExt(), SMAATO_EXT_TYPE_REFERENCE).getBidder(); - final String publisherId = getIfNotNullOrThrow(extImpSmaato, ExtImpSmaato::getPublisherId, "publisherId"); final String adSpaceId = getIfNotNullOrThrow(extImpSmaato, ExtImpSmaato::getAdspaceId, "adspaceId"); @@ -255,12 +257,10 @@ private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp, List private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.BidRequestBuilder bidRequestBuilder, BidRequest bidRequest, String publisherId) { - final Publisher publisher = Publisher.builder() - .id(publisherId) - .build(); - + final Publisher publisher = Publisher.builder().id(publisherId).build(); final Site site = bidRequest.getSite(); final App app = bidRequest.getApp(); + if (site != null) { return bidRequestBuilder.site(site.toBuilder().publisher(publisher).build()); } else if (app != null) { @@ -340,28 +340,59 @@ private Result> extractBids(BidResponse bidResponse, MultiMap he return Result.empty(); } - return Result.withValues(bidResponse.getSeatbid().stream() + final List errors = new ArrayList<>(); + final List bidderBids = bidResponse.getSeatbid().stream() .filter(Objects::nonNull) .map(SeatBid::getBid) .filter(Objects::nonNull) .flatMap(Collection::stream) - .map(bid -> bidderBid(bid, bidResponse.getCur(), headers)) + .map(bid -> bidderBid(bid, bidResponse.getCur(), headers, errors)) .filter(Objects::nonNull) - .collect(Collectors.toList())); + .collect(Collectors.toList()); + + return Result.of(bidderBids, errors); + } + + private BidderBid bidderBid(Bid bid, String currency, MultiMap headers, List errors) { + try { + final String bidAdm = bid.getAdm(); + if (StringUtils.isBlank(bidAdm)) { + throw new PreBidException(String.format("Empty ad markup in bid with id: %s", bid.getId())); + } + final String markupType = getAdMarkupType(headers, bidAdm); + final BidType bidType = getBidType(markupType); + final Bid updatedBid = bid.toBuilder() + .adm(renderAdMarkup(markupType, bidAdm)) + .exp(getTtl(headers)) + .ext(buildExtPrebid(bid, bidType)) + .build(); + return BidderBid.of(updatedBid, bidType, currency); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + return null; + } } - private BidderBid bidderBid(Bid bid, String currency, MultiMap headers) { - final String bidAdm = bid.getAdm(); - if (StringUtils.isBlank(bidAdm)) { - throw new PreBidException(String.format("Empty ad markup in bid with id: %s", bid.getId())); + private ObjectNode buildExtPrebid(Bid bid, BidType bidType) { + final ExtBidPrebidVideo extBidPrebidVideo = getExtBidPrebidVideo(bid, bidType); + final ExtBidPrebid extBidPrebid = ExtBidPrebid.builder().video(extBidPrebidVideo).build(); + return mapper.mapper().valueToTree(ExtPrebid.of(extBidPrebid, null)); + } + + private ExtBidPrebidVideo getExtBidPrebidVideo(Bid bid, BidType bidType) { + final ObjectNode bidExt = bid.getExt(); + if (bidType != BidType.video || bidExt == null) { + return null; } - final String markupType = getAdMarkupType(headers, bidAdm); - final Bid updateBid = bid.toBuilder() - .adm(renderAdMarkup(markupType, bidAdm)) - .exp(getTtl(headers)) - .build(); - return BidderBid.of(updateBid, getBidType(markupType), currency); + final List categories = bid.getCat(); + final String primaryCategory = CollectionUtils.isNotEmpty(categories) ? categories.get(0) : null; + try { + final SmaatoBidExt smaatoBidExt = mapper.mapper().convertValue(bidExt, SmaatoBidExt.class); + return ExtBidPrebidVideo.of(smaatoBidExt.getDuration(), primaryCategory); + } catch (IllegalArgumentException e) { + throw new PreBidException("Invalid bid.ext."); + } } private static int getTtl(MultiMap headers) { @@ -378,14 +409,11 @@ private static String getAdMarkupType(MultiMap headers, String adm) { final String adMarkupType = headers.get(SMT_ADTYPE_HEADER); if (StringUtils.isNotBlank(adMarkupType)) { return adMarkupType; - } - if (adm.startsWith("image")) { + } else if (adm.startsWith("image")) { return SMT_AD_TYPE_IMG; - } - if (adm.startsWith("richmedia")) { + } else if (adm.startsWith("richmedia")) { return SMT_ADTYPE_RICHMEDIA; - } - if (adm.startsWith("%s", - clickEvent.toString(), + clickEvent, HttpUtil.encodeUrl(StringUtils.stripToEmpty(getIfNotNull(img, SmaatoImg::getCtaurl))), StringUtils.stripToEmpty(getIfNotNull(img, SmaatoImg::getUrl)), stripToZero(getIfNotNull(img, SmaatoImg::getW)), stripToZero(getIfNotNull(img, SmaatoImg::getH)), - impressionTracker.toString()); + impressionTracker); } private String extractAdmRichMedia(String adm) { @@ -452,9 +480,9 @@ private String extractAdmRichMedia(String adm) { String.format("\"\"", tracker))); return String.format("
%s%s
", - clickEvent.toString(), + clickEvent, StringUtils.stripToEmpty(getIfNotNull(richmedia.getMediadata(), SmaatoMediaData::getContent)), - impressionTracker.toString()); + impressionTracker); } private T convertAdmToAd(String value, Class className) { diff --git a/src/main/java/org/prebid/server/bidder/smaato/proto/SmaatoBidExt.java b/src/main/java/org/prebid/server/bidder/smaato/proto/SmaatoBidExt.java new file mode 100644 index 00000000000..b3b7a01e5bc --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/smaato/proto/SmaatoBidExt.java @@ -0,0 +1,12 @@ +package org.prebid.server.bidder.smaato.proto; + + +import lombok.AllArgsConstructor; +import lombok.Value; + +@Value +@AllArgsConstructor(staticName = "of") +public class SmaatoBidExt { + + Integer duration; +} From 5653ddba8e6903c138656846f99d9df1fc73381a Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Wed, 4 Aug 2021 14:21:11 +0300 Subject: [PATCH 16/29] Refactoring --- .../server/bidder/smaato/SmaatoBidder.java | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index 9f1870c0c37..2f6af0a1654 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -108,7 +108,7 @@ private BidRequest enrichRequestWithCommonProperties(BidRequest bidRequest) { } private User modifyUser(User user) { - final ExtUser userExt = user != null ? user.getExt() : null; + final ExtUser userExt = getIfNotNull(user, User::getExt); if (userExt == null) { return user; } @@ -142,17 +142,12 @@ private User modifyUser(User user) { } private Site modifySite(Site site) { - if (site == null) { - return null; - } - - final ExtSite siteExt = site.getExt(); + final ExtSite siteExt = getIfNotNull(site, Site::getExt); if (siteExt != null) { final SmaatoSiteExtData data = convertExt(siteExt.getData(), SmaatoSiteExtData.class); - final String keywords = data != null ? data.getKeywords() : null; + final String keywords = getIfNotNull(data, SmaatoSiteExtData::getKeywords); return Site.builder().keywords(keywords).ext(null).build(); } - return site; } @@ -185,7 +180,7 @@ private List> constructPodRequests(BidRequest bidRequest return validImps.stream() .collect(Collectors.groupingBy(SmaatoBidder::extractPod, Collectors.toList())) .values().stream() - .map(imps -> preparePodRequest(bidRequest, imps, errors)) + .map(impsPod -> preparePodRequest(bidRequest, impsPod, errors)) .filter(Objects::nonNull) .map(this::constructHttpRequest) .collect(Collectors.toList()); @@ -213,9 +208,17 @@ private BidRequest preparePodRequest(BidRequest bidRequest, List imps, List } private List> constructIndividualRequests(BidRequest bidRequest, List errors) { + return splitImps(bidRequest.getImp(), errors).stream() + .map(imp -> prepareIndividualRequest(bidRequest, imp, errors)) + .filter(Objects::nonNull) + .map(this::constructHttpRequest) + .collect(Collectors.toList()); + } + + private List splitImps(List imps, List errors) { final List splitImps = new ArrayList<>(); - for (Imp imp : bidRequest.getImp()) { + for (Imp imp : imps) { final Banner banner = imp.getBanner(); final Video video = imp.getVideo(); if (video == null && banner == null) { @@ -231,11 +234,7 @@ private List> constructIndividualRequests(BidRequest bid } } - return splitImps.stream() - .map(imp -> prepareIndividualRequest(bidRequest, imp, errors)) - .filter(Objects::nonNull) - .map(this::constructHttpRequest) - .collect(Collectors.toList()); + return splitImps; } private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp, List errors) { @@ -270,17 +269,14 @@ private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.Bid } private Imp modifyImpForAdSpace(Imp imp, String adSpaceId) { - final Imp.ImpBuilder impBuilder = imp.toBuilder() - .tagid(adSpaceId) - .ext(null); - final Banner banner = imp.getBanner(); - if (banner != null) { - return impBuilder.banner(modifyBanner(banner)).build(); - } else if (imp.getVideo() != null) { - return impBuilder.build(); - } - return imp; + return banner == null && imp.getVideo() == null + ? imp + : imp.toBuilder() + .tagid(adSpaceId) + .banner(banner != null ? modifyBanner(banner) : null) + .ext(null) + .build(); } private static Banner modifyBanner(Banner banner) { @@ -409,14 +405,14 @@ private static String getAdMarkupType(MultiMap headers, String adm) { final String adMarkupType = headers.get(SMT_ADTYPE_HEADER); if (StringUtils.isNotBlank(adMarkupType)) { return adMarkupType; - } else if (adm.startsWith("image")) { + } else if (adm.startsWith("{\"image\":")) { return SMT_AD_TYPE_IMG; - } else if (adm.startsWith("richmedia")) { + } else if (adm.startsWith("{\"richmedia\":")) { return SMT_ADTYPE_RICHMEDIA; } else if (adm.startsWith(" Date: Wed, 4 Aug 2021 14:47:25 +0300 Subject: [PATCH 17/29] Added unit tests --- .../bidder/smaato/SmaatoBidderTest.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index 7511b05d47a..bf17dca3582 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -23,9 +23,11 @@ import org.prebid.server.bidder.model.HttpRequest; import org.prebid.server.bidder.model.HttpResponse; import org.prebid.server.bidder.model.Result; +import org.prebid.server.bidder.smaato.proto.SmaatoBidRequestExt; import org.prebid.server.bidder.smaato.proto.SmaatoSiteExtData; import org.prebid.server.bidder.smaato.proto.SmaatoUserExtData; import org.prebid.server.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.ExtSite; import org.prebid.server.proto.openrtb.ext.request.ExtUser; import org.prebid.server.proto.openrtb.ext.request.smaato.ExtImpSmaato; @@ -58,6 +60,63 @@ public void creationShouldFailOnInvalidEndpointUrl() { assertThatIllegalArgumentException().isThrownBy(() -> new SmaatoBidder("invalid_url", jacksonMapper)); } + @Test + public void makeHttpRequestsShouldModifyUserIfUserExtDataIsPresent() { + // given + final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> + bidRequestBuilder.user(User.builder() + .ext(ExtUser.builder() + .data(mapper.valueToTree(SmaatoUserExtData.of("keywords", "gender", 1))) + .build()) + .build()), identity()); + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getUser) + .extracting(User::getKeywords, User::getGender, User::getYob) + .containsExactly(tuple("keywords", "gender", 1)); + } + + @Test + public void makeHttpRequestsShouldModifySiteIfSiteExtDataIsPresent() { + // given + final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> + bidRequestBuilder.site(Site.builder() + .ext(ExtSite.of(1, mapper.valueToTree(SmaatoSiteExtData.of("keywords")))) + .build()), identity()); + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getSite) + .extracting(Site::getKeywords, Site::getExt) + .containsExactly(tuple("keywords", null)); + } + + @Test + public void makeHttpRequestsShouldSetExt() { + // given + final BidRequest bidRequest = givenBidRequest(identity()); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getExt) + .containsExactly(jacksonMapper.fillExtension(ExtRequest.empty(), + SmaatoBidRequestExt.of("prebid_server_0.4"))); + } + @Test public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { // given From 070780a9855e65181f469932af20bd9d7a81cdcf Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Wed, 4 Aug 2021 16:09:50 +0300 Subject: [PATCH 18/29] Added unit tests --- .../server/bidder/smaato/SmaatoBidder.java | 66 +++--- .../bidder/smaato/SmaatoBidderTest.java | 218 +++++++++++++++++- 2 files changed, 250 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index 2f6af0a1654..1f4a685a478 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -207,6 +207,39 @@ private BidRequest preparePodRequest(BidRequest bidRequest, List imps, List } } + private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.BidRequestBuilder bidRequestBuilder, + BidRequest bidRequest, + String publisherId) { + final Publisher publisher = Publisher.builder().id(publisherId).build(); + final Site site = bidRequest.getSite(); + final App app = bidRequest.getApp(); + + if (site != null) { + return bidRequestBuilder.site(site.toBuilder().publisher(publisher).build()); + } else if (app != null) { + return bidRequestBuilder.app(app.toBuilder().publisher(publisher).build()); + } + throw new PreBidException("Missing Site/App."); + } + + private List modifyImpsForAdBreak(List imps, String adBreakId) { + return IntStream.range(0, imps.size()) + .mapToObj(idx -> modifyImpForAdBreak(imps.get(idx), idx + 1, adBreakId)) + .collect(Collectors.toList()); + } + + private Imp modifyImpForAdBreak(Imp imp, Integer sequence, String tagId) { + final Video modifiedVideo = imp.getVideo().toBuilder() + .sequence(sequence) + .ext(mapper.mapper().createObjectNode().set("context", TextNode.valueOf("adpod"))) + .build(); + return imp.toBuilder() + .tagid(tagId) + .video(modifiedVideo) + .ext(null) + .build(); + } + private List> constructIndividualRequests(BidRequest bidRequest, List errors) { return splitImps(bidRequest.getImp(), errors).stream() .map(imp -> prepareIndividualRequest(bidRequest, imp, errors)) @@ -253,21 +286,6 @@ private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp, List } } - private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.BidRequestBuilder bidRequestBuilder, - BidRequest bidRequest, - String publisherId) { - final Publisher publisher = Publisher.builder().id(publisherId).build(); - final Site site = bidRequest.getSite(); - final App app = bidRequest.getApp(); - - if (site != null) { - return bidRequestBuilder.site(site.toBuilder().publisher(publisher).build()); - } else if (app != null) { - return bidRequestBuilder.app(app.toBuilder().publisher(publisher).build()); - } - throw new PreBidException("Missing Site/App."); - } - private Imp modifyImpForAdSpace(Imp imp, String adSpaceId) { final Banner banner = imp.getBanner(); return banner == null && imp.getVideo() == null @@ -291,24 +309,6 @@ private static Banner modifyBanner(Banner banner) { return banner.toBuilder().w(firstFormat.getW()).h(firstFormat.getH()).build(); } - private List modifyImpsForAdBreak(List imps, String adBreakId) { - return IntStream.range(0, imps.size()) - .mapToObj(idx -> modifyImpForAdBreak(imps.get(idx), idx + 1, adBreakId)) - .collect(Collectors.toList()); - } - - private Imp modifyImpForAdBreak(Imp imp, Integer sequence, String tagId) { - final Video modifiedVideo = imp.getVideo().toBuilder() - .sequence(sequence) - .ext(mapper.mapper().createObjectNode().set("context", TextNode.valueOf("adpod"))) - .build(); - return imp.toBuilder() - .tagid(tagId) - .video(modifiedVideo) - .ext(null) - .build(); - } - private HttpRequest constructHttpRequest(BidRequest bidRequest) { return HttpRequest.builder() .uri(endpointUrl) diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index bf17dca3582..bf15bfcd060 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -10,6 +10,7 @@ import com.iab.openrtb.request.Publisher; import com.iab.openrtb.request.Site; import com.iab.openrtb.request.User; +import com.iab.openrtb.request.Video; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; @@ -17,6 +18,7 @@ import org.junit.Before; import org.junit.Test; import org.prebid.server.VertxTest; +import org.prebid.server.auction.model.Endpoint; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; import org.prebid.server.bidder.model.HttpCall; @@ -28,12 +30,17 @@ import org.prebid.server.bidder.smaato.proto.SmaatoUserExtData; import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidPbs; import org.prebid.server.proto.openrtb.ext.request.ExtSite; import org.prebid.server.proto.openrtb.ext.request.ExtUser; import org.prebid.server.proto.openrtb.ext.request.smaato.ExtImpSmaato; +import java.util.Arrays; import java.util.List; +import java.util.function.BiFunction; import java.util.function.Function; +import java.util.stream.Collectors; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -69,6 +76,7 @@ public void makeHttpRequestsShouldModifyUserIfUserExtDataIsPresent() { .data(mapper.valueToTree(SmaatoUserExtData.of("keywords", "gender", 1))) .build()) .build()), identity()); + // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -117,6 +125,190 @@ public void makeHttpRequestsShouldSetExt() { SmaatoBidRequestExt.of("prebid_server_0.4"))); } + // Pod requests tests + @Test + public void makePodHttpRequestsShouldReturnErrorsForImpsOfInvalidMediaType() { + // given + final BidRequest bidRequest = givenVideoBidRequest(identity(), impBuilder -> impBuilder.video(null)); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly( + BidderError.badInput("Invalid MediaType. Smaato only supports Video for AdPod.")); + } + + @Test + public void makePodHttpRequestsShouldCorrectlyConstructImpPodsAndRequests() { + // given + final BidRequest bidRequest = givenVideoBidRequest(identity(), + impBuilder -> impBuilder.id("1_0"), + impBuilder -> impBuilder.id("1_1"), + impBuilder -> impBuilder.id("2_0")); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final List> requests = result.getValue(); + assertThat(requests).hasSize(2); + assertThat(requests.get(0).getPayload().getImp()) + .extracting(Imp::getId) + .containsExactlyInAnyOrder("1_0", "1_1"); + assertThat(requests.get(1).getPayload().getImp()) + .extracting(Imp::getId) + .containsExactly("2_0"); + } + + @Test + public void makePodHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { + // given + final BidRequest bidRequest = givenVideoBidRequest( + impBuilder -> impBuilder + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(bidderError -> { + assertThat(bidderError.getType()).isEqualTo(BidderError.Type.bad_input); + assertThat(bidderError.getMessage()).startsWith("Cannot deserialize instance"); + }); + } + + @Test + public void makePodHttpRequestsShouldReturnErrorIfImpExtPublisherIdIsAbsent() { + // given + final BidRequest bidRequest = givenVideoBidRequest( + impBuilder -> impBuilder + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( + null, null, "adbreakId"))))); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly(BidderError.badInput("Missing publisherId property.")); + } + + @Test + public void makePodHttpRequestsShouldReturnErrorIfImpExtAdBreakIdIsAbsent() { + // given + final BidRequest bidRequest = givenVideoBidRequest( + impBuilder -> impBuilder + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( + "publisherId", null, null))))); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly(BidderError.badInput("Missing adbreakId property.")); + } + + @Test + public void makePodHttpRequestsShouldEnrichSiteWithPublisherIdIfSiteIsPresentInRequest() { + // given + final BidRequest bidRequest = givenVideoBidRequest( + bidRequestBuilder -> bidRequestBuilder.site(Site.builder().build()).app(null), + impBuilder -> impBuilder + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( + "publisherId", null, "adBreakId"))))); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getSite) + .extracting(Site::getPublisher) + .extracting(Publisher::getId) + .containsExactly("publisherId"); + } + + @Test + public void makePodHttpRequestsShouldEnrichAppWithPublisherIdIfSiteIsAbsentAndAppIsPresentInRequest() { + // given + final BidRequest bidRequest = givenVideoBidRequest( + bidRequestBuilder -> bidRequestBuilder.site(null).app(App.builder().build()), + impBuilder -> impBuilder + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( + "publisherId", null, "adBreakId"))))); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getApp) + .extracting(App::getPublisher) + .extracting(Publisher::getId) + .containsExactly("publisherId"); + } + + @Test + public void makePodHttpRequestsShouldReturnErrorIfSiteAndAppAreAbsentInRequest() { + // given + final BidRequest bidRequest = givenVideoBidRequest(bidRequestBuilder -> + bidRequestBuilder.site(null).app(null), identity()); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()) + .containsExactly(BidderError.badInput("Missing Site/App.")); + } + + @Test + public void makePodHttpRequestsShouldCorrectlyModifyImps() { + // given + final BidRequest bidRequest = givenVideoBidRequest(identity(), + impBuilder -> impBuilder.id("1_0"), + impBuilder -> impBuilder.id("1_1")); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + BiFunction resultCustomizer = + (builder, idx) -> builder + .id(String.format("1_%d", idx)) + .tagid("adbreakId") + .ext(null) + .video(Video.builder() + .ext(mapper.createObjectNode().set("context", TextNode.valueOf("adpod"))) + .sequence(idx + 1) + .build()); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .containsExactlyInAnyOrder( + givenVideoImp(impBuilder -> resultCustomizer.apply(impBuilder, 0)), + givenVideoImp(impBuilder -> resultCustomizer.apply(impBuilder, 1))); + } + + // Individual requests tests @Test public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { // given @@ -529,10 +721,28 @@ public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws Jso assertThat(result.getValue()).isEmpty(); } + private static BidRequest givenVideoBidRequest(Function impCustomizer) { + return givenVideoBidRequest(identity(), impCustomizer); + } + + private static BidRequest givenVideoBidRequest( + Function bidRequestCustomizer, + Function... impCustomizers) { + return bidRequestCustomizer.apply(BidRequest.builder() + .site(Site.builder().build()) + .app(App.builder().build()) + .imp(Arrays.stream(impCustomizers) + .map(SmaatoBidderTest::givenVideoImp) + .collect(Collectors.toList()))) + .ext(ExtRequest.of(ExtRequestPrebid.builder() + .pbs(ExtRequestPrebidPbs.of(Endpoint.openrtb2_video.value())) + .build())) + .build(); + } + private static BidRequest givenBidRequest( Function bidRequestCustomizer, Function impCustomizer) { - return bidRequestCustomizer.apply(BidRequest.builder() .site(Site.builder().build()) .app(App.builder().build()) @@ -544,6 +754,12 @@ private static BidRequest givenBidRequest(Function impCustomizer) { + return impCustomizer.apply(givenImp(identity()).toBuilder() + .video(Video.builder().build())) + .build(); + } + private static Imp givenImp(Function impCustomizer) { return impCustomizer.apply(Imp.builder() .id("123") From 3392cd3ba5fde8b7da10574bf28d4f084bc232ed Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Thu, 5 Aug 2021 11:55:40 +0300 Subject: [PATCH 19/29] Added unit tests --- .../server/bidder/smaato/SmaatoBidder.java | 9 +- .../bidder/smaato/SmaatoBidderTest.java | 240 ++++++++++-------- 2 files changed, 141 insertions(+), 108 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index 1f4a685a478..ed7b0c665c1 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -287,12 +287,9 @@ private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp, List } private Imp modifyImpForAdSpace(Imp imp, String adSpaceId) { - final Banner banner = imp.getBanner(); - return banner == null && imp.getVideo() == null - ? imp - : imp.toBuilder() + return imp.toBuilder() .tagid(adSpaceId) - .banner(banner != null ? modifyBanner(banner) : null) + .banner(getIfNotNull(imp.getBanner(), SmaatoBidder::modifyBanner)) .ext(null) .build(); } @@ -303,7 +300,7 @@ private static Banner modifyBanner(Banner banner) { } final List format = banner.getFormat(); if (CollectionUtils.isEmpty(format)) { - throw new PreBidException(String.format("No sizes provided for Banner %s", format)); + throw new PreBidException("No sizes provided for Banner."); } final Format firstFormat = format.get(0); return banner.toBuilder().w(firstFormat.getW()).h(firstFormat.getH()).build(); diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index bf15bfcd060..69f5aac7305 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -15,6 +15,7 @@ import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; import io.vertx.core.MultiMap; +import org.apache.commons.lang3.BooleanUtils; import org.junit.Before; import org.junit.Test; import org.prebid.server.VertxTest; @@ -125,7 +126,6 @@ public void makeHttpRequestsShouldSetExt() { SmaatoBidRequestExt.of("prebid_server_0.4"))); } - // Pod requests tests @Test public void makePodHttpRequestsShouldReturnErrorsForImpsOfInvalidMediaType() { // given @@ -245,10 +245,8 @@ public void makePodHttpRequestsShouldEnrichAppWithPublisherIdIfSiteIsAbsentAndAp // given final BidRequest bidRequest = givenVideoBidRequest( bidRequestBuilder -> bidRequestBuilder.site(null).app(App.builder().build()), - impBuilder -> impBuilder - .id("123") - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( - "publisherId", null, "adBreakId"))))); + impBuilder -> impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( + "publisherId", null, "adBreakId"))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -308,177 +306,213 @@ public void makePodHttpRequestsShouldCorrectlyModifyImps() { givenVideoImp(impBuilder -> resultCustomizer.apply(impBuilder, 1))); } - // Individual requests tests @Test - public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { + public void makeIndividualHttpRequestsShouldReturnErrorsOfImpsWithInvalidMediaTypes() { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.video(null).banner(null)); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()) + .containsExactly(BidderError.badInput("Invalid MediaType. Smaato only supports Banner and Video.")); + } + + @Test + public void makeIndividualHttpRequestsShouldCorrectlySplitImps() { // given final BidRequest bidRequest = givenBidRequest( + identity(), impBuilder -> impBuilder - .id("123") - .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + .banner(Banner.builder().w(1).h(1).build()) + .video(Video.builder().w(1).h(1).build()), + impBuilder -> impBuilder.banner(Banner.builder().w(1).h(1).build())); + // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).hasSize(1); - assertThat(result.getErrors().get(0).getMessage()).startsWith("Cannot deserialize instance"); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(3) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .allMatch(imp -> BooleanUtils.xor(new Boolean[]{imp.getVideo() != null, imp.getBanner() != null})); + } @Test - public void makeHttpRequestsShouldReturnErrorIfBannerOrVideoImpIsEmpty() { + public void makeIndividualHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { // given - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder.id("impid").banner(null).video(null)); + final BidRequest bidRequest = givenBidRequest(impBuilder -> + impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).hasSize(1); + assertThat(result.getValue()).isEmpty(); assertThat(result.getErrors()).hasSize(1) - .containsOnly(BidderError - .badInput("invalid MediaType. SMAATO only supports Banner and Video. Ignoring ImpID=impid")); + .allSatisfy(bidderError -> { + assertThat(bidderError.getType()).isEqualTo(BidderError.Type.bad_input); + assertThat(bidderError.getMessage()).startsWith("Cannot deserialize instance"); + }); } @Test - public void makeHttpRequestsShouldNotChangeBannerWidthAndHeightIfPresent() { + public void makeIndividualHttpRequestsShouldReturnErrorIfImpExtPublisherIdIsAbsent() { // given final BidRequest bidRequest = givenBidRequest( impBuilder -> impBuilder - .banner(Banner.builder() - .format(singletonList(Format.builder().w(300).h(500).build())) - .w(200) - .h(150) - .build())); + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( + null, "adspaceId", null))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getBanner) - .extracting(Banner::getW, Banner::getH) - .containsOnly(tuple(200, 150)); + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly(BidderError.badInput("Missing publisherId property.")); } @Test - public void makeHttpRequestsShouldSetBannerWidthAndHeightFromFirstFormatIfEmpty() { + public void makeIndividualHttpRequestsShouldReturnErrorIfImpExtAdSpaceIdIsAbsent() { // given - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder - .banner(Banner.builder() - .format(asList(Format.builder().w(300).h(500).build(), - Format.builder().w(450).h(150).build())) - .build())); + final BidRequest bidRequest = givenBidRequest(impBuilder -> + impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( + "publisherId", null, null))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getBanner) - .extracting(Banner::getW, Banner::getH) - .containsOnly(tuple(300, 500)); + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly(BidderError.badInput("Missing adspaceId property.")); } @Test - public void makeHttpRequestsShouldModifyRequestSite() { + public void makeIndividualHttpRequestsShouldEnrichSiteWithPublisherIdIfSiteIsPresentInRequest() { // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("123") - .banner(Banner.builder() - .id("banner_id").format(asList(Format.builder().w(300).h(500).build(), - Format.builder().w(450).h(150).build())).build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpSmaato.of("publisherId", "adspaceId", "adbreakId")))) - .build())) - .site(Site.builder() - .ext(ExtSite.of(null, mapper.valueToTree(SmaatoSiteExtData.of("keywords")))) - .build()) - .build(); + final BidRequest bidRequest = givenBidRequest( + bidRequestBuilder -> bidRequestBuilder.site(Site.builder().build()).app(null), + impBuilder -> impBuilder + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( + "publisherId", "adspaceId", null))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) .extracting(BidRequest::getSite) .extracting(Site::getPublisher) .extracting(Publisher::getId) - .containsOnly("publisherId"); - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .extracting(BidRequest::getSite) - .extracting(Site::getKeywords) - .containsOnly("keywords"); + .containsExactly("publisherId"); } @Test - public void makeHttpRequestsShouldModifyRequestAppPublisherId() { + public void makeIndividualHttpRequestsShouldEnrichAppWithPublisherIdIfSiteIsAbsentAndAppIsPresentInRequest() { // given final BidRequest bidRequest = givenBidRequest( - bidRequestBuilder -> bidRequestBuilder.app(App.builder().build()).site(null), - impBuilder -> impBuilder.ext( - mapper.createObjectNode().set("bidder", mapper.createObjectNode().set( - "publisherId", TextNode.valueOf("publisherId"))))); + bidRequestBuilder -> bidRequestBuilder.site(null).app(App.builder().build()), + impBuilder -> impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( + "publisherId", "adspaceId", null))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) + assertThat(result.getValue()) .extracting(HttpRequest::getPayload) .extracting(BidRequest::getApp) .extracting(App::getPublisher) .extracting(Publisher::getId) - .containsOnly("publisherId"); + .containsExactly("publisherId"); } @Test - public void makeHttpRequestsShouldModifyRequestUser() { + public void makeIndividualHttpRequestsShouldReturnErrorIfSiteAndAppAreAbsentInRequest() { // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("123") - .banner(Banner.builder() - .id("banner_id").format(asList(Format.builder().w(300).h(500) - .build(), - Format.builder().w(450).h(150).build())).build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpSmaato.of("publisherId", "adspaceId", "adbreakId")))) - .build())) - .user(User.builder() - .ext(ExtUser.builder() - .data(mapper.valueToTree(SmaatoUserExtData.of("keywords", "gender", 1))) - .build()) - .build()) - .build(); + final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> + bidRequestBuilder.site(null).app(null), identity()); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()) + .containsExactly(BidderError.badInput("Missing Site/App.")); + } + + @Test + public void makeIndividualHttpRequestsShouldReturnErrorIfBannerSizesAndFormatsAreAbsent() { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.banner(Banner.builder().build())); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly(BidderError.badInput("No sizes provided for Banner.")); + } + + @Test + public void makeIndividualHttpRequestsShouldNotModifyBannerIfBannerSizesArePresent() { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> + impBuilder.banner(Banner.builder().w(1).h(1).build())); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .extracting(BidRequest::getUser) - .extracting(User::getExt) - .containsOnly(ExtUser.builder().build()); - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .extracting(BidRequest::getUser) - .extracting(User::getGender) - .containsOnly("gender"); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBanner) + .containsExactly(Banner.builder().w(1).h(1).build()); + } + + @Test + public void makeIndividualHttpRequestsShouldReplaceBannerSizesWithFirstFormatIfFormatsArePresent() { + // given + final Banner banner = Banner.builder().format(singletonList(Format.builder().w(2).h(2).build())).build(); + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.banner(banner)); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBanner) + .containsExactly(banner.toBuilder().w(2).h(2).build()); + } + + @Test + public void makeIndividualHttpRequestsShouldSetImpTagIdAndRemoveImpExt() { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(mapper.valueToTree( + ExtPrebid.of(null, ExtImpSmaato.of("publisherId", "adspaceId", null))))); + + // when + final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getTagid, Imp::getExt) + .containsExactly(tuple("adspaceId", null)); } @Test @@ -742,11 +776,13 @@ private static BidRequest givenVideoBidRequest( private static BidRequest givenBidRequest( Function bidRequestCustomizer, - Function impCustomizer) { + Function... impCustomizers) { return bidRequestCustomizer.apply(BidRequest.builder() .site(Site.builder().build()) .app(App.builder().build()) - .imp(singletonList(givenImp(impCustomizer)))) + .imp(Arrays.stream(impCustomizers) + .map(SmaatoBidderTest::givenImp) + .collect(Collectors.toList()))) .build(); } From e88eb254df3a9d22d11ae6d10c5fc56596939ff6 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Thu, 5 Aug 2021 12:45:51 +0300 Subject: [PATCH 20/29] Finished unit tests --- .../server/bidder/smaato/SmaatoBidder.java | 4 +- .../bidder/smaato/SmaatoBidderTest.java | 70 ++++++++++++------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index ed7b0c665c1..510511bc128 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -321,9 +321,7 @@ public Result> makeBids(HttpCall httpCall, BidReques try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); return extractBids(bidResponse, httpCall.getResponse().getHeaders()); - } catch (DecodeException e) { - return Result.withError(BidderError.badServerResponse(e.getMessage())); - } catch (PreBidException e) { + } catch (PreBidException | DecodeException e) { return Result.withError(BidderError.badInput(e.getMessage())); } } diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index 69f5aac7305..4d3473dc2fe 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -26,6 +26,7 @@ import org.prebid.server.bidder.model.HttpRequest; import org.prebid.server.bidder.model.HttpResponse; import org.prebid.server.bidder.model.Result; +import org.prebid.server.bidder.smaato.proto.SmaatoBidExt; import org.prebid.server.bidder.smaato.proto.SmaatoBidRequestExt; import org.prebid.server.bidder.smaato.proto.SmaatoSiteExtData; import org.prebid.server.bidder.smaato.proto.SmaatoUserExtData; @@ -36,6 +37,9 @@ import org.prebid.server.proto.openrtb.ext.request.ExtSite; import org.prebid.server.proto.openrtb.ext.request.ExtUser; import org.prebid.server.proto.openrtb.ext.request.smaato.ExtImpSmaato; +import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; +import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo; +import org.prebid.server.util.HttpUtil; import java.util.Arrays; import java.util.List; @@ -43,7 +47,6 @@ import java.util.function.Function; import java.util.stream.Collectors; -import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.function.Function.identity; import static org.assertj.core.api.Assertions.assertThat; @@ -524,10 +527,12 @@ public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { final Result> result = smaatoBidder.makeBids(httpCall, null); // then - assertThat(result.getErrors()).hasSize(1); - assertThat(result.getErrors().get(0).getMessage()).startsWith("Failed to decode: Unrecognized token"); - assertThat(result.getErrors().get(0).getType()).isEqualTo(BidderError.Type.bad_server_response); assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(bidderError -> { + assertThat(bidderError.getType()).isEqualTo(BidderError.Type.bad_input); + assertThat(bidderError.getMessage()).startsWith("Failed to decode:"); + }); } @Test @@ -545,24 +550,25 @@ public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProces } @Test - public void makeBidsShouldReturnErrorIfNoBidAdm() throws JsonProcessingException { + public void makeBidsShouldReturnErrorOnEmptyBidAdm() throws JsonProcessingException { // given final HttpCall httpCall = givenHttpCall(BidRequest.builder().build(), - mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.id("test").impid("123"))), null); + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.id("test").impid("123"))), + HttpUtil.headers()); // when final Result> result = smaatoBidder.makeBids(httpCall, null); // then assertThat(result.getErrors()).hasSize(1) - .containsOnly(BidderError.badInput("Empty ad markup in bid with id: test")); + .containsExactly(BidderError.badInput("Empty ad markup in bid with id: test")); assertThat(result.getValue()).isEmpty(); } @Test public void makeBidsShouldReturnErrorIfNotSupportedMarkupType() throws JsonProcessingException { // given - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-SMT-ADTYPE", "anyType"); + final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "anyType"); final HttpCall httpCall = givenHttpCall(BidRequest.builder() .imp(singletonList(Imp.builder().id("123").build())) .build(), @@ -573,15 +579,15 @@ public void makeBidsShouldReturnErrorIfNotSupportedMarkupType() throws JsonProce final Result> result = smaatoBidder.makeBids(httpCall, null); // then - assertThat(result.getErrors()).hasSize(1) - .containsOnly(BidderError.badInput("Unknown markup type anyType")); assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1) + .containsExactly(BidderError.badInput("Invalid markupType anyType")); } @Test public void makeBidsShouldReturnErrorIfMarkupTypeIsBlank() throws JsonProcessingException { // given - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-SMT-ADTYPE", ""); + final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", ""); final HttpCall httpCall = givenHttpCall(BidRequest.builder() .imp(singletonList(Imp.builder().id("123").build())) .build(), @@ -593,14 +599,14 @@ public void makeBidsShouldReturnErrorIfMarkupTypeIsBlank() throws JsonProcessing // then assertThat(result.getErrors()).hasSize(1) - .containsOnly(BidderError.badInput("Invalid ad markup adm")); + .containsExactly(BidderError.badInput("Invalid ad markup adm.")); assertThat(result.getValue()).isEmpty(); } @Test public void makeBidsShouldReturnErrorIfAdmIsInvalid() throws JsonProcessingException { // given - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-SMT-ADTYPE", ""); + final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", ""); final HttpCall httpCall = givenHttpCall(BidRequest.builder() .imp(singletonList(Imp.builder().id("123").build())) .build(), @@ -611,15 +617,16 @@ public void makeBidsShouldReturnErrorIfAdmIsInvalid() throws JsonProcessingExcep final Result> result = smaatoBidder.makeBids(httpCall, null); // then - assertThat(result.getErrors()).hasSize(1) - .containsOnly(BidderError.badInput("Invalid ad markup {\"image\": invalid")); assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().get(0).getMessage()).startsWith("Cannot decode bid.adm:"); + assertThat(result.getErrors().get(0).getType()).isEqualTo(BidderError.Type.bad_input); } @Test public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsReachmedia() throws JsonProcessingException { // given - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-SMT-ADTYPE", "Richmedia"); + final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "Richmedia"); final HttpCall httpCall = givenHttpCall(BidRequest.builder() .imp(singletonList(Imp.builder().id("123").build())) .build(), @@ -641,16 +648,18 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsReachmedia() throws Json + "Fclick%2F2'), {cache: 'no-cache'});\">
hello
\"\"\"\"") + .ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.builder().build(), null))) + .exp(300) .build(); assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .containsOnly(BidderBid.of(expectedBid, banner, "USD")); + .containsExactly(BidderBid.of(expectedBid, banner, "USD")); } @Test public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsImg() throws JsonProcessingException { // given - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-SMT-ADTYPE", "Img"); + final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "Img"); final HttpCall httpCall = givenHttpCall(BidRequest.builder() .imp(singletonList(Imp.builder().id("123").build())) .build(), @@ -676,16 +685,18 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsImg() throws JsonProcess + "/img/320x50.jpg\" width=\"350\" height=\"50\"/>\"\"\"\"") + .ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.builder().build(), null))) + .exp(300) .build(); assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .containsOnly(BidderBid.of(expectedBid, banner, "USD")); + .containsExactly(BidderBid.of(expectedBid, banner, "USD")); } @Test public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsImgAndParametersAreEmpty() throws JsonProcessingException { // given - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-SMT-ADTYPE", "Img"); + final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "Img"); final HttpCall httpCall = givenHttpCall(BidRequest.builder() .imp(singletonList(Imp.builder().id("123").build())) .build(), @@ -711,10 +722,12 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsImgAndParametersAreEmpty + "/img/320x50.jpg\" width=\"0\" height=\"0\"/>\"\"\"\"") + .ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.builder().build(), null))) + .exp(300) .build(); assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .containsOnly(BidderBid.of(expectedBid, banner, "USD")); + .containsExactly(BidderBid.of(expectedBid, banner, "USD")); } @Test @@ -725,8 +738,13 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsVideo() throws JsonProce .imp(singletonList(Imp.builder().id("123").build())) .build(), mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm(""))), headers); + givenBidResponse(bidBuilder -> + bidBuilder + .impid("123") + .adm("") + .cat(singletonList("Category1")) + .ext(mapper.valueToTree(SmaatoBidExt.of(100))))), headers); // when final Result> result = smaatoBidder.makeBids(httpCall, null); @@ -735,10 +753,14 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsVideo() throws JsonProce final Bid expectedBid = Bid.builder() .impid("123") .adm("Video") + .cat(singletonList("Category1")) + .ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.builder() + .video(ExtBidPrebidVideo.of(100, "Category1")).build(), null))) + .exp(300) .build(); assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .containsOnly(BidderBid.of(expectedBid, video, "USD")); + .containsExactly(BidderBid.of(expectedBid, video, "USD")); } @Test From e48d648a8039d613a86c1c3574692d9dd6e024a4 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Thu, 5 Aug 2021 12:50:14 +0300 Subject: [PATCH 21/29] Fixed styling --- .../java/org/prebid/server/bidder/smaato/proto/SmaatoBidExt.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/proto/SmaatoBidExt.java b/src/main/java/org/prebid/server/bidder/smaato/proto/SmaatoBidExt.java index b3b7a01e5bc..b278d2a084e 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/proto/SmaatoBidExt.java +++ b/src/main/java/org/prebid/server/bidder/smaato/proto/SmaatoBidExt.java @@ -1,6 +1,5 @@ package org.prebid.server.bidder.smaato.proto; - import lombok.AllArgsConstructor; import lombok.Value; From 3e92efa054e91f754245557f2e3ff2906e238dcb Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Thu, 5 Aug 2021 14:14:07 +0300 Subject: [PATCH 22/29] Finished unit tests --- .../prebid/server/bidder/smaato/SmaatoBidderTest.java | 2 +- .../it/openrtb2/smaato/test-auction-smaato-response.json | 9 ++++----- .../it/openrtb2/smaato/test-smaato-bid-request.json | 4 +--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index 4d3473dc2fe..eaf6a22dabd 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -340,7 +340,7 @@ public void makeIndividualHttpRequestsShouldCorrectlySplitImps() { assertThat(result.getValue()).hasSize(3) .extracting(HttpRequest::getPayload) .flatExtracting(BidRequest::getImp) - .allMatch(imp -> BooleanUtils.xor(new Boolean[]{imp.getVideo() != null, imp.getBanner() != null})); + .allMatch(imp -> Boolean.logicalXor(imp.getVideo() != null, imp.getBanner() != null)); } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-auction-smaato-response.json b/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-auction-smaato-response.json index b296d1d15da..886a9d8a04d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-auction-smaato-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-auction-smaato-response.json @@ -7,16 +7,15 @@ "adm": "
\"\"\"\"
", "cid": "cid", "crid": "crid", + "id": "bid_id", + "impid": "imp_id", + "price": 0.01, "ext": { - "format": "BANNER", "prebid": { "type": "banner" }, "origbidcpm": 0.01 - }, - "id": "bid_id", - "impid": "imp_id", - "price": 0.01 + } } ], "group": 0, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-smaato-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-smaato-bid-request.json index 9cac449e820..e69898c274e 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-smaato-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-smaato-bid-request.json @@ -11,8 +11,6 @@ } ], "site": { - "domain": "www.example.com", - "page": "http://www.example.com", "publisher": { "id": "11000" } @@ -32,6 +30,6 @@ } }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.4" } } \ No newline at end of file From edb83e4a64a3c5edf978e3153d36be389690d42a Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Thu, 5 Aug 2021 14:17:08 +0300 Subject: [PATCH 23/29] Removed unused imports --- .../java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index eaf6a22dabd..a8508a19139 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -15,7 +15,6 @@ import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; import io.vertx.core.MultiMap; -import org.apache.commons.lang3.BooleanUtils; import org.junit.Before; import org.junit.Test; import org.prebid.server.VertxTest; From e45cada8b0f4b7f6a6e0ffe144a46b7f68268f7b Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 9 Aug 2021 15:57:23 +0300 Subject: [PATCH 24/29] Refactored bid request modifications --- .../server/bidder/smaato/SmaatoBidder.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index 510511bc128..4c79c705751 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -58,6 +58,7 @@ import java.util.List; import java.util.Objects; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -198,28 +199,28 @@ private BidRequest preparePodRequest(BidRequest bidRequest, List imps, List final String publisherId = getIfNotNullOrThrow(extImpSmaato, ExtImpSmaato::getPublisherId, "publisherId"); final String adBreakId = getIfNotNullOrThrow(extImpSmaato, ExtImpSmaato::getAdbreakId, "adbreakId"); - return enrichRequestWithPublisherId(bidRequest.toBuilder(), bidRequest, publisherId) - .imp(modifyImpsForAdBreak(imps, adBreakId)) - .build(); + return modifyBidRequest(bidRequest, publisherId, () -> modifyImpsForAdBreak(imps, adBreakId)); } catch (PreBidException | IllegalArgumentException e) { errors.add(BidderError.badInput(e.getMessage())); return null; } } - private BidRequest.BidRequestBuilder enrichRequestWithPublisherId(BidRequest.BidRequestBuilder bidRequestBuilder, - BidRequest bidRequest, - String publisherId) { + private BidRequest modifyBidRequest(BidRequest bidRequest, String publisherId, Supplier> impSupplier) { final Publisher publisher = Publisher.builder().id(publisherId).build(); final Site site = bidRequest.getSite(); final App app = bidRequest.getApp(); + final BidRequest.BidRequestBuilder bidRequestBuilder = bidRequest.toBuilder(); if (site != null) { - return bidRequestBuilder.site(site.toBuilder().publisher(publisher).build()); + bidRequestBuilder.site(site.toBuilder().publisher(publisher).build()); } else if (app != null) { - return bidRequestBuilder.app(app.toBuilder().publisher(publisher).build()); + bidRequestBuilder.app(app.toBuilder().publisher(publisher).build()); + } else { + throw new PreBidException("Missing Site/App."); } - throw new PreBidException("Missing Site/App."); + + return bidRequestBuilder.imp(impSupplier.get()).build(); } private List modifyImpsForAdBreak(List imps, String adBreakId) { @@ -277,21 +278,21 @@ private BidRequest prepareIndividualRequest(BidRequest bidRequest, Imp imp, List final String publisherId = getIfNotNullOrThrow(extImpSmaato, ExtImpSmaato::getPublisherId, "publisherId"); final String adSpaceId = getIfNotNullOrThrow(extImpSmaato, ExtImpSmaato::getAdspaceId, "adspaceId"); - return enrichRequestWithPublisherId(bidRequest.toBuilder(), bidRequest, publisherId) - .imp(Collections.singletonList(modifyImpForAdSpace(imp, adSpaceId))) - .build(); + return modifyBidRequest(bidRequest, publisherId, () -> modifyImpForAdSpace(imp, adSpaceId)); } catch (PreBidException | IllegalArgumentException e) { errors.add(BidderError.badInput(e.getMessage())); return null; } } - private Imp modifyImpForAdSpace(Imp imp, String adSpaceId) { - return imp.toBuilder() + private List modifyImpForAdSpace(Imp imp, String adSpaceId) { + final Imp modifiedImp = imp.toBuilder() .tagid(adSpaceId) .banner(getIfNotNull(imp.getBanner(), SmaatoBidder::modifyBanner)) .ext(null) .build(); + + return Collections.singletonList(modifiedImp); } private static Banner modifyBanner(Banner banner) { From 9ff9078f030ab8d381ac9b4b246faf2b8273e1f3 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 16 Aug 2021 12:50:19 +0300 Subject: [PATCH 25/29] Added unit tests, removed redundant comment on ExtImpSmaato --- .../server/bidder/smaato/SmaatoBidder.java | 4 +- .../ext/request/smaato/ExtImpSmaato.java | 3 - .../bidder/smaato/SmaatoBidderTest.java | 79 +++++++++++++++++++ 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index 4c79c705751..a301cba7387 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -229,13 +229,13 @@ private List modifyImpsForAdBreak(List imps, String adBreakId) { .collect(Collectors.toList()); } - private Imp modifyImpForAdBreak(Imp imp, Integer sequence, String tagId) { + private Imp modifyImpForAdBreak(Imp imp, Integer sequence, String adBreakId) { final Video modifiedVideo = imp.getVideo().toBuilder() .sequence(sequence) .ext(mapper.mapper().createObjectNode().set("context", TextNode.valueOf("adpod"))) .build(); return imp.toBuilder() - .tagid(tagId) + .tagid(adBreakId) .video(modifiedVideo) .ext(null) .build(); diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/smaato/ExtImpSmaato.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/smaato/ExtImpSmaato.java index 507b7e9b658..9f6ef557196 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/smaato/ExtImpSmaato.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/smaato/ExtImpSmaato.java @@ -4,9 +4,6 @@ import lombok.AllArgsConstructor; import lombok.Value; -/** - * Defines the contract for bidRequest.imp[i].ext.smaato - */ @AllArgsConstructor(staticName = "of") @Value public class ExtImpSmaato { diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index a8508a19139..1b02f7e2f97 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -655,6 +655,85 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsReachmedia() throws Json .containsExactly(BidderBid.of(expectedBid, banner, "USD")); } + @Test + public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsReachmediaAndAdTypeHeaderIsAbsent() + throws JsonProcessingException { + // given + final HttpCall httpCall = givenHttpCall(BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm("{\"richmedia\":{\"mediadata\":" + + "{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":" + + "[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track" + + "/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\"," + + "\"//prebid-test.smaatolabs.net/track/click/2\"]}}"))), + MultiMap.caseInsensitiveMultiMap()); + + // when + final Result> result = smaatoBidder.makeBids(httpCall, null); + + // then + final Bid expectedBid = Bid.builder() + .impid("123") + .adm("
hello
\"\"\"\"
") + .ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.builder().build(), null))) + .exp(300) + .build(); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsExactly(BidderBid.of(expectedBid, banner, "USD")); + } + + @Test + public void makeBidsShouldReturnErrorIfAdMarkTypeIsReachmediaAndAdmIsEmpty() + throws JsonProcessingException { + final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "Richmedia"); + // given + final HttpCall httpCall = givenHttpCall(BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm("{}"))), + headers); + + // when + final Result> result = smaatoBidder.makeBids(httpCall, null); + + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly(BidderError.badInput("bid.adm.richmedia is empty")); + } + + @Test + public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsVideoAndAdTypeHeaderIsAbsent() + throws JsonProcessingException { + // given + final HttpCall httpCall = givenHttpCall(BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm("> result = smaatoBidder.makeBids(httpCall, null); + + // then + final Bid expectedBid = Bid.builder() + .impid("123") + .adm("Video") + .ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.builder().build(), null))) + .exp(300) + .build(); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsExactly(BidderBid.of(expectedBid, video, "USD")); + } + @Test public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsImg() throws JsonProcessingException { // given From 53ea4932f1c8479cbf573b4e8882e21d5e4d7700 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 16 Aug 2021 17:38:29 +0300 Subject: [PATCH 26/29] Refactored SmaatoBidderTest, added tests for ttl --- .../server/bidder/smaato/SmaatoBidder.java | 9 +- .../config/bidder/SmaatoConfiguration.java | 6 +- .../bidder/smaato/SmaatoBidderTest.java | 337 ++++++++++-------- 3 files changed, 208 insertions(+), 144 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java index a301cba7387..7321e257b80 100644 --- a/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java +++ b/src/main/java/org/prebid/server/bidder/smaato/SmaatoBidder.java @@ -52,6 +52,7 @@ import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo; import org.prebid.server.util.HttpUtil; +import java.time.Clock; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -78,10 +79,12 @@ public class SmaatoBidder implements Bidder { private final String endpointUrl; private final JacksonMapper mapper; + private final Clock clock; - public SmaatoBidder(String endpointUrl, JacksonMapper mapper) { + public SmaatoBidder(String endpointUrl, JacksonMapper mapper, Clock clock) { this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); this.mapper = Objects.requireNonNull(mapper); + this.clock = Objects.requireNonNull(clock); } @Override @@ -387,10 +390,10 @@ private ExtBidPrebidVideo getExtBidPrebidVideo(Bid bid, BidType bidType) { } } - private static int getTtl(MultiMap headers) { + private int getTtl(MultiMap headers) { try { final long expiresAtMillis = Long.parseLong(headers.get(SMT_EXPIRES_HEADER)); - final long currentTimeMillis = System.currentTimeMillis(); + final long currentTimeMillis = clock.millis(); return (int) Math.max((expiresAtMillis - currentTimeMillis) / 1000, 0); } catch (NumberFormatException e) { return DEFAULT_TTL; diff --git a/src/main/java/org/prebid/server/spring/config/bidder/SmaatoConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/SmaatoConfiguration.java index 044bc17f32a..f21f0ee3aa9 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/SmaatoConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/SmaatoConfiguration.java @@ -16,6 +16,7 @@ import org.springframework.context.annotation.PropertySource; import javax.validation.constraints.NotBlank; +import java.time.Clock; @Configuration @PropertySource(value = "classpath:/bidder-config/smaato.yaml", factory = YamlPropertySourceFactory.class) @@ -30,6 +31,9 @@ public class SmaatoConfiguration { @Autowired private JacksonMapper mapper; + @Autowired + private Clock clock; + @Autowired @Qualifier("smaatoConfigurationProperties") private BidderConfigurationProperties configProperties; @@ -45,7 +49,7 @@ BidderDeps smaatoBidderDeps() { return BidderDepsAssembler.forBidder(BIDDER_NAME) .withConfig(configProperties) .usersyncerCreator(UsersyncerCreator.create(externalUrl)) - .bidderCreator(config -> new SmaatoBidder(config.getEndpoint(), mapper)) + .bidderCreator(config -> new SmaatoBidder(config.getEndpoint(), mapper, clock)) .assemble(); } } diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index 1b02f7e2f97..f61a3b2873f 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -16,7 +16,11 @@ import com.iab.openrtb.response.SeatBid; import io.vertx.core.MultiMap; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.prebid.server.VertxTest; import org.prebid.server.auction.model.Endpoint; import org.prebid.server.bidder.model.BidderBid; @@ -40,34 +44,43 @@ import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo; import org.prebid.server.util.HttpUtil; +import java.time.Clock; import java.util.Arrays; import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; import static java.util.Collections.singletonList; -import static java.util.function.Function.identity; +import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.Mockito.when; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.video; public class SmaatoBidderTest extends VertxTest { + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + private static final String ENDPOINT_URL = "https://test.endpoint.com"; private SmaatoBidder smaatoBidder; + @Mock + private Clock clock; + @Before public void setUp() { - smaatoBidder = new SmaatoBidder(ENDPOINT_URL, jacksonMapper); + smaatoBidder = new SmaatoBidder(ENDPOINT_URL, jacksonMapper, clock); } @Test public void creationShouldFailOnInvalidEndpointUrl() { - assertThatIllegalArgumentException().isThrownBy(() -> new SmaatoBidder("invalid_url", jacksonMapper)); + assertThatIllegalArgumentException().isThrownBy(() -> new SmaatoBidder("invalid_url", jacksonMapper, clock)); } @Test @@ -114,7 +127,7 @@ public void makeHttpRequestsShouldModifySiteIfSiteExtDataIsPresent() { @Test public void makeHttpRequestsShouldSetExt() { // given - final BidRequest bidRequest = givenBidRequest(identity()); + final BidRequest bidRequest = givenBidRequest(); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -145,7 +158,8 @@ public void makePodHttpRequestsShouldReturnErrorsForImpsOfInvalidMediaType() { @Test public void makePodHttpRequestsShouldCorrectlyConstructImpPodsAndRequests() { // given - final BidRequest bidRequest = givenVideoBidRequest(identity(), + final BidRequest bidRequest = givenVideoBidRequest( + identity(), impBuilder -> impBuilder.id("1_0"), impBuilder -> impBuilder.id("1_1"), impBuilder -> impBuilder.id("2_0")); @@ -169,9 +183,7 @@ public void makePodHttpRequestsShouldCorrectlyConstructImpPodsAndRequests() { public void makePodHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { // given final BidRequest bidRequest = givenVideoBidRequest( - impBuilder -> impBuilder - .id("123") - .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + impBuilder -> impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -188,11 +200,9 @@ public void makePodHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { @Test public void makePodHttpRequestsShouldReturnErrorIfImpExtPublisherIdIsAbsent() { // given - final BidRequest bidRequest = givenVideoBidRequest( - impBuilder -> impBuilder - .id("123") - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( - null, null, "adbreakId"))))); + final BidRequest bidRequest = givenVideoBidRequest(impBuilder -> + impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpSmaato.of(null, null, "adbreakId"))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -205,11 +215,9 @@ public void makePodHttpRequestsShouldReturnErrorIfImpExtPublisherIdIsAbsent() { @Test public void makePodHttpRequestsShouldReturnErrorIfImpExtAdBreakIdIsAbsent() { // given - final BidRequest bidRequest = givenVideoBidRequest( - impBuilder -> impBuilder - .id("123") - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( - "publisherId", null, null))))); + final BidRequest bidRequest = givenVideoBidRequest(impBuilder -> + impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( + "publisherId", null, null))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -224,10 +232,8 @@ public void makePodHttpRequestsShouldEnrichSiteWithPublisherIdIfSiteIsPresentInR // given final BidRequest bidRequest = givenVideoBidRequest( bidRequestBuilder -> bidRequestBuilder.site(Site.builder().build()).app(null), - impBuilder -> impBuilder - .id("123") - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( - "publisherId", null, "adBreakId"))))); + impBuilder -> impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpSmaato.of("publisherId", null, "adBreakId"))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -247,8 +253,8 @@ public void makePodHttpRequestsShouldEnrichAppWithPublisherIdIfSiteIsAbsentAndAp // given final BidRequest bidRequest = givenVideoBidRequest( bidRequestBuilder -> bidRequestBuilder.site(null).app(App.builder().build()), - impBuilder -> impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( - "publisherId", null, "adBreakId"))))); + impBuilder -> impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpSmaato.of("publisherId", null, "adBreakId"))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -281,7 +287,8 @@ public void makePodHttpRequestsShouldReturnErrorIfSiteAndAppAreAbsentInRequest() @Test public void makePodHttpRequestsShouldCorrectlyModifyImps() { // given - final BidRequest bidRequest = givenVideoBidRequest(identity(), + final BidRequest bidRequest = givenVideoBidRequest( + identity(), impBuilder -> impBuilder.id("1_0"), impBuilder -> impBuilder.id("1_1")); @@ -327,9 +334,10 @@ public void makeIndividualHttpRequestsShouldCorrectlySplitImps() { final BidRequest bidRequest = givenBidRequest( identity(), impBuilder -> impBuilder + .id("123") .banner(Banner.builder().w(1).h(1).build()) .video(Video.builder().w(1).h(1).build()), - impBuilder -> impBuilder.banner(Banner.builder().w(1).h(1).build())); + impBuilder -> impBuilder.id("456").banner(Banner.builder().w(1).h(1).build())); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -364,10 +372,9 @@ public void makeIndividualHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed( @Test public void makeIndividualHttpRequestsShouldReturnErrorIfImpExtPublisherIdIsAbsent() { // given - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( - null, "adspaceId", null))))); + final BidRequest bidRequest = givenBidRequest(impBuilder -> + impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpSmaato.of(null, "adspaceId", null))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -397,9 +404,8 @@ public void makeIndividualHttpRequestsShouldEnrichSiteWithPublisherIdIfSiteIsPre // given final BidRequest bidRequest = givenBidRequest( bidRequestBuilder -> bidRequestBuilder.site(Site.builder().build()).app(null), - impBuilder -> impBuilder - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( - "publisherId", "adspaceId", null))))); + impBuilder -> impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpSmaato.of("publisherId", "adspaceId", null))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -419,8 +425,8 @@ public void makeIndividualHttpRequestsShouldEnrichAppWithPublisherIdIfSiteIsAbse // given final BidRequest bidRequest = givenBidRequest( bidRequestBuilder -> bidRequestBuilder.site(null).app(App.builder().build()), - impBuilder -> impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmaato.of( - "publisherId", "adspaceId", null))))); + impBuilder -> impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpSmaato.of("publisherId", "adspaceId", null))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -502,8 +508,9 @@ public void makeIndividualHttpRequestsShouldReplaceBannerSizesWithFirstFormatIfF @Test public void makeIndividualHttpRequestsShouldSetImpTagIdAndRemoveImpExt() { // given - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(mapper.valueToTree( - ExtPrebid.of(null, ExtImpSmaato.of("publisherId", "adspaceId", null))))); + final BidRequest bidRequest = givenBidRequest(impBuilder -> + impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpSmaato.of("publisherId", "adspaceId", null))))); // when final Result>> result = smaatoBidder.makeHttpRequests(bidRequest); @@ -537,8 +544,7 @@ public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { @Test public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { // given - final HttpCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(null), null); + final HttpCall httpCall = givenHttpCall(null, mapper.writeValueAsString(null), null); // when final Result> result = smaatoBidder.makeBids(httpCall, null); @@ -551,8 +557,9 @@ public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProces @Test public void makeBidsShouldReturnErrorOnEmptyBidAdm() throws JsonProcessingException { // given - final HttpCall httpCall = givenHttpCall(BidRequest.builder().build(), - mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.id("test").impid("123"))), + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.id("test"))), HttpUtil.headers()); // when @@ -568,11 +575,10 @@ public void makeBidsShouldReturnErrorOnEmptyBidAdm() throws JsonProcessingExcept public void makeBidsShouldReturnErrorIfNotSupportedMarkupType() throws JsonProcessingException { // given final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "anyType"); - final HttpCall httpCall = givenHttpCall(BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm("adm"))), headers); + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm("> result = smaatoBidder.makeBids(httpCall, null); @@ -583,15 +589,53 @@ public void makeBidsShouldReturnErrorIfNotSupportedMarkupType() throws JsonProce .containsExactly(BidderError.badInput("Invalid markupType anyType")); } + @Test + public void makeBidsShouldCalculateTtlIfExpirationHeaderIsPresentInResponse() throws JsonProcessingException { + // given + when(clock.millis()).thenReturn(100L); + + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm("> result = smaatoBidder.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isNotEmpty() + .extracting(BidderBid::getBid) + .extracting(Bid::getExp) + .containsExactly(9); + } + + @Test + public void makeBidsShouldSetDefaultTtlIfExpirationHeaderIsAbsentInResponse() throws JsonProcessingException { + // given + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm("> result = smaatoBidder.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isNotEmpty() + .extracting(BidderBid::getBid) + .extracting(Bid::getExp) + .containsExactly(300); + } + @Test public void makeBidsShouldReturnErrorIfMarkupTypeIsBlank() throws JsonProcessingException { // given - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", ""); - final HttpCall httpCall = givenHttpCall(BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm("adm"))), headers); + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm("adm"))), + MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "")); // when final Result> result = smaatoBidder.makeBids(httpCall, null); @@ -605,12 +649,10 @@ public void makeBidsShouldReturnErrorIfMarkupTypeIsBlank() throws JsonProcessing @Test public void makeBidsShouldReturnErrorIfAdmIsInvalid() throws JsonProcessingException { // given - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", ""); - final HttpCall httpCall = givenHttpCall(BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm("{\"image\": invalid"))), headers); + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm("{\"image\": invalid"))), + MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "")); // when final Result> result = smaatoBidder.makeBids(httpCall, null); @@ -625,31 +667,35 @@ public void makeBidsShouldReturnErrorIfAdmIsInvalid() throws JsonProcessingExcep @Test public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsReachmedia() throws JsonProcessingException { // given - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "Richmedia"); - final HttpCall httpCall = givenHttpCall(BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm("{\"richmedia\":{\"mediadata\":" - + "{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":" - + "[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track" - + "/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\"," - + "\"//prebid-test.smaatolabs.net/track/click/2\"]}}"))), headers); + final String adm = "{\"richmedia\":{\"mediadata\":" + + "{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":" + + "[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track" + + "/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\"," + + "\"//prebid-test.smaatolabs.net/track/click/2\"]}}"; + + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm(adm))), + MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "Richmedia")); // when final Result> result = smaatoBidder.makeBids(httpCall, null); // then - final Bid expectedBid = Bid.builder() - .impid("123") - .adm("
hello
\"\"\"\"
") + + "net/track/imp/2\" alt=\"\" width=\"0\" height=\"0\"/>"; + + final Bid expectedBid = Bid.builder() + .impid("123") + .adm(expectedAdm) .ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.builder().build(), null))) .exp(300) .build(); + assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) .containsExactly(BidderBid.of(expectedBid, banner, "USD")); @@ -659,31 +705,35 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsReachmedia() throws Json public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsReachmediaAndAdTypeHeaderIsAbsent() throws JsonProcessingException { // given - final HttpCall httpCall = givenHttpCall(BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm("{\"richmedia\":{\"mediadata\":" - + "{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":" - + "[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track" - + "/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\"," - + "\"//prebid-test.smaatolabs.net/track/click/2\"]}}"))), + final String adm = "{\"richmedia\":{\"mediadata\":" + + "{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":" + + "[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track" + + "/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\"," + + "\"//prebid-test.smaatolabs.net/track/click/2\"]}}"; + + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm(adm))), MultiMap.caseInsensitiveMultiMap()); // when final Result> result = smaatoBidder.makeBids(httpCall, null); // then - final Bid expectedBid = Bid.builder() - .impid("123") - .adm("
hello
\"\"\"\"
") + + "net/track/imp/2\" alt=\"\" width=\"0\" height=\"0\"/>"; + + final Bid expectedBid = Bid.builder() + .impid("123") + .adm(expectedAdm) .ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.builder().build(), null))) .exp(300) .build(); + assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) .containsExactly(BidderBid.of(expectedBid, banner, "USD")); @@ -692,14 +742,11 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsReachmediaAndAdTypeHeade @Test public void makeBidsShouldReturnErrorIfAdMarkTypeIsReachmediaAndAdmIsEmpty() throws JsonProcessingException { - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "Richmedia"); // given - final HttpCall httpCall = givenHttpCall(BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm("{}"))), - headers); + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm("{}"))), + MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "Richmedia")); // when final Result> result = smaatoBidder.makeBids(httpCall, null); @@ -712,11 +759,10 @@ public void makeBidsShouldReturnErrorIfAdMarkTypeIsReachmediaAndAdmIsEmpty() public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsVideoAndAdTypeHeaderIsAbsent() throws JsonProcessingException { // given - final HttpCall httpCall = givenHttpCall(BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(), + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm(" bidBuilder.adm(" httpCall = givenHttpCall(BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm("{\"image\":{\"img\":{\"url\":\"" - + "//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"" - + "//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"" - + "//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/" - + "imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"" - + "//prebid-test.smaatolabs.net/track/click/2\"]}}"))), headers); + final String adm = "{\"image\":{\"img\":{\"url\":\"" + + "//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"" + + "//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"" + + "//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/" + + "imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"" + + "//prebid-test.smaatolabs.net/track/click/2\"]}}"; + + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm(adm))), + MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "Img")); // when final Result> result = smaatoBidder.makeBids(httpCall, null); // then - final Bid expectedBid = Bid.builder() - .impid("123") - .adm("
\"\"\"\"
") + + "track/imp/2\" alt=\"\" width=\"0\" height=\"0\"/>"; + + final Bid expectedBid = Bid.builder() + .impid("123") + .adm(expectedAdm) .ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.builder().build(), null))) .exp(300) .build(); + assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) .containsExactly(BidderBid.of(expectedBid, banner, "USD")); @@ -774,35 +824,39 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsImg() throws JsonProcess @Test public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsImgAndParametersAreEmpty() throws JsonProcessingException { // given - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "Img"); - final HttpCall httpCall = givenHttpCall(BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").adm("{\"image\":{\"img\":{\"url\":\"" - + "//prebid-test.smaatolabs.net/img/320x50.jpg\",\"ctaurl\":\"" - + "//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"" - + "//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/" - + "imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"" - + "//prebid-test.smaatolabs.net/track/click/2\"]}}"))), headers); + final String adm = "{\"image\":{\"img\":{\"url\":\"" + + "//prebid-test.smaatolabs.net/img/320x50.jpg\",\"ctaurl\":\"" + + "//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"" + + "//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/" + + "imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"" + + "//prebid-test.smaatolabs.net/track/click/2\"]}}"; + + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm(adm))), + MultiMap.caseInsensitiveMultiMap().set("X-Smt-Adtype", "Img")); // when final Result> result = smaatoBidder.makeBids(httpCall, null); // then - final Bid expectedBid = Bid.builder() - .impid("123") - .adm("
\"\"\"\"
") + + "track/imp/2\" alt=\"\" width=\"0\" height=\"0\"/>"; + + final Bid expectedBid = Bid.builder() + .impid("123") + .adm(expectedAdm) .ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.builder().build(), null))) .exp(300) .build(); + assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) .containsExactly(BidderBid.of(expectedBid, banner, "USD")); @@ -811,18 +865,15 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsImgAndParametersAreEmpty @Test public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsVideo() throws JsonProcessingException { // given - final MultiMap headers = MultiMap.caseInsensitiveMultiMap().set("X-SMT-ADTYPE", "Video"); - final HttpCall httpCall = givenHttpCall(BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> - bidBuilder - .impid("123") - .adm("") - .cat(singletonList("Category1")) - .ext(mapper.valueToTree(SmaatoBidExt.of(100))))), headers); + final HttpCall httpCall = givenHttpCall( + givenBidRequest(identity()), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> + bidBuilder + .adm("") + .cat(singletonList("Category1")) + .ext(mapper.valueToTree(SmaatoBidExt.of(100))))), + MultiMap.caseInsensitiveMultiMap().set("X-SMT-ADTYPE", "Video")); // when final Result> result = smaatoBidder.makeBids(httpCall, null); @@ -836,6 +887,7 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsVideo() throws JsonProce .video(ExtBidPrebidVideo.of(100, "Category1")).build(), null))) .exp(300) .build(); + assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) .containsExactly(BidderBid.of(expectedBid, video, "USD")); @@ -874,6 +926,10 @@ private static BidRequest givenVideoBidRequest( .build(); } + private static BidRequest givenBidRequest() { + return givenBidRequest(identity()); + } + private static BidRequest givenBidRequest( Function bidRequestCustomizer, Function... impCustomizers) { @@ -909,10 +965,11 @@ private static Imp givenImp(Function impCustomiz .build(); } - private static BidResponse givenBidResponse(Function bidCustomizer) { + private static BidResponse givenBidResponse(UnaryOperator bidCustomizer) { return BidResponse.builder() .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bidCustomizer.apply(Bid.builder()).build())) + .seatbid(singletonList(SeatBid.builder() + .bid(singletonList(bidCustomizer.apply(Bid.builder().impid("123")).build())) .build())) .build(); } From aeccff6b3fd2a4f844bc9954aebe70eba2d22078 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 16 Aug 2021 17:41:48 +0300 Subject: [PATCH 27/29] Added unit test for ttl --- .../bidder/smaato/SmaatoBidderTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index f61a3b2873f..7dbbef7218d 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -610,6 +610,28 @@ public void makeBidsShouldCalculateTtlIfExpirationHeaderIsPresentInResponse() th .containsExactly(9); } + @Test + public void makeBidsShouldSetTtlToZeroIfExpirationHeaderIsPresentInResponseButLessThanCurrentTime() + throws JsonProcessingException { + // given + when(clock.millis()).thenReturn(999999L); + + final HttpCall httpCall = givenHttpCall( + givenBidRequest(), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm("> result = smaatoBidder.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isNotEmpty() + .extracting(BidderBid::getBid) + .extracting(Bid::getExp) + .containsExactly(0); + } + @Test public void makeBidsShouldSetDefaultTtlIfExpirationHeaderIsAbsentInResponse() throws JsonProcessingException { // given From 484c130f3aaedda10edc7230f867b741e9b76fa4 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 16 Aug 2021 17:49:56 +0300 Subject: [PATCH 28/29] Refactored unit tests --- .../bidder/smaato/SmaatoBidderTest.java | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index 7dbbef7218d..102278c60e5 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -98,7 +98,7 @@ public void makeHttpRequestsShouldModifyUserIfUserExtDataIsPresent() { // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) + assertThat(result.getValue()) .extracting(HttpRequest::getPayload) .extracting(BidRequest::getUser) .extracting(User::getKeywords, User::getGender, User::getYob) @@ -117,7 +117,7 @@ public void makeHttpRequestsShouldModifySiteIfSiteExtDataIsPresent() { // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) + assertThat(result.getValue()) .extracting(HttpRequest::getPayload) .extracting(BidRequest::getSite) .extracting(Site::getKeywords, Site::getExt) @@ -134,7 +134,7 @@ public void makeHttpRequestsShouldSetExt() { // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) + assertThat(result.getValue()) .extracting(HttpRequest::getPayload) .extracting(BidRequest::getExt) .containsExactly(jacksonMapper.fillExtension(ExtRequest.empty(), @@ -566,9 +566,8 @@ public void makeBidsShouldReturnErrorOnEmptyBidAdm() throws JsonProcessingExcept final Result> result = smaatoBidder.makeBids(httpCall, null); // then - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badInput("Empty ad markup in bid with id: test")); assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly(BidderError.badInput("Empty ad markup in bid with id: test")); } @Test @@ -585,8 +584,7 @@ public void makeBidsShouldReturnErrorIfNotSupportedMarkupType() throws JsonProce // then assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badInput("Invalid markupType anyType")); + assertThat(result.getErrors()).containsExactly(BidderError.badInput("Invalid markupType anyType")); } @Test @@ -604,7 +602,7 @@ public void makeBidsShouldCalculateTtlIfExpirationHeaderIsPresentInResponse() th // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isNotEmpty() + assertThat(result.getValue()) .extracting(BidderBid::getBid) .extracting(Bid::getExp) .containsExactly(9); @@ -626,7 +624,7 @@ public void makeBidsShouldSetTtlToZeroIfExpirationHeaderIsPresentInResponseButLe // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isNotEmpty() + assertThat(result.getValue()) .extracting(BidderBid::getBid) .extracting(Bid::getExp) .containsExactly(0); @@ -645,7 +643,7 @@ public void makeBidsShouldSetDefaultTtlIfExpirationHeaderIsAbsentInResponse() th // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isNotEmpty() + assertThat(result.getValue()) .extracting(BidderBid::getBid) .extracting(Bid::getExp) .containsExactly(300); @@ -663,9 +661,8 @@ public void makeBidsShouldReturnErrorIfMarkupTypeIsBlank() throws JsonProcessing final Result> result = smaatoBidder.makeBids(httpCall, null); // then - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badInput("Invalid ad markup adm.")); assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly(BidderError.badInput("Invalid ad markup adm.")); } @Test @@ -719,8 +716,7 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsReachmedia() throws Json .build(); assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsExactly(BidderBid.of(expectedBid, banner, "USD")); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, banner, "USD")); } @Test @@ -757,8 +753,7 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsReachmediaAndAdTypeHeade .build(); assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsExactly(BidderBid.of(expectedBid, banner, "USD")); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, banner, "USD")); } @Test @@ -773,6 +768,7 @@ public void makeBidsShouldReturnErrorIfAdMarkTypeIsReachmediaAndAdmIsEmpty() // when final Result> result = smaatoBidder.makeBids(httpCall, null); + // then assertThat(result.getValue()).isEmpty(); assertThat(result.getErrors()).containsExactly(BidderError.badInput("bid.adm.richmedia is empty")); } @@ -797,9 +793,9 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsVideoAndAdTypeHeaderIsAb .ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.builder().build(), null))) .exp(300) .build(); + assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsExactly(BidderBid.of(expectedBid, video, "USD")); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, video, "USD")); } @Test @@ -839,8 +835,7 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsImg() throws JsonProcess .build(); assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsExactly(BidderBid.of(expectedBid, banner, "USD")); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, banner, "USD")); } @Test @@ -880,8 +875,7 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsImgAndParametersAreEmpty .build(); assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsExactly(BidderBid.of(expectedBid, banner, "USD")); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, banner, "USD")); } @Test @@ -911,8 +905,7 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsVideo() throws JsonProce .build(); assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsExactly(BidderBid.of(expectedBid, video, "USD")); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, video, "USD")); } @Test From 007e5122234e15645d766fbc7e79f6bccb6076e7 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 16 Aug 2021 18:06:36 +0300 Subject: [PATCH 29/29] Refactored unit tests --- .../java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java index 102278c60e5..09efb5afd8b 100644 --- a/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smaato/SmaatoBidderTest.java @@ -882,7 +882,7 @@ public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsImgAndParametersAreEmpty public void makeBidsShouldReturnCorrectBidIfAdMarkTypeIsVideo() throws JsonProcessingException { // given final HttpCall httpCall = givenHttpCall( - givenBidRequest(identity()), + givenBidRequest(), mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder .adm("