diff --git a/src/main/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidder.java b/src/main/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidder.java index 5d292f9a093..3bc78b588b0 100644 --- a/src/main/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidder.java +++ b/src/main/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidder.java @@ -1,255 +1,182 @@ package org.prebid.server.bidder.resetdigital; -import com.fasterxml.jackson.core.type.TypeReference; -import com.iab.openrtb.request.Audio; -import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Imp; -import com.iab.openrtb.request.Site; -import com.iab.openrtb.request.Video; import com.iab.openrtb.response.Bid; -import io.vertx.core.MultiMap; -import io.vertx.core.http.HttpMethod; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; import org.prebid.server.bidder.model.BidderError; import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.Price; import org.prebid.server.bidder.model.Result; -import org.prebid.server.bidder.resetdigital.request.ResetDigitalImp; -import org.prebid.server.bidder.resetdigital.request.ResetDigitalImpMediaType; -import org.prebid.server.bidder.resetdigital.request.ResetDigitalImpMediaTypes; -import org.prebid.server.bidder.resetdigital.request.ResetDigitalImpZone; -import org.prebid.server.bidder.resetdigital.request.ResetDigitalRequest; -import org.prebid.server.bidder.resetdigital.request.ResetDigitalSite; -import org.prebid.server.bidder.resetdigital.response.ResetDigitalBid; -import org.prebid.server.bidder.resetdigital.response.ResetDigitalResponse; +import org.prebid.server.currency.CurrencyConversionService; 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.ImpMediaType; -import org.prebid.server.proto.openrtb.ext.request.resetdigital.ExtImpResetDigital; import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.BidderUtil; import org.prebid.server.util.HttpUtil; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.stream.Stream; -public class ResetDigitalBidder implements Bidder { +public class ResetDigitalBidder implements Bidder { - private static final TypeReference> IMP_EXT_TYPE_REFERENCE = - new TypeReference<>() { - }; - private static final String BID_CURRENCY = "USD"; + private static final String DEFAULT_CURRENCY = "USD"; private final String endpointUrl; + private final CurrencyConversionService currencyConversionService; private final JacksonMapper mapper; - public ResetDigitalBidder(String endpointUrl, JacksonMapper mapper) { + public ResetDigitalBidder(String endpointUrl, + CurrencyConversionService currencyConversionService, + JacksonMapper mapper) { + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.currencyConversionService = Objects.requireNonNull(currencyConversionService); this.mapper = Objects.requireNonNull(mapper); } @Override - public Result>> makeHttpRequests(BidRequest request) { - final List> requests = new ArrayList<>(); - final List errors = new ArrayList<>(); + public Result>> makeHttpRequests(BidRequest request) { + final List bannerImps = new ArrayList<>(); + final List videoImps = new ArrayList<>(); + final List audioImps = new ArrayList<>(); + Price bidFloorPrice; - for (Imp imp: request.getImp()) { + for (Imp imp : request.getImp()) { try { - final ExtImpResetDigital extImp = parseImpExt(imp); - final ResetDigitalImp resetDigitalImp = makeImp(request, imp, extImp); - requests.add(makeHttpRequest(request, resetDigitalImp)); + bidFloorPrice = resolveBidFloor(imp, request); } catch (PreBidException e) { - errors.add(BidderError.badInput(e.getMessage())); + return Result.withError(BidderError.badInput(e.getMessage())); } + populateBannerImps(bannerImps, bidFloorPrice, imp); + populateVideoImps(videoImps, bidFloorPrice, imp); + populateAudiImps(audioImps, bidFloorPrice, imp); } - return Result.of(requests, errors); - } - - private ExtImpResetDigital parseImpExt(Imp imp) throws PreBidException { - try { - return mapper.mapper().convertValue(imp.getExt(), IMP_EXT_TYPE_REFERENCE).getBidder(); - } catch (IllegalArgumentException e) { - throw new PreBidException(e.getMessage()); - } + return Result.withValues(getHttpRequests(request, bannerImps, videoImps, audioImps)); } - private static ResetDigitalImp makeImp(BidRequest request, Imp imp, ExtImpResetDigital extImp) { - return ResetDigitalImp.builder() - .bidId(request.getId()) - .impId(imp.getId()) - .mediaTypes(resolveMediaTypes(imp)) - .zoneId(Optional.ofNullable(extImp.getPlacementId()) - .map(ResetDigitalImpZone::of) - .orElse(null)) - .build(); - } + private List> getHttpRequests(BidRequest request, + List bannerImps, + List videoImps, + List audioImps) { - private static ResetDigitalImpMediaTypes resolveMediaTypes(Imp imp) { - return switch (mediaType(imp)) { - case banner -> ResetDigitalImpMediaTypes.banner(makeBanner(imp.getBanner())); - case video -> ResetDigitalImpMediaTypes.video(makeVideo(imp.getVideo())); - case audio -> ResetDigitalImpMediaTypes.audio(makeAudio(imp.getAudio())); - case null, default -> throw new PreBidException( - "Banner, video or audio must be present in the imp %s".formatted(imp.getId())); - }; + return Stream.of(bannerImps, videoImps, audioImps) + .filter(CollectionUtils::isNotEmpty) + .map(imp -> makeHttpRequest(request, imp)) + .toList(); } - private static ImpMediaType mediaType(Imp imp) { - if (imp.getBanner() != null) { - return ImpMediaType.banner; - } - if (imp.getVideo() != null) { - return ImpMediaType.video; - } - if (imp.getAudio() != null) { - return ImpMediaType.audio; - } - - return null; - } + private HttpRequest makeHttpRequest(BidRequest bidRequest, List imp) { + final BidRequest outgoingRequest = bidRequest.toBuilder().imp(imp).build(); - private static ResetDigitalImpMediaType makeBanner(Banner banner) { - return makeMediaType(banner.getW(), banner.getH(), null); + return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper); } - private static ResetDigitalImpMediaType makeVideo(Video video) { - return makeMediaType(video.getW(), video.getH(), video.getMimes()); + private static Imp modifyImp(Imp imp, Price bidFloorPrice) { + return imp.toBuilder() + .bidfloorcur(bidFloorPrice.getCurrency()) + .bidfloor(bidFloorPrice.getValue()) + .build(); } - private static ResetDigitalImpMediaType makeAudio(Audio audio) { - return makeMediaType(null, null, audio.getMimes()); + private Price resolveBidFloor(Imp imp, BidRequest bidRequest) { + final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); + return BidderUtil.isValidPrice(initialBidFloorPrice) + ? convertBidFloor(initialBidFloorPrice, imp.getId(), bidRequest) + : initialBidFloorPrice; } - private static ResetDigitalImpMediaType makeMediaType(Integer width, Integer height, List mimes) { - final boolean hasValidSizes = isValidSizeValue(width) && isValidSizeValue(height); - final boolean hasMimes = CollectionUtils.isNotEmpty(mimes); - - if (!hasValidSizes && !hasMimes) { - return null; + private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest) { + final String bidFloorCur = bidFloorPrice.getCurrency(); + try { + final BigDecimal convertedPrice = currencyConversionService + .convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, DEFAULT_CURRENCY); + + return Price.of(DEFAULT_CURRENCY, convertedPrice); + } catch (PreBidException e) { + throw new PreBidException( + "Unable to convert provided bid floor currency from %s to %s for imp `%s`" + .formatted(bidFloorCur, DEFAULT_CURRENCY, impId)); } - - return ResetDigitalImpMediaType.of( - hasValidSizes ? List.of(List.of(width, height)) : null, - hasMimes ? mimes : null); - } - - private static boolean isValidSizeValue(Integer value) { - return value != null && value > 0; - } - - private HttpRequest makeHttpRequest(BidRequest request, ResetDigitalImp resetDigitalImp) { - final ResetDigitalRequest modifiedRequest = makeRequest(request, resetDigitalImp); - - return HttpRequest.builder() - .method(HttpMethod.POST) - .uri(endpointUrl) - .headers(makeHeaders(request)) - .impIds(Set.of(resetDigitalImp.getImpId())) - .body(mapper.encodeToBytes(modifiedRequest)) - .payload(modifiedRequest) - .build(); - } - - private static ResetDigitalRequest makeRequest(BidRequest request, ResetDigitalImp resetDigitalImp) { - return ResetDigitalRequest.of(makeSite(request.getSite()), Collections.singletonList(resetDigitalImp)); } - private static ResetDigitalSite makeSite(Site site) { - final String domain = site != null ? site.getDomain() : null; - final String page = site != null ? site.getPage() : null; - - return ObjectUtils.anyNotNull(domain, page) - ? ResetDigitalSite.of(domain, page) - : null; + private static void populateBannerImps(List bannerImps, Price bidFloorPrice, Imp imp) { + if (imp.getBanner() != null) { + final Imp bannerImp = imp.toBuilder().video(null).xNative(null).audio(null).build(); + bannerImps.add(modifyImp(bannerImp, bidFloorPrice)); + } } - private static MultiMap makeHeaders(BidRequest request) { - final MultiMap headers = HttpUtil.headers(); - - final Device device = request.getDevice(); - if (device != null) { - HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.ACCEPT_LANGUAGE_HEADER, device.getLanguage()); - HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, device.getUa()); - HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp()); - HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_REAL_IP_HEADER, device.getIp()); + private static void populateVideoImps(List videoImps, Price bidFloorPrice, Imp imp) { + if (imp.getVideo() != null) { + final Imp videoImp = imp.toBuilder().banner(null).xNative(null).audio(null).build(); + videoImps.add(modifyImp(videoImp, bidFloorPrice)); } + } - final Site site = request.getSite(); - if (site != null) { - HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, site.getPage()); + private static void populateAudiImps(List audioImps, Price bidFloorPrice, Imp imp) { + if (imp.getAudio() != null) { + final Imp audioImp = imp.toBuilder().banner(null).xNative(null).video(null).build(); + audioImps.add(modifyImp(audioImp, bidFloorPrice)); } - - return headers; } @Override - public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { - final ResetDigitalResponse bidResponse = mapper.decodeValue( - httpCall.getResponse().getBody(), - ResetDigitalResponse.class); - return Result.withValues(extractBids(bidRequest, bidResponse)); + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + return Result.withValues(extractBids(bidResponse, httpCall.getRequest().getPayload())); } catch (DecodeException | PreBidException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private static List extractBids(BidRequest bidRequest, ResetDigitalResponse bidResponse) { - final List bids = bidResponse == null - ? Collections.emptyList() - : CollectionUtils.emptyIfNull(bidResponse.getBids()).stream().filter(Objects::nonNull).toList(); - - if (bids.size() != 1) { - throw new PreBidException("expected exactly one bid in the response, but got %d".formatted(bids.size())); - } - - final ResetDigitalBid bid = bids.getFirst(); - final Imp correspondingImp = bidRequest.getImp().stream() - .filter(imp -> Objects.equals(imp.getId(), bid.getImpId())) - .findFirst() - .orElseThrow(() -> new PreBidException( - "no matching impression found for ImpID %s".formatted(bid.getImpId()))); - - return Collections.singletonList( - BidderBid.of(makeBid(bid), resolveBidType(correspondingImp), bid.getSeat(), BID_CURRENCY)); - } - - private static Bid makeBid(ResetDigitalBid bid) { - try { - return Bid.builder() - .id(bid.getBidId()) - .price(bid.getCpm()) - .impid(bid.getImpId()) - .cid(bid.getCid()) - .crid(bid.getCrid()) - .adm(bid.getHtml()) - .w(Integer.parseInt(bid.getW())) - .h(Integer.parseInt(bid.getH())) - .build(); - } catch (NumberFormatException e) { - throw new PreBidException(e.getMessage()); + private static List extractBids(BidResponse bidResponse, BidRequest bidRequest) { + if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { + return Collections.emptyList(); } - } - - private static BidType resolveBidType(Imp imp) throws PreBidException { - if (imp.getVideo() != null) { - return BidType.video; + if (bidResponse.getCur() != null && !StringUtils.equalsIgnoreCase(DEFAULT_CURRENCY, bidResponse.getCur())) { + throw new PreBidException("Bidder support only USD currency"); } - if (imp.getAudio() != null) { - return BidType.audio; + return bidsFromResponse(bidResponse, bidRequest); + } + + private static List bidsFromResponse(BidResponse bidResponse, BidRequest bidRequest) { + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .map(bid -> BidderBid.of(bid, getBidType(bid, bidRequest.getImp()), DEFAULT_CURRENCY)) + .toList(); + } + + private static BidType getBidType(Bid bid, List imps) { + final String impId = bid.getImpid(); + for (Imp imp : imps) { + if (imp.getId().equals(impId)) { + if (imp.getBanner() != null) { + return BidType.banner; + } else if (imp.getVideo() != null) { + return BidType.video; + } else if (imp.getAudio() != null) { + return BidType.audio; + } + } } - - return BidType.banner; + throw new PreBidException("Failed to find banner/video/audio impression " + impId); } } diff --git a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImp.java b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImp.java deleted file mode 100644 index 5674c2446a7..00000000000 --- a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImp.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.prebid.server.bidder.resetdigital.request; - -import lombok.Builder; -import lombok.Value; - -@Value -@Builder -public class ResetDigitalImp { - - ResetDigitalImpZone zoneId; - - String bidId; - - String impId; - - ResetDigitalImpMediaTypes mediaTypes; - - ResetDigitalImpExt ext; -} diff --git a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpExt.java b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpExt.java deleted file mode 100644 index 16363a5e6d9..00000000000 --- a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpExt.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.prebid.server.bidder.resetdigital.request; - -import lombok.Value; - -@Value(staticConstructor = "of") -public class ResetDigitalImpExt { - - String gpid; -} diff --git a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpMediaType.java b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpMediaType.java deleted file mode 100644 index cd678266463..00000000000 --- a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpMediaType.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.prebid.server.bidder.resetdigital.request; - -import lombok.Value; - -import java.util.List; - -@Value(staticConstructor = "of") -public class ResetDigitalImpMediaType { - - List> sizes; - - List mimes; -} diff --git a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpMediaTypes.java b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpMediaTypes.java deleted file mode 100644 index 45d714a530b..00000000000 --- a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpMediaTypes.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.prebid.server.bidder.resetdigital.request; - -import lombok.Builder; -import lombok.Value; - -@Value -@Builder -public class ResetDigitalImpMediaTypes { - - ResetDigitalImpMediaType banner; - - ResetDigitalImpMediaType video; - - ResetDigitalImpMediaType audio; - - public static ResetDigitalImpMediaTypes banner(ResetDigitalImpMediaType banner) { - return ResetDigitalImpMediaTypes.builder().banner(banner).build(); - } - - public static ResetDigitalImpMediaTypes video(ResetDigitalImpMediaType video) { - return ResetDigitalImpMediaTypes.builder().video(video).build(); - } - - public static ResetDigitalImpMediaTypes audio(ResetDigitalImpMediaType audio) { - return ResetDigitalImpMediaTypes.builder().audio(audio).build(); - } -} diff --git a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpZone.java b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpZone.java deleted file mode 100644 index 516ee7ec8bd..00000000000 --- a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpZone.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.prebid.server.bidder.resetdigital.request; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Value; - -@Value(staticConstructor = "of") -public class ResetDigitalImpZone { - - @JsonProperty("placementId") - String placementId; -} diff --git a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalRequest.java b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalRequest.java deleted file mode 100644 index bfce161aafa..00000000000 --- a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalRequest.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.prebid.server.bidder.resetdigital.request; - -import lombok.Value; - -import java.util.List; - -@Value(staticConstructor = "of") -public class ResetDigitalRequest { - - ResetDigitalSite site; - - List imps; -} diff --git a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalSite.java b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalSite.java deleted file mode 100644 index f52e5f1d12d..00000000000 --- a/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalSite.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.prebid.server.bidder.resetdigital.request; - -import lombok.Value; - -@Value(staticConstructor = "of") -public class ResetDigitalSite { - - String domain; - - String referrer; -} diff --git a/src/main/java/org/prebid/server/bidder/resetdigital/response/ResetDigitalBid.java b/src/main/java/org/prebid/server/bidder/resetdigital/response/ResetDigitalBid.java deleted file mode 100644 index d4ea28b47c2..00000000000 --- a/src/main/java/org/prebid/server/bidder/resetdigital/response/ResetDigitalBid.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.prebid.server.bidder.resetdigital.response; - -import lombok.Builder; -import lombok.Value; - -import java.math.BigDecimal; - -@Value -@Builder -public class ResetDigitalBid { - - String bidId; - - String impId; - - BigDecimal cpm; - - String cid; - - String crid; - - String adid; - - String w; - - String h; - - String seat; - - String html; -} diff --git a/src/main/java/org/prebid/server/bidder/resetdigital/response/ResetDigitalResponse.java b/src/main/java/org/prebid/server/bidder/resetdigital/response/ResetDigitalResponse.java deleted file mode 100644 index 2dcf57e8d80..00000000000 --- a/src/main/java/org/prebid/server/bidder/resetdigital/response/ResetDigitalResponse.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.prebid.server.bidder.resetdigital.response; - -import lombok.Value; - -import java.util.List; - -@Value(staticConstructor = "of") -public class ResetDigitalResponse { - - List bids; -} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ResetDigitalConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ResetDigitalConfiguration.java index 33823866d5f..4e4de161f66 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/ResetDigitalConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/ResetDigitalConfiguration.java @@ -2,6 +2,7 @@ import org.prebid.server.bidder.BidderDeps; import org.prebid.server.bidder.resetdigital.ResetDigitalBidder; +import org.prebid.server.currency.CurrencyConversionService; import org.prebid.server.json.JacksonMapper; import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties; import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler; @@ -30,12 +31,16 @@ BidderConfigurationProperties configurationProperties() { @Bean BidderDeps resetDigitalBidderDeps(BidderConfigurationProperties resetDigitalConfigurationProperties, @NotBlank @Value("${external-url}") String externalUrl, + CurrencyConversionService currencyConversionService, JacksonMapper mapper) { return BidderDepsAssembler.forBidder(BIDDER_NAME) .withConfig(resetDigitalConfigurationProperties) .usersyncerCreator(UsersyncerCreator.create(externalUrl)) - .bidderCreator(config -> new ResetDigitalBidder(config.getEndpoint(), mapper)) + .bidderCreator(config -> new ResetDigitalBidder( + config.getEndpoint(), + currencyConversionService, + mapper)) .assemble(); } } diff --git a/src/main/resources/bidder-config/resetdigital.yaml b/src/main/resources/bidder-config/resetdigital.yaml index 3ae9e26bf3e..885916d0d8d 100644 --- a/src/main/resources/bidder-config/resetdigital.yaml +++ b/src/main/resources/bidder-config/resetdigital.yaml @@ -4,6 +4,9 @@ adapters: meta-info: maintainer-email: biddersupport@resetdigital.co app-media-types: + - banner + - video + - audio site-media-types: - banner - video @@ -13,6 +16,6 @@ adapters: usersync: cookie-family-name: resetdigital redirect: - url: https://sync.resetdigital.co/csync?redir={{redirect_url}} + url: https://sync.resetdigital.co/csync?pid=rubicon&redir={{redirect_url}} support-cors: false uid-macro: '$USER_ID' diff --git a/src/main/resources/static/bidder-params/resetdigital.json b/src/main/resources/static/bidder-params/resetdigital.json index 07a7bc2b551..3710cfbc598 100644 --- a/src/main/resources/static/bidder-params/resetdigital.json +++ b/src/main/resources/static/bidder-params/resetdigital.json @@ -1,13 +1,23 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Reset Digital Adapter Params", - "description": "A schema which validates params accepted by the Reset Digital adapter", + "title": "ResetDigital Adapter Params", + "description": "A schema which validates params accepted by the ResetDigital adapter", "type": "object", "properties": { - "placement_id": { + "pubId": { "type": "string", - "minLength": 1, - "description": "Placement ID for the Reset Digital ad unit. This is the identifier for the ad unit on the Reset Digital platform, and its optional" + "description": "The publisher's ID provided" + }, + "zoneId": { + "type": "string", + "description": "Zone ID" + }, + "forceBid": { + "type": "boolean", + "description": "Force bids with a test creative" } - } + }, + "required": [ + "pubId" + ] } diff --git a/src/test/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidderTest.java b/src/test/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidderTest.java index ba59b996b9a..dbcc555489a 100644 --- a/src/test/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidderTest.java @@ -4,14 +4,17 @@ import com.iab.openrtb.request.Audio; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Native; -import com.iab.openrtb.request.Site; import com.iab.openrtb.request.Video; import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.VertxTest; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; @@ -19,478 +22,484 @@ 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.resetdigital.request.ResetDigitalImp; -import org.prebid.server.bidder.resetdigital.request.ResetDigitalImpMediaType; -import org.prebid.server.bidder.resetdigital.request.ResetDigitalImpMediaTypes; -import org.prebid.server.bidder.resetdigital.request.ResetDigitalImpZone; -import org.prebid.server.bidder.resetdigital.request.ResetDigitalRequest; -import org.prebid.server.bidder.resetdigital.request.ResetDigitalSite; -import org.prebid.server.bidder.resetdigital.response.ResetDigitalBid; -import org.prebid.server.bidder.resetdigital.response.ResetDigitalResponse; -import org.prebid.server.proto.openrtb.ext.ExtPrebid; -import org.prebid.server.proto.openrtb.ext.request.resetdigital.ExtImpResetDigital; +import org.prebid.server.currency.CurrencyConversionService; +import org.prebid.server.exception.PreBidException; import java.math.BigDecimal; import java.util.List; -import java.util.Set; import java.util.function.UnaryOperator; -import java.util.stream.Stream; +import static java.util.Collections.singletonList; 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.AssertionsForClassTypes.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; import static org.prebid.server.proto.openrtb.ext.response.BidType.audio; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.video; -import static org.prebid.server.util.HttpUtil.ACCEPT_HEADER; -import static org.prebid.server.util.HttpUtil.ACCEPT_LANGUAGE_HEADER; -import static org.prebid.server.util.HttpUtil.APPLICATION_JSON_CONTENT_TYPE; -import static org.prebid.server.util.HttpUtil.CONTENT_TYPE_HEADER; -import static org.prebid.server.util.HttpUtil.REFERER_HEADER; -import static org.prebid.server.util.HttpUtil.USER_AGENT_HEADER; -import static org.prebid.server.util.HttpUtil.X_FORWARDED_FOR_HEADER; -import static org.prebid.server.util.HttpUtil.X_REAL_IP_HEADER; -import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; +@ExtendWith(MockitoExtension.class) public class ResetDigitalBidderTest extends VertxTest { public static final String ENDPOINT_URL = "https://test.endpoint.com"; + @Mock + private CurrencyConversionService currencyConversionService; + private ResetDigitalBidder target; @BeforeEach public void setUp() { - target = new ResetDigitalBidder(ENDPOINT_URL, jacksonMapper); + target = new ResetDigitalBidder(ENDPOINT_URL, currencyConversionService, jacksonMapper); } @Test public void creationShouldFailOnInvalidEndpointUrl() { assertThatIllegalArgumentException().isThrownBy(() -> - new ResetDigitalBidder("invalid_url", jacksonMapper)); + new ResetDigitalBidder("invalid_url", currencyConversionService, jacksonMapper)); } @Test - public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { + public void makeHttpRequestShouldReturnEmptyResponseIfAbsentAnyTypeInImp() { // given - final BidRequest bidRequest = givenBidRequest( - imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null)))) + .build(); // when - final Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.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 value"); - }); + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()).hasSize(0); } @Test - public void makeHttpRequestsShouldMakeOneRequestPerImp() { + public void makeHttpRequestShouldReturnEmptyResponseIfxNativeImpTypePresent() { // given - final BidRequest bidRequest = givenBidRequest( - imp -> imp.id("givenImp1"), - imp -> imp.id("givenImp2"), - imp -> imp.id("givenImp3")); + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null) + .xNative(Native.builder().build())))) + .build(); - //when - final Result>> result = target.makeHttpRequests(bidRequest); + // when + final Result>> result = target.makeHttpRequests(bidRequest); - //then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(3) + // then + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()).hasSize(0); + } + + @Test + public void makeHttpRequestShouldReturnSeparateResponseWithBannerAndVideoAndAudioImp() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(impBuilder -> impBuilder + .audio(Audio.builder().build()) + .video(Video.builder().build())))) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()).hasSize(3); + assertThat(result.getValue().get(0)) .extracting(HttpRequest::getPayload) - .flatExtracting(ResetDigitalRequest::getImps) - .extracting(ResetDigitalImp::getImpId) - .containsOnly("givenImp1", "givenImp2", "givenImp3"); + .extracting(BidRequest::getImp) + .extracting(a -> a.getFirst()) + .extracting(Imp::getBanner) + .isNotNull(); - assertThat(result.getValue()).hasSize(3) - .extracting(HttpRequest::getImpIds) - .containsOnly(Set.of("givenImp1"), Set.of("givenImp2"), Set.of("givenImp3")); + assertThat(result.getValue().get(1)) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getImp) + .extracting(a -> a.getFirst()) + .extracting(Imp::getVideo) + .isNotNull(); + + assertThat(result.getValue().get(2)) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getImp) + .extracting(a -> a.getFirst()) + .extracting(Imp::getAudio) + .isNotNull(); } @Test - public void makeHttpRequestsShouldHaveCorrectUri() { + public void makeHttpRequestShouldReturnSeparateResponseWithBannerAndVideoImp() { // given - final BidRequest bidRequest = givenBidRequest(imp -> imp.id("givenImp")); + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(impBuilder -> impBuilder + .video(Video.builder().build())))) + .build(); - //when - final Result>> result = target.makeHttpRequests(bidRequest); + // when + final Result>> result = target.makeHttpRequests(bidRequest); - //then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getUri) - .containsOnly(ENDPOINT_URL); + // then + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()).hasSize(2); + assertThat(result.getValue().get(0)) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getImp) + .extracting(a -> a.getFirst()) + .extracting(Imp::getBanner) + .isNotNull(); + + assertThat(result.getValue().get(1)) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getImp) + .extracting(a -> a.getFirst()) + .extracting(Imp::getVideo) + .isNotNull(); } @Test - public void makeHttpRequestsShouldReturnExpectedHeaders() { + public void makeHttpRequestShouldReturnSeparateResponseWithBannerAndAudioImp() { // given - final BidRequest bidRequest = givenBidRequest(identity()).toBuilder() - .device(Device.builder().ip("ip").ua("ua").language("lang").build()) - .site(Site.builder().page("page").build()) + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(impBuilder -> impBuilder + .audio(Audio.builder().build())))) .build(); // when - final Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.makeHttpRequests(bidRequest); // then - assertThat(result.getValue()).hasSize(1).first() - .extracting(HttpRequest::getHeaders) - .satisfies(headers -> { - assertThat(headers.get(CONTENT_TYPE_HEADER)).isEqualTo(APPLICATION_JSON_CONTENT_TYPE); - assertThat(headers.get(ACCEPT_HEADER)).isEqualTo(APPLICATION_JSON_VALUE); - assertThat(headers.get(USER_AGENT_HEADER)).isEqualTo("ua"); - assertThat(headers.get(ACCEPT_LANGUAGE_HEADER)).isEqualTo("lang"); - assertThat(headers.get(X_FORWARDED_FOR_HEADER)).isEqualTo("ip"); - assertThat(headers.get(X_REAL_IP_HEADER)).isEqualTo("ip"); - assertThat(headers.get(REFERER_HEADER)).isEqualTo("page"); - }); - assertThat(result.getErrors()).isEmpty(); + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()).hasSize(2); + assertThat(result.getValue().get(0)) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getImp) + .extracting(a -> a.getFirst()) + .extracting(Imp::getBanner) + .isNotNull(); + + assertThat(result.getValue().get(1)) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getImp) + .extracting(a -> a.getFirst()) + .extracting(Imp::getAudio) + .isNotNull(); } @Test - public void makeHttpRequestsShouldReturnExpectedHeadersWhenDeviceAndSiteAreNotPresent() { + public void makeHttpRequestShouldReturnSeparateResponseWithVideoAndAudioImp() { // given - final BidRequest bidRequest = givenBidRequest(identity()); + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(impBuilder -> impBuilder + .banner(null) + .video(Video.builder().build()) + .audio(Audio.builder().build())))) + .build(); // when - final Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.makeHttpRequests(bidRequest); // then - assertThat(result.getValue()).hasSize(1).first() - .extracting(HttpRequest::getHeaders) - .satisfies(headers -> { - assertThat(headers.get(CONTENT_TYPE_HEADER)).isEqualTo(APPLICATION_JSON_CONTENT_TYPE); - assertThat(headers.get(ACCEPT_HEADER)).isEqualTo(APPLICATION_JSON_VALUE); - assertThat(headers.get(USER_AGENT_HEADER)).isNull(); - assertThat(headers.get(ACCEPT_LANGUAGE_HEADER)).isNull(); - assertThat(headers.get(X_FORWARDED_FOR_HEADER)).isNull(); - assertThat(headers.get(X_REAL_IP_HEADER)).isNull(); - assertThat(headers.get(REFERER_HEADER)).isNull(); - }); - assertThat(result.getErrors()).isEmpty(); + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()).hasSize(2); + assertThat(result.getValue().get(0)) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getImp) + .extracting(a -> a.getFirst()) + .extracting(Imp::getVideo) + .isNotNull(); + + assertThat(result.getValue().get(1)) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getImp) + .extracting(a -> a.getFirst()) + .extracting(Imp::getAudio) + .isNotNull(); } @Test - public void makeHttpRequestsShouldReturnBannerRequestFromBannerImp() { + public void makeHttpRequestShouldReturnResponseOnlyWithBannerImp() { // given - final BidRequest bidRequest = givenBidRequest(imp -> imp - .id("impId") - .banner(Banner.builder().w(1).h(2).build())) - .toBuilder() - .id("requestId") + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) .build(); - //when - final Result>> result = target.makeHttpRequests(bidRequest); + // when + final Result>> result = target.makeHttpRequests(bidRequest); - //then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) + // then + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()) .extracting(HttpRequest::getPayload) - .flatExtracting(ResetDigitalRequest::getImps) - .containsOnly(ResetDigitalImp.builder() - .impId("impId") - .bidId("requestId") - .zoneId(ResetDigitalImpZone.of("placementId")) - .mediaTypes(ResetDigitalImpMediaTypes.banner( - ResetDigitalImpMediaType.of(List.of(List.of(1, 2)), null))) - .build()); + .flatExtracting(BidRequest::getImp) + .allSatisfy(imp -> { + assertThat(imp.getBanner()).isNotNull(); + assertThat(imp.getVideo()).isNull(); + assertThat(imp.getAudio()).isNull(); + }); } @Test - public void makeHttpRequestsShouldReturnVideoRequestFromVideoImp() { + public void makeHttpRequestShouldReturnResponseOnlyWithVideoImp() { // given - final BidRequest bidRequest = givenBidRequest(imp -> imp - .id("impId") - .banner(null) - .video(Video.builder().w(1).h(2).mimes(List.of("mime1", "mime2")).build())) - .toBuilder() - .id("requestId") + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null) + .video(Video.builder().build())))) .build(); - //when - final Result>> result = target.makeHttpRequests(bidRequest); + // when + final Result>> result = target.makeHttpRequests(bidRequest); - //then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) + // then + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()) .extracting(HttpRequest::getPayload) - .flatExtracting(ResetDigitalRequest::getImps) - .containsOnly(ResetDigitalImp.builder() - .impId("impId") - .bidId("requestId") - .zoneId(ResetDigitalImpZone.of("placementId")) - .mediaTypes(ResetDigitalImpMediaTypes.video(ResetDigitalImpMediaType.of( - List.of(List.of(1, 2)), - List.of("mime1", "mime2")))) - .build()); + .flatExtracting(BidRequest::getImp) + .allSatisfy(imp -> { + assertThat(imp.getBanner()).isNull(); + assertThat(imp.getVideo()).isNotNull(); + assertThat(imp.getAudio()).isNull(); + }); } @Test - public void makeHttpRequestsShouldReturnAudioRequestFromAudioImp() { + public void makeHttpRequestShouldReturnResponseOnlyWithAudioImp() { // given - final BidRequest bidRequest = givenBidRequest(imp -> imp - .id("impId") - .banner(null) - .audio(Audio.builder().mimes(List.of("mime1", "mime2")).build())) - .toBuilder() - .id("requestId") + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null) + .audio(Audio.builder().build())))) .build(); - //when - final Result>> result = target.makeHttpRequests(bidRequest); + // when + final Result>> result = target.makeHttpRequests(bidRequest); - //then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) + // then + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()) .extracting(HttpRequest::getPayload) - .flatExtracting(ResetDigitalRequest::getImps) - .containsOnly(ResetDigitalImp.builder() - .impId("impId") - .bidId("requestId") - .zoneId(ResetDigitalImpZone.of("placementId")) - .mediaTypes(ResetDigitalImpMediaTypes.audio( - ResetDigitalImpMediaType.of(null, List.of("mime1", "mime2")))) - .build()); + .flatExtracting(BidRequest::getImp) + .allSatisfy(imp -> { + assertThat(imp.getBanner()).isNull(); + assertThat(imp.getVideo()).isNull(); + assertThat(imp.getAudio()).isNotNull(); + }); } @Test - public void makeHttpRequestsShouldFailWhenImpIsNative() { + public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { // given - final BidRequest bidRequest = givenBidRequest(imp -> imp - .id("impId") - .banner(null) - .xNative(Native.builder().build())) - .toBuilder() - .id("requestId") - .build(); + final BidderCall httpCall = givenHttpCall(null, "invalid"); - //when - final Result>> result = target.makeHttpRequests(bidRequest); + // when + final Result> result = target.makeBids(httpCall, null); - //then + // then + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + assertThat(error.getMessage()).startsWith("Failed to decode: Unrecognized token"); + }); assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()).hasSize(1).allSatisfy(bidderError -> { - assertThat(bidderError.getType()).isEqualTo(BidderError.Type.bad_input); - assertThat(bidderError.getMessage()).isEqualTo("Banner, video or audio must be present in the imp impId"); - }); } @Test - public void makeHttpRequestsShouldReturnCorrectSite() { + public void makeHttpRequestsShouldConvertCurrencyIfRequestCurrencyDoesNotMatchBidderCurrency() { // given - final BidRequest bidRequest = givenBidRequest(identity()) - .toBuilder() - .site(Site.builder().domain("domain").page("page").build()) - .build(); + given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) + .willReturn(BigDecimal.TEN); - //when - final Result>> result = target.makeHttpRequests(bidRequest); + final BidRequest bidRequest = givenBidRequest( + impBuilder -> impBuilder.bidfloor(BigDecimal.ONE).bidfloorcur("EUR")); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); - //then + // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) + assertThat(result.getValue()) .extracting(HttpRequest::getPayload) - .flatExtracting(ResetDigitalRequest::getSite) - .containsOnly(ResetDigitalSite.of("domain", "page")); + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBidfloor, Imp::getBidfloorcur) + .containsOnly(tuple(BigDecimal.TEN, "USD")); } @Test - public void makeBidsShouldReturnErrorWhenResponseCanNotBeParsed() { + public void makeHttpRequestsShouldReturnErrorMessageOnFailedCurrencyConversion() { // given - final BidderCall httpCall = givenHttpCall("invalid"); + given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) + .willThrow(PreBidException.class); + + final BidRequest bidRequest = givenBidRequest( + impCustomizer -> impCustomizer.bidfloor(BigDecimal.ONE).bidfloorcur("EUR")); // when - final Result> actual = target.makeBids(httpCall, null); + final Result>> result = target.makeHttpRequests(bidRequest); // then - assertThat(actual.getValue()).isEmpty(); - assertThat(actual.getErrors()).hasSize(1) - .allSatisfy(error -> { - assertThat(error.getMessage()).startsWith("Failed to decode: Unrecognized token 'invalid':"); - assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); - }); + assertThat(result.getErrors()).allSatisfy(bidderError -> { + assertThat(bidderError.getType()) + .isEqualTo(BidderError.Type.bad_input); + assertThat(bidderError.getMessage()) + .isEqualTo("Unable to convert provided bid floor currency from EUR to USD for imp `123`"); + }); } @Test - public void makeBidsShouldReturnEmptyBidsWhenResponseDoesNotHaveBids() throws JsonProcessingException { + public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidResponse()); + final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(null)); // when - final Result> actual = target.makeBids(httpCall, null); + final Result> result = target.makeBids(httpCall, null); // then - assertThat(actual.getValue()).isEmpty(); - assertThat(actual.getErrors()) - .containsOnly(BidderError.badServerResponse("expected exactly one bid in the response, but got 0")); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); } @Test - public void makeBidsShouldReturnEmptyBidsWhenResponseHasMoreThanOneBid() throws JsonProcessingException { + public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidResponse(identity(), identity())); + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(BidResponse.builder().build())); // when - final Result> actual = target.makeBids(httpCall, null); + final Result> result = target.makeBids(httpCall, null); // then - assertThat(actual.getValue()).isEmpty(); - assertThat(actual.getErrors()) - .containsOnly(BidderError.badServerResponse("expected exactly one bid in the response, but got 2")); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); } @Test - public void makeBidsShouldReturnEmptyBidsWhenResponseDoesNotHaveBidThatMatchesAnyImp() - throws JsonProcessingException { - + public void makeBidsShouldReturnBannerBidIfBannerIsPresentInRequestImp() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidResponse(bid -> bid.impId("impId1"))); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); // when - final Result> actual = target.makeBids(httpCall, givenBidRequest(imp -> imp.id("impId2"))); + final Result> result = target.makeBids(httpCall, null); // then - assertThat(actual.getValue()).isEmpty(); - assertThat(actual.getErrors()) - .containsOnly(BidderError.badServerResponse("no matching impression found for ImpID impId1")); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), banner, "USD")); } @Test - public void makeBidsShouldReturnBannerBidSuccessfully() throws JsonProcessingException { + public void makeBidsShouldReturnVideoBidIfVideoIsPresentInRequestImp() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidResponse(bid -> bid - .cpm(BigDecimal.TEN) - .cid("cid") - .html("html") - .crid("crid") - .seat("seat") - .impId("impId") - .bidId("bidId") - .w("1") - .h("2"))); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").video(Video.builder().build()).build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); // when - final Result> result = target.makeBids( - httpCall, - givenBidRequest(imp -> imp.id("impId").banner(Banner.builder().build()))); + final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); - final Bid expectedBid = Bid.builder() - .adm("html") - .price(BigDecimal.TEN) - .cid("cid") - .w(1) - .h(2) - .impid("impId") - .id("bidId") - .crid("crid") - .build(); - assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, banner, "seat", "USD")); + assertThat(result.getValue()) + .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), video, "USD")); } @Test - public void makeBidsShouldReturnVideoBidSuccessfully() throws JsonProcessingException { + public void makeBidsShouldReturnAudioBidIfAudioIsPresentInRequestImp() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidResponse(bid -> bid - .cpm(BigDecimal.TEN) - .cid("cid") - .html("html") - .crid("crid") - .seat("seat") - .impId("impId") - .bidId("bidId") - .w("1") - .h("2"))); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").audio(Audio.builder().build()).build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); // when - final Result> result = target.makeBids( - httpCall, - givenBidRequest(imp -> imp.id("impId").video(Video.builder().build()))); + final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); - final Bid expectedBid = Bid.builder() - .adm("html") - .price(BigDecimal.TEN) - .cid("cid") - .w(1) - .h(2) - .impid("impId") - .id("bidId") - .crid("crid") - .build(); - assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, video, "seat", "USD")); + assertThat(result.getValue()) + .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), audio, "USD")); } @Test - public void makeBidsShouldReturnAudioBidSuccessfully() throws JsonProcessingException { + public void makeBidsShouldReturnErrorBidIfBidTypeIsAbsentInRequestImp() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidResponse(bid -> bid - .cpm(BigDecimal.TEN) - .cid("cid") - .html("html") - .crid("crid") - .seat("seat") - .impId("impId") - .bidId("bidId") - .w("1") - .h("2"))); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); // when - final Result> result = target.makeBids( - httpCall, - givenBidRequest(imp -> imp.id("impId").audio(Audio.builder().build()))); + final Result> result = target.makeBids(httpCall, null); // then - assertThat(result.getErrors()).isEmpty(); - final Bid expectedBid = Bid.builder() - .adm("html") - .price(BigDecimal.TEN) - .cid("cid") - .w(1) - .h(2) - .impid("impId") - .id("bidId") - .crid("crid") - .build(); - assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, audio, "seat", "USD")); + assertThat(result.getErrors()).hasSize(1) + .extracting(BidderError::getMessage) + .containsExactly("Failed to find banner/video/audio impression 123"); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnErrorIfBidCurIsNotUsd() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").build())) + .build(), + mapper.writeValueAsString(givenBidResponse(identity()).toBuilder().cur("EUR").build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).hasSize(1) + .extracting(BidderError::getMessage) + .containsExactly("Bidder support only USD currency"); + assertThat(result.getValue()).isEmpty(); } - private static BidRequest givenBidRequest(UnaryOperator... impCustomizers) { - return BidRequest.builder() - .imp(Stream.of(impCustomizers).map(ResetDigitalBidderTest::givenImp).toList()) + private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer, + UnaryOperator impCustomizer) { + + return bidRequestCustomizer.apply(BidRequest.builder() + .imp(singletonList(givenImp(impCustomizer)))) .build(); } + private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { + return givenBidRequest(identity(), impCustomizer); + } + private static Imp givenImp(UnaryOperator impCustomizer) { return impCustomizer.apply(Imp.builder() - .id("impId") - .banner(Banner.builder().w(23).h(25).build()) - .ext(mapper.valueToTree(ExtPrebid.of( - null, - ExtImpResetDigital.of("placementId"))))) + .id("123") + .banner(Banner.builder().w(23).h(25).build())) .build(); } - private static BidderCall givenHttpCall(String body) { + private static BidResponse givenBidResponse(UnaryOperator bidCustomizer) { + return BidResponse.builder() + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bidCustomizer.apply(Bid.builder()).build())) + .build())) + .cur("USD") + .build(); + } + + private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { return BidderCall.succeededHttp( - HttpRequest.builder().payload(null).build(), + HttpRequest.builder().payload(bidRequest).build(), HttpResponse.of(200, null, body), null); } - - private String givenBidResponse(UnaryOperator... bidCustomizers) - throws JsonProcessingException { - - return mapper.writeValueAsString(ResetDigitalResponse.of( - Stream.of(bidCustomizers) - .map(bidCustomizer -> bidCustomizer.apply(ResetDigitalBid.builder()).build()) - .toList())); - } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-request.json b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-request.json index 9fffce432ec..9ee75999128 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-request.json @@ -1,31 +1,20 @@ { - "id": "12345", + "id": "request_id", "imp": [ { - "id": "001", + "id": "imp_id", "banner": { - "h": 300, - "w": 250 + "w": 300, + "h": 250 }, "ext": { "resetdigital": { - "placement_id": "placement-id-1" + "pubId": "lb.ads", + "zoneId": "publisherTestID" } } } ], - "site": { - "domain": "https://test.com", - "page": "https://test.com/2016/06/12" - }, - "cur": [ - "USD" - ], - "device": { - "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", - "ip": "127.0.0.1", - "language": "EN" - }, "tmax": 5000, "regs": { "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-response.json b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-response.json index 53308c326ee..556bc6d3217 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-response.json @@ -1,18 +1,19 @@ { - "id": "12345", + "id": "request_id", "seatbid": [ { "bid": [ { - "adm": "test markup", - "cid": "1002088", - "crid": "1000763-1002088", - "id": "01", - "impid": "001", - "price": 1.00, + "id": "bid_id", + "impid": "imp_id", + "exp": 300, + "price": 3.33, + "adm": "adm001", + "adid": "adid001", + "cid": "cid001", + "crid": "crid001", "w": 300, "h": 250, - "exp": 300, "ext": { "prebid": { "type": "banner", @@ -20,7 +21,7 @@ "adaptercode": "resetdigital" } }, - "origbidcpm": 1.00, + "origbidcpm": 3.33, "origbidcur": "USD" } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-request.json index 3b12e3d3c8a..43d9d50841b 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-request.json @@ -1,25 +1,57 @@ { - "imps": [ + "id": "request_id", + "imp": [ { - "bid_id": "12345", - "imp_id": "001", - "media_types": { - "banner": { - "sizes": [ - [ - 250, - 300 - ] - ] - } + "id": "imp_id", + "secure": 1, + "banner": { + "w": 300, + "h": 250 }, - "zone_id": { - "placementId": "placement-id-1" + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "pubId": "lb.ads", + "zoneId": "publisherTestID" + } } } ], + "source": { + "tid": "${json-unit.any-string}" + }, "site": { - "domain": "https://test.com", - "referrer": "https://test.com/2016/06/12" + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": "${json-unit.any-number}", + "cur": [ + "USD" + ], + "regs": { + "ext": { + "gdpr": 0 + } + }, + "ext": { + "prebid": { + "server": { + "externalurl": "http://localhost:8080", + "gvlid": 1, + "datacenter": "local", + "endpoint": "/openrtb2/auction" + } + } } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-response.json index 3397880777f..631464ca55d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-response.json @@ -1,16 +1,21 @@ { - "bids": [ + "id": "request_id", + "seatbid": [ { - "bid_id": "01", - "imp_id": "001", - "cpm": 1.00, - "cid": "1002088", - "crid": "1000763-1002088", - "adid": "1002088", - "w": "300", - "h": "250", - "seat": "resetdigital", - "html": "test markup" + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 3.33, + "adid": "adid001", + "crid": "crid001", + "cid": "cid001", + "adm": "adm001", + "h": 250, + "w": 300 + } + ] } - ] + ], + "cur": "USD" }