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 3bc78b588b0..5d292f9a093 100644 --- a/src/main/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidder.java +++ b/src/main/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidder.java @@ -1,182 +1,255 @@ 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 com.iab.openrtb.response.BidResponse; -import com.iab.openrtb.response.SeatBid; +import io.vertx.core.MultiMap; +import io.vertx.core.http.HttpMethod; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.ObjectUtils; 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.currency.CurrencyConversionService; +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.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.stream.Stream; +import java.util.Optional; +import java.util.Set; -public class ResetDigitalBidder implements Bidder { +public class ResetDigitalBidder implements Bidder { - private static final String DEFAULT_CURRENCY = "USD"; + private static final TypeReference> IMP_EXT_TYPE_REFERENCE = + new TypeReference<>() { + }; + private static final String BID_CURRENCY = "USD"; private final String endpointUrl; - private final CurrencyConversionService currencyConversionService; private final JacksonMapper mapper; - public ResetDigitalBidder(String endpointUrl, - CurrencyConversionService currencyConversionService, - JacksonMapper mapper) { - + public ResetDigitalBidder(String endpointUrl, 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 bannerImps = new ArrayList<>(); - final List videoImps = new ArrayList<>(); - final List audioImps = new ArrayList<>(); - Price bidFloorPrice; + public Result>> makeHttpRequests(BidRequest request) { + final List> requests = new ArrayList<>(); + final List errors = new ArrayList<>(); - for (Imp imp : request.getImp()) { + for (Imp imp: request.getImp()) { try { - bidFloorPrice = resolveBidFloor(imp, request); + final ExtImpResetDigital extImp = parseImpExt(imp); + final ResetDigitalImp resetDigitalImp = makeImp(request, imp, extImp); + requests.add(makeHttpRequest(request, resetDigitalImp)); } catch (PreBidException e) { - return Result.withError(BidderError.badInput(e.getMessage())); + errors.add(BidderError.badInput(e.getMessage())); } - populateBannerImps(bannerImps, bidFloorPrice, imp); - populateVideoImps(videoImps, bidFloorPrice, imp); - populateAudiImps(audioImps, bidFloorPrice, imp); } - return Result.withValues(getHttpRequests(request, bannerImps, videoImps, audioImps)); + 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()); + } } - private List> getHttpRequests(BidRequest request, - List bannerImps, - List videoImps, - List 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(); + } - return Stream.of(bannerImps, videoImps, audioImps) - .filter(CollectionUtils::isNotEmpty) - .map(imp -> makeHttpRequest(request, imp)) - .toList(); + 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())); + }; } - private HttpRequest makeHttpRequest(BidRequest bidRequest, List imp) { - final BidRequest outgoingRequest = bidRequest.toBuilder().imp(imp).build(); + 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 BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper); + return null; } - private static Imp modifyImp(Imp imp, Price bidFloorPrice) { - return imp.toBuilder() - .bidfloorcur(bidFloorPrice.getCurrency()) - .bidfloor(bidFloorPrice.getValue()) - .build(); + private static ResetDigitalImpMediaType makeBanner(Banner banner) { + return makeMediaType(banner.getW(), banner.getH(), null); } - 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 makeVideo(Video video) { + return makeMediaType(video.getW(), video.getH(), video.getMimes()); } - 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)); - } + private static ResetDigitalImpMediaType makeAudio(Audio audio) { + return makeMediaType(null, null, audio.getMimes()); } - 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 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; } + + return ResetDigitalImpMediaType.of( + hasValidSizes ? List.of(List.of(width, height)) : null, + hasMimes ? mimes : null); } - 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)); - } + private static boolean isValidSizeValue(Integer value) { + return value != null && value > 0; } - 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)); + 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 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()); + } + + final Site site = request.getSite(); + if (site != null) { + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, site.getPage()); } + + return headers; } @Override - public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { - final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - return Result.withValues(extractBids(bidResponse, httpCall.getRequest().getPayload())); + final ResetDigitalResponse bidResponse = mapper.decodeValue( + httpCall.getResponse().getBody(), + ResetDigitalResponse.class); + return Result.withValues(extractBids(bidRequest, bidResponse)); } catch (DecodeException | PreBidException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private static List extractBids(BidResponse bidResponse, BidRequest bidRequest) { - if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { - return Collections.emptyList(); + 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()); } - if (bidResponse.getCur() != null && !StringUtils.equalsIgnoreCase(DEFAULT_CURRENCY, bidResponse.getCur())) { - throw new PreBidException("Bidder support only USD currency"); + } + + private static BidType resolveBidType(Imp imp) throws PreBidException { + if (imp.getVideo() != null) { + return BidType.video; } - 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; - } - } + if (imp.getAudio() != null) { + return BidType.audio; } - throw new PreBidException("Failed to find banner/video/audio impression " + impId); + + return BidType.banner; } } 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 new file mode 100644 index 00000000000..5674c2446a7 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImp.java @@ -0,0 +1,19 @@ +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 new file mode 100644 index 00000000000..16363a5e6d9 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpExt.java @@ -0,0 +1,9 @@ +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 new file mode 100644 index 00000000000..cd678266463 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpMediaType.java @@ -0,0 +1,13 @@ +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 new file mode 100644 index 00000000000..45d714a530b --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpMediaTypes.java @@ -0,0 +1,27 @@ +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 new file mode 100644 index 00000000000..516ee7ec8bd --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalImpZone.java @@ -0,0 +1,11 @@ +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 new file mode 100644 index 00000000000..bfce161aafa --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalRequest.java @@ -0,0 +1,13 @@ +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 new file mode 100644 index 00000000000..f52e5f1d12d --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/resetdigital/request/ResetDigitalSite.java @@ -0,0 +1,11 @@ +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 new file mode 100644 index 00000000000..d4ea28b47c2 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/resetdigital/response/ResetDigitalBid.java @@ -0,0 +1,31 @@ +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 new file mode 100644 index 00000000000..2dcf57e8d80 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/resetdigital/response/ResetDigitalResponse.java @@ -0,0 +1,11 @@ +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 4e4de161f66..33823866d5f 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,7 +2,6 @@ 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; @@ -31,16 +30,12 @@ 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(), - currencyConversionService, - mapper)) + .bidderCreator(config -> new ResetDigitalBidder(config.getEndpoint(), mapper)) .assemble(); } } diff --git a/src/main/resources/bidder-config/resetdigital.yaml b/src/main/resources/bidder-config/resetdigital.yaml index 885916d0d8d..3ae9e26bf3e 100644 --- a/src/main/resources/bidder-config/resetdigital.yaml +++ b/src/main/resources/bidder-config/resetdigital.yaml @@ -4,9 +4,6 @@ adapters: meta-info: maintainer-email: biddersupport@resetdigital.co app-media-types: - - banner - - video - - audio site-media-types: - banner - video @@ -16,6 +13,6 @@ adapters: usersync: cookie-family-name: resetdigital redirect: - url: https://sync.resetdigital.co/csync?pid=rubicon&redir={{redirect_url}} + url: https://sync.resetdigital.co/csync?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 3710cfbc598..07a7bc2b551 100644 --- a/src/main/resources/static/bidder-params/resetdigital.json +++ b/src/main/resources/static/bidder-params/resetdigital.json @@ -1,23 +1,13 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "ResetDigital Adapter Params", - "description": "A schema which validates params accepted by the ResetDigital adapter", + "title": "Reset Digital Adapter Params", + "description": "A schema which validates params accepted by the Reset Digital adapter", "type": "object", "properties": { - "pubId": { + "placement_id": { "type": "string", - "description": "The publisher's ID provided" - }, - "zoneId": { - "type": "string", - "description": "Zone ID" - }, - "forceBid": { - "type": "boolean", - "description": "Force bids with a test creative" + "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" } - }, - "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 dbcc555489a..ba59b996b9a 100644 --- a/src/test/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidderTest.java @@ -4,17 +4,14 @@ 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; @@ -22,484 +19,478 @@ 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.currency.CurrencyConversionService; -import org.prebid.server.exception.PreBidException; +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 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, currencyConversionService, jacksonMapper); + target = new ResetDigitalBidder(ENDPOINT_URL, jacksonMapper); } @Test public void creationShouldFailOnInvalidEndpointUrl() { assertThatIllegalArgumentException().isThrownBy(() -> - new ResetDigitalBidder("invalid_url", currencyConversionService, jacksonMapper)); + new ResetDigitalBidder("invalid_url", jacksonMapper)); } @Test - public void makeHttpRequestShouldReturnEmptyResponseIfAbsentAnyTypeInImp() { + public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null)))) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).hasSize(0); - assertThat(result.getValue()).hasSize(0); - } - - @Test - public void makeHttpRequestShouldReturnEmptyResponseIfxNativeImpTypePresent() { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null) - .xNative(Native.builder().build())))) - .build(); + final BidRequest bidRequest = givenBidRequest( + imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); // when - final Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).hasSize(0); - assertThat(result.getValue()).hasSize(0); + 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"); + }); } @Test - public void makeHttpRequestShouldReturnSeparateResponseWithBannerAndVideoAndAudioImp() { + public void makeHttpRequestsShouldMakeOneRequestPerImp() { // 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); + final BidRequest bidRequest = givenBidRequest( + imp -> imp.id("givenImp1"), + imp -> imp.id("givenImp2"), + imp -> imp.id("givenImp3")); - // then - assertThat(result.getErrors()).hasSize(0); - assertThat(result.getValue()).hasSize(3); - assertThat(result.getValue().get(0)) - .extracting(HttpRequest::getPayload) - .extracting(BidRequest::getImp) - .extracting(a -> a.getFirst()) - .extracting(Imp::getBanner) - .isNotNull(); + //when + final Result>> result = target.makeHttpRequests(bidRequest); - assertThat(result.getValue().get(1)) + //then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(3) .extracting(HttpRequest::getPayload) - .extracting(BidRequest::getImp) - .extracting(a -> a.getFirst()) - .extracting(Imp::getVideo) - .isNotNull(); + .flatExtracting(ResetDigitalRequest::getImps) + .extracting(ResetDigitalImp::getImpId) + .containsOnly("givenImp1", "givenImp2", "givenImp3"); - assertThat(result.getValue().get(2)) - .extracting(HttpRequest::getPayload) - .extracting(BidRequest::getImp) - .extracting(a -> a.getFirst()) - .extracting(Imp::getAudio) - .isNotNull(); + assertThat(result.getValue()).hasSize(3) + .extracting(HttpRequest::getImpIds) + .containsOnly(Set.of("givenImp1"), Set.of("givenImp2"), Set.of("givenImp3")); } @Test - public void makeHttpRequestShouldReturnSeparateResponseWithBannerAndVideoImp() { + public void makeHttpRequestsShouldHaveCorrectUri() { // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(givenImp(impBuilder -> impBuilder - .video(Video.builder().build())))) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); + final BidRequest bidRequest = givenBidRequest(imp -> imp.id("givenImp")); - // 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(); + //when + final Result>> result = target.makeHttpRequests(bidRequest); - assertThat(result.getValue().get(1)) - .extracting(HttpRequest::getPayload) - .extracting(BidRequest::getImp) - .extracting(a -> a.getFirst()) - .extracting(Imp::getVideo) - .isNotNull(); + //then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getUri) + .containsOnly(ENDPOINT_URL); } @Test - public void makeHttpRequestShouldReturnSeparateResponseWithBannerAndAudioImp() { + public void makeHttpRequestsShouldReturnExpectedHeaders() { // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(givenImp(impBuilder -> impBuilder - .audio(Audio.builder().build())))) + final BidRequest bidRequest = givenBidRequest(identity()).toBuilder() + .device(Device.builder().ip("ip").ua("ua").language("lang").build()) + .site(Site.builder().page("page").build()) .build(); // when - final Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.makeHttpRequests(bidRequest); // 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::getAudio) - .isNotNull(); + 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(); } @Test - public void makeHttpRequestShouldReturnSeparateResponseWithVideoAndAudioImp() { + public void makeHttpRequestsShouldReturnExpectedHeadersWhenDeviceAndSiteAreNotPresent() { // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(givenImp(impBuilder -> impBuilder - .banner(null) - .video(Video.builder().build()) - .audio(Audio.builder().build())))) - .build(); + final BidRequest bidRequest = givenBidRequest(identity()); // when - final Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.makeHttpRequests(bidRequest); // 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::getVideo) - .isNotNull(); - - assertThat(result.getValue().get(1)) - .extracting(HttpRequest::getPayload) - .extracting(BidRequest::getImp) - .extracting(a -> a.getFirst()) - .extracting(Imp::getAudio) - .isNotNull(); + 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(); } @Test - public void makeHttpRequestShouldReturnResponseOnlyWithBannerImp() { + public void makeHttpRequestsShouldReturnBannerRequestFromBannerImp() { // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(givenImp(identity()))) + final BidRequest bidRequest = givenBidRequest(imp -> imp + .id("impId") + .banner(Banner.builder().w(1).h(2).build())) + .toBuilder() + .id("requestId") .build(); - // when - final Result>> result = target.makeHttpRequests(bidRequest); + //when + final Result>> result = target.makeHttpRequests(bidRequest); - // then - assertThat(result.getErrors()).hasSize(0); - assertThat(result.getValue()) + //then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .allSatisfy(imp -> { - assertThat(imp.getBanner()).isNotNull(); - assertThat(imp.getVideo()).isNull(); - assertThat(imp.getAudio()).isNull(); - }); + .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()); } @Test - public void makeHttpRequestShouldReturnResponseOnlyWithVideoImp() { + public void makeHttpRequestsShouldReturnVideoRequestFromVideoImp() { // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null) - .video(Video.builder().build())))) + 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") .build(); - // when - final Result>> result = target.makeHttpRequests(bidRequest); + //when + final Result>> result = target.makeHttpRequests(bidRequest); - // then - assertThat(result.getErrors()).hasSize(0); - assertThat(result.getValue()) + //then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .allSatisfy(imp -> { - assertThat(imp.getBanner()).isNull(); - assertThat(imp.getVideo()).isNotNull(); - assertThat(imp.getAudio()).isNull(); - }); + .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()); } @Test - public void makeHttpRequestShouldReturnResponseOnlyWithAudioImp() { + public void makeHttpRequestsShouldReturnAudioRequestFromAudioImp() { // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null) - .audio(Audio.builder().build())))) + final BidRequest bidRequest = givenBidRequest(imp -> imp + .id("impId") + .banner(null) + .audio(Audio.builder().mimes(List.of("mime1", "mime2")).build())) + .toBuilder() + .id("requestId") .build(); - // when - final Result>> result = target.makeHttpRequests(bidRequest); + //when + final Result>> result = target.makeHttpRequests(bidRequest); - // then - assertThat(result.getErrors()).hasSize(0); - assertThat(result.getValue()) + //then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .allSatisfy(imp -> { - assertThat(imp.getBanner()).isNull(); - assertThat(imp.getVideo()).isNull(); - assertThat(imp.getAudio()).isNotNull(); - }); + .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()); } @Test - public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { + public void makeHttpRequestsShouldFailWhenImpIsNative() { // given - final BidderCall httpCall = givenHttpCall(null, "invalid"); + final BidRequest bidRequest = givenBidRequest(imp -> imp + .id("impId") + .banner(null) + .xNative(Native.builder().build())) + .toBuilder() + .id("requestId") + .build(); - // when - final Result> result = target.makeBids(httpCall, null); + //when + final Result>> result = target.makeHttpRequests(bidRequest); - // 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"); - }); + //then 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 makeHttpRequestsShouldConvertCurrencyIfRequestCurrencyDoesNotMatchBidderCurrency() { + public void makeHttpRequestsShouldReturnCorrectSite() { // given - given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) - .willReturn(BigDecimal.TEN); - - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder.bidfloor(BigDecimal.ONE).bidfloorcur("EUR")); + final BidRequest bidRequest = givenBidRequest(identity()) + .toBuilder() + .site(Site.builder().domain("domain").page("page").build()) + .build(); - // when - final Result>> result = target.makeHttpRequests(bidRequest); + //when + final Result>> result = target.makeHttpRequests(bidRequest); - // then + //then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) + assertThat(result.getValue()).hasSize(1) .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getBidfloor, Imp::getBidfloorcur) - .containsOnly(tuple(BigDecimal.TEN, "USD")); + .flatExtracting(ResetDigitalRequest::getSite) + .containsOnly(ResetDigitalSite.of("domain", "page")); } @Test - public void makeHttpRequestsShouldReturnErrorMessageOnFailedCurrencyConversion() { + public void makeBidsShouldReturnErrorWhenResponseCanNotBeParsed() { // given - given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) - .willThrow(PreBidException.class); - - final BidRequest bidRequest = givenBidRequest( - impCustomizer -> impCustomizer.bidfloor(BigDecimal.ONE).bidfloorcur("EUR")); + final BidderCall httpCall = givenHttpCall("invalid"); // when - final Result>> result = target.makeHttpRequests(bidRequest); + final Result> actual = target.makeBids(httpCall, null); // then - 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`"); - }); + 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); + }); } @Test - public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { + public void makeBidsShouldReturnEmptyBidsWhenResponseDoesNotHaveBids() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(null)); + final BidderCall httpCall = givenHttpCall(givenBidResponse()); // when - final Result> result = target.makeBids(httpCall, null); + final Result> actual = target.makeBids(httpCall, null); // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); + assertThat(actual.getValue()).isEmpty(); + assertThat(actual.getErrors()) + .containsOnly(BidderError.badServerResponse("expected exactly one bid in the response, but got 0")); } @Test - public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException { + public void makeBidsShouldReturnEmptyBidsWhenResponseHasMoreThanOneBid() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(BidResponse.builder().build())); + final BidderCall httpCall = givenHttpCall(givenBidResponse(identity(), identity())); // when - final Result> result = target.makeBids(httpCall, null); + final Result> actual = target.makeBids(httpCall, null); // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); + assertThat(actual.getValue()).isEmpty(); + assertThat(actual.getErrors()) + .containsOnly(BidderError.badServerResponse("expected exactly one bid in the response, but got 2")); } @Test - public void makeBidsShouldReturnBannerBidIfBannerIsPresentInRequestImp() throws JsonProcessingException { - // given - 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")))); + public void makeBidsShouldReturnEmptyBidsWhenResponseDoesNotHaveBidThatMatchesAnyImp() + throws JsonProcessingException { - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), banner, "USD")); - } - - @Test - public void makeBidsShouldReturnVideoBidIfVideoIsPresentInRequestImp() throws JsonProcessingException { // given - 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")))); + final BidderCall httpCall = givenHttpCall(givenBidResponse(bid -> bid.impId("impId1"))); // when - final Result> result = target.makeBids(httpCall, null); + final Result> actual = target.makeBids(httpCall, givenBidRequest(imp -> imp.id("impId2"))); // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), video, "USD")); + assertThat(actual.getValue()).isEmpty(); + assertThat(actual.getErrors()) + .containsOnly(BidderError.badServerResponse("no matching impression found for ImpID impId1")); } @Test - public void makeBidsShouldReturnAudioBidIfAudioIsPresentInRequestImp() throws JsonProcessingException { + public void makeBidsShouldReturnBannerBidSuccessfully() throws JsonProcessingException { // given - 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")))); + 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"))); // when - final Result> result = target.makeBids(httpCall, null); + final Result> result = target.makeBids( + httpCall, + givenBidRequest(imp -> imp.id("impId").banner(Banner.builder().build()))); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), audio, "USD")); + 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")); } @Test - public void makeBidsShouldReturnErrorBidIfBidTypeIsAbsentInRequestImp() throws JsonProcessingException { + public void makeBidsShouldReturnVideoBidSuccessfully() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); + 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"))); // when - final Result> result = target.makeBids(httpCall, null); + final Result> result = target.makeBids( + httpCall, + givenBidRequest(imp -> imp.id("impId").video(Video.builder().build()))); // then - assertThat(result.getErrors()).hasSize(1) - .extracting(BidderError::getMessage) - .containsExactly("Failed to find banner/video/audio impression 123"); - assertThat(result.getValue()).isEmpty(); + 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")); } @Test - public void makeBidsShouldReturnErrorIfBidCurIsNotUsd() throws JsonProcessingException { + public void makeBidsShouldReturnAudioBidSuccessfully() 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())); + 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"))); // when - final Result> result = target.makeBids(httpCall, null); + final Result> result = target.makeBids( + httpCall, + givenBidRequest(imp -> imp.id("impId").audio(Audio.builder().build()))); // then - assertThat(result.getErrors()).hasSize(1) - .extracting(BidderError::getMessage) - .containsExactly("Bidder support only USD currency"); - assertThat(result.getValue()).isEmpty(); - } - - private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer, - UnaryOperator impCustomizer) { - - return bidRequestCustomizer.apply(BidRequest.builder() - .imp(singletonList(givenImp(impCustomizer)))) + 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")); } - private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { - return givenBidRequest(identity(), impCustomizer); + private static BidRequest givenBidRequest(UnaryOperator... impCustomizers) { + return BidRequest.builder() + .imp(Stream.of(impCustomizers).map(ResetDigitalBidderTest::givenImp).toList()) + .build(); } private static Imp givenImp(UnaryOperator impCustomizer) { return impCustomizer.apply(Imp.builder() - .id("123") - .banner(Banner.builder().w(23).h(25).build())) + .id("impId") + .banner(Banner.builder().w(23).h(25).build()) + .ext(mapper.valueToTree(ExtPrebid.of( + null, + ExtImpResetDigital.of("placementId"))))) .build(); } - 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) { + private static BidderCall givenHttpCall(String body) { return BidderCall.succeededHttp( - HttpRequest.builder().payload(bidRequest).build(), + HttpRequest.builder().payload(null).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 9ee75999128..9fffce432ec 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,20 +1,31 @@ { - "id": "request_id", + "id": "12345", "imp": [ { - "id": "imp_id", + "id": "001", "banner": { - "w": 300, - "h": 250 + "h": 300, + "w": 250 }, "ext": { "resetdigital": { - "pubId": "lb.ads", - "zoneId": "publisherTestID" + "placement_id": "placement-id-1" } } } ], + "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 556bc6d3217..53308c326ee 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,19 +1,18 @@ { - "id": "request_id", + "id": "12345", "seatbid": [ { "bid": [ { - "id": "bid_id", - "impid": "imp_id", - "exp": 300, - "price": 3.33, - "adm": "adm001", - "adid": "adid001", - "cid": "cid001", - "crid": "crid001", + "adm": "test markup", + "cid": "1002088", + "crid": "1000763-1002088", + "id": "01", + "impid": "001", + "price": 1.00, "w": 300, "h": 250, + "exp": 300, "ext": { "prebid": { "type": "banner", @@ -21,7 +20,7 @@ "adaptercode": "resetdigital" } }, - "origbidcpm": 3.33, + "origbidcpm": 1.00, "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 43d9d50841b..3b12e3d3c8a 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,57 +1,25 @@ { - "id": "request_id", - "imp": [ + "imps": [ { - "id": "imp_id", - "secure": 1, - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "tid": "${json-unit.any-string}", - "bidder": { - "pubId": "lb.ads", - "zoneId": "publisherTestID" + "bid_id": "12345", + "imp_id": "001", + "media_types": { + "banner": { + "sizes": [ + [ + 250, + 300 + ] + ] } + }, + "zone_id": { + "placementId": "placement-id-1" } } ], - "source": { - "tid": "${json-unit.any-string}" - }, "site": { - "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" - } - } + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" } } 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 631464ca55d..3397880777f 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,21 +1,16 @@ { - "id": "request_id", - "seatbid": [ + "bids": [ { - "bid": [ - { - "id": "bid_id", - "impid": "imp_id", - "price": 3.33, - "adid": "adid001", - "crid": "crid001", - "cid": "cid001", - "adm": "adm001", - "h": 250, - "w": 300 - } - ] + "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" } - ], - "cur": "USD" + ] }