From 28ef392dbe86e6bbc8dc6442074acad7d415d240 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 15 Nov 2021 14:37:24 +0200 Subject: [PATCH 01/26] Actualize Appnexus bidder changes (#1570) --- .../bidder/appnexus/AppnexusBidder.java | 472 +++++++++-------- .../proto/AppnexusImpExtAppnexus.java | 4 +- .../bidder/appnexus/proto/AppnexusReqExt.java | 9 - .../proto/AppnexusReqExtAppnexus.java | 12 +- .../config/bidder/AppnexusConfiguration.java | 25 +- .../config/bidder/FacebookConfiguration.java | 41 +- .../resources/bidder-config/appnexus.yaml | 1 + .../bidder/appnexus/AppnexusBidderTest.java | 487 +++++++----------- .../org/prebid/server/it/ApplicationTest.java | 3 +- .../it/amp/test-appnexus-bid-request.json | 4 + .../test-appnexus-bid-request-1.json | 15 + .../test-appnexus-bid-request-2.json | 4 + ...est-auction-rubicon-appnexus-response.json | 398 +++++++------- .../test-appnexus-bid-request-1.json | 4 + 14 files changed, 710 insertions(+), 769 deletions(-) delete mode 100644 src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusReqExt.java diff --git a/src/main/java/org/prebid/server/bidder/appnexus/AppnexusBidder.java b/src/main/java/org/prebid/server/bidder/appnexus/AppnexusBidder.java index b096a8ebf85..facd6ae01ef 100644 --- a/src/main/java/org/prebid/server/bidder/appnexus/AppnexusBidder.java +++ b/src/main/java/org/prebid/server/bidder/appnexus/AppnexusBidder.java @@ -3,8 +3,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.App; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Format; import com.iab.openrtb.request.Imp; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; @@ -25,7 +27,6 @@ import org.prebid.server.bidder.appnexus.proto.AppnexusImpExt; import org.prebid.server.bidder.appnexus.proto.AppnexusImpExtAppnexus; import org.prebid.server.bidder.appnexus.proto.AppnexusKeyVal; -import org.prebid.server.bidder.appnexus.proto.AppnexusReqExt; import org.prebid.server.bidder.appnexus.proto.AppnexusReqExtAppnexus; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; @@ -35,7 +36,6 @@ 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.ExtIncludeBrandCategory; import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtApp; import org.prebid.server.proto.openrtb.ext.request.ExtAppPrebid; @@ -45,9 +45,10 @@ import org.prebid.server.proto.openrtb.ext.request.ExtRequestTargeting; import org.prebid.server.proto.openrtb.ext.request.appnexus.ExtImpAppnexus; import org.prebid.server.proto.openrtb.ext.response.BidType; -import org.prebid.server.util.BidderUtil; import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo; +import org.prebid.server.util.BidderUtil; import org.prebid.server.util.HttpUtil; +import org.prebid.server.util.ObjectUtil; import java.math.BigDecimal; import java.util.ArrayList; @@ -61,12 +62,14 @@ import java.util.Random; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; public class AppnexusBidder implements Bidder { private static final int AD_POSITION_ABOVE_THE_FOLD = 1; // openrtb.AdPosition.AdPositionAboveTheFold private static final int AD_POSITION_BELOW_THE_FOLD = 3; // openrtb.AdPosition.AdPositionBelowTheFold private static final int MAX_IMP_PER_REQUEST = 10; + private static final int DEFAULT_PLATFORM_ID = 5; private static final String POD_SEPARATOR = "_"; private static final Map IAB_CATEGORIES = new HashMap<>(); @@ -173,12 +176,14 @@ public class AppnexusBidder implements Bidder { } private final String endpointUrl; + private final Integer headerBiddingSource; private final JacksonMapper mapper; private final Random rand = new Random(); - public AppnexusBidder(String endpointUrl, JacksonMapper mapper) { + public AppnexusBidder(String endpointUrl, Integer platformId, JacksonMapper mapper) { this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.headerBiddingSource = ObjectUtils.defaultIfNull(platformId, DEFAULT_PLATFORM_ID); this.mapper = Objects.requireNonNull(mapper); } @@ -197,8 +202,9 @@ public Result>> makeHttpRequests(BidRequest bidRequ generateAdPodId = ObjectUtils.defaultIfNull(generateAdPodId, impGenerateAdPodId); if (!Objects.equals(generateAdPodId, impGenerateAdPodId)) { - return Result.withError(BidderError.badInput( + errors.add(BidderError.badInput( "Generate ad pod option should be same for all pods in request")); + return Result.withErrors(errors); } processedImps.add(impWithExtProperties.getImp()); @@ -211,101 +217,209 @@ public Result>> makeHttpRequests(BidRequest bidRequ } } + if (processedImps.isEmpty()) { + return Result.withErrors(errors); + } + final String url = constructUrl(uniqueIds, errors); return Result.of(constructRequests(bidRequest, processedImps, url, generateAdPodId), errors); } private String makeDefaultDisplayManagerVer(BidRequest bidRequest) { - if (bidRequest.getApp() != null) { - final ExtApp extApp = bidRequest.getApp().getExt(); - final ExtAppPrebid prebid = extApp != null ? extApp.getPrebid() : null; - if (prebid != null) { - final String source = prebid.getSource(); - final String version = prebid.getVersion(); - - if (source != null && version != null) { - return String.format("%s-%s", source, version); - } - } + final ExtApp extApp = ObjectUtil.getIfNotNull(bidRequest.getApp(), App::getExt); + final ExtAppPrebid prebid = ObjectUtil.getIfNotNull(extApp, ExtApp::getPrebid); + + final String source = ObjectUtil.getIfNotNull(prebid, ExtAppPrebid::getSource); + final String version = ObjectUtil.getIfNotNull(prebid, ExtAppPrebid::getVersion); + + return ObjectUtils.allNotNull(source, version) + ? String.format("%s-%s", source, version) + : null; + } + + private ImpWithExtProperties processImp(Imp imp, String defaultDisplayManagerVer) { + final ExtImpAppnexus appnexusExt = validateAndResolveImpExt(imp); + + final Imp.ImpBuilder impBuilder = imp.toBuilder() + .banner(makeBanner(imp.getBanner(), appnexusExt)) + .ext(makeImpExt(appnexusExt)); + + final String invCode = appnexusExt.getInvCode(); + if (StringUtils.isNotBlank(invCode)) { + impBuilder.tagid(invCode); + } + + final BigDecimal reserve = appnexusExt.getReserve(); + if (!BidderUtil.isValidPrice(imp.getBidfloor()) && BidderUtil.isValidPrice(reserve)) { + impBuilder.bidfloor(reserve); + } + + if (StringUtils.isBlank(imp.getDisplaymanagerver()) && StringUtils.isNotBlank(defaultDisplayManagerVer)) { + impBuilder.displaymanagerver(defaultDisplayManagerVer); } - return null; + + return ImpWithExtProperties.of(impBuilder.build(), appnexusExt.getMember(), appnexusExt.getGenerateAdPodId()); } - /** - * The Appnexus API requires a Member ID in the URL if it present in the request. This means the request may fail - * if different impressions have different member IDs. - */ - private static void validateMemberId(Set uniqueIds) { - if (uniqueIds.size() > 1) { - throw new PreBidException( - String.format("All request.imp[i].ext.appnexus.member params must match. Request contained: %s", - String.join(", ", uniqueIds))); + private ExtImpAppnexus validateAndResolveImpExt(Imp imp) { + try { + final ExtImpAppnexus ext = mapper.mapper() + .convertValue(imp.getExt(), APPNEXUS_EXT_TYPE_REFERENCE) + .getBidder(); + + final ExtImpAppnexus resolvedExt = resolveLegacyParameters(ext); + validateExtImpAppnexus(resolvedExt); + + return resolvedExt; + } catch (IllegalArgumentException e) { + throw new PreBidException(e.getMessage(), e); } } - private static boolean isVideoRequest(BidRequest bidRequest) { - final ExtRequest requestExt = bidRequest.getExt(); - final ExtRequestPrebid prebid = requestExt != null ? requestExt.getPrebid() : null; - final ExtRequestPrebidPbs pbs = prebid != null ? prebid.getPbs() : null; - final String endpointName = pbs != null ? pbs.getEndpoint() : null; + private static ExtImpAppnexus resolveLegacyParameters(ExtImpAppnexus extImpAppnexus) { + if (!shouldReplaceWithLegacyParameters(extImpAppnexus)) { + return extImpAppnexus; + } - return StringUtils.equals(endpointName, Endpoint.openrtb2_video.value()); + final Integer resolvedPlacementId = ObjectUtils.defaultIfNull( + extImpAppnexus.getLegacyPlacementId(), extImpAppnexus.getPlacementId()); + final String resolvedInvCode = ObjectUtils.defaultIfNull( + extImpAppnexus.getInvCode(), extImpAppnexus.getLegacyInvCode()); + final String resolvedTrafficSourceCode = ObjectUtils.defaultIfNull( + extImpAppnexus.getTrafficSourceCode(), extImpAppnexus.getLegacyTrafficSourceCode()); + + return extImpAppnexus.toBuilder() + .placementId(resolvedPlacementId) + .invCode(resolvedInvCode) + .trafficSourceCode(resolvedTrafficSourceCode) + .build(); } - private ExtRequest updateRequestExt(BidRequest bidRequest) { - final ExtRequest requestExt = bidRequest.getExt(); - if (isIncludeBrandCategory(requestExt)) { - return updateRequestExt(requestExt, true, null); + private static boolean shouldReplaceWithLegacyParameters(ExtImpAppnexus extImpAppnexus) { + final boolean setPlacementId = extImpAppnexus.getPlacementId() == null + && extImpAppnexus.getLegacyPlacementId() != null; + final boolean setInvCode = extImpAppnexus.getInvCode() == null + && extImpAppnexus.getLegacyInvCode() != null; + final boolean setTrafficSourceCode = extImpAppnexus.getTrafficSourceCode() == null + && extImpAppnexus.getLegacyTrafficSourceCode() != null; + + return setPlacementId || setInvCode || setTrafficSourceCode; + } + + private static void validateExtImpAppnexus(ExtImpAppnexus extImpAppnexus) { + final int placementId = ObjectUtils.defaultIfNull(extImpAppnexus.getPlacementId(), 0); + if (placementId == 0 && StringUtils.isAnyBlank(extImpAppnexus.getInvCode(), extImpAppnexus.getMember())) { + throw new PreBidException("No placement or member+invcode provided"); } - return requestExt; } - private ExtRequest updateRequestExt(ExtRequest requestExt, boolean includeBrandCategory, String adpodId) { - return mapper.fillExtension( - ExtRequest.of(requestExt.getPrebid()), - AppnexusReqExt.of(AppnexusReqExtAppnexus.of(includeBrandCategory, includeBrandCategory, adpodId))); + private ObjectNode makeImpExt(ExtImpAppnexus appnexusExt) { + final AppnexusImpExtAppnexus appnexusImpExt = AppnexusImpExtAppnexus.builder() + .placementId(appnexusExt.getPlacementId()) + .keywords(makeKeywords(appnexusExt.getKeywords())) + .trafficSourceCode(appnexusExt.getTrafficSourceCode()) + .usePmtRule(appnexusExt.getUsePmtRule()) + .privateSizes(appnexusExt.getPrivateSizes()) + .build(); + + return mapper.mapper().valueToTree(AppnexusImpExt.of(appnexusImpExt)); } - private ExtRequest updateRequestExtForVideo(BidRequest bidRequest) { - final ExtRequest requestExt = bidRequest.getExt(); - return updateRequestExt(requestExt, isIncludeBrandCategory(requestExt), Long.toUnsignedString(rand.nextLong())); + private static String makeKeywords(List keywords) { + final String resolvedKeywords = CollectionUtils.emptyIfNull(keywords).stream() + .filter(entry -> entry.getKey() != null) + .flatMap(AppnexusBidder::extractKeywords) + .collect(Collectors.joining(",")); + + return StringUtils.stripToNull(resolvedKeywords); } - private static boolean isIncludeBrandCategory(ExtRequest extRequest) { - final ExtRequestPrebid prebid = extRequest != null ? extRequest.getPrebid() : null; - final ExtRequestTargeting targeting = prebid != null ? prebid.getTargeting() : null; - final ExtIncludeBrandCategory includebrandcategory = targeting != null - ? targeting.getIncludebrandcategory() - : null; - return includebrandcategory != null; + private static Stream extractKeywords(AppnexusKeyVal appnexusKeyVal) { + final String key = appnexusKeyVal.getKey(); + final List values = appnexusKeyVal.getValue(); + return CollectionUtils.isNotEmpty(values) + ? values.stream().map(value -> String.format("%s=%s", key, value)) + : Stream.of(key); + } + + private static Banner makeBanner(Banner banner, ExtImpAppnexus appnexusExt) { + if (banner == null) { + return null; + } + final Integer width = banner.getW(); + final Integer height = banner.getH(); + + final List formats = banner.getFormat(); + final Format firstFormat = CollectionUtils.isNotEmpty(formats) ? formats.get(0) : null; + + final boolean replaceWithFirstFormat = firstFormat != null && width == null && height == null; + + final Integer resolvedWidth = replaceWithFirstFormat ? firstFormat.getW() : width; + final Integer resolvedHeight = replaceWithFirstFormat ? firstFormat.getH() : height; + + final Integer position = resolvePosition(appnexusExt.getPosition()); + + return position != null || replaceWithFirstFormat + ? banner.toBuilder().pos(position).w(resolvedWidth).h(resolvedHeight).build() + : banner; + } + + private static Integer resolvePosition(String position) { + final Integer posAbove = Objects.equals(position, "above") ? AD_POSITION_ABOVE_THE_FOLD : null; + final Integer posBelow = Objects.equals(position, "below") ? AD_POSITION_BELOW_THE_FOLD : null; + return posAbove != null ? posAbove : posBelow; } private String constructUrl(Set ids, List errors) { - if (CollectionUtils.isNotEmpty(ids)) { - final String url = String.format("%s?member_id=%s", endpointUrl, ids.iterator().next()); - try { - validateMemberId(ids); - } catch (PreBidException e) { - errors.add(BidderError.badInput(e.getMessage())); - } - return url; + validateMemberIds(ids, errors); + return CollectionUtils.isNotEmpty(ids) + ? String.format("%s?member_id=%s", endpointUrl, ids.iterator().next()) + : endpointUrl; + } + + private static void validateMemberIds(Set uniqueIds, List errors) { + if (uniqueIds.size() > 1) { + errors.add(BidderError.badInput( + "All request.imp[i].ext.appnexus.member params must match. Request contained: " + + String.join(", ", uniqueIds))); } - return endpointUrl; } private List> constructRequests(BidRequest bidRequest, List imps, String url, Boolean generateAdPodId) { - if (isVideoRequest(bidRequest) && BooleanUtils.isTrue(generateAdPodId)) { - return groupImpsByPod(imps) - .values().stream() - .map(podImps -> splitHttpRequests(bidRequest, updateRequestExtForVideo(bidRequest), podImps, url)) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } else { - return splitHttpRequests(bidRequest, updateRequestExt(bidRequest), imps, url); - } + + final String requestEndpointName = extractEndpointName(bidRequest); + final boolean isVideoRequest = StringUtils.equals(requestEndpointName, Endpoint.openrtb2_video.value()); + final boolean isAmpRequest = StringUtils.equals(requestEndpointName, Endpoint.openrtb2_amp.value()); + + return isVideoRequest && BooleanUtils.isTrue(generateAdPodId) + ? constructPodRequests(bidRequest, imps, url) + : constructPartitionedRequests(bidRequest, imps, url, isVideoRequest, isAmpRequest); + } + + private static String extractEndpointName(BidRequest bidRequest) { + final ExtRequest requestExt = bidRequest.getExt(); + final ExtRequestPrebid prebid = requestExt != null ? requestExt.getPrebid() : null; + final ExtRequestPrebidPbs pbs = prebid != null ? prebid.getPbs() : null; + return pbs != null ? pbs.getEndpoint() : null; + } + + private List> constructPodRequests(BidRequest bidRequest, + List imps, + String url) { + + return groupImpsByPod(imps) + .values().stream() + .map(podImps -> splitHttpRequests( + bidRequest, updateRequestExtForVideo(bidRequest.getExt()), podImps, url)) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + private ExtRequest updateRequestExtForVideo(ExtRequest extRequest) { + return updateRequestExt(extRequest, true, false, Long.toUnsignedString(rand.nextLong())); } private Map> groupImpsByPod(List processedImps) { @@ -313,24 +427,61 @@ private Map> groupImpsByPod(List processedImps) { .collect(Collectors.groupingBy(imp -> StringUtils.substringBefore(imp.getId(), POD_SEPARATOR))); } + private List> constructPartitionedRequests(BidRequest bidRequest, + List imps, + String url, + boolean isVideoRequest, + boolean isAmpRequest) { + + final ExtRequest updatedExtRequest = updateRequestExt( + bidRequest.getExt(), isVideoRequest, isAmpRequest, null); + + return splitHttpRequests(bidRequest, updatedExtRequest, imps, url); + } + + private ExtRequest updateRequestExt(ExtRequest extRequest, + boolean isVideoRequest, + boolean isAmpRequest, + String adPodId) { + + final Boolean includeBrandCategory = isIncludeBrandCategory(extRequest); + final AppnexusReqExtAppnexus appnexus = AppnexusReqExtAppnexus.builder() + .includeBrandCategory(includeBrandCategory) + .brandCategoryUniqueness(includeBrandCategory) + .isAmp(BooleanUtils.toInteger(isAmpRequest)) + .adpodId(adPodId) + .headerBiddingSource(headerBiddingSource + BooleanUtils.toInteger(isVideoRequest)) + .build(); + + final ExtRequestPrebid extRequestPrebid = ObjectUtil.getIfNotNull(extRequest, ExtRequest::getPrebid); + final ObjectNode appnexusNode = mapper.mapper().createObjectNode() + .set("appnexus", mapper.mapper().valueToTree(appnexus)); + + return mapper.fillExtension(ExtRequest.of(extRequestPrebid), appnexusNode); + } + + private static Boolean isIncludeBrandCategory(ExtRequest extRequest) { + final ExtRequestPrebid prebid = extRequest != null ? extRequest.getPrebid() : null; + final ExtRequestTargeting targeting = prebid != null ? prebid.getTargeting() : null; + return targeting != null && targeting.getIncludebrandcategory() != null ? true : null; + } + private List> splitHttpRequests(BidRequest bidRequest, ExtRequest requestExt, List imps, String url) { - final List> result = ListUtils.partition(imps, MAX_IMP_PER_REQUEST) + + return ListUtils.partition(imps, MAX_IMP_PER_REQUEST) .stream() .map(impsChunk -> createHttpRequest(bidRequest, requestExt, impsChunk, url)) .collect(Collectors.toList()); - - return result.isEmpty() - ? Collections.singletonList(createHttpRequest(bidRequest, requestExt, imps, url)) - : result; } private HttpRequest createHttpRequest(BidRequest bidRequest, ExtRequest requestExt, List imps, String url) { + final BidRequest outgoingRequest = bidRequest.toBuilder() .imp(imps) .ext(requestExt) @@ -345,185 +496,76 @@ private HttpRequest createHttpRequest(BidRequest bidRequest, .build(); } - private ImpWithExtProperties processImp(Imp imp, String defaultDisplayManagerVer) { - if (imp.getAudio() != null) { - throw new PreBidException( - String.format("Appnexus doesn't support audio Imps. Ignoring Imp ID=%s", imp.getId())); - } - - final ExtImpAppnexus appnexusExt = parseAndValidateAppnexusExt(imp); - - final Imp.ImpBuilder impBuilder = imp.toBuilder() - .banner(makeBanner(imp.getBanner(), appnexusExt)) - .ext(mapper.mapper().valueToTree(makeAppnexusImpExt(appnexusExt))); - - final String invCode = appnexusExt.getInvCode(); - if (StringUtils.isNotBlank(invCode)) { - impBuilder.tagid(invCode); - } - - final BigDecimal reserve = appnexusExt.getReserve(); - if (!BidderUtil.isValidPrice(imp.getBidfloor()) && BidderUtil.isValidPrice(reserve)) { - impBuilder.bidfloor(reserve); // This will be broken for non-USD currency. - } - - // Populate imp.displaymanagerver if the SDK failed to do it. - if (StringUtils.isBlank(imp.getDisplaymanagerver()) && StringUtils.isNotBlank(defaultDisplayManagerVer)) { - impBuilder.displaymanagerver(defaultDisplayManagerVer); - } - - return ImpWithExtProperties.of(impBuilder.build(), appnexusExt.getMember(), - appnexusExt.getGenerateAdPodId()); - } - - private static AppnexusImpExt makeAppnexusImpExt(ExtImpAppnexus appnexusExt) { - return AppnexusImpExt.of( - AppnexusImpExtAppnexus.of(appnexusExt.getPlacementId(), makeKeywords(appnexusExt.getKeywords()), - appnexusExt.getTrafficSourceCode(), appnexusExt.getUsePmtRule(), - appnexusExt.getPrivateSizes())); - } - - private static Banner makeBanner(Banner banner, ExtImpAppnexus appnexusExt) { - Banner result = null; - if (banner != null) { - final String position = appnexusExt.getPosition(); - final Integer posAbove = Objects.equals(position, "above") ? AD_POSITION_ABOVE_THE_FOLD : null; - final Integer posBelow = Objects.equals(position, "below") ? AD_POSITION_BELOW_THE_FOLD : null; - final Integer pos = posAbove != null ? posAbove : posBelow; - - final boolean isFormatsPresent = CollectionUtils.isNotEmpty(banner.getFormat()); - final Integer width = isFormatsPresent && banner.getW() == null && banner.getH() == null - ? banner.getFormat().get(0).getW() : banner.getW(); - - final Integer height = isFormatsPresent && banner.getH() == null && banner.getW() == null - ? banner.getFormat().get(0).getH() : banner.getH(); - - if (pos != null || !Objects.equals(width, banner.getW()) || !Objects.equals(height, banner.getH())) { - result = banner.toBuilder() - .pos(pos) - .w(width) - .h(height) - .build(); - } else { - result = banner; - } - } - return result; - } - - private static String makeKeywords(List keywords) { - if (CollectionUtils.isEmpty(keywords)) { - return null; - } - - final List kvs = new ArrayList<>(); - for (AppnexusKeyVal keyVal : keywords) { - final String key = keyVal.getKey(); - final List values = keyVal.getValue(); - if (values == null || values.isEmpty()) { - kvs.add(key); - } else { - for (String value : values) { - kvs.add(String.format("%s=%s", key, value)); - } - } - } - - return String.join(",", kvs); - } - - private ExtImpAppnexus parseAndValidateAppnexusExt(Imp imp) { - ExtImpAppnexus ext; - try { - ext = mapper.mapper().convertValue(imp.getExt(), APPNEXUS_EXT_TYPE_REFERENCE).getBidder(); - } catch (IllegalArgumentException e) { - throw new PreBidException(e.getMessage(), e); - } - - // Accept legacy Appnexus parameters if we don't have modern ones - // Don't worry if both is set as validation rules should prevent, and this is temporary anyway. - boolean setPlacementId = ext.getPlacementId() == null && ext.getLegacyPlacementId() != null; - boolean setInvCode = ext.getInvCode() == null && ext.getLegacyInvCode() != null; - boolean setTrafficSourceCode = ext.getTrafficSourceCode() == null && ext.getLegacyTrafficSourceCode() != null; - if (setPlacementId || setInvCode || setTrafficSourceCode) { - ext = ext.toBuilder() - .placementId(setPlacementId ? ext.getLegacyPlacementId() : ext.getPlacementId()) - .invCode(setInvCode ? ext.getLegacyInvCode() : ext.getInvCode()) - .trafficSourceCode(setTrafficSourceCode ? ext.getLegacyTrafficSourceCode() - : ext.getTrafficSourceCode()) - .build(); - } - - final Integer placementId = ext.getPlacementId(); - if ((placementId == null || Objects.equals(placementId, 0)) - && (StringUtils.isBlank(ext.getInvCode()) || StringUtils.isBlank(ext.getMember()))) { - throw new PreBidException("No placement or member+invcode provided"); - } - - return ext; - } - @Override public Result> makeBids(HttpCall httpCall, BidRequest bidRequest) { + final List errors = new ArrayList<>(); try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - return Result.withValues(extractBids(bidResponse)); + return Result.of(extractBids(bidResponse, errors), errors); } catch (DecodeException | PreBidException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private List extractBids(BidResponse bidResponse) { + private List extractBids(BidResponse bidResponse, List errors) { return bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid()) ? Collections.emptyList() - : bidsFromResponse(bidResponse); + : bidsFromResponse(bidResponse, errors); } - private List bidsFromResponse(BidResponse bidResponse) { + private List bidsFromResponse(BidResponse bidResponse, List errors) { return bidResponse.getSeatbid().stream() .filter(Objects::nonNull) .map(SeatBid::getBid) .filter(Objects::nonNull) .flatMap(Collection::stream) - .map(bid -> bidderBid(bid, bidResponse.getCur())) + .map(bid -> toBidderBid(bid, bidResponse.getCur(), errors)) + .filter(Objects::nonNull) .collect(Collectors.toList()); } - private BidderBid bidderBid(Bid bid, String currency) { - final AppnexusBidExtAppnexus appnexus = parseAppnexusBidExt(bid.getExt()).getAppnexus(); + private BidderBid toBidderBid(Bid bid, String currency, List errors) { + final AppnexusBidExtAppnexus appnexus; + try { + appnexus = parseAppnexusBidExt(bid.getExt()).getAppnexus(); + } catch (IllegalArgumentException | JsonProcessingException e) { + errors.add(BidderError.badServerResponse(e.getMessage())); + return null; + } + if (appnexus == null) { - throw new PreBidException("bidResponse.bid.ext.appnexus should be defined"); + errors.add(BidderError.badServerResponse("bidResponse.bid.ext.appnexus should be defined")); + return null; } final String iabCategory = iabCategory(appnexus.getBrandCategoryId()); List cat = bid.getCat(); if (iabCategory != null) { - cat = Collections.singletonList(iabCategory); + cat = List.of(iabCategory); } else if (CollectionUtils.isNotEmpty(bid.getCat())) { // create empty categories array to force bid to be rejected cat = Collections.emptyList(); } - final Bid modifiedBid = bid.toBuilder().cat(cat).build(); - return BidderBid.of(modifiedBid, bidType(appnexus.getBidAdType()), currency, appnexus.getDealPriority(), + return BidderBid.of( + bid.toBuilder().cat(cat).build(), + bidType(appnexus.getBidAdType()), + currency, + appnexus.getDealPriority(), makeExtBidVideo(appnexus)); } private static ExtBidPrebidVideo makeExtBidVideo(AppnexusBidExtAppnexus extAppnexus) { final AppnexusBidExtCreative appnexusBidExtCreative = extAppnexus.getCreativeInfo(); - final AppnexusBidExtVideo appnexusBidExtVideo = appnexusBidExtCreative != null - ? appnexusBidExtCreative.getVideo() - : null; + final AppnexusBidExtVideo appnexusBidExtVideo = + ObjectUtil.getIfNotNull(appnexusBidExtCreative, AppnexusBidExtCreative::getVideo); final Integer duration = appnexusBidExtVideo != null ? appnexusBidExtVideo.getDuration() : null; return duration != null ? ExtBidPrebidVideo.of(duration, null) : null; } private static String iabCategory(Integer brandId) { - if (brandId == null) { - return null; - } - return IAB_CATEGORIES.get(brandId); + return brandId != null ? IAB_CATEGORIES.get(brandId) : null; } private static BidType bidType(Integer bidAdType) { @@ -546,17 +588,11 @@ private static BidType bidType(Integer bidAdType) { } } - private AppnexusBidExt parseAppnexusBidExt(ObjectNode bidExt) { + private AppnexusBidExt parseAppnexusBidExt(ObjectNode bidExt) throws JsonProcessingException { if (bidExt == null) { throw new PreBidException("bidResponse.bid.ext should be defined for appnexus"); } - final AppnexusBidExt appnexusBidExt; - try { - appnexusBidExt = mapper.mapper().treeToValue(bidExt, AppnexusBidExt.class); - } catch (JsonProcessingException e) { - throw new PreBidException(e.getMessage(), e); - } - return appnexusBidExt; + return mapper.mapper().treeToValue(bidExt, AppnexusBidExt.class); } } diff --git a/src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusImpExtAppnexus.java b/src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusImpExtAppnexus.java index d13dd6787d2..e1e6040e6be 100644 --- a/src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusImpExtAppnexus.java +++ b/src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusImpExtAppnexus.java @@ -2,10 +2,12 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Value; -@AllArgsConstructor(staticName = "of") @Value +@Builder +@AllArgsConstructor(staticName = "of") public class AppnexusImpExtAppnexus { Integer placementId; diff --git a/src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusReqExt.java b/src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusReqExt.java deleted file mode 100644 index 42d837763f9..00000000000 --- a/src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusReqExt.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.prebid.server.bidder.appnexus.proto; - -import lombok.Value; - -@Value(staticConstructor = "of") -public class AppnexusReqExt { - - AppnexusReqExtAppnexus appnexus; -} diff --git a/src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusReqExtAppnexus.java b/src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusReqExtAppnexus.java index c6153d6eb71..ef1bcd74627 100644 --- a/src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusReqExtAppnexus.java +++ b/src/main/java/org/prebid/server/bidder/appnexus/proto/AppnexusReqExtAppnexus.java @@ -1,15 +1,21 @@ package org.prebid.server.bidder.appnexus.proto; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; import lombok.Value; -@AllArgsConstructor(staticName = "of") -@Value +@Builder +@Value(staticConstructor = "of") public class AppnexusReqExtAppnexus { Boolean includeBrandCategory; Boolean brandCategoryUniqueness; + Integer isAmp; + + @JsonProperty("hb_source") + Integer headerBiddingSource; + String adpodId; } diff --git a/src/main/java/org/prebid/server/spring/config/bidder/AppnexusConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/AppnexusConfiguration.java index b5a654cae71..bbb865e26d0 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/AppnexusConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/AppnexusConfiguration.java @@ -1,5 +1,8 @@ package org.prebid.server.spring.config.bidder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import org.prebid.server.bidder.BidderDeps; import org.prebid.server.bidder.appnexus.AppnexusBidder; import org.prebid.server.json.JacksonMapper; @@ -12,6 +15,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; import javax.validation.constraints.NotBlank; @@ -21,21 +25,26 @@ public class AppnexusConfiguration { private static final String BIDDER_NAME = "appnexus"; - @Bean("appnexusConfigurationProperties") - @ConfigurationProperties("adapters.appnexus") - BidderConfigurationProperties configurationProperties() { - return new BidderConfigurationProperties(); - } - @Bean - BidderDeps appnexusBidderDeps(BidderConfigurationProperties appnexusConfigurationProperties, + BidderDeps appnexusBidderDeps(AppnexusConfigurationProperties appnexusConfigurationProperties, @NotBlank @Value("${external-url}") String externalUrl, JacksonMapper mapper) { return BidderDepsAssembler.forBidder(BIDDER_NAME) .withConfig(appnexusConfigurationProperties) .usersyncerCreator(UsersyncerCreator.create(externalUrl)) - .bidderCreator(config -> new AppnexusBidder(config.getEndpoint(), mapper)) + .bidderCreator(config -> new AppnexusBidder( + config.getEndpoint(), appnexusConfigurationProperties.getPlatformId(), mapper)) .assemble(); } + + @Data + @EqualsAndHashCode(callSuper = true) + @NoArgsConstructor + @Component("appnexusConfigurationProperties") + @ConfigurationProperties("adapters.appnexus") + private static class AppnexusConfigurationProperties extends BidderConfigurationProperties { + + Integer platformId; + } } diff --git a/src/main/java/org/prebid/server/spring/config/bidder/FacebookConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/FacebookConfiguration.java index 2d3c8f3fb3d..3a459aba2c0 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/FacebookConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/FacebookConfiguration.java @@ -3,6 +3,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.BidderDeps; import org.prebid.server.bidder.facebook.FacebookBidder; import org.prebid.server.json.JacksonMapper; @@ -10,17 +11,15 @@ import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler; import org.prebid.server.spring.config.bidder.util.UsersyncerCreator; import org.prebid.server.spring.env.YamlPropertySourceFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; -import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import java.util.function.Function; @Configuration @PropertySource(value = "classpath:/bidder-config/facebook.yaml", factory = YamlPropertySourceFactory.class) @@ -28,34 +27,22 @@ public class FacebookConfiguration { private static final String BIDDER_NAME = "audienceNetwork"; - @Autowired - private JacksonMapper mapper; - - @Autowired - @Qualifier("facebookConfigurationProperties") - private FacebookConfigurationProperties configProperties; - - @Bean("facebookConfigurationProperties") - @ConfigurationProperties("adapters.facebook") - FacebookConfigurationProperties configurationProperties() { - return new FacebookConfigurationProperties(); - } - @Bean - BidderDeps facebookBidderDeps(BidderConfigurationProperties facebookConfigurationProperties, - @NotBlank @Value("${external-url}") String externalUrl, + BidderDeps facebookBidderDeps(FacebookConfigurationProperties facebookConfigurationProperties, JacksonMapper mapper) { - return BidderDepsAssembler.forBidder(BIDDER_NAME) - .withConfig(configProperties) - .usersyncerCreator(UsersyncerCreator.create(null)) - .bidderCreator(configProperties.getEnabled() - ? config -> new FacebookBidder( + final Function> bidderCreator = + config -> new FacebookBidder( config.getEndpoint(), config.getPlatformId(), config.getAppSecret(), - configProperties.getTimeoutNotificationUrlTemplate(), mapper) - : null) + facebookConfigurationProperties.getTimeoutNotificationUrlTemplate(), + mapper); + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(facebookConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(null)) + .bidderCreator(facebookConfigurationProperties.getEnabled() ? bidderCreator : null) .assemble(); } @@ -63,6 +50,8 @@ BidderDeps facebookBidderDeps(BidderConfigurationProperties facebookConfiguratio @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor + @Component("facebookConfigurationProperties") + @ConfigurationProperties("adapters.facebook") private static class FacebookConfigurationProperties extends BidderConfigurationProperties { @NotNull diff --git a/src/main/resources/bidder-config/appnexus.yaml b/src/main/resources/bidder-config/appnexus.yaml index ba17301002a..8ab4dfca78c 100644 --- a/src/main/resources/bidder-config/appnexus.yaml +++ b/src/main/resources/bidder-config/appnexus.yaml @@ -21,3 +21,4 @@ adapters: cookie-family-name: adnxs type: redirect support-cors: false + platform-id: 5 diff --git a/src/test/java/org/prebid/server/bidder/appnexus/AppnexusBidderTest.java b/src/test/java/org/prebid/server/bidder/appnexus/AppnexusBidderTest.java index 653caf22f0a..4c7867b02fe 100644 --- a/src/test/java/org/prebid/server/bidder/appnexus/AppnexusBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/appnexus/AppnexusBidderTest.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.App; -import com.iab.openrtb.request.Audio; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.BidRequest.BidRequestBuilder; @@ -11,12 +10,11 @@ import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Imp.ImpBuilder; import com.iab.openrtb.request.Native; -import com.iab.openrtb.request.Regs; -import com.iab.openrtb.request.User; import com.iab.openrtb.request.Video; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; +import lombok.Value; import org.assertj.core.groups.Tuple; import org.junit.Before; import org.junit.Test; @@ -27,7 +25,6 @@ import org.prebid.server.bidder.appnexus.proto.AppnexusImpExt; import org.prebid.server.bidder.appnexus.proto.AppnexusImpExtAppnexus; import org.prebid.server.bidder.appnexus.proto.AppnexusKeyVal; -import org.prebid.server.bidder.appnexus.proto.AppnexusReqExt; import org.prebid.server.bidder.appnexus.proto.AppnexusReqExtAppnexus; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; @@ -39,12 +36,10 @@ import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtApp; import org.prebid.server.proto.openrtb.ext.request.ExtAppPrebid; -import org.prebid.server.proto.openrtb.ext.request.ExtRegs; import org.prebid.server.proto.openrtb.ext.request.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidPbs; import org.prebid.server.proto.openrtb.ext.request.ExtRequestTargeting; -import org.prebid.server.proto.openrtb.ext.request.ExtUser; import org.prebid.server.proto.openrtb.ext.request.appnexus.ExtImpAppnexus; import org.prebid.server.proto.openrtb.ext.request.appnexus.ExtImpAppnexus.ExtImpAppnexusBuilder; import org.prebid.server.proto.openrtb.ext.response.BidType; @@ -52,7 +47,6 @@ import java.io.IOException; import java.math.BigDecimal; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -61,7 +55,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; @@ -80,88 +73,11 @@ public class AppnexusBidderTest extends VertxTest { @Before public void setUp() { - appnexusBidder = new AppnexusBidder(ENDPOINT_URL, jacksonMapper); + appnexusBidder = new AppnexusBidder(ENDPOINT_URL, null, jacksonMapper); } @Test - public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestContainsNotSupportedAudioMediaType() { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(Collections.singletonList(Imp.builder().id("23").audio(Audio.builder().build()) - .build())) - .build(); - - // when - final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .flatExtracting(BidRequest::getImp) - .isEmpty(); - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badInput("Appnexus doesn't support audio Imps. Ignoring Imp ID=23")); - } - - @Test - public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList( - Imp.builder() - .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()))) - .build())) - .build(); - - // when - final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).hasSize(1); - assertThat(result.getErrors().get(0).getMessage()).startsWith("Cannot deserialize value"); - assertThat(result.getValue()).hasSize(1); - } - - @Test - public void makeHttpRequestsShouldReturnErrorIfExtImpAppnexusDoesNotContainPlacementIdAndMemberId() { - // given - final BidRequest bidRequest = givenBidRequest( - identity(), - impBuilder -> impBuilder.video(Video.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(null).member(null).invCode("invCode")); - - // when - final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1); - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badInput("No placement or member+invcode provided")); - } - - @Test - public void makeHttpRequestsShouldReturnErrorIfImpsContainDifferentMemberIds() { - // given - final Imp imp1 = givenImp(impBuilder -> - impBuilder.ext(givenExt(extBuilder -> extBuilder.placementId(12).member("member1"))) - .video(Video.builder().build())); - final Imp imp2 = givenImp(impBuilder -> - impBuilder.ext(givenExt(builder -> builder.placementId(12).member("member2"))) - .banner(Banner.builder().build())); - final BidRequest bidRequest = BidRequest.builder().imp(asList(imp1, imp2)).build(); - - // when - final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1); - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badInput("All request.imp[i].ext.appnexus.member params must match. " - + "Request contained: member2, member1")); - } - - @Test - public void makeHttpRequestsShouldSetImpDisplaymanagerverFromAppExtPrebid() { + public void makeHttpRequestsShouldSetImpDisplaymanagerverFromAppExtPrebidIfAbsent() { // given final BidRequest bidRequest = givenBidRequest( bidRequestBuilder -> bidRequestBuilder @@ -176,11 +92,11 @@ public void makeHttpRequestsShouldSetImpDisplaymanagerverFromAppExtPrebid() { // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) + assertThat(result.getValue()) .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) .flatExtracting(BidRequest::getImp) .extracting(Imp::getDisplaymanagerver) - .containsOnly("some source-any version"); + .containsExactly("some source-any version"); } @Test @@ -199,7 +115,7 @@ public void makeHttpRequestsShouldNotModifyImpDisplaymanagerverIfExtAppPrebidIsN // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) + assertThat(result.getValue()) .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) .flatExtracting(BidRequest::getImp) .extracting(Imp::getDisplaymanagerver) @@ -276,147 +192,200 @@ public void makeHttpRequestsShouldNotModifyImpDisplaymanagerverIfItExists() { } @Test - public void makeHttpRequestsShouldSetRequestExtAppnexusTrueWhenPrimaryAdserverIsOne() { + public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { // given - final ExtRequestPrebid requestPrebid = ExtRequestPrebid.builder() - .targeting(ExtRequestTargeting.builder() - .includebrandcategory(ExtIncludeBrandCategory.of(1, null, null, null)) - .build()) + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList( + Imp.builder() + .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()))) + .build())) .build(); + // when + final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getMessage()).startsWith("Cannot deserialize value"); + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); + }); + } + + @Test + public void makeHttpRequestsShouldReturnErrorIfExtImpAppnexusPlacementIdAndBothInvCodeAndMemberIdAreEmpty() { + // given final BidRequest bidRequest = givenBidRequest( - bidRequestBuilder -> bidRequestBuilder - .ext(ExtRequest.of(requestPrebid)), + identity(), + identity(), + extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(null).member(null).invCode(null)); + + // when + final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()) + .containsExactly(BidderError.badInput("No placement or member+invcode provided")); + } + + @Test + public void makeHttpRequestsShouldSetBannerIfRequestImpIsBanner() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), impBuilder -> impBuilder.banner(Banner.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20)); + extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).invCode("tagid")); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1) .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .extracting(BidRequest::getExt).isNotNull() - .extracting(ext -> mapper.convertValue(ext.getProperties(), AppnexusReqExt.class)) - .extracting(AppnexusReqExt::getAppnexus) - .containsOnly(AppnexusReqExtAppnexus.of(true, true, null)); + .flatExtracting(BidRequest::getImp).hasSize(1) + .extracting(Imp::getBanner).doesNotContainNull(); } @Test - public void makeHttpRequestsShouldUpdateRequestExtAppnexusTrueWhenPrimaryAdserverIsNotNull() { + public void makeHttpRequestsShouldSetBannerSizesFromExistingFirstFormatElement() { // given - final ExtRequestPrebid requestPrebid = ExtRequestPrebid.builder() - .targeting(ExtRequestTargeting.builder() - .includebrandcategory(ExtIncludeBrandCategory.of(null, null, null, null)) - .build()) - .build(); - final BidRequest bidRequest = givenBidRequest( - bidRequestBuilder -> bidRequestBuilder - .ext(ExtRequest.of(requestPrebid)), - impBuilder -> impBuilder.banner(Banner.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20)); + identity(), + impBuilder -> impBuilder.banner(Banner.builder() + .format(singletonList(Format.builder().w(100).h(200).build())) + .build()), + extImpAppnexusBuilder -> extImpAppnexusBuilder + .placementId(20) + .invCode("tagid") + .reserve(BigDecimal.TEN)); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1) .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .extracting(BidRequest::getExt).isNotNull() - .extracting(ext -> mapper.convertValue(ext.getProperties(), AppnexusReqExt.class)) - .extracting(AppnexusReqExt::getAppnexus) - .containsOnly(AppnexusReqExtAppnexus.of(true, true, null)); + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBanner) + .containsOnly(Banner.builder().w(100).h(200) + .format(singletonList(Format.builder().w(100).h(200).build())).build()); } @Test - public void makeHttpRequestsShouldNotUpdateRequestExtAppnexusWhenIncludeBrandCategoryIsMissing() { + public void makeHttpRequestsShouldSetBannerPosAbove() { // given - final ExtRequestPrebid requestPrebid = ExtRequestPrebid.builder() - .targeting(ExtRequestTargeting.builder().includebrandcategory(null).build()) - .build(); - final BidRequest bidRequest = givenBidRequest( - bidRequestBuilder -> bidRequestBuilder - .ext(jacksonMapper.fillExtension( - ExtRequest.of(requestPrebid), - AppnexusReqExt.of(AppnexusReqExtAppnexus.of(false, true, null)))), + identity(), impBuilder -> impBuilder.banner(Banner.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20)); + extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).position("above")); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1) .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .extracting(BidRequest::getExt).isNotNull() - .extracting(ext -> mapper.convertValue(ext.getProperties(), AppnexusReqExt.class)) - .extracting(AppnexusReqExt::getAppnexus) - .containsOnly(AppnexusReqExtAppnexus.of(false, true, null)); + .extracting(BidRequest::getImp).hasSize(1) + .extracting(imps -> imps.iterator().next().getBanner()).containsOnly(Banner.builder().pos(1).build()); } @Test - public void makeHttpRequestsShouldSetRequestUrlWithMemberIdParam() { + public void makeHttpRequestsShouldSetBannerPosBelow() { // given final BidRequest bidRequest = givenBidRequest( identity(), - identity(), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).invCode("tagid").member("member_param")); + impBuilder -> impBuilder.banner(Banner.builder().build()), + extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).position("below")); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getValue()) - .hasSize(1) - .element(0).returns("http://test/auction?member_id=member_param", HttpRequest::getUri); + assertThat(result.getValue()).hasSize(1) + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .extracting(BidRequest::getImp).hasSize(1) + .extracting(imps -> imps.iterator().next().getBanner()).containsOnly(Banner.builder().pos(3).build()); } @Test - public void makeHttpRequestsShouldSetRequestUrlWithoutMemberIdIfItMissedRequestBodyImps() { + public void makeHttpRequestsShouldSetAppnexusImpExtParamsIfPresent() { // given final BidRequest bidRequest = givenBidRequest( identity(), - identity(), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).invCode("tagid")); + impBuilder -> impBuilder.banner(Banner.builder().build()), + extImpAppnexusBuilder -> extImpAppnexusBuilder + .placementId(20) + .trafficSourceCode("tsc") + .keywords(List.of(AppnexusKeyVal.of("key1", List.of("val1")))) + .usePmtRule(true) + .privateSizes(mapper.createObjectNode())); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getValue()) - .hasSize(1) - .element(0).returns(ENDPOINT_URL, HttpRequest::getUri); + assertThat(result.getValue()).hasSize(1) + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .extracting(BidRequest::getImp) + .extracting(imps -> imps.get(0).getExt()) + .extracting(jsonNodes -> mapper.treeToValue(jsonNodes, AppnexusImpExt.class)) + .extracting(AppnexusImpExt::getAppnexus) + .extracting( + AppnexusImpExtAppnexus::getPlacementId, + AppnexusImpExtAppnexus::getTrafficSourceCode, + AppnexusImpExtAppnexus::getKeywords, + AppnexusImpExtAppnexus::getUsePmtRule, + AppnexusImpExtAppnexus::getPrivateSizes) + .containsOnly(Tuple.tuple(20, "tsc", "key1=val1", true, mapper.createObjectNode())); } @Test - public void makeHttpRequestsShouldReturnErrorIfExtImpAppnexusDoesNotContainPlacementIdAndInvCode() { + public void makeHttpRequestsShouldUpdateImpExtAppnexusWithKeywords() { // given + final List keywords = List.of( + AppnexusKeyVal.of("key1", null), + AppnexusKeyVal.of("key2", List.of("value1", "value2")), + AppnexusKeyVal.of(null, null)); + final BidRequest bidRequest = givenBidRequest( identity(), - impBuilder -> impBuilder.video(Video.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(null).member("member").invCode(null)); + identity(), + extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(1).keywords(keywords)); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getValue()).hasSize(1); - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badInput("No placement or member+invcode provided")); + assertThat(result.getErrors()).isEmpty(); + + final AppnexusImpExt expectedImpExt = AppnexusImpExt.of( + AppnexusImpExtAppnexus.builder() + .placementId(1) + .keywords("key1,key2=value1,key2=value2") + .build()); + + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .containsExactly(mapper.valueToTree(expectedImpExt)); } @Test - public void makeHttpRequestsShouldReturnErrorIfExtImpAppnexusPlacementIdAndBothInvCodeAndMemberIdAreEmpty() { + public void makeHttpRequestsShouldHonorLegacyParams() { // given final BidRequest bidRequest = givenBidRequest( identity(), - identity(), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(null).member(null).invCode(null)); + impBuilder -> impBuilder.banner(Banner.builder().build()), + extImpAppnexusBuilder -> extImpAppnexusBuilder + .placementId(null) + .legacyPlacementId(101) + .invCode(null) + .legacyInvCode("legacyInvCode1") + .trafficSourceCode(null) + .legacyTrafficSourceCode("legacyTrafficSourceCode1")); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); @@ -425,10 +394,11 @@ public void makeHttpRequestsShouldReturnErrorIfExtImpAppnexusPlacementIdAndBothI assertThat(result.getValue()).hasSize(1) .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) .flatExtracting(BidRequest::getImp) - .isEmpty(); - - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badInput("No placement or member+invcode provided")); + .extracting(Imp::getTagid, Imp::getExt) + .containsOnly(tuple( + "legacyInvCode1", + mapper.valueToTree(AppnexusImpExt.of( + AppnexusImpExtAppnexus.of(101, null, "legacyTrafficSourceCode1", null, null))))); } @Test @@ -521,157 +491,100 @@ public void makeHttpRequestsShouldSetReserveIfImpBidFloorIsNegative() { } @Test - public void makeHttpRequestsShouldSetNativeIfRequestImpIsNative() { + public void makeHttpRequestsShouldReturnErrorIfImpsContainDifferentMemberIds() { // given - final BidRequest bidRequest = givenBidRequest( - identity(), - impBuilder -> impBuilder.xNative(Native.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).invCode("tagid")); + final Imp imp1 = givenImp(impBuilder -> + impBuilder.ext(givenExt(extBuilder -> extBuilder.placementId(12).member("member1"))) + .video(Video.builder().build())); + final Imp imp2 = givenImp(impBuilder -> + impBuilder.ext(givenExt(builder -> builder.placementId(12).member("member2"))) + .banner(Banner.builder().build())); + final BidRequest bidRequest = BidRequest.builder().imp(List.of(imp1, imp2)).build(); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .flatExtracting(BidRequest::getImp).hasSize(1) - .extracting(Imp::getXNative).doesNotContainNull(); + assertThat(result.getValue()).hasSize(1); + assertThat(result.getErrors()) + .containsExactly(BidderError.badInput("All request.imp[i].ext.appnexus.member params must match. " + + "Request contained: member2, member1")); } @Test - public void makeHttpRequestsShouldSetVideoTypeIfRequestImpIsVideo() { + public void makeHttpRequestsShouldUpdateRequestExtAppnexus() { // given - final BidRequest bidRequest = givenBidRequest( - identity(), - impBuilder -> impBuilder.video(Video.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).invCode("tagid")); - - // when - final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .flatExtracting(BidRequest::getImp).hasSize(1) - .extracting(Imp::getVideo).doesNotContainNull(); - } + final ExtRequestPrebid requestPrebid = ExtRequestPrebid.builder() + .targeting(ExtRequestTargeting.builder() + .includebrandcategory(ExtIncludeBrandCategory.of(null, null, null, null)) + .build()) + .build(); - @Test - public void makeHttpRequestsShouldSetBannerIfRequestImpIsBanner() { - // given final BidRequest bidRequest = givenBidRequest( - identity(), + bidRequestBuilder -> bidRequestBuilder + .ext(ExtRequest.of(requestPrebid)), impBuilder -> impBuilder.banner(Banner.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).invCode("tagid")); + extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20)); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); // then + assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1) .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .flatExtracting(BidRequest::getImp).hasSize(1) - .extracting(Imp::getBanner).doesNotContainNull(); + .extracting(BidRequest::getExt) + .extracting(extRequest -> extRequest.getProperty("appnexus")) + .containsExactly(mapper.valueToTree( + AppnexusReqExtAppnexus.builder() + .includeBrandCategory(true) + .brandCategoryUniqueness(true) + .isAmp(0) + .headerBiddingSource(5) + .build())); } @Test - public void makeHttpRequestsShouldSetBannerSizesFromExistingFirstFormatElement() { + public void makeHttpRequestsShouldSetRequestUrlWithMemberIdParam() { // given final BidRequest bidRequest = givenBidRequest( identity(), - impBuilder -> impBuilder.banner(Banner.builder() - .format(singletonList(Format.builder().w(100).h(200).build())) - .build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder - .placementId(20) - .invCode("tagid") - .reserve(BigDecimal.TEN)); - - // when - final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getBanner) - .containsOnly(Banner.builder().w(100).h(200) - .format(singletonList(Format.builder().w(100).h(200).build())).build()); - } - - @Test - public void makeHttpRequestsShouldSetBannerPosAbove() { - // given - final BidRequest bidRequest = givenBidRequest( identity(), - impBuilder -> impBuilder.banner(Banner.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).position("above")); + extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).invCode("tagid").member("member_param")); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .extracting(BidRequest::getImp).hasSize(1) - .extracting(imps -> imps.iterator().next().getBanner()).containsOnly(Banner.builder().pos(1).build()); + assertThat(result.getValue()) + .hasSize(1) + .element(0).returns("http://test/auction?member_id=member_param", HttpRequest::getUri); } @Test - public void makeHttpRequestsShouldSetBannerPosBelow() { + public void makeHttpRequestsShouldSetRequestUrlWithoutMemberIdIfItMissedRequestBodyImps() { // given final BidRequest bidRequest = givenBidRequest( identity(), - impBuilder -> impBuilder.banner(Banner.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).position("below")); - - // when - final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .extracting(BidRequest::getImp).hasSize(1) - .extracting(imps -> imps.iterator().next().getBanner()).containsOnly(Banner.builder().pos(3).build()); - } - - @Test - public void makeHttpRequestsShouldSetPlacementIdAndTrafficSourceCodeIfPresent() { - // given - final BidRequest bidRequest = givenBidRequest( identity(), - impBuilder -> impBuilder.banner(Banner.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).trafficSourceCode("tsc").keywords(asList( - AppnexusKeyVal.of("key1", asList("abc", "def")), - AppnexusKeyVal.of("key2", asList("123", "456"))))); + extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).invCode("tagid")); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); // then - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .extracting(BidRequest::getImp) - .extracting(imps -> imps.get(0).getExt()) - .extracting(jsonNodes -> mapper.treeToValue(jsonNodes, AppnexusImpExt.class)) - .extracting(AppnexusImpExt::getAppnexus) - .extracting( - AppnexusImpExtAppnexus::getPlacementId, - AppnexusImpExtAppnexus::getTrafficSourceCode, - AppnexusImpExtAppnexus::getKeywords) - .containsOnly(Tuple.tuple(20, "tsc", "key1=abc,key1=def,key2=123,key2=456")); + assertThat(result.getValue()) + .hasSize(1) + .element(0).returns(ENDPOINT_URL, HttpRequest::getUri); } @Test - public void makeHttpRequestShouldReturnHttpRequestWithExtUserConsentField() { + public void makeHttpRequestsShouldSetNativeIfRequestImpIsNative() { // given final BidRequest bidRequest = givenBidRequest( - bidRequestBuilder -> bidRequestBuilder - .user(User.builder() - .ext(ExtUser.builder().consent("consent").build()) - .build()), - impBuilder -> impBuilder.banner(Banner.builder().build()), - identity()); + identity(), + impBuilder -> impBuilder.xNative(Native.builder().build()), + extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).invCode("tagid")); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); @@ -679,19 +592,17 @@ public void makeHttpRequestShouldReturnHttpRequestWithExtUserConsentField() { // then assertThat(result.getValue()).hasSize(1) .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .extracting(BidRequest::getUser) - .extracting(User::getExt) - .containsOnly(ExtUser.builder().consent("consent").build()); + .flatExtracting(BidRequest::getImp).hasSize(1) + .extracting(Imp::getXNative).doesNotContainNull(); } @Test - public void makeHttpRequestShouldReturnHttpRequestWithExtRegsGdprField() { + public void makeHttpRequestsShouldSetVideoTypeIfRequestImpIsVideo() { // given final BidRequest bidRequest = givenBidRequest( - bidRequestBuilder -> bidRequestBuilder - .regs(Regs.of(0, ExtRegs.of(1, null))), - impBuilder -> impBuilder.banner(Banner.builder().build()), - identity()); + identity(), + impBuilder -> impBuilder.video(Video.builder().build()), + extImpAppnexusBuilder -> extImpAppnexusBuilder.placementId(20).invCode("tagid")); // when final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); @@ -699,8 +610,8 @@ public void makeHttpRequestShouldReturnHttpRequestWithExtRegsGdprField() { // then assertThat(result.getValue()).hasSize(1) .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .extracting(BidRequest::getRegs) - .containsOnly(Regs.of(0, ExtRegs.of(1, null))); + .flatExtracting(BidRequest::getImp).hasSize(1) + .extracting(Imp::getVideo).doesNotContainNull(); } @Test @@ -725,34 +636,6 @@ public void makeHttpRequestShouldReturnSplitedHttpRequestWhenImpMoreThanMaxImpPe .containsOnly(10, 10, 10, 6); } - @Test - public void makeHttpRequestsShouldHonorLegacyParams() { - // given - final BidRequest bidRequest = givenBidRequest( - identity(), - impBuilder -> impBuilder.banner(Banner.builder().build()), - extImpAppnexusBuilder -> extImpAppnexusBuilder - .placementId(null) - .legacyPlacementId(101) - .invCode(null) - .legacyInvCode("legacyInvCode1") - .trafficSourceCode(null) - .legacyTrafficSourceCode("legacyTrafficSourceCode1")); - - // when - final Result>> result = appnexusBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getTagid, Imp::getExt) - .containsOnly(tuple( - "legacyInvCode1", - mapper.valueToTree(AppnexusImpExt.of( - AppnexusImpExtAppnexus.of(101, null, "legacyTrafficSourceCode1", null, null))))); - } - @Test public void makeHttpRequestShouldReturnSingleRequestWhenOnePod() { // given @@ -1246,4 +1129,10 @@ private static String givenBidResponse( return givenBidResponse(identity(), bidExtCustomizer); } + + @Value(staticConstructor = "of") + private static class AppnexusReqExt { + + AppnexusReqExtAppnexus appnexus; + } } diff --git a/src/test/java/org/prebid/server/it/ApplicationTest.java b/src/test/java/org/prebid/server/it/ApplicationTest.java index d4cbdd91c3b..ec62bf14827 100644 --- a/src/test/java/org/prebid/server/it/ApplicationTest.java +++ b/src/test/java/org/prebid/server/it/ApplicationTest.java @@ -115,8 +115,7 @@ public void openrtb2AuctionShouldRespondWithBidsFromRubiconAndAppnexus() throws .willReturn(aResponse() .withTransformers("cache-response-transformer") .withTransformerParameter("matcherName", - "openrtb2/rubicon_appnexus/test-cache-matcher-rubicon-appnexus.json") - )); + "openrtb2/rubicon_appnexus/test-cache-matcher-rubicon-appnexus.json"))); // when final Response response = given(SPEC) diff --git a/src/test/resources/org/prebid/server/it/amp/test-appnexus-bid-request.json b/src/test/resources/org/prebid/server/it/amp/test-appnexus-bid-request.json index ff366ec78bb..bcee58792d6 100644 --- a/src/test/resources/org/prebid/server/it/amp/test-appnexus-bid-request.json +++ b/src/test/resources/org/prebid/server/it/amp/test-appnexus-bid-request.json @@ -59,6 +59,10 @@ } }, "ext": { + "appnexus" : { + "is_amp" : 1, + "hb_source" : 5 + }, "prebid": { "currency": { "rates": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-appnexus-bid-request-1.json b/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-appnexus-bid-request-1.json index 76a59a84727..44709910dfd 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-appnexus-bid-request-1.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-appnexus-bid-request-1.json @@ -38,6 +38,17 @@ "placement_id": 9880618 } } + }, + { + "id" : "impId32", + "audio" : { + "mimes" : [ "video/mp4" ] + }, + "ext" : { + "appnexus" : { + "placement_id" : 10433394 + } + } } ], "site": { @@ -139,6 +150,10 @@ } }, "ext": { + "appnexus": { + "is_amp": 0, + "hb_source": 5 + }, "prebid": { "debug": 1, "aliases": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-appnexus-bid-request-2.json b/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-appnexus-bid-request-2.json index 3ba6d62910e..cc8f43f86c0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-appnexus-bid-request-2.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-appnexus-bid-request-2.json @@ -127,6 +127,10 @@ } }, "ext": { + "appnexus": { + "is_amp": 0, + "hb_source": 5 + }, "prebid": { "debug": 1, "aliases": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-auction-rubicon-appnexus-response.json b/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-auction-rubicon-appnexus-response.json index ff51738e1ea..993b134d89f 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-auction-rubicon-appnexus-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus/test-auction-rubicon-appnexus-response.json @@ -1,6 +1,137 @@ { "id": "tid", "seatbid": [ + { + "bid": [ + { + "id": "f227a07f-1579-4465-bc5e-5c5b02a0c180", + "impid": "impStoredAuctionResponse", + "price": 0.8, + "crid": "crid1", + "ext": { + "origbidcpm": 0.8, + "origbidcur": "USD", + "prebid": { + "type": "banner", + "targeting": { + "hb_pb_rubicon": "0.80", + "hb_cache_id_rubicon": "c75130ed-bcdd-4821-ad91-90cf835615c5", + "hb_bidder_rubicon": "rubicon", + "hb_cache_path_rubicon": "{{ cache.path }}", + "hb_cache_host_rubicon": "{{ cache.host }}" + }, + "cache": { + "bids": { + "url": "{{ cache.resource_url }}c75130ed-bcdd-4821-ad91-90cf835615c5", + "cacheId": "c75130ed-bcdd-4821-ad91-90cf835615c5" + } + }, + "events": { + "win": "{{ event.url }}t=win&b=f227a07f-1579-4465-bc5e-5c5b02a0c180&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs", + "imp": "{{ event.url }}t=imp&b=f227a07f-1579-4465-bc5e-5c5b02a0c180&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs" + } + } + } + }, + { + "id": "880290288", + "impid": "impId1", + "price": 8.43, + "adm": "", + "crid": "crid1", + "w": 300, + "h": 250, + "exp": 120, + "ext": { + "rp": { + "targeting": [ + { + "key": "rpfl_1001", + "values": [ + "2_tier0100" + ] + } + ] + }, + "origbidcpm": 8.43, + "prebid": { + "type": "video", + "targeting": { + "hb_size_rubicon": "300x250", + "hb_cache_id": "683fe79f-6df7-4971-ac70-820e0486992d", + "hb_cache_path_rubicon": "{{ cache.path }}", + "hb_cache_host_rubicon": "{{ cache.host }}", + "hb_pb": "8.40", + "hb_pb_rubicon": "8.40", + "hb_cache_id_rubicon": "683fe79f-6df7-4971-ac70-820e0486992d", + "hb_cache_path": "{{ cache.path }}", + "hb_uuid": "b2528f73-96ab-42ab-8f15-fbe6ed779a26", + "hb_size": "300x250", + "hb_uuid_rubicon": "b2528f73-96ab-42ab-8f15-fbe6ed779a26", + "hb_bidder": "rubicon", + "hb_bidder_rubicon": "rubicon", + "hb_cache_host": "{{ cache.host }}" + }, + "cache": { + "bids": { + "url": "{{ cache.resource_url }}683fe79f-6df7-4971-ac70-820e0486992d", + "cacheId": "683fe79f-6df7-4971-ac70-820e0486992d" + }, + "vastXml": { + "url": "{{ cache.resource_url }}b2528f73-96ab-42ab-8f15-fbe6ed779a26", + "cacheId": "b2528f73-96ab-42ab-8f15-fbe6ed779a26" + } + }, + "events": { + "win": "{{ event.url }}t=win&b=880290288&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs", + "imp": "{{ event.url }}t=imp&b=880290288&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs" + } + } + } + }, + { + "id": "466223845", + "impid": "impId2", + "price": 4.26, + "adm": "adm2", + "crid": "crid2", + "w": 300, + "h": 600, + "ext": { + "origbidcpm": 4.26, + "prebid": { + "type": "banner", + "targeting": { + "hb_size_rubicon": "300x600", + "hb_cache_id": "4fe59ef5-6fb4-48c5-88b6-9870257fc49e", + "hb_cache_path_rubicon": "{{ cache.path }}", + "hb_cache_host_rubicon": "{{ cache.host }}", + "hb_pb": "4.20", + "hb_pb_rubicon": "4.20", + "hb_cache_id_rubicon": "4fe59ef5-6fb4-48c5-88b6-9870257fc49e", + "hb_cache_path": "{{ cache.path }}", + "hb_size": "300x600", + "hb_bidder": "rubicon", + "hb_bidder_rubicon": "rubicon", + "hb_cache_host": "{{ cache.host }}" + }, + "cache": { + "bids": { + "url": "{{ cache.resource_url }}4fe59ef5-6fb4-48c5-88b6-9870257fc49e", + "cacheId": "4fe59ef5-6fb4-48c5-88b6-9870257fc49e" + } + }, + "events": { + "win": "{{ event.url }}t=win&b=466223845&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs", + "imp": "{{ event.url }}t=imp&b=466223845&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs" + } + } + } + } + ], + "seat": "rubicon", + "group": 0 + }, { "bid": [ { @@ -9,6 +140,8 @@ "price": 0.9, "crid": "crid1", "ext": { + "origbidcpm": 0.9, + "origbidcur": "USD", "prebid": { "type": "banner", "targeting": { @@ -33,9 +166,7 @@ "win": "{{ event.url }}t=win&b=a121a07f-1579-4465-bc5e-5c5b02a0c421&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs", "imp": "{{ event.url }}t=imp&b=a121a07f-1579-4465-bc5e-5c5b02a0c421&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs" } - }, - "origbidcpm": 0.9, - "origbidcur": "USD" + } } }, { @@ -53,6 +184,15 @@ "w": 300, "h": 250, "ext": { + "appnexus": { + "brand_id": 1, + "auction_id": 8189378542222915032, + "bidder_id": 2, + "bid_ad_type": 0, + "ranking_price": 0.0 + }, + "origbidcpm": 5.5, + "origbidcur": "USD", "prebid": { "type": "banner", "targeting": { @@ -79,16 +219,7 @@ "win": "{{ event.url }}t=win&b=7706636740145184841&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs", "imp": "{{ event.url }}t=imp&b=7706636740145184841&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs" } - }, - "appnexus": { - "brand_id": 1, - "auction_id": 8189378542222915032, - "bidder_id": 2, - "bid_ad_type": 0, - "ranking_price": 0.0 - }, - "origbidcpm": 5.5, - "origbidcur": "USD" + } } }, { @@ -107,6 +238,15 @@ "IAB20-3" ], "ext": { + "appnexus": { + "brand_id": 1, + "brand_category_id": 1, + "auction_id": 5607483846416358664, + "bidder_id": 2, + "bid_ad_type": 3 + }, + "origbidcpm": 1.0, + "origbidcur": "USD", "prebid": { "type": "native", "targeting": { @@ -131,153 +271,13 @@ "win": "{{ event.url }}t=win&b=928185755156387460&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs", "imp": "{{ event.url }}t=imp&b=928185755156387460&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs" } - }, - "appnexus": { - "brand_id": 1, - "brand_category_id": 1, - "auction_id": 5607483846416358664, - "bidder_id": 2, - "bid_ad_type": 3 - }, - "origbidcpm": 1.0, - "origbidcur": "USD" + } } } ], "seat": "appnexus", "group": 0 }, - { - "bid": [ - { - "id": "f227a07f-1579-4465-bc5e-5c5b02a0c180", - "impid": "impStoredAuctionResponse", - "price": 0.8, - "crid": "crid1", - "ext": { - "prebid": { - "type": "banner", - "targeting": { - "hb_pb_rubicon": "0.80", - "hb_cache_id_rubicon": "c75130ed-bcdd-4821-ad91-90cf835615c5", - "hb_bidder_rubicon": "rubicon", - "hb_cache_path_rubicon": "{{ cache.path }}", - "hb_cache_host_rubicon": "{{ cache.host }}" - }, - "cache": { - "bids": { - "url": "{{ cache.resource_url }}c75130ed-bcdd-4821-ad91-90cf835615c5", - "cacheId": "c75130ed-bcdd-4821-ad91-90cf835615c5" - } - }, - "events": { - "win": "{{ event.url }}t=win&b=f227a07f-1579-4465-bc5e-5c5b02a0c180&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs", - "imp": "{{ event.url }}t=imp&b=f227a07f-1579-4465-bc5e-5c5b02a0c180&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs" - } - }, - "origbidcpm": 0.8, - "origbidcur": "USD" - } - }, - { - "id": "880290288", - "impid": "impId1", - "price": 8.43, - "adm": "", - "crid": "crid1", - "w": 300, - "h": 250, - "exp": 120, - "ext": { - "prebid": { - "type": "video", - "targeting": { - "hb_size_rubicon": "300x250", - "hb_cache_id": "683fe79f-6df7-4971-ac70-820e0486992d", - "hb_cache_path_rubicon": "{{ cache.path }}", - "hb_cache_host_rubicon": "{{ cache.host }}", - "hb_pb": "8.40", - "hb_pb_rubicon": "8.40", - "hb_cache_id_rubicon": "683fe79f-6df7-4971-ac70-820e0486992d", - "hb_cache_path": "{{ cache.path }}", - "hb_uuid": "b2528f73-96ab-42ab-8f15-fbe6ed779a26", - "hb_size": "300x250", - "hb_uuid_rubicon": "b2528f73-96ab-42ab-8f15-fbe6ed779a26", - "hb_bidder": "rubicon", - "hb_bidder_rubicon": "rubicon", - "hb_cache_host": "{{ cache.host }}" - }, - "cache": { - "bids": { - "url": "{{ cache.resource_url }}683fe79f-6df7-4971-ac70-820e0486992d", - "cacheId": "683fe79f-6df7-4971-ac70-820e0486992d" - }, - "vastXml": { - "url": "{{ cache.resource_url }}b2528f73-96ab-42ab-8f15-fbe6ed779a26", - "cacheId": "b2528f73-96ab-42ab-8f15-fbe6ed779a26" - } - }, - "events": { - "win": "{{ event.url }}t=win&b=880290288&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs", - "imp": "{{ event.url }}t=imp&b=880290288&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs" - } - }, - "rp": { - "targeting": [ - { - "key": "rpfl_1001", - "values": [ - "2_tier0100" - ] - } - ] - }, - "origbidcpm": 8.43 - } - }, - { - "id": "466223845", - "impid": "impId2", - "price": 4.26, - "adm": "adm2", - "crid": "crid2", - "w": 300, - "h": 600, - "ext": { - "prebid": { - "type": "banner", - "targeting": { - "hb_size_rubicon": "300x600", - "hb_cache_id": "4fe59ef5-6fb4-48c5-88b6-9870257fc49e", - "hb_cache_path_rubicon": "{{ cache.path }}", - "hb_cache_host_rubicon": "{{ cache.host }}", - "hb_pb": "4.20", - "hb_pb_rubicon": "4.20", - "hb_cache_id_rubicon": "4fe59ef5-6fb4-48c5-88b6-9870257fc49e", - "hb_cache_path": "{{ cache.path }}", - "hb_size": "300x600", - "hb_bidder": "rubicon", - "hb_bidder_rubicon": "rubicon", - "hb_cache_host": "{{ cache.host }}" - }, - "cache": { - "bids": { - "url": "{{ cache.resource_url }}4fe59ef5-6fb4-48c5-88b6-9870257fc49e", - "cacheId": "4fe59ef5-6fb4-48c5-88b6-9870257fc49e" - } - }, - "events": { - "win": "{{ event.url }}t=win&b=466223845&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs", - "imp": "{{ event.url }}t=imp&b=466223845&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs" - } - }, - "origbidcpm": 4.26 - } - } - ], - "seat": "rubicon", - "group": 0 - }, { "bid": [ { @@ -296,6 +296,16 @@ "w": 300, "h": 250, "ext": { + "appnexus": { + "brand_id": 350, + "brand_category_id": 350, + "auction_id": 8189378542222915031, + "bidder_id": 2, + "bid_ad_type": 0, + "ranking_price": 0.0 + }, + "origbidcpm": 5.0, + "origbidcur": "USD", "prebid": { "type": "banner", "targeting": { @@ -316,17 +326,7 @@ "win": "{{ event.url }}t=win&b=7706636740145184840&a=5001&aid=tid&ts=1000&bidder=appnexusAlias&f=i&int=dmbjs", "imp": "{{ event.url }}t=imp&b=7706636740145184840&a=5001&aid=tid&ts=1000&bidder=appnexusAlias&f=i&int=dmbjs" } - }, - "appnexus": { - "brand_id": 350, - "brand_category_id": 350, - "auction_id": 8189378542222915031, - "bidder_id": 2, - "bid_ad_type": 0, - "ranking_price": 0.0 - }, - "origbidcpm": 5.0, - "origbidcur": "USD" + } } } ], @@ -341,6 +341,8 @@ "cache": [ { "uri": "{{ cache.endpoint }}", + "requestbody": "{\"puts\":[{\"type\":\"json\",\"value\":{\"id\":\"7706636740145184840\",\"impid\":\"impId3\",\"price\":5,\"adm\":\"some-test-ad\",\"adid\":\"29681110\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=29681110\",\"cid\":\"958\",\"crid\":\"29681110\",\"cat\":[],\"w\":300,\"h\":250,\"ext\":{\"appnexus\":{\"brand_id\":350,\"brand_category_id\":350,\"auction_id\":8189378542222915031,\"bidder_id\":2,\"bid_ad_type\":0,\"ranking_price\":0.0},\"origbidcpm\":5,\"origbidcur\":\"USD\",\"prebid\":{\"type\":\"banner\",\"events\":{\"win\":\"{{ event.url }}t=win&b=7706636740145184840&a=5001&aid=tid&ts=1000&bidder=appnexusAlias&f=i&int=dmbjs\",\"imp\":\"{{ event.url }}t=imp&b=7706636740145184840&a=5001&aid=tid&ts=1000&bidder=appnexusAlias&f=i&int=dmbjs\"}}},\"wurl\":\"{{ event.url }}t=win&b=7706636740145184840&a=5001&aid=tid&ts=1000&bidder=appnexusAlias&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"f227a07f-1579-4465-bc5e-5c5b02a0c180\",\"impid\":\"impStoredAuctionResponse\",\"price\":0.8,\"crid\":\"crid1\",\"ext\":{\"origbidcpm\":0.8,\"origbidcur\":\"USD\",\"prebid\":{\"type\":\"banner\",\"events\":{\"win\":\"{{ event.url }}t=win&b=f227a07f-1579-4465-bc5e-5c5b02a0c180&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\",\"imp\":\"{{ event.url }}t=imp&b=f227a07f-1579-4465-bc5e-5c5b02a0c180&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"}}},\"wurl\":\"{{ event.url }}t=win&b=f227a07f-1579-4465-bc5e-5c5b02a0c180&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"928185755156387460\",\"impid\":\"impId131\",\"price\":1,\"adm\":\"{\\\"assets\\\":[{\\\"id\\\":0,\\\"title\\\":{\\\"text\\\":\\\"This is an example Prebid Native creative\\\"}},{\\\"id\\\":1,\\\"img\\\":{\\\"url\\\":\\\"http://vcdn.adnxs.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\\\",\\\"w\\\":3000,\\\"h\\\":2250,\\\"ext\\\":{\\\"appnexus\\\":{\\\"prevent_crop\\\":0}}}},{\\\"id\\\":2,\\\"data\\\":{\\\"value\\\":\\\"Prebid.org\\\"}},{\\\"id\\\":3,\\\"data\\\":{\\\"value\\\":\\\"ThisisaPrebidNativeCreative.Therearemanylikeit,butthisoneismine.\\\"}}],\\\"link\\\":{\\\"url\\\":\\\"http://nym1-ib.adnxs.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\\\"},\\\"imptrackers\\\":[\\\"http://nym1-ib.adnxs.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\\\"],\\\"jstracker\\\":\\\"\\\"}\",\"adid\":\"69595837\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=69595837\",\"cid\":\"958\",\"crid\":\"69595837\",\"cat\":[\"IAB20-3\"],\"ext\":{\"appnexus\":{\"brand_id\":1,\"brand_category_id\":1,\"auction_id\":5607483846416358664,\"bidder_id\":2,\"bid_ad_type\":3},\"origbidcpm\":1,\"origbidcur\":\"USD\",\"prebid\":{\"type\":\"native\",\"events\":{\"win\":\"{{ event.url }}t=win&b=928185755156387460&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\",\"imp\":\"{{ event.url }}t=imp&b=928185755156387460&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"}}},\"wurl\":\"{{ event.url }}t=win&b=928185755156387460&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"a121a07f-1579-4465-bc5e-5c5b02a0c421\",\"impid\":\"impStoredAuctionResponse\",\"price\":0.9,\"crid\":\"crid1\",\"ext\":{\"origbidcpm\":0.9,\"origbidcur\":\"USD\",\"prebid\":{\"type\":\"banner\",\"events\":{\"win\":\"{{ event.url }}t=win&b=a121a07f-1579-4465-bc5e-5c5b02a0c421&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\",\"imp\":\"{{ event.url }}t=imp&b=a121a07f-1579-4465-bc5e-5c5b02a0c421&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"}}},\"wurl\":\"{{ event.url }}t=win&b=a121a07f-1579-4465-bc5e-5c5b02a0c421&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"880290288\",\"impid\":\"impId1\",\"price\":8.43,\"adm\":\"\",\"crid\":\"crid1\",\"w\":300,\"h\":250,\"ext\":{\"rp\":{\"targeting\":[{\"key\":\"rpfl_1001\",\"values\":[\"2_tier0100\"]}]},\"origbidcpm\":8.43,\"prebid\":{\"type\":\"video\",\"events\":{\"win\":\"{{ event.url }}t=win&b=880290288&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\",\"imp\":\"{{ event.url }}t=imp&b=880290288&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"}}},\"wurl\":\"{{ event.url }}t=win&b=880290288&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"7706636740145184841\",\"impid\":\"impId3\",\"price\":5.5,\"adm\":\"some-test-ad\",\"adid\":\"29681110\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=29681110\",\"cid\":\"958\",\"crid\":\"29681110\",\"w\":300,\"h\":250,\"ext\":{\"appnexus\":{\"brand_id\":1,\"auction_id\":8189378542222915032,\"bidder_id\":2,\"bid_ad_type\":0,\"ranking_price\":0.0},\"origbidcpm\":5.5,\"origbidcur\":\"USD\",\"prebid\":{\"type\":\"banner\",\"events\":{\"win\":\"{{ event.url }}t=win&b=7706636740145184841&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\",\"imp\":\"{{ event.url }}t=imp&b=7706636740145184841&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"}}},\"wurl\":\"{{ event.url }}t=win&b=7706636740145184841&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"466223845\",\"impid\":\"impId2\",\"price\":4.26,\"adm\":\"adm2\",\"crid\":\"crid2\",\"w\":300,\"h\":600,\"ext\":{\"origbidcpm\":4.26,\"prebid\":{\"type\":\"banner\",\"events\":{\"win\":\"{{ event.url }}t=win&b=466223845&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\",\"imp\":\"{{ event.url }}t=imp&b=466223845&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"}}},\"wurl\":\"{{ event.url }}t=win&b=466223845&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"xml\",\"value\":\"\",\"ttlseconds\":120,\"aid\":\"tid\"}]}", + "responsebody": "{\"responses\":[{\"uuid\":\"91912e5b-dfa8-42bc-9c7e-df6ce0449c19\"},{\"uuid\":\"c75130ed-bcdd-4821-ad91-90cf835615c5\"},{\"uuid\":\"6cf69b42-96f5-4ba1-a984-a9b4d8ff21cf\"},{\"uuid\":\"765e116a-5773-49d5-a648-0b97a9907a4e\"},{\"uuid\":\"683fe79f-6df7-4971-ac70-820e0486992d\"},{\"uuid\":\"117431c9-807a-41e1-82a7-dcd8f8875493\"},{\"uuid\":\"4fe59ef5-6fb4-48c5-88b6-9870257fc49e\"},{\"uuid\":\"b2528f73-96ab-42ab-8f15-fbe6ed779a26\"}]}", "requestheaders": { "Accept": [ "application/json" @@ -349,20 +351,23 @@ "application/json;charset=utf-8" ] }, - "requestbody": "{\"puts\":[{\"type\":\"json\",\"value\":{\"id\":\"466223845\",\"impid\":\"impId2\",\"price\":4.26,\"adm\":\"adm2\",\"crid\":\"crid2\",\"w\":300,\"h\":600,\"ext\":{\"origbidcpm\":4.26,\"prebid\":{\"type\":\"banner\",\"events\":{\"win\":\"http://localhost:8080/event?t=win&b=466223845&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\",\"imp\":\"http://localhost:8080/event?t=imp&b=466223845&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"}}},\"wurl\":\"http://localhost:8080/event?t=win&b=466223845&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"7706636740145184841\",\"impid\":\"impId3\",\"price\":5.5,\"adm\":\"some-test-ad\",\"adid\":\"29681110\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=29681110\",\"cid\":\"958\",\"crid\":\"29681110\",\"w\":300,\"h\":250,\"ext\":{\"appnexus\":{\"brand_id\":1,\"auction_id\":8189378542222915032,\"bidder_id\":2,\"bid_ad_type\":0,\"ranking_price\":0.0},\"origbidcpm\":5.5,\"origbidcur\":\"USD\",\"prebid\":{\"type\":\"banner\",\"events\":{\"win\":\"http://localhost:8080/event?t=win&b=7706636740145184841&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\",\"imp\":\"http://localhost:8080/event?t=imp&b=7706636740145184841&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"}}},\"wurl\":\"http://localhost:8080/event?t=win&b=7706636740145184841&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"7706636740145184840\",\"impid\":\"impId3\",\"price\":5,\"adm\":\"some-test-ad\",\"adid\":\"29681110\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=29681110\",\"cid\":\"958\",\"crid\":\"29681110\",\"cat\":[],\"w\":300,\"h\":250,\"ext\":{\"appnexus\":{\"brand_id\":350,\"brand_category_id\":350,\"auction_id\":8189378542222915031,\"bidder_id\":2,\"bid_ad_type\":0,\"ranking_price\":0.0},\"origbidcpm\":5,\"origbidcur\":\"USD\",\"prebid\":{\"type\":\"banner\",\"events\":{\"win\":\"http://localhost:8080/event?t=win&b=7706636740145184840&a=5001&aid=tid&ts=1000&bidder=appnexusAlias&f=i&int=dmbjs\",\"imp\":\"http://localhost:8080/event?t=imp&b=7706636740145184840&a=5001&aid=tid&ts=1000&bidder=appnexusAlias&f=i&int=dmbjs\"}}},\"wurl\":\"http://localhost:8080/event?t=win&b=7706636740145184840&a=5001&aid=tid&ts=1000&bidder=appnexusAlias&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"a121a07f-1579-4465-bc5e-5c5b02a0c421\",\"impid\":\"impStoredAuctionResponse\",\"price\":0.9,\"crid\":\"crid1\",\"ext\":{\"origbidcpm\":0.9,\"origbidcur\":\"USD\",\"prebid\":{\"type\":\"banner\",\"events\":{\"win\":\"http://localhost:8080/event?t=win&b=a121a07f-1579-4465-bc5e-5c5b02a0c421&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\",\"imp\":\"http://localhost:8080/event?t=imp&b=a121a07f-1579-4465-bc5e-5c5b02a0c421&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"}}},\"wurl\":\"http://localhost:8080/event?t=win&b=a121a07f-1579-4465-bc5e-5c5b02a0c421&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"928185755156387460\",\"impid\":\"impId131\",\"price\":1,\"adm\":\"{\\\"assets\\\":[{\\\"id\\\":0,\\\"title\\\":{\\\"text\\\":\\\"This is an example Prebid Native creative\\\"}},{\\\"id\\\":1,\\\"img\\\":{\\\"url\\\":\\\"http://vcdn.adnxs.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\\\",\\\"w\\\":3000,\\\"h\\\":2250,\\\"ext\\\":{\\\"appnexus\\\":{\\\"prevent_crop\\\":0}}}},{\\\"id\\\":2,\\\"data\\\":{\\\"value\\\":\\\"Prebid.org\\\"}},{\\\"id\\\":3,\\\"data\\\":{\\\"value\\\":\\\"ThisisaPrebidNativeCreative.Therearemanylikeit,butthisoneismine.\\\"}}],\\\"link\\\":{\\\"url\\\":\\\"http://nym1-ib.adnxs.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\\\"},\\\"imptrackers\\\":[\\\"http://nym1-ib.adnxs.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\\\"],\\\"jstracker\\\":\\\"\\\"}\",\"adid\":\"69595837\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=69595837\",\"cid\":\"958\",\"crid\":\"69595837\",\"cat\":[\"IAB20-3\"],\"ext\":{\"appnexus\":{\"brand_id\":1,\"brand_category_id\":1,\"auction_id\":5607483846416358664,\"bidder_id\":2,\"bid_ad_type\":3},\"origbidcpm\":1,\"origbidcur\":\"USD\",\"prebid\":{\"type\":\"native\",\"events\":{\"win\":\"http://localhost:8080/event?t=win&b=928185755156387460&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\",\"imp\":\"http://localhost:8080/event?t=imp&b=928185755156387460&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"}}},\"wurl\":\"http://localhost:8080/event?t=win&b=928185755156387460&a=5001&aid=tid&ts=1000&bidder=appnexus&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"f227a07f-1579-4465-bc5e-5c5b02a0c180\",\"impid\":\"impStoredAuctionResponse\",\"price\":0.8,\"crid\":\"crid1\",\"ext\":{\"origbidcpm\":0.8,\"origbidcur\":\"USD\",\"prebid\":{\"type\":\"banner\",\"events\":{\"win\":\"http://localhost:8080/event?t=win&b=f227a07f-1579-4465-bc5e-5c5b02a0c180&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\",\"imp\":\"http://localhost:8080/event?t=imp&b=f227a07f-1579-4465-bc5e-5c5b02a0c180&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"}}},\"wurl\":\"http://localhost:8080/event?t=win&b=f227a07f-1579-4465-bc5e-5c5b02a0c180&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"json\",\"value\":{\"id\":\"880290288\",\"impid\":\"impId1\",\"price\":8.43,\"adm\":\"\",\"crid\":\"crid1\",\"w\":300,\"h\":250,\"ext\":{\"rp\":{\"targeting\":[{\"key\":\"rpfl_1001\",\"values\":[\"2_tier0100\"]}]},\"origbidcpm\":8.43,\"prebid\":{\"type\":\"video\",\"events\":{\"win\":\"http://localhost:8080/event?t=win&b=880290288&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\",\"imp\":\"http://localhost:8080/event?t=imp&b=880290288&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"}}},\"wurl\":\"http://localhost:8080/event?t=win&b=880290288&a=5001&aid=tid&ts=1000&bidder=rubicon&f=i&int=dmbjs\"},\"aid\":\"tid\"},{\"type\":\"xml\",\"value\":\"\",\"ttlseconds\":120,\"aid\":\"tid\"}]}", - "responsebody": "{\"responses\":[{\"uuid\":\"4fe59ef5-6fb4-48c5-88b6-9870257fc49e\"},{\"uuid\":\"117431c9-807a-41e1-82a7-dcd8f8875493\"},{\"uuid\":\"91912e5b-dfa8-42bc-9c7e-df6ce0449c19\"},{\"uuid\":\"765e116a-5773-49d5-a648-0b97a9907a4e\"},{\"uuid\":\"6cf69b42-96f5-4ba1-a984-a9b4d8ff21cf\"},{\"uuid\":\"c75130ed-bcdd-4821-ad91-90cf835615c5\"},{\"uuid\":\"683fe79f-6df7-4971-ac70-820e0486992d\"},{\"uuid\":\"b2528f73-96ab-42ab-8f15-fbe6ed779a26\"}]}", "status": 200 } ], - "appnexus": [ + "rubicon": [ { - "uri": "{{ appnexus.exchange_uri }}?member_id=103", + "uri": "{{ rubicon.exchange_uri }}?tk_xint=dmbjs", + "requestbody": "{\"id\":\"tid\",\"imp\":[{\"id\":\"impId1\",\"video\":{\"mimes\":[\"mimes\"],\"minduration\":20,\"maxduration\":60,\"protocols\":[1],\"w\":300,\"h\":250,\"startdelay\":5,\"skipmin\":0,\"skipafter\":0,\"playbackmethod\":[1],\"ext\":{\"skip\":5,\"skipdelay\":1,\"rp\":{\"size_id\":15},\"videotype\":\"rewarded\"}},\"ext\":{\"rp\":{\"zone_id\":4001,\"target\":{\"rating\":[\"5-star\"],\"prodtype\":[\"tech\"],\"page\":[\"http://www.example.com\"]},\"track\":{\"mint\":\"\",\"mint_version\":\"\"}},\"maxbids\":1}}],\"site\":{\"domain\":\"www.example.com\",\"page\":\"http://www.example.com\",\"publisher\":{\"ext\":{\"rp\":{\"account_id\":2001}}},\"ext\":{\"amp\":0,\"rp\":{\"site_id\":3001}}},\"device\":{\"ua\":\"userAgent\",\"dnt\":2,\"ip\":\"80.215.195.0\",\"pxratio\":4.2,\"language\":\"en\",\"ifa\":\"ifaId\",\"ext\":{\"rp\":{\"pixelratio\":4.2}}},\"user\":{\"buyeruid\":\"J5VLCWQP-26-CWFT\",\"data\":[{\"segment\":[{\"id\":\"segmentId1\"},{\"id\":\"segmentId2\"}],\"ext\":{\"segtax\":4}}],\"ext\":{\"consent\":\"CPBCa-mPBCa-mAAAAAENA0CAAEAAAAAAACiQAaQAwAAgAgABoAAAAAA\",\"eids\":[{\"source\":\"adserver.org\",\"uids\":[{\"id\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\",\"ext\":{\"rtiPartner\":\"TDID\"}}]},{\"source\":\"liveintent.com\",\"uids\":[{\"id\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"ext\":{\"segments\":[\"999\",\"888\"]}},{\"source\":\"pubcid\",\"id\":\"29cfaea8-a429-48fc-9537-8a19a8eb4f0d\"}],\"tpid\":[{\"source\":\"tdid\",\"uid\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\"},{\"source\":\"liveintent.com\",\"uid\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"rp\":{\"target\":{\"ucat\":[\"new\"],\"search\":[\"iphone\"],\"LIseg\":[\"999\",\"888\"],\"iab\":[\"segmentId1\",\"segmentId2\"]}}}},\"at\":1,\"tmax\":5000,\"source\":{\"fd\":1,\"tid\":\"tid\",\"ext\":{\"schain\":{\"ver\":\"1.0\",\"complete\":1,\"nodes\":[{\"asi\":\"superads.com\",\"sid\":\"123\",\"hp\":1}]}}},\"regs\":{\"ext\":{\"us_privacy\":\"1YNN\"}}}", + "responsebody": "{\"id\":\"bidResponseId1\",\"seatbid\":[{\"bid\":[{\"id\":\"880290288\",\"impid\":\"impId1\",\"price\":8.43,\"adm\":\"\",\"crid\":\"crid1\",\"w\":300,\"h\":250,\"ext\":{\"rp\":{\"targeting\":[{\"key\":\"rpfl_1001\",\"values\":[\"2_tier0100\"]}]}}}],\"seat\":\"seatId1\",\"group\":0}]}", "requestheaders": { "Accept": [ "application/json" ], "x-prebid": [ - "pbs-java/1.70.0" + "pbs-java/1.79.0" + ], + "User-Agent": [ + "prebid-server/1.0" ], "Sec-GPC": [ "1" @@ -371,20 +376,21 @@ "application/json;charset=utf-8" ] }, - "requestbody": "{\"id\":\"tid\",\"imp\":[{\"id\":\"impId3\",\"banner\":{\"format\":[{\"w\":300,\"h\":250},{\"w\":300,\"h\":600}],\"w\":300,\"h\":250,\"pos\":3},\"tagid\":\"abc\",\"bidfloor\":1.0,\"ext\":{\"appnexus\":{\"keywords\":\"foo=bar,foo=baz\",\"traffic_source_code\":\"trafficSource\"}}},{\"id\":\"impId131\",\"native\":{\"request\":\"{\\\"ver\\\":\\\"1.1\\\",\\\"context\\\":1,\\\"contextsubtype\\\":11,\\\"plcmttype\\\":4,\\\"plcmtcnt\\\":1,\\\"assets\\\":[{\\\"id\\\":0,\\\"required\\\":1,\\\"title\\\":{\\\"len\\\":500}},{\\\"id\\\":1,\\\"required\\\":1,\\\"img\\\":{\\\"type\\\":3,\\\"wmin\\\":1,\\\"hmin\\\":1}},{\\\"id\\\":2,\\\"required\\\":0,\\\"data\\\":{\\\"type\\\":4,\\\"len\\\":200}},{\\\"id\\\":3,\\\"required\\\":0,\\\"data\\\":{\\\"type\\\":2,\\\"len\\\":15000}},{\\\"id\\\":4,\\\"required\\\":0,\\\"data\\\":{\\\"len\\\":40}},{\\\"id\\\":5,\\\"required\\\":0,\\\"data\\\":{\\\"type\\\":11}}]}\",\"ver\":\"1.1\"},\"ext\":{\"appnexus\":{\"placement_id\":9880618}}}],\"site\":{\"domain\":\"www.example.com\",\"page\":\"http://www.example.com\",\"publisher\":{\"id\":\"5001\",\"domain\":\"example.com\"},\"ext\":{\"amp\":0}},\"device\":{\"ua\":\"userAgent\",\"dnt\":2,\"ip\":\"80.215.195.0\",\"pxratio\":4.2,\"language\":\"en\",\"ifa\":\"ifaId\",\"ext\":{\"prebid\":{\"interstitial\":{\"minwidthperc\":50,\"minheightperc\":60}}}},\"user\":{\"buyeruid\":\"12345\",\"data\":[{\"segment\":[{\"id\":\"segmentId1\"},{\"id\":\"segmentId2\"}],\"ext\":{\"segtax\":4}}],\"ext\":{\"consent\":\"CPBCa-mPBCa-mAAAAAENA0CAAEAAAAAAACiQAaQAwAAgAgABoAAAAAA\",\"eids\":[{\"source\":\"adserver.org\",\"uids\":[{\"id\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\",\"ext\":{\"rtiPartner\":\"TDID\"}}]},{\"source\":\"liveintent.com\",\"uids\":[{\"id\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"ext\":{\"segments\":[\"999\",\"888\"]}},{\"source\":\"pubcid\",\"id\":\"29cfaea8-a429-48fc-9537-8a19a8eb4f0d\"}]}},\"at\":1,\"tmax\":5000,\"cur\":[\"USD\"],\"source\":{\"fd\":1,\"tid\":\"tid\",\"ext\":{\"schain\":{\"ver\":\"1.0\"}}},\"regs\":{\"ext\":{\"us_privacy\":\"1YNN\"}},\"ext\":{\"prebid\":{\"debug\":1,\"aliases\":{\"appnexusAlias\":\"appnexus\"},\"currency\":{\"rates\":{\"EUR\":{\"USD\":1.2406},\"USD\":{\"EUR\":0.811}},\"usepbsrates\":false},\"targeting\":{\"pricegranularity\":{\"precision\":2,\"ranges\":[{\"max\":20,\"increment\":0.1}]},\"includewinners\":true,\"includebidderkeys\":true},\"cache\":{\"bids\":{},\"vastxml\":{\"ttlseconds\":120}},\"events\":{},\"auctiontimestamp\":1000,\"integration\":\"dmbjs\",\"channel\":{\"name\":\"web\"},\"pbs\":{\"endpoint\":\"/openrtb2/auction\"}}}}", - "responsebody": "{\"id\":\"tid\",\"seatbid\":[{\"seat\":\"958\",\"bid\":[{\"id\":\"7706636740145184841\",\"impid\":\"impId3\",\"price\":5.5,\"adid\":\"29681110\",\"adm\":\"some-test-ad\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=29681110\",\"cid\":\"958\",\"crid\":\"29681110\",\"h\":250,\"w\":300,\"ext\":{\"appnexus\":{\"brand_id\":1,\"auction_id\":8189378542222915032,\"bidder_id\":2,\"bid_ad_type\":0,\"ranking_price\":0.0}}},{\"id\":\"928185755156387460\",\"impid\":\"impId131\",\"price\":1.0,\"adid\":\"69595837\",\"adm\":\"{\\\"assets\\\":[{\\\"id\\\":0,\\\"title\\\":{\\\"text\\\":\\\"This is an example Prebid Native creative\\\"}},{\\\"id\\\":1,\\\"img\\\":{\\\"url\\\":\\\"http://vcdn.adnxs.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\\\",\\\"w\\\":3000,\\\"h\\\":2250,\\\"ext\\\":{\\\"appnexus\\\":{\\\"prevent_crop\\\":0}}}},{\\\"id\\\":2,\\\"data\\\":{\\\"value\\\":\\\"Prebid.org\\\"}},{\\\"id\\\":3,\\\"data\\\":{\\\"value\\\":\\\"ThisisaPrebidNativeCreative.Therearemanylikeit,butthisoneismine.\\\"}}],\\\"link\\\":{\\\"url\\\":\\\"http://nym1-ib.adnxs.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\\\"},\\\"imptrackers\\\":[\\\"http://nym1-ib.adnxs.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\\\"],\\\"jstracker\\\":\\\"\\\"}\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=69595837\",\"cid\":\"958\",\"crid\":\"69595837\",\"ext\":{\"appnexus\":{\"brand_id\":1,\"brand_category_id\":1,\"auction_id\":5607483846416358664,\"bidder_id\":2,\"bid_ad_type\":3}}}]}],\"bidid\":\"5778926625248726496\",\"cur\":\"USD\"}", "status": 200 - } - ], - "appnexusAlias": [ + }, { - "uri": "{{ appnexus.exchange_uri }}?member_id=104", + "uri": "{{ rubicon.exchange_uri }}?tk_xint=dmbjs", + "requestbody": "{\"id\":\"tid\",\"imp\":[{\"id\":\"impId2\",\"banner\":{\"format\":[{\"w\":300,\"h\":600}],\"w\":300,\"h\":600,\"ext\":{\"rp\":{\"size_id\":10,\"mime\":\"text/html\"}}},\"ext\":{\"rp\":{\"zone_id\":7001,\"target\":{\"page\":[\"http://www.example.com\"]},\"track\":{\"mint\":\"\",\"mint_version\":\"\"}},\"maxbids\":1}}],\"site\":{\"domain\":\"www.example.com\",\"page\":\"http://www.example.com\",\"publisher\":{\"ext\":{\"rp\":{\"account_id\":5001}}},\"ext\":{\"amp\":0,\"rp\":{\"site_id\":6001}}},\"device\":{\"ua\":\"userAgent\",\"dnt\":2,\"ip\":\"80.215.195.0\",\"pxratio\":4.2,\"language\":\"en\",\"ifa\":\"ifaId\",\"ext\":{\"rp\":{\"pixelratio\":4.2}}},\"user\":{\"buyeruid\":\"J5VLCWQP-26-CWFT\",\"data\":[{\"segment\":[{\"id\":\"segmentId1\"},{\"id\":\"segmentId2\"}],\"ext\":{\"segtax\":4}}],\"ext\":{\"consent\":\"CPBCa-mPBCa-mAAAAAENA0CAAEAAAAAAACiQAaQAwAAgAgABoAAAAAA\",\"eids\":[{\"source\":\"adserver.org\",\"uids\":[{\"id\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\",\"ext\":{\"rtiPartner\":\"TDID\"}}]},{\"source\":\"liveintent.com\",\"uids\":[{\"id\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"ext\":{\"segments\":[\"999\",\"888\"]}},{\"source\":\"pubcid\",\"id\":\"29cfaea8-a429-48fc-9537-8a19a8eb4f0d\"}],\"tpid\":[{\"source\":\"tdid\",\"uid\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\"},{\"source\":\"liveintent.com\",\"uid\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"rp\":{\"target\":{\"LIseg\":[\"999\",\"888\"],\"iab\":[\"segmentId1\",\"segmentId2\"]}}}},\"at\":1,\"tmax\":5000,\"source\":{\"fd\":1,\"tid\":\"tid\",\"ext\":{\"schain\":{\"ver\":\"1.0\",\"complete\":1,\"nodes\":[{\"asi\":\"superads.com\",\"sid\":\"123\",\"hp\":1}]}}},\"regs\":{\"ext\":{\"us_privacy\":\"1YNN\"}}}", + "responsebody": "{\"id\":\"bidResponseId2\",\"seatbid\":[{\"bid\":[{\"id\":\"466223845\",\"impid\":\"impId2\",\"price\":4.26,\"adm\":\"adm2\",\"crid\":\"crid2\",\"w\":300,\"h\":600},{\"id\":\"zeroPriceBidId\",\"impid\":\"impId2\",\"price\":0,\"adm\":\"adm2\",\"crid\":\"crid2\",\"w\":300,\"h\":600}],\"seat\":\"seatId2\",\"group\":0}]}", "requestheaders": { "Accept": [ "application/json" ], "x-prebid": [ - "pbs-java/1.70.0" + "pbs-java/1.79.0" + ], + "User-Agent": [ + "prebid-server/1.0" ], "Sec-GPC": [ "1" @@ -393,23 +399,20 @@ "application/json;charset=utf-8" ] }, - "requestbody": "{\"id\":\"tid\",\"imp\":[{\"id\":\"impId3\",\"banner\":{\"format\":[{\"w\":300,\"h\":250},{\"w\":300,\"h\":600}],\"w\":300,\"h\":250,\"pos\":1},\"tagid\":\"abc\",\"bidfloor\":1.0,\"ext\":{\"appnexus\":{\"keywords\":\"foo=barAlias,foo=bazAlias\",\"traffic_source_code\":\"trafficSourceAlias\"}}}],\"site\":{\"domain\":\"www.example.com\",\"page\":\"http://www.example.com\",\"publisher\":{\"id\":\"5001\",\"domain\":\"example.com\"},\"ext\":{\"amp\":0}},\"device\":{\"ua\":\"userAgent\",\"dnt\":2,\"ip\":\"80.215.195.0\",\"pxratio\":4.2,\"language\":\"en\",\"ifa\":\"ifaId\",\"ext\":{\"prebid\":{\"interstitial\":{\"minwidthperc\":50,\"minheightperc\":60}}}},\"user\":{\"buyeruid\":\"12345\",\"data\":[{\"segment\":[{\"id\":\"segmentId1\"},{\"id\":\"segmentId2\"}],\"ext\":{\"segtax\":4}}],\"ext\":{\"consent\":\"CPBCa-mPBCa-mAAAAAENA0CAAEAAAAAAACiQAaQAwAAgAgABoAAAAAA\",\"eids\":[{\"source\":\"adserver.org\",\"uids\":[{\"id\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\",\"ext\":{\"rtiPartner\":\"TDID\"}}]},{\"source\":\"liveintent.com\",\"uids\":[{\"id\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"ext\":{\"segments\":[\"999\",\"888\"]}},{\"source\":\"pubcid\",\"id\":\"29cfaea8-a429-48fc-9537-8a19a8eb4f0d\"}]}},\"at\":1,\"tmax\":5000,\"cur\":[\"USD\"],\"source\":{\"fd\":1,\"tid\":\"tid\",\"ext\":{\"schain\":{\"ver\":\"1.0\"}}},\"regs\":{\"ext\":{\"us_privacy\":\"1YNN\"}},\"ext\":{\"prebid\":{\"debug\":1,\"aliases\":{\"appnexusAlias\":\"appnexus\"},\"currency\":{\"rates\":{\"EUR\":{\"USD\":1.2406},\"USD\":{\"EUR\":0.811}},\"usepbsrates\":false},\"targeting\":{\"pricegranularity\":{\"precision\":2,\"ranges\":[{\"max\":20,\"increment\":0.1}]},\"includewinners\":true,\"includebidderkeys\":true},\"cache\":{\"bids\":{},\"vastxml\":{\"ttlseconds\":120}},\"events\":{},\"auctiontimestamp\":1000,\"integration\":\"dmbjs\",\"channel\":{\"name\":\"web\"},\"pbs\":{\"endpoint\":\"/openrtb2/auction\"}}}}", - "responsebody": "{\"id\":\"tid\",\"seatbid\":[{\"seat\":\"959\",\"bid\":[{\"id\":\"7706636740145184840\",\"impid\":\"impId3\",\"price\":5.0,\"adid\":\"29681110\",\"adm\":\"some-test-ad\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=29681110\",\"cid\":\"958\",\"crid\":\"29681110\",\"h\":250,\"w\":300,\"cat\":[\"IAB20-3\"],\"ext\":{\"appnexus\":{\"brand_id\":350,\"brand_category_id\":350,\"auction_id\":8189378542222915031,\"bidder_id\":2,\"bid_ad_type\":0,\"ranking_price\":0.0}}}]}],\"bidid\":\"5778926625248726496\",\"cur\":\"USD\"}", "status": 200 } ], - "rubicon": [ + "appnexus": [ { - "uri": "{{ rubicon.exchange_uri }}?tk_xint=dmbjs", + "uri": "{{ appnexus.exchange_uri }}?member_id=103", + "requestbody": "{\"id\":\"tid\",\"imp\":[{\"id\":\"impId3\",\"banner\":{\"format\":[{\"w\":300,\"h\":250},{\"w\":300,\"h\":600}],\"w\":300,\"h\":250,\"pos\":3},\"tagid\":\"abc\",\"bidfloor\":1.0,\"ext\":{\"appnexus\":{\"keywords\":\"foo=bar,foo=baz\",\"traffic_source_code\":\"trafficSource\"}}},{\"id\":\"impId131\",\"native\":{\"request\":\"{\\\"ver\\\":\\\"1.1\\\",\\\"context\\\":1,\\\"contextsubtype\\\":11,\\\"plcmttype\\\":4,\\\"plcmtcnt\\\":1,\\\"assets\\\":[{\\\"id\\\":0,\\\"required\\\":1,\\\"title\\\":{\\\"len\\\":500}},{\\\"id\\\":1,\\\"required\\\":1,\\\"img\\\":{\\\"type\\\":3,\\\"wmin\\\":1,\\\"hmin\\\":1}},{\\\"id\\\":2,\\\"required\\\":0,\\\"data\\\":{\\\"type\\\":4,\\\"len\\\":200}},{\\\"id\\\":3,\\\"required\\\":0,\\\"data\\\":{\\\"type\\\":2,\\\"len\\\":15000}},{\\\"id\\\":4,\\\"required\\\":0,\\\"data\\\":{\\\"len\\\":40}},{\\\"id\\\":5,\\\"required\\\":0,\\\"data\\\":{\\\"type\\\":11}}]}\",\"ver\":\"1.1\"},\"ext\":{\"appnexus\":{\"placement_id\":9880618}}},{\"id\":\"impId32\",\"audio\":{\"mimes\":[\"video/mp4\"]},\"ext\":{\"appnexus\":{\"placement_id\":10433394}}}],\"site\":{\"domain\":\"www.example.com\",\"page\":\"http://www.example.com\",\"publisher\":{\"id\":\"5001\",\"domain\":\"example.com\"},\"ext\":{\"amp\":0}},\"device\":{\"ua\":\"userAgent\",\"dnt\":2,\"ip\":\"80.215.195.0\",\"pxratio\":4.2,\"language\":\"en\",\"ifa\":\"ifaId\",\"ext\":{\"prebid\":{\"interstitial\":{\"minwidthperc\":50,\"minheightperc\":60}}}},\"user\":{\"buyeruid\":\"12345\",\"data\":[{\"segment\":[{\"id\":\"segmentId1\"},{\"id\":\"segmentId2\"}],\"ext\":{\"segtax\":4}}],\"ext\":{\"consent\":\"CPBCa-mPBCa-mAAAAAENA0CAAEAAAAAAACiQAaQAwAAgAgABoAAAAAA\",\"eids\":[{\"source\":\"adserver.org\",\"uids\":[{\"id\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\",\"ext\":{\"rtiPartner\":\"TDID\"}}]},{\"source\":\"liveintent.com\",\"uids\":[{\"id\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"ext\":{\"segments\":[\"999\",\"888\"]}},{\"source\":\"pubcid\",\"id\":\"29cfaea8-a429-48fc-9537-8a19a8eb4f0d\"}]}},\"at\":1,\"tmax\":5000,\"cur\":[\"USD\"],\"source\":{\"fd\":1,\"tid\":\"tid\",\"ext\":{\"schain\":{\"ver\":\"1.0\"}}},\"regs\":{\"ext\":{\"us_privacy\":\"1YNN\"}},\"ext\":{\"prebid\":{\"debug\":1,\"aliases\":{\"appnexusAlias\":\"appnexus\"},\"currency\":{\"rates\":{\"EUR\":{\"USD\":1.2406},\"USD\":{\"EUR\":0.811}},\"usepbsrates\":false},\"targeting\":{\"pricegranularity\":{\"precision\":2,\"ranges\":[{\"max\":20,\"increment\":0.1}]},\"includewinners\":true,\"includebidderkeys\":true},\"cache\":{\"bids\":{},\"vastxml\":{\"ttlseconds\":120}},\"events\":{},\"auctiontimestamp\":1000,\"integration\":\"dmbjs\",\"channel\":{\"name\":\"web\"},\"pbs\":{\"endpoint\":\"/openrtb2/auction\"}},\"appnexus\":{\"is_amp\":0,\"hb_source\":5}}}", + "responsebody": "{\"id\":\"tid\",\"seatbid\":[{\"seat\":\"958\",\"bid\":[{\"id\":\"7706636740145184841\",\"impid\":\"impId3\",\"price\":5.5,\"adid\":\"29681110\",\"adm\":\"some-test-ad\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=29681110\",\"cid\":\"958\",\"crid\":\"29681110\",\"h\":250,\"w\":300,\"ext\":{\"appnexus\":{\"brand_id\":1,\"auction_id\":8189378542222915032,\"bidder_id\":2,\"bid_ad_type\":0,\"ranking_price\":0.0}}},{\"id\":\"928185755156387460\",\"impid\":\"impId131\",\"price\":1.0,\"adid\":\"69595837\",\"adm\":\"{\\\"assets\\\":[{\\\"id\\\":0,\\\"title\\\":{\\\"text\\\":\\\"This is an example Prebid Native creative\\\"}},{\\\"id\\\":1,\\\"img\\\":{\\\"url\\\":\\\"http://vcdn.adnxs.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\\\",\\\"w\\\":3000,\\\"h\\\":2250,\\\"ext\\\":{\\\"appnexus\\\":{\\\"prevent_crop\\\":0}}}},{\\\"id\\\":2,\\\"data\\\":{\\\"value\\\":\\\"Prebid.org\\\"}},{\\\"id\\\":3,\\\"data\\\":{\\\"value\\\":\\\"ThisisaPrebidNativeCreative.Therearemanylikeit,butthisoneismine.\\\"}}],\\\"link\\\":{\\\"url\\\":\\\"http://nym1-ib.adnxs.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\\\"},\\\"imptrackers\\\":[\\\"http://nym1-ib.adnxs.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\\\"],\\\"jstracker\\\":\\\"\\\"}\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=69595837\",\"cid\":\"958\",\"crid\":\"69595837\",\"ext\":{\"appnexus\":{\"brand_id\":1,\"brand_category_id\":1,\"auction_id\":5607483846416358664,\"bidder_id\":2,\"bid_ad_type\":3}}}]}],\"bidid\":\"5778926625248726496\",\"cur\":\"USD\"}", "requestheaders": { "Accept": [ "application/json" ], "x-prebid": [ - "pbs-java/1.70.0" - ], - "User-Agent": [ - "prebid-server/1.0" + "pbs-java/1.79.0" ], "Sec-GPC": [ "1" @@ -418,21 +421,20 @@ "application/json;charset=utf-8" ] }, - "requestbody": "{\"id\":\"tid\",\"imp\":[{\"id\":\"impId1\",\"video\":{\"mimes\":[\"mimes\"],\"minduration\":20,\"maxduration\":60,\"protocols\":[1],\"w\":300,\"h\":250,\"startdelay\":5,\"skipmin\":0,\"skipafter\":0,\"playbackmethod\":[1],\"ext\":{\"skip\":5,\"skipdelay\":1,\"rp\":{\"size_id\":15},\"videotype\":\"rewarded\"}},\"ext\":{\"rp\":{\"zone_id\":4001,\"target\":{\"rating\":[\"5-star\"],\"prodtype\":[\"tech\"],\"page\":[\"http://www.example.com\"]},\"track\":{\"mint\":\"\",\"mint_version\":\"\"}},\"maxbids\":1}}],\"site\":{\"domain\":\"www.example.com\",\"page\":\"http://www.example.com\",\"publisher\":{\"ext\":{\"rp\":{\"account_id\":2001}}},\"ext\":{\"amp\":0,\"rp\":{\"site_id\":3001}}},\"device\":{\"ua\":\"userAgent\",\"dnt\":2,\"ip\":\"80.215.195.0\",\"pxratio\":4.2,\"language\":\"en\",\"ifa\":\"ifaId\",\"ext\":{\"rp\":{\"pixelratio\":4.2}}},\"user\":{\"buyeruid\":\"J5VLCWQP-26-CWFT\",\"data\":[{\"segment\":[{\"id\":\"segmentId1\"},{\"id\":\"segmentId2\"}],\"ext\":{\"segtax\":4}}],\"ext\":{\"consent\":\"CPBCa-mPBCa-mAAAAAENA0CAAEAAAAAAACiQAaQAwAAgAgABoAAAAAA\",\"eids\":[{\"source\":\"adserver.org\",\"uids\":[{\"id\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\",\"ext\":{\"rtiPartner\":\"TDID\"}}]},{\"source\":\"liveintent.com\",\"uids\":[{\"id\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"ext\":{\"segments\":[\"999\",\"888\"]}},{\"source\":\"pubcid\",\"id\":\"29cfaea8-a429-48fc-9537-8a19a8eb4f0d\"}],\"tpid\":[{\"source\":\"tdid\",\"uid\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\"},{\"source\":\"liveintent.com\",\"uid\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"rp\":{\"target\":{\"ucat\":[\"new\"],\"search\":[\"iphone\"],\"LIseg\":[\"999\",\"888\"],\"iab\":[\"segmentId1\",\"segmentId2\"]}}}},\"at\":1,\"tmax\":5000,\"source\":{\"fd\":1,\"tid\":\"tid\",\"ext\":{\"schain\":{\"ver\":\"1.0\",\"complete\":1,\"nodes\":[{\"asi\":\"superads.com\",\"sid\":\"123\",\"hp\":1}]}}},\"regs\":{\"ext\":{\"us_privacy\":\"1YNN\"}}}", - "responsebody": "{\"id\":\"bidResponseId1\",\"seatbid\":[{\"bid\":[{\"id\":\"880290288\",\"impid\":\"impId1\",\"price\":8.43,\"adm\":\"\",\"crid\":\"crid1\",\"w\":300,\"h\":250,\"ext\":{\"rp\":{\"targeting\":[{\"key\":\"rpfl_1001\",\"values\":[\"2_tier0100\"]}]}}}],\"seat\":\"seatId1\",\"group\":0}]}", "status": 200 - }, + } + ], + "appnexusAlias": [ { - "uri": "{{ rubicon.exchange_uri }}?tk_xint=dmbjs", + "uri": "{{ appnexus.exchange_uri }}?member_id=104", + "requestbody": "{\"id\":\"tid\",\"imp\":[{\"id\":\"impId3\",\"banner\":{\"format\":[{\"w\":300,\"h\":250},{\"w\":300,\"h\":600}],\"w\":300,\"h\":250,\"pos\":1},\"tagid\":\"abc\",\"bidfloor\":1.0,\"ext\":{\"appnexus\":{\"keywords\":\"foo=barAlias,foo=bazAlias\",\"traffic_source_code\":\"trafficSourceAlias\"}}}],\"site\":{\"domain\":\"www.example.com\",\"page\":\"http://www.example.com\",\"publisher\":{\"id\":\"5001\",\"domain\":\"example.com\"},\"ext\":{\"amp\":0}},\"device\":{\"ua\":\"userAgent\",\"dnt\":2,\"ip\":\"80.215.195.0\",\"pxratio\":4.2,\"language\":\"en\",\"ifa\":\"ifaId\",\"ext\":{\"prebid\":{\"interstitial\":{\"minwidthperc\":50,\"minheightperc\":60}}}},\"user\":{\"buyeruid\":\"12345\",\"data\":[{\"segment\":[{\"id\":\"segmentId1\"},{\"id\":\"segmentId2\"}],\"ext\":{\"segtax\":4}}],\"ext\":{\"consent\":\"CPBCa-mPBCa-mAAAAAENA0CAAEAAAAAAACiQAaQAwAAgAgABoAAAAAA\",\"eids\":[{\"source\":\"adserver.org\",\"uids\":[{\"id\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\",\"ext\":{\"rtiPartner\":\"TDID\"}}]},{\"source\":\"liveintent.com\",\"uids\":[{\"id\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"ext\":{\"segments\":[\"999\",\"888\"]}},{\"source\":\"pubcid\",\"id\":\"29cfaea8-a429-48fc-9537-8a19a8eb4f0d\"}]}},\"at\":1,\"tmax\":5000,\"cur\":[\"USD\"],\"source\":{\"fd\":1,\"tid\":\"tid\",\"ext\":{\"schain\":{\"ver\":\"1.0\"}}},\"regs\":{\"ext\":{\"us_privacy\":\"1YNN\"}},\"ext\":{\"prebid\":{\"debug\":1,\"aliases\":{\"appnexusAlias\":\"appnexus\"},\"currency\":{\"rates\":{\"EUR\":{\"USD\":1.2406},\"USD\":{\"EUR\":0.811}},\"usepbsrates\":false},\"targeting\":{\"pricegranularity\":{\"precision\":2,\"ranges\":[{\"max\":20,\"increment\":0.1}]},\"includewinners\":true,\"includebidderkeys\":true},\"cache\":{\"bids\":{},\"vastxml\":{\"ttlseconds\":120}},\"events\":{},\"auctiontimestamp\":1000,\"integration\":\"dmbjs\",\"channel\":{\"name\":\"web\"},\"pbs\":{\"endpoint\":\"/openrtb2/auction\"}},\"appnexus\":{\"is_amp\":0,\"hb_source\":5}}}", + "responsebody": "{\"id\":\"tid\",\"seatbid\":[{\"seat\":\"959\",\"bid\":[{\"id\":\"7706636740145184840\",\"impid\":\"impId3\",\"price\":5.0,\"adid\":\"29681110\",\"adm\":\"some-test-ad\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=29681110\",\"cid\":\"958\",\"crid\":\"29681110\",\"h\":250,\"w\":300,\"cat\":[\"IAB20-3\"],\"ext\":{\"appnexus\":{\"brand_id\":350,\"brand_category_id\":350,\"auction_id\":8189378542222915031,\"bidder_id\":2,\"bid_ad_type\":0,\"ranking_price\":0.0}}}]}],\"bidid\":\"5778926625248726496\",\"cur\":\"USD\"}", "requestheaders": { "Accept": [ "application/json" ], "x-prebid": [ - "pbs-java/1.70.0" - ], - "User-Agent": [ - "prebid-server/1.0" + "pbs-java/1.79.0" ], "Sec-GPC": [ "1" @@ -441,8 +443,6 @@ "application/json;charset=utf-8" ] }, - "requestbody": "{\"id\":\"tid\",\"imp\":[{\"id\":\"impId2\",\"banner\":{\"format\":[{\"w\":300,\"h\":600}],\"w\":300,\"h\":600,\"ext\":{\"rp\":{\"size_id\":10,\"mime\":\"text/html\"}}},\"ext\":{\"rp\":{\"zone_id\":7001,\"target\":{\"page\":[\"http://www.example.com\"]},\"track\":{\"mint\":\"\",\"mint_version\":\"\"}},\"maxbids\":1}}],\"site\":{\"domain\":\"www.example.com\",\"page\":\"http://www.example.com\",\"publisher\":{\"ext\":{\"rp\":{\"account_id\":5001}}},\"ext\":{\"amp\":0,\"rp\":{\"site_id\":6001}}},\"device\":{\"ua\":\"userAgent\",\"dnt\":2,\"ip\":\"80.215.195.0\",\"pxratio\":4.2,\"language\":\"en\",\"ifa\":\"ifaId\",\"ext\":{\"rp\":{\"pixelratio\":4.2}}},\"user\":{\"buyeruid\":\"J5VLCWQP-26-CWFT\",\"data\":[{\"segment\":[{\"id\":\"segmentId1\"},{\"id\":\"segmentId2\"}],\"ext\":{\"segtax\":4}}],\"ext\":{\"consent\":\"CPBCa-mPBCa-mAAAAAENA0CAAEAAAAAAACiQAaQAwAAgAgABoAAAAAA\",\"eids\":[{\"source\":\"adserver.org\",\"uids\":[{\"id\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\",\"ext\":{\"rtiPartner\":\"TDID\"}}]},{\"source\":\"liveintent.com\",\"uids\":[{\"id\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"ext\":{\"segments\":[\"999\",\"888\"]}},{\"source\":\"pubcid\",\"id\":\"29cfaea8-a429-48fc-9537-8a19a8eb4f0d\"}],\"tpid\":[{\"source\":\"tdid\",\"uid\":\"cd96870f-f53d-4986-a08e-cd1612fb13b0\"},{\"source\":\"liveintent.com\",\"uid\":\"efcf3a33-2eaf-4d6b-bf11-a411f134278c\"}],\"rp\":{\"target\":{\"LIseg\":[\"999\",\"888\"],\"iab\":[\"segmentId1\",\"segmentId2\"]}}}},\"at\":1,\"tmax\":5000,\"source\":{\"fd\":1,\"tid\":\"tid\",\"ext\":{\"schain\":{\"ver\":\"1.0\",\"complete\":1,\"nodes\":[{\"asi\":\"superads.com\",\"sid\":\"123\",\"hp\":1}]}}},\"regs\":{\"ext\":{\"us_privacy\":\"1YNN\"}}}", - "responsebody": "{\"id\":\"bidResponseId2\",\"seatbid\":[{\"bid\":[{\"id\":\"466223845\",\"impid\":\"impId2\",\"price\":4.26,\"adm\":\"adm2\",\"crid\":\"crid2\",\"w\":300,\"h\":600},{\"id\":\"zeroPriceBidId\",\"impid\":\"impId2\",\"price\":0,\"adm\":\"adm2\",\"crid\":\"crid2\",\"w\":300,\"h\":600}],\"seat\":\"seatId2\",\"group\":0}]}", "status": 200 } ] @@ -810,14 +810,6 @@ } } }, - "errors": { - "appnexus": [ - { - "code": 2, - "message": "Appnexus doesn't support audio Imps. Ignoring Imp ID=impId32" - } - ] - }, "warnings": { "prebid": [ { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus_multi_bid/test-appnexus-bid-request-1.json b/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus_multi_bid/test-appnexus-bid-request-1.json index 76ba0a905c0..aae470c6c65 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus_multi_bid/test-appnexus-bid-request-1.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/rubicon_appnexus_multi_bid/test-appnexus-bid-request-1.json @@ -79,6 +79,10 @@ } }, "ext": { + "appnexus": { + "is_amp": 0, + "hb_source": 5 + }, "prebid": { "debug": 0, "currency": { From 8f5638872885e0cf6c8bd6c51ef28dcb9b1e8167 Mon Sep 17 00:00:00 2001 From: rpanchyk Date: Mon, 15 Nov 2021 14:37:48 +0200 Subject: [PATCH 02/26] Fix site and app propagation in video stored request processor (#1578) --- .../auction/VideoStoredRequestProcessor.java | 38 ++++++--- .../VideoStoredRequestProcessorTest.java | 81 +++++++++++++------ 2 files changed, 82 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/prebid/server/auction/VideoStoredRequestProcessor.java b/src/main/java/org/prebid/server/auction/VideoStoredRequestProcessor.java index dd1cd540bdb..ccaa5017f75 100644 --- a/src/main/java/org/prebid/server/auction/VideoStoredRequestProcessor.java +++ b/src/main/java/org/prebid/server/auction/VideoStoredRequestProcessor.java @@ -316,22 +316,14 @@ private BidRequest mergeWithDefaultBidRequest(BidRequestVideo videoRequest, List final Site site = videoRequest.getSite(); if (site != null) { - final Content content = videoRequest.getContent(); - if (content != null) { - bidRequestBuilder.site(site.toBuilder() - .content(content) - .build()); - } + final Site updatedSite = updateSite(site, videoRequest); + bidRequestBuilder.site(updatedSite); } final App app = videoRequest.getApp(); if (app != null) { - final Content content = videoRequest.getContent(); - if (content != null) { - bidRequestBuilder.app(app.toBuilder() - .content(content) - .build()); - } + final App updatedApp = updateApp(app, videoRequest); + bidRequestBuilder.app(updatedApp); } final Device device = videoRequest.getDevice(); @@ -372,6 +364,28 @@ private BidRequest mergeWithDefaultBidRequest(BidRequestVideo videoRequest, List .build(); } + private static Site updateSite(Site site, BidRequestVideo videoRequest) { + final Content content = videoRequest.getContent(); + if (content != null) { + return site.toBuilder() + .content(content) + .build(); + } + + return site; + } + + private static App updateApp(App app, BidRequestVideo videoRequest) { + final Content content = videoRequest.getContent(); + if (content != null) { + return app.toBuilder() + .content(content) + .build(); + } + + return app; + } + private void addRequiredOpenRtbFields(BidRequest.BidRequestBuilder bidRequestBuilder) { bidRequestBuilder.cur(Collections.singletonList(currency)); } diff --git a/src/test/java/org/prebid/server/auction/VideoStoredRequestProcessorTest.java b/src/test/java/org/prebid/server/auction/VideoStoredRequestProcessorTest.java index 8a7caa17a05..96c35561cb3 100644 --- a/src/test/java/org/prebid/server/auction/VideoStoredRequestProcessorTest.java +++ b/src/test/java/org/prebid/server/auction/VideoStoredRequestProcessorTest.java @@ -1,6 +1,7 @@ package org.prebid.server.auction; import com.fasterxml.jackson.core.JsonProcessingException; +import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Content; import com.iab.openrtb.request.Imp; @@ -40,7 +41,7 @@ import org.prebid.server.validation.VideoRequestValidator; import java.math.BigDecimal; -import java.util.Arrays; +import java.util.List; import java.util.function.UnaryOperator; import static java.util.Arrays.asList; @@ -50,6 +51,7 @@ import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; +import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -105,7 +107,7 @@ public void setUp() throws JsonProcessingException { } @Test - public void shouldReturnFailedFutureWhenFetchStoredIsFailed() { + public void shouldFailWhenFetchStoredIsFailed() { // given given(applicationSettings.getVideoStoredData(any(), anySet(), anySet(), any())) .willReturn(Future.failedFuture("ERROR")); @@ -119,7 +121,7 @@ public void shouldReturnFailedFutureWhenFetchStoredIsFailed() { } @Test - public void shouldReturnFutureWithMergedStoredAndDefaultRequest() { + public void shouldReturnMergedStoredAndDefaultRequest() { // given final User user = User.builder() .yob(123) @@ -138,11 +140,12 @@ public void shouldReturnFutureWithMergedStoredAndDefaultRequest() { .cacheconfig(CacheConfig.of(42)) .bcat(singletonList("bcat")) .badv(singletonList("badv")), - UnaryOperator.identity()); + identity()); + final List pods = singletonList(Pod.of(123, 20, STORED_POD_ID)); final BidRequestVideo requestVideo = givenValidDataResult( - UnaryOperator.identity(), - builder -> builder.pods(singletonList(Pod.of(123, 20, STORED_POD_ID)))); + identity(), + builder -> builder.pods(pods)); final StoredDataResult storedDataResult = StoredDataResult.of( singletonMap(STORED_REQUEST_ID, jacksonMapper.encodeToString(storedVideo)), @@ -152,7 +155,7 @@ public void shouldReturnFutureWithMergedStoredAndDefaultRequest() { given(applicationSettings.getVideoStoredData(any(), anySet(), anySet(), any())) .willReturn(Future.succeededFuture(storedDataResult)); given(validator.validPods(any(), any())) - .willReturn(WithPodErrors.of(singletonList(Pod.of(123, 20, STORED_POD_ID)), emptyList())); + .willReturn(WithPodErrors.of(pods, emptyList())); // when final Future> result = target.processVideoRequest(null, STORED_REQUEST_ID, @@ -208,16 +211,44 @@ public void shouldReturnFutureWithMergedStoredAndDefaultRequest() { .ext(ExtRequest.of(ext)) .build(); - assertThat(result.result()).isEqualTo(WithPodErrors.of(expectedMergedRequest, emptyList())); + assertThat(result.result()) + .isEqualTo(WithPodErrors.of(expectedMergedRequest, emptyList())); } @Test - public void processVideoRequestShouldFailWhenThereAreNoStoredImpsFound() { + public void shouldReturnBidRequestWithSiteAndAppWithoutVideoContent() { + // given + final Site site = Site.builder().id("siteId").build(); + final App app = App.builder().id("appId").build(); + final List pods = singletonList(Pod.of(123, 20, STORED_POD_ID)); + + final BidRequestVideo requestVideo = givenValidDataResult( + builder -> builder.site(site).app(app).content(null), + builder -> builder.pods(pods)); + + given(applicationSettings.getVideoStoredData(any(), anySet(), anySet(), any())) + .willReturn(Future.succeededFuture(StoredDataResult.of(emptyMap(), emptyMap(), emptyList()))); + given(validator.validPods(any(), any())) + .willReturn(WithPodErrors.of(pods, emptyList())); + + // when + final Future> result = target.processVideoRequest(null, STORED_REQUEST_ID, + singleton(STORED_POD_ID), requestVideo); + + // then + assertThat(result.result()) + .extracting(WithPodErrors::getData) + .extracting(BidRequest::getSite, BidRequest::getApp) + .containsExactly(site, app); + } + + @Test + public void shouldFailWhenThereAreNoStoredImpsFound() { // given final Pod pod1 = Pod.of(1, 2, "333"); final Pod pod2 = Pod.of(2, 3, "222"); final BidRequestVideo requestVideo = givenValidDataResult( - UnaryOperator.identity(), + identity(), builder -> builder.pods(asList(pod1, pod2))); final StoredDataResult storedDataResult = StoredDataResult.of(emptyMap(), emptyMap(), emptyList()); @@ -254,16 +285,16 @@ public void processVideoRequestShouldFailWhenThereAreNoStoredImpsFound() { } @Test - public void shouldReturnFutureWithCorrectAdPodDurationIfRequireExactDurationIsTrue() { + public void shouldReturnCorrectAdPodDurationIfRequireExactDurationIsTrue() { // given - final BidRequestVideo storedVideo = givenValidDataResult(builder -> builder - .cacheconfig(CacheConfig.of(42)), - UnaryOperator.identity()); + final BidRequestVideo storedVideo = givenValidDataResult( + builder -> builder.cacheconfig(CacheConfig.of(42)), + identity()); final BidRequestVideo requestVideo = givenValidDataResult( - UnaryOperator.identity(), + identity(), builder -> builder.requireExactDuration(true) - .durationRangeSec(Arrays.asList(30, 60, 80)) + .durationRangeSec(asList(30, 60, 80)) .pods(singletonList(Pod.of(123, 30, STORED_POD_ID)))); final StoredDataResult storedDataResult = StoredDataResult.of( @@ -319,21 +350,21 @@ public void shouldReturnFutureWithCorrectAdPodDurationIfRequireExactDurationIsTr .build(); assertThat(result.result().getData().getImp()) - .isEqualTo(Arrays.asList(expectedImp1, expectedImp2, expectedImp3)); + .isEqualTo(asList(expectedImp1, expectedImp2, expectedImp3)); } @Test - public void shouldReturnFutureWithCorrectPriceGranularityInRequest() { + public void shouldReturnCorrectPriceGranularityInRequest() { // given - final BidRequestVideo storedVideo = givenValidDataResult(builder -> builder + final BidRequestVideo storedVideo = givenValidDataResult( + builder -> builder .cacheconfig(CacheConfig.of(42)) .bcat(singletonList("bcat")) .badv(singletonList("badv")), - UnaryOperator.identity()); + identity()); final PriceGranularity priceGranularity = PriceGranularity.createFromExtPriceGranularity( ExtPriceGranularity.of(1, - singletonList(ExtGranularityRange.of(new BigDecimal(10), new BigDecimal("0.5")))) - ); + singletonList(ExtGranularityRange.of(new BigDecimal(10), new BigDecimal("0.5"))))); final BidRequestVideo requestVideo = givenValidDataResult( bidRequestVideoBuilder -> bidRequestVideoBuilder.pricegranularity(priceGranularity), @@ -372,9 +403,9 @@ private BidRequestVideo givenValidDataResult( UnaryOperator podconfigCustomizer) { return requestCustomizer.apply(BidRequestVideo.builder() - .storedrequestid("storedrequestid") - .podconfig(podconfigCustomizer.apply(Podconfig.builder() - .durationRangeSec(asList(200, 100))) + .storedrequestid(STORED_REQUEST_ID) + .podconfig(podconfigCustomizer + .apply(Podconfig.builder().durationRangeSec(asList(200, 100))) .build()) .site(Site.builder().id("siteId").build()) .video(Video.builder().mimes(singletonList("mime")).protocols(singletonList(123)).build())) From 39e24a6489ff599445489b85e8188469669547ba Mon Sep 17 00:00:00 2001 From: mtuchkova <47950518+mtuchkova@users.noreply.github.com> Date: Mon, 15 Nov 2021 14:56:57 +0200 Subject: [PATCH 03/26] Timeout tests (#1551) --- .../prebid/server/functional/AmpSpec.groovy | 5 - .../server/functional/AuctionSpec.groovy | 119 ++++++++++++++++++ .../prebid/server/functional/BaseSpec.groovy | 5 + .../server/functional/CookieSyncSpec.groovy | 35 ++++++ .../prebid/server/functional/EventSpec.groovy | 29 +++++ .../server/functional/HttpSettingsSpec.groovy | 8 +- .../server/functional/SetuidSpec.groovy | 36 ++++++ .../prebid/server/functional/SmokeSpec.groovy | 9 +- .../server/functional/VtrackSpec.groovy | 28 +++++ .../{request/setuid => }/UidsCookie.groovy | 5 +- .../functional/model/db/StoredRequest.groovy | 5 + .../model/request/auction/Prebid.groovy | 2 +- ...uest.groovy => PrebidStoredRequest.groovy} | 2 +- .../model/request/setuid/UidWithExpiry.groovy | 2 +- .../response/setuid/SetuidResponse.groovy | 6 +- .../service/PrebidServerService.groovy | 15 ++- .../container/PrebidServerContainer.groovy | 2 +- 17 files changed, 290 insertions(+), 23 deletions(-) create mode 100644 src/test/groovy/org/prebid/server/functional/CookieSyncSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/EventSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/SetuidSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/VtrackSpec.groovy rename src/test/groovy/org/prebid/server/functional/model/{request/setuid => }/UidsCookie.groovy (80%) rename src/test/groovy/org/prebid/server/functional/model/request/auction/{StoredRequest.groovy => PrebidStoredRequest.groovy} (85%) diff --git a/src/test/groovy/org/prebid/server/functional/AmpSpec.groovy b/src/test/groovy/org/prebid/server/functional/AmpSpec.groovy index e306f0801f0..9cac8224eb1 100644 --- a/src/test/groovy/org/prebid/server/functional/AmpSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/AmpSpec.groovy @@ -4,7 +4,6 @@ import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.service.PrebidServerService -import org.prebid.server.functional.util.PBSUtils import spock.lang.Shared import spock.lang.Unroll @@ -145,8 +144,4 @@ class AmpSpec extends BaseSpec { AmpRequest.defaultAmpRequest || "valid AMP request" new AmpRequest() || "invalid AMP request" } - - private static int getRandomTimeout() { - PBSUtils.getRandomNumber(MIN_TIMEOUT, MAX_TIMEOUT) - } } diff --git a/src/test/groovy/org/prebid/server/functional/AuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/AuctionSpec.groovy index f08f9b70e4f..6e10a245f74 100644 --- a/src/test/groovy/org/prebid/server/functional/AuctionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/AuctionSpec.groovy @@ -1,14 +1,24 @@ package org.prebid.server.functional +import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.PrebidStoredRequest +import org.prebid.server.functional.service.PrebidServerService +import org.prebid.server.functional.util.PBSUtils +import spock.lang.Shared import spock.lang.Unroll import static org.prebid.server.functional.util.SystemProperties.PBS_VERSION class AuctionSpec extends BaseSpec { + private static final int DEFAULT_TIMEOUT = getRandomTimeout() private static final String PBS_VERSION_HEADER = "pbs-java/$PBS_VERSION" + @Shared + PrebidServerService prebidServerService = pbsServiceFactory.getService(["auction.max-timeout-ms" : MAX_TIMEOUT as String, + "auction.default-timeout-ms": DEFAULT_TIMEOUT as String]) + @Unroll def "PBS should return version in response header for auction request for #description"() { @@ -23,4 +33,113 @@ class AuctionSpec extends BaseSpec { BidRequest.defaultBidRequest || "valid bid request" new BidRequest() || "invalid bid request" } + + def "PBS should apply timeout from stored request when it's not specified in the auction request"() { + given: "Default basic BidRequest with generic bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + tmax = null + ext.prebid.storedRequest = new PrebidStoredRequest(id: PBSUtils.randomNumber.toString()) + } + + and: "Default stored request with timeout" + def timeout = getRandomTimeout() + def storedRequestModel = BidRequest.defaultStoredRequest.tap { + tmax = timeout + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getDbStoredRequest(bidRequest, storedRequestModel) + storedRequestDao.save(storedRequest) + + when: "PBS processes auction request" + prebidServerService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain timeout from the stored request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.tmax == timeout as Long + } + + @Unroll + def "PBS should prefer timeout from the auction request when stored request timeout is #tmax"() { + given: "Default basic BidRequest with generic bidder" + def timeout = getRandomTimeout() + def bidRequest = BidRequest.defaultBidRequest.tap { + tmax = timeout + ext.prebid.storedRequest = new PrebidStoredRequest(id: PBSUtils.randomNumber.toString()) + } + + and: "Default stored request" + def storedRequestModel = BidRequest.defaultStoredRequest.tap { + it.tmax = tmaxStoredRequest + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getDbStoredRequest(bidRequest, storedRequestModel) + storedRequestDao.save(storedRequest) + + when: "PBS processes auction request" + prebidServerService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain timeout from the request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.tmax == timeout as Long + + where: + tmaxStoredRequest << [null, getRandomTimeout()] + } + + @Unroll + def "PBS should honor max timeout from the settings for auction request"() { + given: "Default basic BidRequest with generic bidder" + def bidRequest = BidRequest.defaultBidRequest.tap { + tmax = autcionRequestTimeout + ext.prebid.storedRequest = new PrebidStoredRequest(id: PBSUtils.randomNumber.toString()) + } + + and: "Default stored request" + def storedRequest = BidRequest.defaultStoredRequest.tap { + it.tmax = storedRequestTimeout + } + + and: "Save storedRequest into DB" + def storedRequestModel = StoredRequest.getDbStoredRequest(bidRequest, storedRequest) + storedRequestDao.save(storedRequestModel) + + when: "PBS processes auction request" + prebidServerService.sendAuctionRequest(bidRequest) + + then: "Bidder request timeout should correspond to the maximum from the settings" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.tmax == MAX_TIMEOUT as Long + + where: + autcionRequestTimeout || storedRequestTimeout + MAX_TIMEOUT + 1 || null + null || MAX_TIMEOUT + 1 + MAX_TIMEOUT + 1 || MAX_TIMEOUT + 1 + } + + def "PBS should honor default timeout for auction request"() { + given: "Default basic BidRequest without timeout" + def bidRequest = BidRequest.defaultBidRequest.tap { + tmax = null + ext.prebid.storedRequest = new PrebidStoredRequest(id: PBSUtils.randomNumber.toString()) + } + + and: "Default stored request without timeout" + def storedRequest = BidRequest.defaultStoredRequest.tap { + it.tmax = null + } + + and: "Save storedRequest into DB" + def storedRequestModel = StoredRequest.getDbStoredRequest(bidRequest, storedRequest) + storedRequestDao.save(storedRequestModel) + + when: "PBS processes auction request" + prebidServerService.sendAuctionRequest(bidRequest) + + then: "Bidder request timeout should correspond to the maximum from the settings" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.tmax == DEFAULT_TIMEOUT as Long + } } diff --git a/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy index f929121adcb..6007c8e80c9 100644 --- a/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy @@ -12,6 +12,7 @@ import org.prebid.server.functional.testcontainers.PbsServiceFactory import org.prebid.server.functional.testcontainers.scaffolding.Bidder import org.prebid.server.functional.testcontainers.scaffolding.PrebidCache import org.prebid.server.functional.util.ObjectMapperWrapper +import org.prebid.server.functional.util.PBSUtils import spock.lang.Specification import static org.prebid.server.functional.testcontainers.Dependencies.mysqlContainer @@ -48,4 +49,8 @@ abstract class BaseSpec extends Specification { repository.removeAllDatabaseData() pbsServiceFactory.stopContainers() } + + protected static int getRandomTimeout() { + PBSUtils.getRandomNumber(MIN_TIMEOUT, MAX_TIMEOUT) + } } diff --git a/src/test/groovy/org/prebid/server/functional/CookieSyncSpec.groovy b/src/test/groovy/org/prebid/server/functional/CookieSyncSpec.groovy new file mode 100644 index 00000000000..7ea2af2844d --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/CookieSyncSpec.groovy @@ -0,0 +1,35 @@ +package org.prebid.server.functional + +import org.prebid.server.functional.model.UidsCookie +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.request.cookiesync.CookieSyncRequest +import org.prebid.server.functional.service.PrebidServerException +import org.prebid.server.functional.util.PBSUtils +import spock.lang.PendingFeature + +class CookieSyncSpec extends BaseSpec { + + @PendingFeature + def "PBS should return an error for cookie_sync request when the timeout time is exceeded"() { + given: "PBS with timeout configuration" + // Using minimal allowed time for timeout (1ms) to get timeout error. + def pbsService = pbsServiceFactory.getService(["cookie-sync.default-timeout-ms": "1"]) + + and: "Default CookieSyncRequest with account" + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest + cookieSyncRequest.account = PBSUtils.randomNumber.toString() + def uidsCookie = UidsCookie.defaultUidsCookie + + and: "Account in the DB" + def account = new Account(uuid: cookieSyncRequest.account, eventsEnabled: true) + accountDao.save(account) + + when: "PBS processes cookie sync request" + pbsService.sendCookieSyncRequest(cookieSyncRequest, uidsCookie) + + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == 500 + assert exception.responseBody.contains("Timed out while executing SQL query") + } +} diff --git a/src/test/groovy/org/prebid/server/functional/EventSpec.groovy b/src/test/groovy/org/prebid/server/functional/EventSpec.groovy new file mode 100644 index 00000000000..03865692155 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/EventSpec.groovy @@ -0,0 +1,29 @@ +package org.prebid.server.functional + +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.request.event.EventRequest +import org.prebid.server.functional.service.PrebidServerException + +class EventSpec extends BaseSpec { + + def "PBS should return an error for event request when the timeout time is exceeded"() { + given: "PBS with timeout configuration" + // Using minimal allowed time for timeout (1ms) to get timeout error. + def pbsService = pbsServiceFactory.getService(["event.default-timeout-ms": "1"]) + + and: "Default EventRequest" + def eventRequest = EventRequest.defaultEventRequest + + and: "Account in the DB" + def account = new Account(uuid: eventRequest.accountId, eventsEnabled: true) + accountDao.save(account) + + when: "PBS processes event request" + pbsService.sendEventRequest(eventRequest) + + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == 500 + assert exception.responseBody.contains("Timed out while executing SQL query") + } +} diff --git a/src/test/groovy/org/prebid/server/functional/HttpSettingsSpec.groovy b/src/test/groovy/org/prebid/server/functional/HttpSettingsSpec.groovy index 47c607fc64c..fcd830fd8ce 100644 --- a/src/test/groovy/org/prebid/server/functional/HttpSettingsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/HttpSettingsSpec.groovy @@ -6,7 +6,7 @@ import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.event.EventRequest import org.prebid.server.functional.model.request.setuid.SetuidRequest -import org.prebid.server.functional.model.request.setuid.UidsCookie +import org.prebid.server.functional.model.UidsCookie import org.prebid.server.functional.model.request.vtrack.VtrackRequest import org.prebid.server.functional.model.request.vtrack.xml.Vast import org.prebid.server.functional.service.PrebidServerException @@ -117,8 +117,10 @@ class HttpSettingsSpec extends BaseSpec { def response = prebidServerService.sendSetUidRequest(request, uidsCookie) then: "Response should contain uids cookie" - assert response.uidsCookie - assert !response.responseBody?.isEmpty() + assert response.uidsCookie.bday + assert !response.uidsCookie.tempUIDs + assert !response.uidsCookie.uids + assert response.responseBody == ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png") and: "There should be only one account request" assert httpSettings.getRequestCount(request.account) == 1 diff --git a/src/test/groovy/org/prebid/server/functional/SetuidSpec.groovy b/src/test/groovy/org/prebid/server/functional/SetuidSpec.groovy new file mode 100644 index 00000000000..b5793fc4f9b --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/SetuidSpec.groovy @@ -0,0 +1,36 @@ +package org.prebid.server.functional + +import org.prebid.server.functional.model.UidsCookie +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.request.setuid.SetuidRequest +import org.prebid.server.functional.service.PrebidServerException +import org.prebid.server.functional.util.PBSUtils +import spock.lang.PendingFeature + +class SetuidSpec extends BaseSpec { + + @PendingFeature + def "PBS should return an error for setuid request when the timeout time is exceeded"() { + given: "PBS with timeout configuration" + // Using minimal allowed time for timeout (1ms) to get timeout error. + def pbsService = pbsServiceFactory.getService(["setuid.default-timeout-ms": "1"]) + + and: "Default setuid request with account" + def request = SetuidRequest.defaultSetuidRequest + def uidsCookie = UidsCookie.defaultUidsCookie + request.account = PBSUtils.randomNumber.toString() + + and: "Account in the DB" + def account = new Account(uuid: request.account, eventsEnabled: true) + accountDao.save(account) + + when: "PBS processes setuid request" + pbsService.sendSetUidRequest(request, uidsCookie) + + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == 500 + assert exception.responseBody.contains("Timed out while executing SQL query") + + } +} diff --git a/src/test/groovy/org/prebid/server/functional/SmokeSpec.groovy b/src/test/groovy/org/prebid/server/functional/SmokeSpec.groovy index c6894e1e778..2c52a691b2c 100644 --- a/src/test/groovy/org/prebid/server/functional/SmokeSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/SmokeSpec.groovy @@ -8,7 +8,7 @@ import org.prebid.server.functional.model.request.cookiesync.CookieSyncRequest import org.prebid.server.functional.model.request.event.EventRequest import org.prebid.server.functional.model.request.logging.httpinteraction.HttpInteractionRequest import org.prebid.server.functional.model.request.setuid.SetuidRequest -import org.prebid.server.functional.model.request.setuid.UidsCookie +import org.prebid.server.functional.model.UidsCookie import org.prebid.server.functional.model.request.vtrack.VtrackRequest import org.prebid.server.functional.model.request.vtrack.xml.Vast import org.prebid.server.functional.model.response.cookiesync.CookieSyncResponse @@ -107,8 +107,11 @@ class SmokeSpec extends BaseSpec { def response = defaultPbsService.sendSetUidRequest(request, uidsCookie) then: "Response should contain uids cookie" - assert response.uidsCookie - assert !response.responseBody?.isEmpty() + assert response.uidsCookie.bday + assert !response.uidsCookie.tempUIDs + assert !response.uidsCookie.uids + assert response.responseBody == + ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png") } def "PBS should get uids cookie"() { diff --git a/src/test/groovy/org/prebid/server/functional/VtrackSpec.groovy b/src/test/groovy/org/prebid/server/functional/VtrackSpec.groovy new file mode 100644 index 00000000000..bfb2200bed0 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/VtrackSpec.groovy @@ -0,0 +1,28 @@ +package org.prebid.server.functional + +import org.prebid.server.functional.model.request.vtrack.VtrackRequest +import org.prebid.server.functional.model.request.vtrack.xml.Vast +import org.prebid.server.functional.service.PrebidServerException +import org.prebid.server.functional.util.PBSUtils + +class VtrackSpec extends BaseSpec { + + def "PBS should return an error for vtrack request when the timeout time is exceeded"() { + given: "PBS with timeout configuration" + // Using minimal allowed time for timeout (1ms) to get timeout error. + def pbsService = pbsServiceFactory.getService(["vtrack.default-timeout-ms": "1"]) + + and: "Default VtrackRequest" + def payload = PBSUtils.randomNumber.toString() + def request = VtrackRequest.getDefaultVtrackRequest(mapper.encodeXml(Vast.getDefaultVastModel(payload))) + def accountId = PBSUtils.randomNumber.toString() + + when: "PBS processes vtrack request" + pbsService.sendVtrackRequest(request, accountId) + + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == 500 + assert exception.responseBody.contains("Timed out while executing SQL query") + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidsCookie.groovy b/src/test/groovy/org/prebid/server/functional/model/UidsCookie.groovy similarity index 80% rename from src/test/groovy/org/prebid/server/functional/model/request/setuid/UidsCookie.groovy rename to src/test/groovy/org/prebid/server/functional/model/UidsCookie.groovy index 4897eb7b672..ce11fdd8c6b 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidsCookie.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/UidsCookie.groovy @@ -1,7 +1,8 @@ -package org.prebid.server.functional.model.request.setuid +package org.prebid.server.functional.model import com.fasterxml.jackson.annotation.JsonFormat import groovy.transform.ToString +import org.prebid.server.functional.model.request.setuid.UidWithExpiry import java.time.Clock import java.time.ZonedDateTime @@ -12,7 +13,7 @@ class UidsCookie { Map uids Map tempUIDs Boolean optout - @JsonFormat(pattern = "yyyy-MM-dd'T'hh:mm:ss.SSS'Z'", timezone = "UTC") + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") ZonedDateTime bday static UidsCookie getDefaultUidsCookie() { diff --git a/src/test/groovy/org/prebid/server/functional/model/db/StoredRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/db/StoredRequest.groovy index 0b41da34e10..abb53cbc57c 100644 --- a/src/test/groovy/org/prebid/server/functional/model/db/StoredRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/db/StoredRequest.groovy @@ -33,4 +33,9 @@ class StoredRequest { static StoredRequest getDbStoredRequest(AmpRequest ampRequest, BidRequest bidRequest) { new StoredRequest(reqid: ampRequest.tagId, accountId: ampRequest.account, requestData: bidRequest) } + + static StoredRequest getDbStoredRequest(BidRequest bidRequest, BidRequest storedRequest) { + new StoredRequest(reqid: bidRequest.ext.prebid.storedRequest.id, accountId: bidRequest.site.publisher.id, + requestData: storedRequest) + } } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy index 515e760b5cc..413e9f0cec7 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy @@ -12,7 +12,7 @@ class Prebid { Integer debug Targeting targeting PrebidCache cache - StoredRequest storedRequest + PrebidStoredRequest storedRequest Amp amp Channel channel List schains diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidStoredRequest.groovy similarity index 85% rename from src/test/groovy/org/prebid/server/functional/model/request/auction/StoredRequest.groovy rename to src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidStoredRequest.groovy index d392a3ab817..549a70e3b58 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/PrebidStoredRequest.groovy @@ -3,7 +3,7 @@ package org.prebid.server.functional.model.request.auction import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -class StoredRequest { +class PrebidStoredRequest { String id } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy b/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy index 6cd6b83f689..d272e34099c 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy @@ -9,6 +9,6 @@ import java.time.ZonedDateTime class UidWithExpiry { String uid - @JsonFormat(pattern = "yyyy-MM-dd'T'hh:mm:ss.SSS'Z'", timezone = "UTC") + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") ZonedDateTime expires } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy index 3bef4783280..b499891449c 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.response.setuid import groovy.transform.ToString -import io.restassured.http.Cookie +import org.prebid.server.functional.model.UidsCookie @ToString(includeNames = true, ignoreNulls = true) class SetuidResponse { - Cookie uidsCookie - String responseBody + UidsCookie uidsCookie + Byte[] responseBody } diff --git a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy index 32e2db11e72..80b533a5dc2 100644 --- a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy +++ b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy @@ -6,6 +6,7 @@ import io.restassured.authentication.BasicAuthScheme import io.restassured.builder.RequestSpecBuilder import io.restassured.response.Response import io.restassured.specification.RequestSpecification +import org.prebid.server.functional.model.UidsCookie import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.mock.services.prebidcache.response.PrebidCacheResponse import org.prebid.server.functional.model.request.amp.AmpRequest @@ -14,7 +15,6 @@ import org.prebid.server.functional.model.request.cookiesync.CookieSyncRequest import org.prebid.server.functional.model.request.event.EventRequest import org.prebid.server.functional.model.request.logging.httpinteraction.HttpInteractionRequest import org.prebid.server.functional.model.request.setuid.SetuidRequest -import org.prebid.server.functional.model.request.setuid.UidsCookie import org.prebid.server.functional.model.request.vtrack.VtrackRequest import org.prebid.server.functional.model.response.amp.AmpResponse import org.prebid.server.functional.model.response.amp.RawAmpResponse @@ -147,8 +147,8 @@ class PrebidServerService { checkResponseStatusCode(response) def setuidResponse = new SetuidResponse() - setuidResponse.uidsCookie = response.detailedCookie("uids") - setuidResponse.responseBody = response.asString() + setuidResponse.uidsCookie = getDecodedUidsCookie(response) + setuidResponse.responseBody = response.asByteArray() setuidResponse } @@ -277,6 +277,15 @@ class PrebidServerService { response.headers().collectEntries { [it.name, it.value] } } + private UidsCookie getDecodedUidsCookie(Response response) { + def uids = response.detailedCookie("uids")?.value + if (uids) { + return mapper.decode(new String(Base64.urlDecoder.decode(uids)), UidsCookie) + } else { + throw new IllegalStateException("uids cookie is missing in response") + } + } + List getLogsByTime(Instant testStart, Instant testEnd = Instant.now()) { if (!testEnd.isAfter(testStart)) { diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy index ee13e094c6d..ae34e2fe71e 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy @@ -75,7 +75,7 @@ LIMIT 1 } PrebidServerContainer withDebug() { - withEnv("JAVA_TOOL_OPTIONS", "-agentlib:jdwp=transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=n") + withEnv("JAVA_TOOL_OPTIONS", "-agentlib:jdwp=transport=dt_socket,address=*:$DEBUG_PORT,server=y,suspend=n") } void withMysql(String host, int port, String dbname, String user, String password) { From f708b094c5f6a43865ec34ef4c5dfb38afee82d3 Mon Sep 17 00:00:00 2001 From: mtuchkova <47950518+mtuchkova@users.noreply.github.com> Date: Mon, 15 Nov 2021 14:57:17 +0200 Subject: [PATCH 04/26] Tests for bidders not satisfying the json-schema (#1567) --- .../server/functional/BidderParamsSpec.groovy | 96 +++++++++++++++++++ .../functional/StoredResponseSpec.groovy | 38 ++++++++ .../functional/model/bidder/AppNexus.groovy | 4 +- .../functional/model/bidder/Generic.groovy | 1 + .../config/AccountAnalyticsConfig.groovy | 4 +- .../model/config/AccountAuctionConfig.groovy | 4 +- .../model/config/AccountCcpaConfig.groovy | 4 +- .../model/config/AccountConfig.groovy | 4 +- .../config/AccountCookieSyncConfig.groovy | 4 +- .../model/config/AccountGdprConfig.groovy | 4 +- .../config/AccountHooksConfiguration.groovy | 4 +- .../model/config/AccountPrivacyConfig.groovy | 4 +- .../model/config/ExecutionGroup.groovy | 4 +- .../functional/model/config/HookId.groovy | 4 +- .../model/config/PurposeConfig.groovy | 4 +- .../model/config/SpecialFeatureConfig.groovy | 4 +- .../functional/model/db/StoredResponse.groovy | 8 +- .../model/request/amp/AmpRequest.groovy | 4 +- .../model/request/auction/ImpExt.groovy | 3 - .../model/request/auction/ImpExtPrebid.groovy | 4 + .../model/request/auction/MultiBid.groovy | 4 +- .../model/request/auction/Prebid.groovy | 4 +- .../model/request/auction/RegsExt.groovy | 4 +- .../auction/StoredAuctionResponse.groovy | 9 ++ .../model/request/auction/Targeting.groovy | 4 +- .../cookiesync/CookieSyncRequest.groovy | 4 +- .../model/response/amp/AmpResponseExt.groovy | 3 +- .../util/ObjectMapperWrapper.groovy | 2 +- .../prebid/server/functional/db_schema.sql | 4 +- 29 files changed, 195 insertions(+), 49 deletions(-) create mode 100644 src/test/groovy/org/prebid/server/functional/StoredResponseSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/auction/StoredAuctionResponse.groovy diff --git a/src/test/groovy/org/prebid/server/functional/BidderParamsSpec.groovy b/src/test/groovy/org/prebid/server/functional/BidderParamsSpec.groovy index 47fca4e090e..bd3564fc633 100644 --- a/src/test/groovy/org/prebid/server/functional/BidderParamsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/BidderParamsSpec.groovy @@ -4,18 +4,23 @@ import io.qameta.allure.Issue import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.bidder.Generic import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Device import org.prebid.server.functional.model.request.auction.Geo +import org.prebid.server.functional.model.request.auction.PrebidStoredRequest import org.prebid.server.functional.model.request.auction.RegsExt import org.prebid.server.functional.model.request.vtrack.VtrackRequest import org.prebid.server.functional.model.request.vtrack.xml.Vast import org.prebid.server.functional.model.response.auction.ErrorType import org.prebid.server.functional.testcontainers.PBSTest import org.prebid.server.functional.util.PBSUtils +import spock.lang.PendingFeature import spock.lang.Unroll import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS +import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID @PBSTest class BidderParamsSpec extends BaseSpec { @@ -273,4 +278,95 @@ class BidderParamsSpec extends BaseSpec { assert response.ext?.errors[ErrorType.GENERIC]*.code == [999] assert response.ext?.errors[ErrorType.GENERIC]*.message == ["no empty host accepted"] } + + @PendingFeature + def "PBS should reject bidder when bidder params from request doesn't satisfy json-schema for auction request"() { + given: "BidRequest with bad bidder datatype" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0].ext.prebid.bidder.generic.exampleProperty = PBSUtils.randomNumber + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder should be dropped" + assert response.ext?.errors[ErrorType.GENERIC]*.code == [999] + assert response.ext?.errors[ErrorType.GENERIC]*.message == + ["WARNING: request.imp[0].ext.prebid.bidder.generic was dropped with a reason: " + + "request.imp[0].ext.prebid.bidder.generic failed validation" + + "\$.exampleProperty: integer found, string expected", + "WARNING: request.imp[0].ext must contain at least one valid bidder"] + + and: "PBS should not call bidder" + assert bidder.getRequestCount(bidRequest.id) == 0 + + and: "seatbid should be empty" + assert response.seatbid.isEmpty() + } + + @PendingFeature + def "PBS should reject bidder when bidder params from stored request doesn't satisfy json-schema for auction request"() { + given: "BidRequest with stored request, without imp" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.storedRequest = new PrebidStoredRequest(id: PBSUtils.randomNumber) + imp = null + } + + and: "Default stored request with bad bidder datatype" + def storedRequestModel = BidRequest.defaultStoredRequest.tap { + imp[0].ext.prebid.bidder.generic.exampleProperty = PBSUtils.randomNumber + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getDbStoredRequest(bidRequest, storedRequestModel) + storedRequestDao.save(storedRequest) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder should be dropped" + assert response.ext?.errors[ErrorType.GENERIC]*.code == [999] + assert response.ext?.errors[ErrorType.GENERIC]*.message == + ["WARNING: request.imp[0].ext.prebid.bidder.generic was dropped with a reason: " + + "request.imp[0].ext.prebid.bidder.generic failed validation" + + "\$.exampleProperty: integer found, string expected", + "WARNING: request.imp[0].ext must contain at least one valid bidder"] + + and: "PBS should not call bidder" + assert bidder.getRequestCount(bidRequest.id) == 0 + + and: "seatbid should be empty" + assert response.seatbid.isEmpty() + } + + @PendingFeature + def "PBS should reject bidder when bidder params from stored request doesn't satisfy json-schema for amp request"() { + given: "AmpRequest with bad bidder datatype" + def ampRequest = AmpRequest.defaultAmpRequest + def ampStoredRequest = BidRequest.defaultBidRequest.tap { + site.publisher.id = ampRequest.account + imp[0].ext.prebid.bidder.generic.exampleProperty = PBSUtils.randomNumber + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = defaultPbsService.sendAmpRequest(ampRequest) + + then: "Bidder should be dropped" + assert response.ext?.errors[ErrorType.GENERIC]*.code == [999] + assert response.ext?.errors[PREBID]*.message == + ["WARNING: request.imp[0].ext.prebid.bidder.generic was dropped with a reason: " + + "request.imp[0].ext.prebid.bidder.generic failed validation" + + "\$.exampleProperty: integer found, string expected", + "WARNING: request.imp[0].ext must contain at least one valid bidder"] + + and: "PBS should not call bidder" + assert bidder.getRequestCount(ampStoredRequest.id) == 0 + + and: "targeting should be empty" + assert response.targeting.isEmpty() + } } diff --git a/src/test/groovy/org/prebid/server/functional/StoredResponseSpec.groovy b/src/test/groovy/org/prebid/server/functional/StoredResponseSpec.groovy new file mode 100644 index 00000000000..4e5f7525fd8 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/StoredResponseSpec.groovy @@ -0,0 +1,38 @@ +package org.prebid.server.functional + +import org.prebid.server.functional.model.db.StoredResponse +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.StoredAuctionResponse +import org.prebid.server.functional.model.response.auction.BidResponse +import org.prebid.server.functional.testcontainers.PBSTest +import org.prebid.server.functional.util.PBSUtils +import spock.lang.PendingFeature + +@PBSTest +class StoredResponseSpec extends BaseSpec { + + @PendingFeature + def "PBS should not fail auction with storedAuctionResponse when request bidder params doesn't satisfy json-schema"() { + given: "BidRequest with bad bidder datatype and storedAuctionResponse" + def storedResponseId = PBSUtils.randomNumber + def bidRequest = BidRequest.defaultBidRequest.tap { + imp[0].ext.prebid.bidder.generic.exampleProperty = PBSUtils.randomNumber + imp[0].ext.prebid.storedAuctionResponse = new StoredAuctionResponse(id: storedResponseId) + } + + and: "Stored response in DB" + def responseData = BidResponse.getDefaultBidResponse(bidRequest) + def storedResponse = new StoredResponse(resid: storedResponseId, responseData: responseData) + storedResponseDao.save(storedResponse) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Response should correspond to stored response" + assert response.seatbid == responseData.seatbid + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/AppNexus.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/AppNexus.groovy index 63fb2dd386c..0e79ec00157 100644 --- a/src/test/groovy/org/prebid/server/functional/model/bidder/AppNexus.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/bidder/AppNexus.groovy @@ -1,12 +1,12 @@ package org.prebid.server.functional.model.bidder -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString import org.prebid.server.functional.util.PBSUtils @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) class AppNexus implements BidderAdapter { Integer placementId diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/Generic.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/Generic.groovy index e2ddc71dc0c..8bf9d029d10 100644 --- a/src/test/groovy/org/prebid/server/functional/model/bidder/Generic.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/bidder/Generic.groovy @@ -2,6 +2,7 @@ package org.prebid.server.functional.model.bidder class Generic implements BidderAdapter { + Object exampleProperty Integer firstParam Integer secondParam } diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountAnalyticsConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountAnalyticsConfig.groovy index 96dcf8dcf30..fdbc492b93b 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountAnalyticsConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountAnalyticsConfig.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class AccountAnalyticsConfig { Map auctionEvents diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy index fcad48ccab9..053cd84877a 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class AccountAuctionConfig { String priceGranularity diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountCcpaConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountCcpaConfig.groovy index dc7d0e27d00..155889280f6 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountCcpaConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountCcpaConfig.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class AccountCcpaConfig { Boolean enabled diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy index 48dda7885c3..f344c203618 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy @@ -1,6 +1,6 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.EqualsAndHashCode import groovy.transform.ToString @@ -8,7 +8,7 @@ import org.prebid.server.functional.model.AccountStatus @EqualsAndHashCode @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class AccountConfig { String id diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountCookieSyncConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountCookieSyncConfig.groovy index 5c76a655244..4a3c852bd63 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountCookieSyncConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountCookieSyncConfig.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class AccountCookieSyncConfig { Integer defaultLimit diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountGdprConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountGdprConfig.groovy index 3782cb82e7e..850c95aa2c2 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountGdprConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountGdprConfig.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class AccountGdprConfig { Boolean enabled diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountHooksConfiguration.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountHooksConfiguration.groovy index 2dc0336662e..f9b5b675aa3 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountHooksConfiguration.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountHooksConfiguration.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class AccountHooksConfiguration { ExecutionPlan executionPlan diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy index 4cd33fd8a4d..ef4932f9711 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountPrivacyConfig.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class AccountPrivacyConfig { AccountGdprConfig gdpr diff --git a/src/test/groovy/org/prebid/server/functional/model/config/ExecutionGroup.groovy b/src/test/groovy/org/prebid/server/functional/model/config/ExecutionGroup.groovy index 45f15a29cd8..d67d45bcb49 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/ExecutionGroup.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/ExecutionGroup.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class ExecutionGroup { Long timeout diff --git a/src/test/groovy/org/prebid/server/functional/model/config/HookId.groovy b/src/test/groovy/org/prebid/server/functional/model/config/HookId.groovy index 765de8d9068..cda44911ce4 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/HookId.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/HookId.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class HookId { String moduleCode diff --git a/src/test/groovy/org/prebid/server/functional/model/config/PurposeConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/PurposeConfig.groovy index e944200980c..08a21ae226d 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/PurposeConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/PurposeConfig.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class PurposeConfig { PurposeEnforcement enforcePurpose diff --git a/src/test/groovy/org/prebid/server/functional/model/config/SpecialFeatureConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/SpecialFeatureConfig.groovy index e8c9cf96b72..c02f0406dcc 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/SpecialFeatureConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/SpecialFeatureConfig.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.config -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class SpecialFeatureConfig { Boolean enforce diff --git a/src/test/groovy/org/prebid/server/functional/model/db/StoredResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/db/StoredResponse.groovy index 2e75ddd9144..0d733b108f2 100644 --- a/src/test/groovy/org/prebid/server/functional/model/db/StoredResponse.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/db/StoredResponse.groovy @@ -21,9 +21,9 @@ class StoredResponse { @GeneratedValue(strategy = IDENTITY) @Column(name = "id") Integer id - @Column(name = "uuid") - String uuid - @Column(name = "config") + @Column(name = "resid") + String resid + @Column(name = "responseData") @Convert(converter = StoredResponseConfigTypeConverter) - BidResponse config + BidResponse responseData } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy index e7230cd0b81..f2b98f89967 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy @@ -1,12 +1,12 @@ package org.prebid.server.functional.model.request.amp -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString import org.prebid.server.functional.util.PBSUtils @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) class AmpRequest { String tagId diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExt.groovy index 2d7c9f2c4e1..5538572912e 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExt.groovy @@ -10,12 +10,9 @@ import org.prebid.server.functional.model.bidder.Rubicon class ImpExt { ImpExtPrebid prebid - Generic generic - @Deprecated Rubicon rubicon - @Deprecated @JsonProperty("appnexus") AppNexus appNexus diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExtPrebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExtPrebid.groovy index 29200bd300b..dc9c603e31e 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExtPrebid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/ImpExtPrebid.groovy @@ -1,11 +1,15 @@ package org.prebid.server.functional.model.request.auction +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy) class ImpExtPrebid { Bidder bidder + StoredAuctionResponse storedAuctionResponse static ImpExtPrebid getDefaultImpExtPrebid() { new ImpExtPrebid().tap { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/MultiBid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/MultiBid.groovy index 22fd5451629..8a8a1ac5fa0 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/MultiBid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/MultiBid.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.request.auction -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = false) -@JsonNaming(PropertyNamingStrategy.LowerCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) class MultiBid { String bidder diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy index 413e9f0cec7..faaa43d5f5d 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.request.auction -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString import org.prebid.server.functional.model.bidder.BidderName -@JsonNaming(PropertyNamingStrategy.LowerCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) @ToString(includeNames = true, ignoreNulls = true) class Prebid { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy index 2647e3ac020..2ea1796a76b 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.request.auction -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) class RegsExt { Integer gdpr diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredAuctionResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredAuctionResponse.groovy new file mode 100644 index 00000000000..8c21cf3f5a4 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredAuctionResponse.groovy @@ -0,0 +1,9 @@ +package org.prebid.server.functional.model.request.auction + +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +class StoredAuctionResponse { + + String id +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Targeting.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Targeting.groovy index b27c0e9c267..3ae3aa6ad07 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Targeting.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Targeting.groovy @@ -1,11 +1,11 @@ package org.prebid.server.functional.model.request.auction -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.LowerCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.LowerCaseStrategy) class Targeting { PriceGranularity priceGranularity diff --git a/src/test/groovy/org/prebid/server/functional/model/request/cookiesync/CookieSyncRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/cookiesync/CookieSyncRequest.groovy index df3e4858fb6..72f569d1679 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/cookiesync/CookieSyncRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/cookiesync/CookieSyncRequest.groovy @@ -1,7 +1,7 @@ package org.prebid.server.functional.model.request.cookiesync import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString import org.prebid.server.functional.model.bidder.BidderName @@ -9,7 +9,7 @@ import org.prebid.server.functional.model.bidder.BidderName import static org.prebid.server.functional.model.bidder.BidderName.GENERIC @ToString(includeNames = true, ignoreNulls = true) -@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) class CookieSyncRequest { List bidders diff --git a/src/test/groovy/org/prebid/server/functional/model/response/amp/AmpResponseExt.groovy b/src/test/groovy/org/prebid/server/functional/model/response/amp/AmpResponseExt.groovy index a9363559dbb..8c42c5068b2 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/amp/AmpResponseExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/amp/AmpResponseExt.groovy @@ -2,9 +2,10 @@ package org.prebid.server.functional.model.response.amp import org.prebid.server.functional.model.response.BidderError import org.prebid.server.functional.model.response.Debug +import org.prebid.server.functional.model.response.auction.ErrorType class AmpResponseExt { Debug debug - Map> errors + Map> errors } diff --git a/src/test/groovy/org/prebid/server/functional/util/ObjectMapperWrapper.groovy b/src/test/groovy/org/prebid/server/functional/util/ObjectMapperWrapper.groovy index ca845de713a..bf44c768156 100644 --- a/src/test/groovy/org/prebid/server/functional/util/ObjectMapperWrapper.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/ObjectMapperWrapper.groovy @@ -35,7 +35,7 @@ class ObjectMapperWrapper { } Map toMap(Object object) { - mapper.convertValue(object, Map.class) + mapper.convertValue(object, Map) } JsonNode toJsonNode(String jsonString) { diff --git a/src/test/resources/org/prebid/server/functional/db_schema.sql b/src/test/resources/org/prebid/server/functional/db_schema.sql index 47dc48e509e..8540e1441c3 100644 --- a/src/test/resources/org/prebid/server/functional/db_schema.sql +++ b/src/test/resources/org/prebid/server/functional/db_schema.sql @@ -42,6 +42,6 @@ CREATE TABLE stored_imps CREATE TABLE stored_responses ( id SERIAL PRIMARY KEY, - uuid varchar(40) NOT NULL, - config varchar(1024) + resid varchar(40) NOT NULL, + responseData varchar(1024) ); From 0b1c14442dd24b2b88994892229481152e1ef232 Mon Sep 17 00:00:00 2001 From: mtuchkova <47950518+mtuchkova@users.noreply.github.com> Date: Mon, 15 Nov 2021 15:41:10 +0200 Subject: [PATCH 05/26] Update tests for PBC metrics (#1574) --- .../prebid/server/functional/BaseSpec.groovy | 10 ++++ .../{MetricsSpec.groovy => CacheSpec.groovy} | 48 ++++++++----------- .../model/request/auction/BidRequest.groovy | 3 ++ .../model/response/auction/Cache.groovy | 7 +++ .../model/response/auction/CacheAsset.groovy | 7 +++ .../model/response/auction/Prebid.groovy | 1 + 6 files changed, 48 insertions(+), 28 deletions(-) rename src/test/groovy/org/prebid/server/functional/{MetricsSpec.groovy => CacheSpec.groovy} (70%) create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/auction/Cache.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/auction/CacheAsset.groovy diff --git a/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy index 6007c8e80c9..8c0ae6afdd9 100644 --- a/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy @@ -53,4 +53,14 @@ abstract class BaseSpec extends Specification { protected static int getRandomTimeout() { PBSUtils.getRandomNumber(MIN_TIMEOUT, MAX_TIMEOUT) } + + protected static Number getCurrentMetricValue(String name) { + def response = defaultPbsService.sendCollectedMetricsRequest() + response[name] ?: 0 + } + + protected static void flushMetrics() { + // flushing PBS metrics by receiving collected metrics so that each new test works with a fresh state + defaultPbsService.sendCollectedMetricsRequest() + } } diff --git a/src/test/groovy/org/prebid/server/functional/MetricsSpec.groovy b/src/test/groovy/org/prebid/server/functional/CacheSpec.groovy similarity index 70% rename from src/test/groovy/org/prebid/server/functional/MetricsSpec.groovy rename to src/test/groovy/org/prebid/server/functional/CacheSpec.groovy index 3980f84179d..b7482deb3e4 100644 --- a/src/test/groovy/org/prebid/server/functional/MetricsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/CacheSpec.groovy @@ -1,22 +1,15 @@ package org.prebid.server.functional import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.Targeting import org.prebid.server.functional.model.request.vtrack.VtrackRequest import org.prebid.server.functional.model.request.vtrack.xml.Vast import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.testcontainers.PBSTest import org.prebid.server.functional.util.PBSUtils -import spock.lang.PendingFeature -// TODO: move metrics tests to the respective specifications as the metrics are a part of normal PBS operation -// TODO: this won't work as is for banner type as we need to signal PBS to store bid in the cache @PBSTest -class MetricsSpec extends BaseSpec { - - def setup() { - // flushing PBS metrics by receiving collected metrics so that each new test works with a fresh state - defaultPbsService.sendCollectedMetricsRequest() - } +class CacheSpec extends BaseSpec { def "PBS should update prebid_cache.creative_size.xml metric when xml creative is received"() { given: "Current value of metric prebid_cache.requests.ok" @@ -41,12 +34,11 @@ class MetricsSpec extends BaseSpec { assert metrics["account.${accountId}.prebid_cache.requests.ok" as String] == 1 } - @PendingFeature def "PBS should update prebid_cache.creative_size.json metric when json creative is received"() { given: "Current value of metric prebid_cache.requests.ok" def initialValue = getCurrentMetricValue("prebid_cache.requests.ok") - and: "Default BidRequest" + and: "Default BidRequest with cache, targeting" def bidRequest = BidRequest.defaultBidRequest bidRequest.enableCache() @@ -58,14 +50,13 @@ class MetricsSpec extends BaseSpec { and: "Set bidder response" bidder.setResponse(bidRequest.id, bidResponse) - when: "PBS processes amp request" + when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) and: "PBS processes collected metrics request" def metrics = defaultPbsService.sendCollectedMetricsRequest() then: "prebid_cache.creative_size.json should be update" - def creativeSize = adm.bytes.length assert metrics["prebid_cache.creative_size.json"] == creativeSize assert metrics["prebid_cache.requests.ok"] == initialValue + 1 @@ -75,28 +66,29 @@ class MetricsSpec extends BaseSpec { assert metrics["account.${accountId}.prebid_cache.requests.ok" as String] == 1 } - def "PBS should increase request_time metric when auction was held"() { - given: "Current value of metric request_time" - def initialValue = getCurrentMetricValue("request_time") - - and: "Default basic BidRequest with generic bidder" + def "PBS should cache bids when targeting is specified"() { + given: "Default BidRequest with cache, targeting" def bidRequest = BidRequest.defaultBidRequest + bidRequest.enableCache() + bidRequest.ext.prebid.targeting = new Targeting() when: "PBS processes auction request" defaultPbsService.sendAuctionRequest(bidRequest) - and: "PBS processes collected metrics request" - def response = defaultPbsService.sendCollectedMetricsRequest() + then: "PBS should call PBC" + assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 1 + } - then: "Response should contain metrics" - assert response.size() > 0 + def "PBS should not cache bids when targeting isn't specified"() { + given: "Default BidRequest with cache" + def bidRequest = BidRequest.defaultBidRequest + bidRequest.enableCache() + bidRequest.ext.prebid.targeting = null - and: "request_time metric should be increased" - assert response["request_time"] == initialValue + 1 - } + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) - private static int getCurrentMetricValue(String name) { - def response = defaultPbsService.sendCollectedMetricsRequest() - response[name] as Integer ?: 0 + then: "PBS should not call PBC" + assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 0 } } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy index 92ae6d4bc71..04d0dcc0d70 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy @@ -70,6 +70,9 @@ class BidRequest { if (ext.prebid == null) { ext.prebid = new Prebid() } + if (ext.prebid.targeting == null) { + ext.prebid.targeting = new Targeting() + } if (ext.prebid.cache == null) { ext.prebid.cache = new PrebidCache() } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Cache.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Cache.groovy new file mode 100644 index 00000000000..66edb605962 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Cache.groovy @@ -0,0 +1,7 @@ +package org.prebid.server.functional.model.response.auction + +class Cache { + + CacheAsset bids + CacheAsset vastXml +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/CacheAsset.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/CacheAsset.groovy new file mode 100644 index 00000000000..823802729f6 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/CacheAsset.groovy @@ -0,0 +1,7 @@ +package org.prebid.server.functional.model.response.auction + +class CacheAsset { + + String url + String cacheId +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Prebid.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Prebid.groovy index 143582391f9..f5851e77bc8 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/Prebid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Prebid.groovy @@ -5,4 +5,5 @@ class Prebid { MediaType type Map targeting String targetbiddercode + Cache cache } From a45832571f7433297068fb2e13155b9b2426d254 Mon Sep 17 00:00:00 2001 From: rpanchyk Date: Tue, 16 Nov 2021 12:06:57 +0200 Subject: [PATCH 06/26] Add maven parents to submodules (#1579) --- extra/bundle/pom.xml | 10 ++++++++-- extra/modules/pom.xml | 11 ++++++++++- pom.xml | 17 ++++++++--------- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index d6fd6ecbedf..5db15912859 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -2,11 +2,17 @@ 4.0.0 - org.prebid + + org.prebid + prebid-server-aggregator + 1.79.0-SNAPSHOT + ../../extra/pom.xml + + prebid-server-bundle - 1.79.0-SNAPSHOT prebid-server-bundle + Creates bundle (fat jar) with PBS-Core and other submodules listed as dependency UTF-8 diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index 6715bf31ecd..469dfe6cf45 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -2,11 +2,20 @@ 4.0.0 + + org.prebid + prebid-server-aggregator + 1.79.0-SNAPSHOT + ../../extra/pom.xml + + org.prebid.server.hooks.modules all-modules - 1.79.0-SNAPSHOT pom + all-modules + Umbrellas all PBS modules + ortb2-blocking diff --git a/pom.xml b/pom.xml index d2ee57657d6..533d8229995 100644 --- a/pom.xml +++ b/pom.xml @@ -2,15 +2,20 @@ 4.0.0 - org.prebid + + org.prebid + prebid-server-aggregator + 1.79.0-SNAPSHOT + extra/pom.xml + + prebid-server - 1.79.0-SNAPSHOT prebid-server Prebid Server (Server-side Header Bidding) Prebid.org - http://prebid.org/ + https://prebid.org/ @@ -432,12 +437,6 @@ ${bytebuddy.version} test - - net.bytebuddy - byte-buddy - ${bytebuddy.version} - test - org.spockframework spock-core From 0c9fde94efde8710d8ebe9b1de6a9ed0dc8dcaa1 Mon Sep 17 00:00:00 2001 From: rpanchyk Date: Tue, 16 Nov 2021 12:19:55 +0200 Subject: [PATCH 07/26] Prebid Server prepare release 1.79.0 --- extra/bundle/pom.xml | 6 +++--- extra/modules/ortb2-blocking/pom.xml | 2 +- extra/modules/pom.xml | 4 ++-- extra/pom.xml | 4 ++-- pom.xml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index 5db15912859..6ab6632cd43 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 1.79.0-SNAPSHOT + 1.79.0 ../../extra/pom.xml @@ -75,12 +75,12 @@ org.prebid prebid-server - 1.79.0-SNAPSHOT + 1.79.0 org.prebid.server.hooks.modules ortb2-blocking - 1.79.0-SNAPSHOT + 1.79.0 diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml index b1d39f63d68..f6469ff3536 100644 --- a/extra/modules/ortb2-blocking/pom.xml +++ b/extra/modules/ortb2-blocking/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 1.79.0-SNAPSHOT + 1.79.0 ortb2-blocking diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index 469dfe6cf45..3a618377a34 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 1.79.0-SNAPSHOT + 1.79.0 ../../extra/pom.xml @@ -26,7 +26,7 @@ 11 11 - 1.79.0-SNAPSHOT + 1.79.0 1.18.4 diff --git a/extra/pom.xml b/extra/pom.xml index fa96982a4e5..7840bdaf7e2 100644 --- a/extra/pom.xml +++ b/extra/pom.xml @@ -4,14 +4,14 @@ org.prebid prebid-server-aggregator - 1.79.0-SNAPSHOT + 1.79.0 pom https://github.com/prebid/prebid-server-java scm:git:git@github.com:prebid/prebid-server-java.git scm:git:git@github.com:prebid/prebid-server-java.git - HEAD + 1.79.0 diff --git a/pom.xml b/pom.xml index 533d8229995..6d811aa942f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 1.79.0-SNAPSHOT + 1.79.0 extra/pom.xml From 87a69acf5e2f82fe65f9226bee492cfa418abbe9 Mon Sep 17 00:00:00 2001 From: rpanchyk Date: Tue, 16 Nov 2021 12:49:40 +0200 Subject: [PATCH 08/26] Prebid Server prepare for next development iteration (#1580) --- extra/bundle/pom.xml | 6 +++--- extra/modules/ortb2-blocking/pom.xml | 2 +- extra/modules/pom.xml | 4 ++-- extra/pom.xml | 4 ++-- pom.xml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index 6ab6632cd43..a22e97f7758 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 1.79.0 + 1.80.0-SNAPSHOT ../../extra/pom.xml @@ -75,12 +75,12 @@ org.prebid prebid-server - 1.79.0 + 1.80.0-SNAPSHOT org.prebid.server.hooks.modules ortb2-blocking - 1.79.0 + 1.80.0-SNAPSHOT diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml index f6469ff3536..cd9c41d20c7 100644 --- a/extra/modules/ortb2-blocking/pom.xml +++ b/extra/modules/ortb2-blocking/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 1.79.0 + 1.80.0-SNAPSHOT ortb2-blocking diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index 3a618377a34..93a084cf9e6 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 1.79.0 + 1.80.0-SNAPSHOT ../../extra/pom.xml @@ -26,7 +26,7 @@ 11 11 - 1.79.0 + 1.80.0-SNAPSHOT 1.18.4 diff --git a/extra/pom.xml b/extra/pom.xml index 7840bdaf7e2..34869446a71 100644 --- a/extra/pom.xml +++ b/extra/pom.xml @@ -4,14 +4,14 @@ org.prebid prebid-server-aggregator - 1.79.0 + 1.80.0-SNAPSHOT pom https://github.com/prebid/prebid-server-java scm:git:git@github.com:prebid/prebid-server-java.git scm:git:git@github.com:prebid/prebid-server-java.git - 1.79.0 + HEAD diff --git a/pom.xml b/pom.xml index 6d811aa942f..4f9ece75ebd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 1.79.0 + 1.80.0-SNAPSHOT extra/pom.xml From 879d6c41e3640518a257b97556993a52a094f01e Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Fri, 19 Nov 2021 09:57:24 +0200 Subject: [PATCH 09/26] Add gpid to supported imp ext fields (#1585) --- .../requestfactory/Ortb2ImplicitParametersResolver.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2ImplicitParametersResolver.java b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2ImplicitParametersResolver.java index ecd9ea5fc53..a54382e0b69 100644 --- a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2ImplicitParametersResolver.java +++ b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2ImplicitParametersResolver.java @@ -43,7 +43,6 @@ import org.prebid.server.util.ObjectUtil; import org.prebid.server.util.StreamUtil; -import java.util.Arrays; import java.util.Collections; import java.util.Currency; import java.util.HashSet; @@ -62,8 +61,8 @@ public class Ortb2ImplicitParametersResolver { private static final String PREBID_EXT = "prebid"; private static final String BIDDER_EXT = "bidder"; - private static final Set IMP_EXT_NON_BIDDER_FIELDS = Collections.unmodifiableSet(new HashSet<>( - Arrays.asList(PREBID_EXT, "context", "all", "general", "skadn", "data"))); + private static final Set IMP_EXT_NON_BIDDER_FIELDS = + Set.of(PREBID_EXT, "context", "all", "general", "skadn", "data", "gpid"); private final boolean shouldCacheOnlyWinningBids; private final String adServerCurrency; From 0bc7d5c47682558c6c460a8399706a4e1c702eb3 Mon Sep 17 00:00:00 2001 From: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com> Date: Tue, 23 Nov 2021 13:13:28 +0200 Subject: [PATCH 10/26] Fix typo in Viewdeos alias. (#1586) --- src/main/resources/bidder-config/adtelligent.yaml | 4 ++-- .../org/prebid/server/it/test-application.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/bidder-config/adtelligent.yaml b/src/main/resources/bidder-config/adtelligent.yaml index d77336e071d..a9d0f2e516f 100644 --- a/src/main/resources/bidder-config/adtelligent.yaml +++ b/src/main/resources/bidder-config/adtelligent.yaml @@ -12,7 +12,7 @@ adapters: url: redirect-url: cookie-family-name: mediafuse - viewdoes: + viewdeos: enabled: false endpoint: http://ghb.sync.viewdeos.com/pbs/ortb meta-info: @@ -21,7 +21,7 @@ adapters: usersync: url: redirect-url: - cookie-family-name: viewdoes + cookie-family-name: viewdeos meta-info: maintainer-email: hb@adtelligent.com app-media-types: diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index 5cc7be651b9..eed5365eb3e 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -39,7 +39,7 @@ adapters.adtarget.endpoint=http://localhost:8090/adtarget-exchange adapters.adtelligent.enabled=true adapters.adtelligent.endpoint=http://localhost:8090/adtelligent-exchange adapters.adtelligent.aliases.mediafuse.enabled=true -adapters.adtelligent.aliases.viewdoes.enabled=true +adapters.adtelligent.aliases.viewdeos.enabled=true adapters.advangelists.enabled=true adapters.advangelists.endpoint=http://localhost:8090/advangelists-exchange?pubid={{PublisherID}} adapters.adxcg.enabled=true From c603ecc6c3356086d5e5389b2208e14c4471f3fe Mon Sep 17 00:00:00 2001 From: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com> Date: Tue, 23 Nov 2021 15:18:45 +0200 Subject: [PATCH 11/26] New Adapter: Streamkey (Alias for Adtelligent) (#1587) --- src/main/resources/bidder-config/adtelligent.yaml | 13 +++++++++++++ .../prebid/server/it/test-application.properties | 1 + 2 files changed, 14 insertions(+) diff --git a/src/main/resources/bidder-config/adtelligent.yaml b/src/main/resources/bidder-config/adtelligent.yaml index a9d0f2e516f..879c3ec55e9 100644 --- a/src/main/resources/bidder-config/adtelligent.yaml +++ b/src/main/resources/bidder-config/adtelligent.yaml @@ -22,6 +22,19 @@ adapters: url: redirect-url: cookie-family-name: viewdeos + streamkey: + endpoint: http://ghb.hb.streamkey.net/pbs/ortb + meta-info: + maintainer-email: contact@streamkey.tv + app-media-types: + - video + site-media-types: + - video + vendor-id: 0 + usersync: + url: + redirect-url: + cookie-family-name: streamkey meta-info: maintainer-email: hb@adtelligent.com app-media-types: diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index eed5365eb3e..acbea9d6d73 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -40,6 +40,7 @@ adapters.adtelligent.enabled=true adapters.adtelligent.endpoint=http://localhost:8090/adtelligent-exchange adapters.adtelligent.aliases.mediafuse.enabled=true adapters.adtelligent.aliases.viewdeos.enabled=true +adapters.adtelligent.aliases.streamkey.enabled=true adapters.advangelists.enabled=true adapters.advangelists.endpoint=http://localhost:8090/advangelists-exchange?pubid={{PublisherID}} adapters.adxcg.enabled=true From 69c69c07903df456e76888e1a9c86e953227fa87 Mon Sep 17 00:00:00 2001 From: bretg Date: Wed, 24 Nov 2021 05:33:34 -0500 Subject: [PATCH 12/26] Adding adagio's usersync uid param (#1593) --- src/main/resources/bidder-config/adagio.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/bidder-config/adagio.yaml b/src/main/resources/bidder-config/adagio.yaml index d73dc8ee15c..0b3023aa3cb 100644 --- a/src/main/resources/bidder-config/adagio.yaml +++ b/src/main/resources/bidder-config/adagio.yaml @@ -13,7 +13,7 @@ adapters: vendor-id: 617 usersync: url: https://mp.4dex.io/sync?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect= - redirect-url: /setuid?bidder=adagio&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}} + redirect-url: /setuid?bidder=adagio&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&uid={{UID}} cookie-family-name: adagio type: redirect support-cors: false From 895ea08f671da8e60e244497aa65e750db155d0e Mon Sep 17 00:00:00 2001 From: Bugxyb Date: Fri, 26 Nov 2021 17:40:35 +0800 Subject: [PATCH 13/26] Algorix: add Server Region Support (#1598) Co-authored-by: xunyunbo --- .../server/bidder/algorix/AlgorixBidder.java | 23 +++++++++++++++++++ .../ext/request/algorix/ExtImpAlgorix.java | 3 +++ src/main/resources/bidder-config/algorix.yaml | 2 +- .../static/bidder-params/algorix.json | 4 ++++ .../bidder/algorix/AlgorixBidderTest.java | 6 ++--- .../algorix/test-algorix-bid-request.json | 3 ++- .../algorix/test-auction-algorix-request.json | 3 ++- 7 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/algorix/AlgorixBidder.java b/src/main/java/org/prebid/server/bidder/algorix/AlgorixBidder.java index 095f0d95aa3..1f9df344b0b 100644 --- a/src/main/java/org/prebid/server/bidder/algorix/AlgorixBidder.java +++ b/src/main/java/org/prebid/server/bidder/algorix/AlgorixBidder.java @@ -40,6 +40,7 @@ public class AlgorixBidder implements Bidder { new TypeReference>() { }; + private static final String URL_REGION_MACRO = "{HOST}"; private static final String URL_SID_MACRO = "{SID}"; private static final String URL_TOKEN_MACRO = "{TOKEN}"; @@ -132,6 +133,27 @@ private static boolean isValidSizeValue(Integer value) { return value != null && value > 0; } + /** + * get Region Info From Algorix Ext Imp + * Default For Global EP, APAC for apse EP, USE for use EP + * + * @param extImp Algorix Ext Imp + * @return Region String + */ + private static String getRegionInfo(ExtImpAlgorix extImp) { + if (Objects.isNull(extImp.getRegion())) { + return "xyz"; + } + switch (extImp.getRegion()) { + case "APAC": + return "apac.xyz"; + case "USE": + return "use.xyz"; + default: + return "xyz"; + } + } + /** * Replace url macro * @@ -141,6 +163,7 @@ private static boolean isValidSizeValue(Integer value) { */ private static String resolveUrl(String endpoint, ExtImpAlgorix extImp) { return endpoint + .replace(URL_REGION_MACRO, getRegionInfo(extImp)) .replace(URL_SID_MACRO, extImp.getSid()) .replace(URL_TOKEN_MACRO, extImp.getToken()); } diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/algorix/ExtImpAlgorix.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/algorix/ExtImpAlgorix.java index aeed9e8b0a3..082bd748712 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/algorix/ExtImpAlgorix.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/algorix/ExtImpAlgorix.java @@ -20,4 +20,7 @@ public class ExtImpAlgorix { @JsonProperty("appId") String appId; + + @JsonProperty("region") + String region; } diff --git a/src/main/resources/bidder-config/algorix.yaml b/src/main/resources/bidder-config/algorix.yaml index b635834e2e6..cc1de94d5f6 100644 --- a/src/main/resources/bidder-config/algorix.yaml +++ b/src/main/resources/bidder-config/algorix.yaml @@ -1,6 +1,6 @@ adapters: algorix: - endpoint: https://xyz.svr-algorix.com/rtb/sa?sid={SID}&token={TOKEN} + endpoint: https://{HOST}.svr-algorix.com/rtb/sa?sid={SID}&token={TOKEN} meta-info: maintainer-email: prebid@algorix.co app-media-types: diff --git a/src/main/resources/static/bidder-params/algorix.json b/src/main/resources/static/bidder-params/algorix.json index 4be2025a7ad..95c94d4064e 100644 --- a/src/main/resources/static/bidder-params/algorix.json +++ b/src/main/resources/static/bidder-params/algorix.json @@ -21,6 +21,10 @@ "appId": { "type": "string", "description": "An ID which identifies this app of the impression" + }, + "region": { + "type": "string", + "description": "Server region for PBS request, null for global, APAC for SG Region, US for USE Region" } }, "required": ["sid", "token"] diff --git a/src/test/java/org/prebid/server/bidder/algorix/AlgorixBidderTest.java b/src/test/java/org/prebid/server/bidder/algorix/AlgorixBidderTest.java index 34a4981801b..0ab7ec0a4e7 100644 --- a/src/test/java/org/prebid/server/bidder/algorix/AlgorixBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/algorix/AlgorixBidderTest.java @@ -40,7 +40,7 @@ */ public class AlgorixBidderTest extends VertxTest { - private static final String ENDPOINT_URL = "https://xyz.svr-algorix.com/rtb/sa?sid={SID}&token={TOKEN}"; + private static final String ENDPOINT_URL = "https://{HOST}.svr-algorix.com/rtb/sa?sid={SID}&token={TOKEN}"; private AlgorixBidder algorixBidder; @@ -99,7 +99,7 @@ public void makeHttpRequestsShouldCreateCorrectURL() { assertThat(result.getValue()) .hasSize(1) .extracting(HttpRequest::getUri) - .containsExactly("https://xyz.svr-algorix.com/rtb/sa?sid=testSid&token=testToken"); + .containsExactly("https://apac.xyz.svr-algorix.com/rtb/sa?sid=testSid&token=testToken"); } @Test @@ -261,7 +261,7 @@ private static Imp givenImp(Function impCustomiz .id("123") .banner(Banner.builder().id("banner_id").build()) .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAlgorix.of("testSid", "testToken", "testPlacementId", "testAppId"))))) + ExtImpAlgorix.of("testSid", "testToken", "testPlacementId", "testAppId", "APAC"))))) .build(); } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-request.json index d2c97e8b7b4..6466e22c3aa 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-algorix-bid-request.json @@ -13,7 +13,8 @@ "sid": "testSid", "token": "testToken", "placementId": "testPlacementId", - "appId": "testAppId" + "appId": "testAppId", + "region": "APAC" } } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-request.json b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-request.json index 730a5acb1d8..424724a77cf 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/algorix/test-auction-algorix-request.json @@ -13,7 +13,8 @@ "sid": "testSid", "token": "testToken", "placementId": "testPlacementId", - "appId": "testAppId" + "appId": "testAppId", + "region": "APAC" } } } From a01debe42bf4c128dcfbee64946e496da530f83f Mon Sep 17 00:00:00 2001 From: mtuchkova <47950518+mtuchkova@users.noreply.github.com> Date: Fri, 26 Nov 2021 12:42:08 +0200 Subject: [PATCH 14/26] Tests for debug log for ccpa,coppa,gdpr (#1577) * Tests for debug log for ccpa,coppa,gdpr * fix after review * fix after review * fix after review * fix after review Co-authored-by: mtuchkova --- .../prebid/server/functional/CcpaSpec.groovy | 68 ------ .../model/request/amp/AmpRequest.groovy | 3 +- .../model/request/auction/Geo.groovy | 4 + .../model/request/auction/UserExt.groovy | 3 +- .../functional/model/response/Debug.groovy | 2 + .../response/auction/DebugPrivacy.groovy | 16 ++ .../model/response/auction/ErrorType.groovy | 2 +- .../model/response/auction/Privacy.groovy | 11 + .../model/response/auction/PrivacyCcpa.groovy | 12 ++ .../response/auction/PrivacyCoppa.groovy | 9 + .../model/response/auction/PrivacyTcf.groovy | 15 ++ .../service/PrebidServerService.groovy | 2 +- .../functional/{ => tests}/AmpSpec.groovy | 2 +- .../{ => tests}/AnalyticsSpec.groovy | 2 +- .../functional/{ => tests}/AuctionSpec.groovy | 2 +- .../functional/{ => tests}/BaseSpec.groovy | 17 +- .../{ => tests}/BidValidationSpec.groovy | 2 +- .../{ => tests}/BidderParamsSpec.groovy | 16 +- .../functional/{ => tests}/CacheSpec.groovy | 2 +- .../{ => tests}/CookieSyncSpec.groovy | 2 +- .../functional/{ => tests}/DealsSpec.groovy | 2 +- .../functional/{ => tests}/DebugSpec.groovy | 2 +- .../functional/{ => tests}/EventSpec.groovy | 2 +- .../{ => tests}/HttpInteractionSpec.groovy | 2 +- .../{ => tests}/HttpSettingsSpec.groovy | 2 +- .../{ => tests}/InfoBiddersSpec.groovy | 2 +- .../{ => tests}/MultibidSpec.groovy | 2 +- .../functional/{ => tests}/SchainSpec.groovy | 2 +- .../functional/{ => tests}/SetuidSpec.groovy | 2 +- .../functional/{ => tests}/SmokeSpec.groovy | 2 +- .../{ => tests}/StoredResponseSpec.groovy | 2 +- .../functional/{ => tests}/VtrackSpec.groovy | 2 +- .../functional/tests/privacy/CcpaSpec.groovy | 197 ++++++++++++++++++ .../functional/tests/privacy/CoppaSpec.groovy | 143 +++++++++++++ .../functional/tests/privacy/GdprSpec.groovy | 167 +++++++++++++++ .../tests/privacy/PrivacyBaseSpec.groovy | 60 ++++++ .../server/functional/util/PBSUtils.groovy | 2 +- .../util/privacy/BogusConsent.groovy | 18 ++ .../util/privacy/CcpaConsent.groovy | 34 +++ .../util/privacy/ConsentString.groovy | 9 + .../functional/util/privacy/TcfConsent.groovy | 98 +++++++++ 41 files changed, 836 insertions(+), 108 deletions(-) delete mode 100644 src/test/groovy/org/prebid/server/functional/CcpaSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/auction/DebugPrivacy.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/auction/Privacy.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyCcpa.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyCoppa.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyTcf.groovy rename src/test/groovy/org/prebid/server/functional/{ => tests}/AmpSpec.groovy (99%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/AnalyticsSpec.groovy (97%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/AuctionSpec.groovy (99%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/BaseSpec.groovy (79%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/BidValidationSpec.groovy (99%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/BidderParamsSpec.groovy (96%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/CacheSpec.groovy (98%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/CookieSyncSpec.groovy (97%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/DealsSpec.groovy (99%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/DebugSpec.groovy (99%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/EventSpec.groovy (96%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/HttpInteractionSpec.groovy (99%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/HttpSettingsSpec.groovy (99%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/InfoBiddersSpec.groovy (97%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/MultibidSpec.groovy (98%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/SchainSpec.groovy (99%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/SetuidSpec.groovy (97%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/SmokeSpec.groovy (99%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/StoredResponseSpec.groovy (97%) rename src/test/groovy/org/prebid/server/functional/{ => tests}/VtrackSpec.groovy (96%) create mode 100644 src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/tests/privacy/CoppaSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/tests/privacy/GdprSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/util/privacy/BogusConsent.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/util/privacy/CcpaConsent.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/util/privacy/ConsentString.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/util/privacy/TcfConsent.groovy diff --git a/src/test/groovy/org/prebid/server/functional/CcpaSpec.groovy b/src/test/groovy/org/prebid/server/functional/CcpaSpec.groovy deleted file mode 100644 index 35d7eba8de6..00000000000 --- a/src/test/groovy/org/prebid/server/functional/CcpaSpec.groovy +++ /dev/null @@ -1,68 +0,0 @@ -package org.prebid.server.functional - -import org.prebid.server.functional.model.config.AccountCcpaConfig -import org.prebid.server.functional.model.config.AccountConfig -import org.prebid.server.functional.model.config.AccountPrivacyConfig -import org.prebid.server.functional.model.db.Account -import org.prebid.server.functional.model.request.auction.BidRequest -import org.prebid.server.functional.model.request.auction.Device -import org.prebid.server.functional.model.request.auction.Geo -import org.prebid.server.functional.model.request.auction.RegsExt -import org.prebid.server.functional.testcontainers.PBSTest -import org.prebid.server.functional.util.PBSUtils - -@PBSTest -class CcpaSpec extends BaseSpec { - - // TODO: extend ccpa test with actual fields that we should mask - def "PBS should mask publisher info when privacy.ccpa.enabled = true in account config"() { - given: "Default basic generic BidRequest" - def bidRequest = BidRequest.defaultBidRequest - def valid_ccpa = "1YYY" - bidRequest.regs.ext = new RegsExt(gdpr: 0, usPrivacy: valid_ccpa) - def lat = PBSUtils.getFractionalRandomNumber(0, 90) - def lon = PBSUtils.getFractionalRandomNumber(0, 90) - bidRequest.device = new Device(geo: new Geo(lat: lat, lon: lon)) - - and: "Save account config into DB" - def ccpa = new AccountCcpaConfig(enabled: true) - def privacy = new AccountPrivacyConfig(ccpa: ccpa) - def accountConfig = new AccountConfig(privacy: privacy) - def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig) - accountDao.save(account) - - when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request should contain masked values" - def bidderRequests = bidder.getBidderRequest(bidRequest.id) - assert bidderRequests.device?.geo?.lat == PBSUtils.getRoundFractionalNumber(lat, 2) - assert bidderRequests.device?.geo?.lon == PBSUtils.getRoundFractionalNumber(lon, 2) - } - - // TODO: extend this ccpa test with actual fields that we should mask - def "PBS should not mask publisher info when privacy.ccpa.enabled = false in account config"() { - given: "Default basic generic BidRequest" - def bidRequest = BidRequest.defaultBidRequest - def valid_ccpa = "1YYY" - bidRequest.regs.ext = new RegsExt(gdpr: 0, usPrivacy: valid_ccpa) - def lat = PBSUtils.getFractionalRandomNumber(0, 90) - def lon = PBSUtils.getFractionalRandomNumber(0, 90) - bidRequest.device = new Device(geo: new Geo(lat: lat, lon: lon)) - - and: "Save account config into DB" - def ccpa = new AccountCcpaConfig(enabled: false) - def privacy = new AccountPrivacyConfig(ccpa: ccpa) - def accountConfig = new AccountConfig(privacy: privacy) - def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig) - accountDao.save(account) - - when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(bidRequest) - - then: "Bidder request should contain not masked values" - def bidderRequests = bidder.getBidderRequest(bidRequest.id) - assert bidderRequests.device?.geo?.lat == lat - assert bidderRequests.device?.geo?.lon == lon - } -} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy index f2b98f89967..1b4129e5365 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString import org.prebid.server.functional.util.PBSUtils +import org.prebid.server.functional.util.privacy.ConsentString @ToString(includeNames = true, ignoreNulls = true) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) @@ -20,7 +21,7 @@ class AmpRequest { String slot String curl Integer account - String gdprConsent + ConsentString gdprConsent String targeting Integer consentType Boolean gdprApplies diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Geo.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Geo.groovy index 09792638fed..02426801908 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Geo.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Geo.groovy @@ -1,7 +1,11 @@ package org.prebid.server.functional.model.request.auction +import groovy.transform.AutoClone +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString +@AutoClone +@EqualsAndHashCode @ToString(includeNames = true, ignoreNulls = true) class Geo { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy index 79b4b7b8c38..4ccee84ff0e 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy @@ -1,9 +1,10 @@ package org.prebid.server.functional.model.request.auction import groovy.transform.ToString +import org.prebid.server.functional.util.privacy.ConsentString @ToString(includeNames = true, ignoreNulls = true) class UserExt { - String consent + ConsentString consent } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/Debug.groovy b/src/test/groovy/org/prebid/server/functional/model/response/Debug.groovy index 925b20a0322..89de6fef872 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/Debug.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/Debug.groovy @@ -3,12 +3,14 @@ package org.prebid.server.functional.model.response import groovy.transform.ToString import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.response.auction.HttpCall +import org.prebid.server.functional.model.response.auction.DebugPrivacy @ToString(includeNames = true, ignoreNulls = true) class Debug { Map> httpcalls BidRequest resolvedrequest + DebugPrivacy privacy Map> getBidders() { def result = httpcalls?.findAll { it.key != "cache" } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/DebugPrivacy.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/DebugPrivacy.groovy new file mode 100644 index 00000000000..4bbca29735d --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/DebugPrivacy.groovy @@ -0,0 +1,16 @@ +package org.prebid.server.functional.model.response.auction + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.ToString +import org.prebid.server.functional.model.bidder.BidderName + +@ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) +class DebugPrivacy { + + Privacy originPrivacy + Privacy resolvedPrivacy + Map> privacyActionsPerBidder + List errors +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy index d332b378277..059462dc652 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/ErrorType.groovy @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonValue enum ErrorType { - GENERAL, GENERIC, RUBICON, APPNEXUS, PREBID + GENERAL, GENERIC, RUBICON, APPNEXUS, PREBID, CACHE @JsonValue String getValue() { diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/Privacy.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/Privacy.groovy new file mode 100644 index 00000000000..355a1026729 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/Privacy.groovy @@ -0,0 +1,11 @@ +package org.prebid.server.functional.model.response.auction + +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +class Privacy { + + PrivacyTcf tcf + PrivacyCcpa ccpa + PrivacyCoppa coppa +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyCcpa.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyCcpa.groovy new file mode 100644 index 00000000000..af38ff8c342 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyCcpa.groovy @@ -0,0 +1,12 @@ +package org.prebid.server.functional.model.response.auction + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) +class PrivacyCcpa { + + String usPrivacy +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyCoppa.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyCoppa.groovy new file mode 100644 index 00000000000..6983c7da01a --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyCoppa.groovy @@ -0,0 +1,9 @@ +package org.prebid.server.functional.model.response.auction + +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +class PrivacyCoppa { + + Integer coppa +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyTcf.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyTcf.groovy new file mode 100644 index 00000000000..9a32f521dfc --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/PrivacyTcf.groovy @@ -0,0 +1,15 @@ +package org.prebid.server.functional.model.response.auction + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) +class PrivacyTcf { + + String gdpr + String tcfConsentString + Integer tcfConsentVersion + Boolean inEea +} diff --git a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy index 80b533a5dc2..783bdc70ad4 100644 --- a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy +++ b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy @@ -61,7 +61,7 @@ class PrebidServerService { private final RequestSpecification requestSpecification private final RequestSpecification adminRequestSpecification - private final Logger log = LoggerFactory.getLogger(PrebidServerService.class) + private final Logger log = LoggerFactory.getLogger(PrebidServerService) PrebidServerService(PrebidServerContainer pbsContainer, ObjectMapperWrapper mapper) { def authenticationScheme = new BasicAuthScheme() diff --git a/src/test/groovy/org/prebid/server/functional/AmpSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AmpSpec.groovy similarity index 99% rename from src/test/groovy/org/prebid/server/functional/AmpSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/AmpSpec.groovy index 9cac8224eb1..979873d32d8 100644 --- a/src/test/groovy/org/prebid/server/functional/AmpSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/AmpSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.amp.AmpRequest diff --git a/src/test/groovy/org/prebid/server/functional/AnalyticsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AnalyticsSpec.groovy similarity index 97% rename from src/test/groovy/org/prebid/server/functional/AnalyticsSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/AnalyticsSpec.groovy index 8ad3a09ba56..6591643d3b4 100644 --- a/src/test/groovy/org/prebid/server/functional/AnalyticsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/AnalyticsSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.mock.services.pubstack.PubStackResponse import org.prebid.server.functional.model.request.auction.BidRequest diff --git a/src/test/groovy/org/prebid/server/functional/AuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy similarity index 99% rename from src/test/groovy/org/prebid/server/functional/AuctionSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy index 6e10a245f74..f972616961a 100644 --- a/src/test/groovy/org/prebid/server/functional/AuctionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.auction.BidRequest diff --git a/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy similarity index 79% rename from src/test/groovy/org/prebid/server/functional/BaseSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy index 8c0ae6afdd9..9a7decccfca 100644 --- a/src/test/groovy/org/prebid/server/functional/BaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BaseSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.repository.HibernateRepositoryService import org.prebid.server.functional.repository.dao.AccountDao @@ -7,6 +7,7 @@ import org.prebid.server.functional.repository.dao.StoredImpDao import org.prebid.server.functional.repository.dao.StoredRequestDao import org.prebid.server.functional.repository.dao.StoredResponseDao import org.prebid.server.functional.service.PrebidServerService +import org.prebid.server.functional.testcontainers.Dependencies import org.prebid.server.functional.testcontainers.PBSTest import org.prebid.server.functional.testcontainers.PbsServiceFactory import org.prebid.server.functional.testcontainers.scaffolding.Bidder @@ -15,20 +16,16 @@ import org.prebid.server.functional.util.ObjectMapperWrapper import org.prebid.server.functional.util.PBSUtils import spock.lang.Specification -import static org.prebid.server.functional.testcontainers.Dependencies.mysqlContainer -import static org.prebid.server.functional.testcontainers.Dependencies.networkServiceContainer -import static org.prebid.server.functional.testcontainers.Dependencies.objectMapperWrapper - @PBSTest abstract class BaseSpec extends Specification { - protected static final ObjectMapperWrapper mapper = objectMapperWrapper - protected static final PbsServiceFactory pbsServiceFactory = new PbsServiceFactory(networkServiceContainer, objectMapperWrapper) - protected static final Bidder bidder = new Bidder(networkServiceContainer, objectMapperWrapper) - protected static final PrebidCache prebidCache = new PrebidCache(networkServiceContainer, objectMapperWrapper) + protected static final ObjectMapperWrapper mapper = Dependencies.objectMapperWrapper + protected static final PbsServiceFactory pbsServiceFactory = new PbsServiceFactory(Dependencies.networkServiceContainer, Dependencies.objectMapperWrapper) + protected static final Bidder bidder = new Bidder(Dependencies.networkServiceContainer, Dependencies.objectMapperWrapper) + protected static final PrebidCache prebidCache = new PrebidCache(Dependencies.networkServiceContainer, Dependencies.objectMapperWrapper) protected static final PrebidServerService defaultPbsService = pbsServiceFactory.getService([:]) - protected static final HibernateRepositoryService repository = new HibernateRepositoryService(mysqlContainer) + protected static final HibernateRepositoryService repository = new HibernateRepositoryService(Dependencies.mysqlContainer) protected static final AccountDao accountDao = repository.accountDao protected static final ConfigDao configDao = repository.configDao protected static final StoredImpDao storedImp = repository.storedImpDao diff --git a/src/test/groovy/org/prebid/server/functional/BidValidationSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidValidationSpec.groovy similarity index 99% rename from src/test/groovy/org/prebid/server/functional/BidValidationSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/BidValidationSpec.groovy index 853cbf9e175..ef6b41e087d 100644 --- a/src/test/groovy/org/prebid/server/functional/BidValidationSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidValidationSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.request.auction.App diff --git a/src/test/groovy/org/prebid/server/functional/BidderParamsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy similarity index 96% rename from src/test/groovy/org/prebid/server/functional/BidderParamsSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy index bd3564fc633..70a6080044b 100644 --- a/src/test/groovy/org/prebid/server/functional/BidderParamsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import io.qameta.allure.Issue import org.prebid.server.functional.model.bidder.BidderName @@ -16,10 +16,12 @@ import org.prebid.server.functional.model.request.vtrack.xml.Vast import org.prebid.server.functional.model.response.auction.ErrorType import org.prebid.server.functional.testcontainers.PBSTest import org.prebid.server.functional.util.PBSUtils +import org.prebid.server.functional.util.privacy.CcpaConsent import spock.lang.PendingFeature import spock.lang.Unroll import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS +import static org.prebid.server.functional.util.privacy.CcpaConsent.Signal.ENFORCED import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID @PBSTest @@ -142,8 +144,8 @@ class BidderParamsSpec extends BaseSpec { and: "Default basic generic BidRequest" def bidRequest = BidRequest.defaultBidRequest - def valid_ccpa = "1YYY" - bidRequest.regs.ext = new RegsExt(gdpr: 0, usPrivacy: valid_ccpa) + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + bidRequest.regs.ext = new RegsExt(usPrivacy: validCcpa) def lat = PBSUtils.getFractionalRandomNumber(0, 90) def lon = PBSUtils.getFractionalRandomNumber(0, 90) bidRequest.device = new Device(geo: new Geo(lat: lat, lon: lon)) @@ -153,8 +155,8 @@ class BidderParamsSpec extends BaseSpec { then: "Bidder request should contain masked values" def bidderRequests = bidder.getBidderRequest(bidRequest.id) - assert bidderRequests.device?.geo?.lat == PBSUtils.getRoundFractionalNumber(lat, 2) - assert bidderRequests.device?.geo?.lon == PBSUtils.getRoundFractionalNumber(lon, 2) + assert bidderRequests.device?.geo?.lat == PBSUtils.getRoundedFractionalNumber(lat, 2) + assert bidderRequests.device?.geo?.lon == PBSUtils.getRoundedFractionalNumber(lon, 2) where: adapterDefault | generic @@ -170,8 +172,8 @@ class BidderParamsSpec extends BaseSpec { and: "Default basic generic BidRequest" def bidRequest = BidRequest.defaultBidRequest - def valid_ccpa = "1YYY" - bidRequest.regs.ext = new RegsExt(gdpr: 0, usPrivacy: valid_ccpa) + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + bidRequest.regs.ext = new RegsExt(usPrivacy: validCcpa) def lat = PBSUtils.getFractionalRandomNumber(0, 90) def lon = PBSUtils.getFractionalRandomNumber(0, 90) bidRequest.device = new Device(geo: new Geo(lat: lat, lon: lon)) diff --git a/src/test/groovy/org/prebid/server/functional/CacheSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy similarity index 98% rename from src/test/groovy/org/prebid/server/functional/CacheSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy index b7482deb3e4..3cff3a72a54 100644 --- a/src/test/groovy/org/prebid/server/functional/CacheSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Targeting diff --git a/src/test/groovy/org/prebid/server/functional/CookieSyncSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/CookieSyncSpec.groovy similarity index 97% rename from src/test/groovy/org/prebid/server/functional/CookieSyncSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/CookieSyncSpec.groovy index 7ea2af2844d..fdab989362f 100644 --- a/src/test/groovy/org/prebid/server/functional/CookieSyncSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/CookieSyncSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.UidsCookie import org.prebid.server.functional.model.db.Account diff --git a/src/test/groovy/org/prebid/server/functional/DealsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/DealsSpec.groovy similarity index 99% rename from src/test/groovy/org/prebid/server/functional/DealsSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/DealsSpec.groovy index eb970d44328..8c4e2a6d616 100644 --- a/src/test/groovy/org/prebid/server/functional/DealsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/DealsSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.MultiBid diff --git a/src/test/groovy/org/prebid/server/functional/DebugSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/DebugSpec.groovy similarity index 99% rename from src/test/groovy/org/prebid/server/functional/DebugSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/DebugSpec.groovy index 670a7302176..4f1011b2208 100644 --- a/src/test/groovy/org/prebid/server/functional/DebugSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/DebugSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.apache.commons.lang3.StringUtils import org.prebid.server.functional.model.bidder.BidderName diff --git a/src/test/groovy/org/prebid/server/functional/EventSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/EventSpec.groovy similarity index 96% rename from src/test/groovy/org/prebid/server/functional/EventSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/EventSpec.groovy index 03865692155..b920bd3930f 100644 --- a/src/test/groovy/org/prebid/server/functional/EventSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/EventSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.request.event.EventRequest diff --git a/src/test/groovy/org/prebid/server/functional/HttpInteractionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/HttpInteractionSpec.groovy similarity index 99% rename from src/test/groovy/org/prebid/server/functional/HttpInteractionSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/HttpInteractionSpec.groovy index c7a5bcae0b8..2a0d5efad46 100644 --- a/src/test/groovy/org/prebid/server/functional/HttpInteractionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/HttpInteractionSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.bidder.Generic diff --git a/src/test/groovy/org/prebid/server/functional/HttpSettingsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/HttpSettingsSpec.groovy similarity index 99% rename from src/test/groovy/org/prebid/server/functional/HttpSettingsSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/HttpSettingsSpec.groovy index fcd830fd8ce..86dec7566e2 100644 --- a/src/test/groovy/org/prebid/server/functional/HttpSettingsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/HttpSettingsSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.mock.services.httpsettings.HttpAccountsResponse diff --git a/src/test/groovy/org/prebid/server/functional/InfoBiddersSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/InfoBiddersSpec.groovy similarity index 97% rename from src/test/groovy/org/prebid/server/functional/InfoBiddersSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/InfoBiddersSpec.groovy index 2e2686ca8b2..772e81d8317 100644 --- a/src/test/groovy/org/prebid/server/functional/InfoBiddersSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/InfoBiddersSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils diff --git a/src/test/groovy/org/prebid/server/functional/MultibidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/MultibidSpec.groovy similarity index 98% rename from src/test/groovy/org/prebid/server/functional/MultibidSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/MultibidSpec.groovy index 5bdcfff6b31..bf0c8fe3daa 100644 --- a/src/test/groovy/org/prebid/server/functional/MultibidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/MultibidSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.MultiBid diff --git a/src/test/groovy/org/prebid/server/functional/SchainSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SchainSpec.groovy similarity index 99% rename from src/test/groovy/org/prebid/server/functional/SchainSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/SchainSpec.groovy index ab44917f56d..cccde9a83ec 100644 --- a/src/test/groovy/org/prebid/server/functional/SchainSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SchainSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.PrebidSchain diff --git a/src/test/groovy/org/prebid/server/functional/SetuidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetuidSpec.groovy similarity index 97% rename from src/test/groovy/org/prebid/server/functional/SetuidSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/SetuidSpec.groovy index b5793fc4f9b..60b4f680eef 100644 --- a/src/test/groovy/org/prebid/server/functional/SetuidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetuidSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.UidsCookie import org.prebid.server.functional.model.db.Account diff --git a/src/test/groovy/org/prebid/server/functional/SmokeSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SmokeSpec.groovy similarity index 99% rename from src/test/groovy/org/prebid/server/functional/SmokeSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/SmokeSpec.groovy index 2c52a691b2c..374dae4518c 100644 --- a/src/test/groovy/org/prebid/server/functional/SmokeSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SmokeSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredRequest diff --git a/src/test/groovy/org/prebid/server/functional/StoredResponseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/StoredResponseSpec.groovy similarity index 97% rename from src/test/groovy/org/prebid/server/functional/StoredResponseSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/StoredResponseSpec.groovy index 4e5f7525fd8..049d6557fc9 100644 --- a/src/test/groovy/org/prebid/server/functional/StoredResponseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/StoredResponseSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.db.StoredResponse import org.prebid.server.functional.model.request.auction.BidRequest diff --git a/src/test/groovy/org/prebid/server/functional/VtrackSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/VtrackSpec.groovy similarity index 96% rename from src/test/groovy/org/prebid/server/functional/VtrackSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/VtrackSpec.groovy index bfb2200bed0..b5e71d1a22e 100644 --- a/src/test/groovy/org/prebid/server/functional/VtrackSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/VtrackSpec.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional +package org.prebid.server.functional.tests import org.prebid.server.functional.model.request.vtrack.VtrackRequest import org.prebid.server.functional.model.request.vtrack.xml.Vast diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaSpec.groovy new file mode 100644 index 00000000000..9439a0ca0f0 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaSpec.groovy @@ -0,0 +1,197 @@ +package org.prebid.server.functional.tests.privacy + +import org.prebid.server.functional.model.bidder.BidderName +import org.prebid.server.functional.model.config.AccountCcpaConfig +import org.prebid.server.functional.model.config.AccountConfig +import org.prebid.server.functional.model.config.AccountPrivacyConfig +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.testcontainers.PBSTest +import org.prebid.server.functional.util.privacy.BogusConsent +import org.prebid.server.functional.util.privacy.CcpaConsent +import spock.lang.PendingFeature + +import static org.prebid.server.functional.util.privacy.CcpaConsent.Signal.ENFORCED + +@PBSTest +class CcpaSpec extends PrivacyBaseSpec { + + // TODO: extend ccpa test with actual fields that we should mask + def "PBS should mask publisher info when privacy.ccpa.enabled = true in account config"() { + given: "Default ccpa BidRequest" + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + def bidRequest = getCcpaBidRequest(validCcpa) + + and: "Save account config into DB" + def ccpa = new AccountCcpaConfig(enabled: true) + def privacy = new AccountPrivacyConfig(ccpa: ccpa) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain masked values" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + assert bidderRequests.device?.geo == maskGeo(bidRequest) + } + + // TODO: extend this ccpa test with actual fields that we should mask + def "PBS should not mask publisher info when privacy.ccpa.enabled = false in account config"() { + given: "Default ccpa BidRequest" + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + def bidRequest = getCcpaBidRequest(validCcpa) + + and: "Save account config into DB" + def ccpa = new AccountCcpaConfig(enabled: false) + def privacy = new AccountPrivacyConfig(ccpa: ccpa) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain not masked values" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + assert bidderRequests.device?.geo?.lat == bidRequest.device.geo.lat + assert bidderRequests.device?.geo?.lon == bidRequest.device.geo.lon + } + + @PendingFeature + def "PBS should add debug log for auction request when valid ccpa was passed"() { + given: "Default ccpa BidRequest" + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + def bidRequest = getCcpaBidRequest(validCcpa) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain debug log" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.ccpa?.usPrivacy == validCcpa as String + privacy.resolvedPrivacy?.ccpa?.usPrivacy == validCcpa as String + + !privacy.originPrivacy?.coppa?.coppa + !privacy.resolvedPrivacy?.coppa?.coppa + + !privacy.originPrivacy?.tcf?.gdpr + !privacy.originPrivacy?.tcf?.tcfConsentString + !privacy.originPrivacy?.tcf?.tcfConsentVersion + !privacy.originPrivacy?.tcf?.inEea + !privacy.resolvedPrivacy?.tcf?.gdpr + !privacy.resolvedPrivacy?.tcf?.tcfConsentString + !privacy.resolvedPrivacy?.tcf?.tcfConsentVersion + !privacy.resolvedPrivacy?.tcf?.inEea + + privacy.privacyActionsPerBidder[BidderName.GENERIC] == + ["Geolocation was masked in request to bidder according to CCPA policy."] + + privacy.errors?.isEmpty() + } + } + + @PendingFeature + def "PBS should add debug log for auction request when invalid ccpa was passed"() { + given: "Default ccpa BidRequest" + def invalidCcpa = new BogusConsent() + def bidRequest = getCcpaBidRequest(invalidCcpa) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should not contain error" + assert !response.ext?.errors + + and: "Response should contain debug log with error" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.ccpa?.usPrivacy == invalidCcpa as String + privacy.resolvedPrivacy?.ccpa?.usPrivacy == invalidCcpa as String + + privacy.privacyActionsPerBidder[BidderName.GENERIC].isEmpty() + + privacy.errors == ["CCPA consent $invalidCcpa has invalid format: us_privacy must specify 'N' " + + "or 'n', 'Y' or 'y', '-' for the explicit notice" as String] + } + } + + @PendingFeature + def "PBS should add debug log for amp request when valid ccpa was passed"() { + given: "Default AmpRequest" + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + def ampRequest = getCcpaAmpRequest(validCcpa) + + and: "Save storedRequest into DB" + def ampStoredRequest = bidRequestWithGeo + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = defaultPbsService.sendAmpRequest(ampRequest) + + then: "Response should contain debug log" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.ccpa?.usPrivacy == validCcpa as String + privacy.resolvedPrivacy?.ccpa?.usPrivacy == validCcpa as String + + !privacy.originPrivacy?.coppa?.coppa + !privacy.resolvedPrivacy?.coppa?.coppa + + !privacy.originPrivacy?.tcf?.gdpr + !privacy.originPrivacy?.tcf?.tcfConsentString + !privacy.originPrivacy?.tcf?.tcfConsentVersion + !privacy.originPrivacy?.tcf?.inEea + !privacy.resolvedPrivacy?.tcf?.gdpr + !privacy.resolvedPrivacy?.tcf?.tcfConsentString + !privacy.resolvedPrivacy?.tcf?.tcfConsentVersion + !privacy.resolvedPrivacy?.tcf?.inEea + + privacy.privacyActionsPerBidder[BidderName.GENERIC] == + ["Geolocation was masked in request to bidder according to CCPA policy."] + + privacy.errors?.isEmpty() + } + } + + @PendingFeature + def "PBS should add debug log for amp request when invalid ccpa was passed"() { + given: "Default AmpRequest" + def invalidCcpa = new BogusConsent() + def ampRequest = getCcpaAmpRequest(invalidCcpa) + + and: "Save storedRequest into DB" + def ampStoredRequest = bidRequestWithGeo + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = defaultPbsService.sendAmpRequest(ampRequest) + + then: "Response should not contain error" + assert !response.ext?.errors + + and: "Response should contain debug log with error" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.ccpa?.usPrivacy == invalidCcpa as String + privacy.resolvedPrivacy?.ccpa?.usPrivacy == invalidCcpa as String + + privacy.privacyActionsPerBidder[BidderName.GENERIC].isEmpty() + + privacy.errors == + ["Amp request parameter consent_string or gdpr_consent have invalid format: $invalidCcpa" as String] + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/CoppaSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/CoppaSpec.groovy new file mode 100644 index 00000000000..a41d40b903f --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/CoppaSpec.groovy @@ -0,0 +1,143 @@ +package org.prebid.server.functional.tests.privacy + +import org.prebid.server.functional.model.bidder.BidderName +import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.request.amp.AmpRequest +import org.prebid.server.functional.testcontainers.PBSTest +import spock.lang.PendingFeature + +@PBSTest +class CoppaSpec extends PrivacyBaseSpec { + + @PendingFeature + def "PBS should add debug log for auction request when coppa = 0 was passed"() { + given: "Default COPPA BidRequest" + def bidRequest = bidRequestWithGeo.tap { + regs.coppa = 0 + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain debug log" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.coppa?.coppa == bidRequest.regs.coppa + privacy.resolvedPrivacy?.coppa?.coppa == bidRequest.regs.coppa + + !privacy.originPrivacy?.tcf?.gdpr + !privacy.originPrivacy?.tcf?.tcfConsentString + !privacy.originPrivacy?.tcf?.tcfConsentVersion + !privacy.originPrivacy?.tcf?.inEea + !privacy.resolvedPrivacy?.tcf?.gdpr + !privacy.resolvedPrivacy?.tcf?.tcfConsentString + !privacy.resolvedPrivacy?.tcf?.tcfConsentVersion + !privacy.resolvedPrivacy?.tcf?.inEea + + !privacy.originPrivacy?.ccpa?.usPrivacy + !privacy.resolvedPrivacy?.ccpa?.usPrivacy + + privacy.privacyActionsPerBidder[BidderName.GENERIC].isEmpty() + + privacy.errors?.isEmpty() + } + } + + @PendingFeature + def "PBS should add debug log for auction request when coppa = 1 was passed"() { + given: "Default COPPA BidRequest" + def bidRequest = bidRequestWithGeo.tap { + regs.coppa = 1 + } + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain debug log" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.coppa?.coppa == bidRequest.regs.coppa + privacy.resolvedPrivacy?.coppa?.coppa == bidRequest.regs.coppa + + privacy.privacyActionsPerBidder[BidderName.GENERIC] == + ["Geolocation and address were removed from request to bidder according to CCPA policy."] + + privacy.errors?.isEmpty() + } + } + + @PendingFeature + def "PBS should add debug log for amp request when coppa = 0 was passed"() { + given: "Default AmpRequest" + def ampRequest = AmpRequest.defaultAmpRequest + + and: "Save storedRequest into DB" + def ampStoredRequest = bidRequestWithGeo.tap { + regs.coppa = 0 + } + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = defaultPbsService.sendAmpRequest(ampRequest) + + then: "Response should contain debug log" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.coppa?.coppa == ampStoredRequest.regs.coppa + privacy.resolvedPrivacy?.coppa?.coppa == ampStoredRequest.regs.coppa + + !privacy.originPrivacy?.tcf?.gdpr + !privacy.originPrivacy?.tcf?.tcfConsentString + !privacy.originPrivacy?.tcf?.tcfConsentVersion + !privacy.originPrivacy?.tcf?.inEea + !privacy.resolvedPrivacy?.tcf?.gdpr + !privacy.resolvedPrivacy?.tcf?.tcfConsentString + !privacy.resolvedPrivacy?.tcf?.tcfConsentVersion + !privacy.resolvedPrivacy?.tcf?.inEea + + !privacy.originPrivacy?.ccpa?.usPrivacy + !privacy.resolvedPrivacy?.ccpa?.usPrivacy + + privacy.privacyActionsPerBidder[BidderName.GENERIC].isEmpty() + + privacy.errors?.isEmpty() + } + } + + @PendingFeature + def "PBS should add debug log for amp request when coppa = 1 was passed"() { + given: "Default AmpRequest" + def ampRequest = AmpRequest.defaultAmpRequest + + and: "Save storedRequest into DB" + def ampStoredRequest = bidRequestWithGeo.tap { + regs.coppa = 1 + } + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = defaultPbsService.sendAmpRequest(ampRequest) + + then: "Response should contain debug log" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.coppa?.coppa == ampStoredRequest.regs.coppa + privacy.resolvedPrivacy?.coppa?.coppa == ampStoredRequest.regs.coppa + + privacy.privacyActionsPerBidder[BidderName.GENERIC] == + ["Geolocation and address were removed from request to bidder according to CCPA policy."] + + privacy.errors?.isEmpty() + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprSpec.groovy new file mode 100644 index 00000000000..ff07ceaf0fb --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprSpec.groovy @@ -0,0 +1,167 @@ +package org.prebid.server.functional.tests.privacy + +import org.prebid.server.functional.model.bidder.BidderName +import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.testcontainers.PBSTest +import org.prebid.server.functional.util.privacy.BogusConsent +import org.prebid.server.functional.util.privacy.TcfConsent +import spock.lang.PendingFeature + +import static org.prebid.server.functional.util.privacy.TcfConsent.PurposeId.BASIC_ADS + +@PBSTest +class GdprSpec extends PrivacyBaseSpec { + + @PendingFeature + def "PBS should add debug log for auction request when valid gdpr was passed"() { + given: "Default gdpr BidRequest" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .build() + + def bidRequest = getGdprBidRequest(validConsentString) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain debug log" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.tcf?.gdpr == "1" + privacy.originPrivacy?.tcf?.tcfConsentString == validConsentString as String + !privacy.originPrivacy?.tcf?.tcfConsentVersion + !privacy.originPrivacy?.tcf?.inEea + privacy.resolvedPrivacy?.tcf?.gdpr == "1" + privacy.resolvedPrivacy?.tcf?.tcfConsentString == validConsentString as String + privacy.resolvedPrivacy?.tcf?.tcfConsentVersion == 2 + !privacy.resolvedPrivacy?.tcf?.inEea + + !privacy.originPrivacy?.ccpa?.usPrivacy + !privacy.resolvedPrivacy?.ccpa?.usPrivacy + + !privacy.originPrivacy?.coppa?.coppa + privacy.resolvedPrivacy?.coppa?.coppa == 0 + + privacy.privacyActionsPerBidder[BidderName.GENERIC] == + ["Geolocation was masked in request to bidder according to TCF policy."] + + privacy.errors?.isEmpty() + } + } + + @PendingFeature + def "PBS should add debug log for auction request when invalid gdpr was passed"() { + given: "Default gdpr BidRequest" + def invalidConsentString = new BogusConsent() + def bidRequest = getGdprBidRequest(invalidConsentString) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should not contain ext.errors" + assert !response.ext?.errors + + and: "Response should contain debug log with error" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.tcf?.gdpr == "1" + privacy.originPrivacy?.tcf?.tcfConsentString == invalidConsentString as String + !privacy.originPrivacy?.tcf?.tcfConsentVersion + !privacy.originPrivacy?.tcf?.inEea + privacy.resolvedPrivacy?.tcf?.gdpr == "1" + privacy.resolvedPrivacy?.tcf?.tcfConsentString == invalidConsentString as String + privacy.resolvedPrivacy?.tcf?.tcfConsentVersion == 2 + !privacy.resolvedPrivacy?.tcf?.inEea + + privacy.privacyActionsPerBidder[BidderName.GENERIC].isEmpty() + + privacy.errors == ["Placeholder: invalid consent string"] + } + } + + @PendingFeature + def "PBS should add debug log for amp request when valid gdpr was passed"() { + given: "AmpRequest with consent string" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .build() + + def ampRequest = getGdprAmpRequest(validConsentString) + + and: "Save storedRequest into DB" + def ampStoredRequest = bidRequestWithGeo + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = defaultPbsService.sendAmpRequest(ampRequest) + + then: "Response should contain debug log" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.tcf?.gdpr == "1" + privacy.originPrivacy?.tcf?.tcfConsentString == validConsentString as String + !privacy.originPrivacy?.tcf?.tcfConsentVersion + !privacy.originPrivacy?.tcf?.inEea + privacy.resolvedPrivacy?.tcf?.gdpr == "1" + privacy.resolvedPrivacy?.tcf?.tcfConsentString == validConsentString as String + privacy.resolvedPrivacy?.tcf?.tcfConsentVersion == 2 + !privacy.resolvedPrivacy?.tcf?.inEea + + !privacy.originPrivacy?.ccpa?.usPrivacy + !privacy.resolvedPrivacy?.ccpa?.usPrivacy + + !privacy.originPrivacy?.coppa?.coppa + privacy.resolvedPrivacy?.coppa?.coppa == 0 + + privacy.privacyActionsPerBidder[BidderName.GENERIC] == + ["Geolocation was masked in request to bidder according to TCF policy."] + + privacy.errors?.isEmpty() + } + } + + @PendingFeature + def "PBS should add debug log for amp request when invalid gdpr was passed"() { + given: "Default AmpRequest" + def invalidConsentString = new BogusConsent() + def ampRequest = getGdprAmpRequest(invalidConsentString) + + and: "Save storedRequest into DB" + def ampStoredRequest = bidRequestWithGeo + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = defaultPbsService.sendAmpRequest(ampRequest) + + then: "Response should not contain ext.errors" + assert !response.ext?.errors + + and: "Response should contain debug log with error" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.tcf?.gdpr == "1" + privacy.originPrivacy?.tcf?.tcfConsentString == invalidConsentString as String + !privacy.originPrivacy?.tcf?.tcfConsentVersion + !privacy.originPrivacy?.tcf?.inEea + privacy.resolvedPrivacy?.tcf?.gdpr == "1" + privacy.resolvedPrivacy?.tcf?.tcfConsentString == invalidConsentString as String + privacy.resolvedPrivacy?.tcf?.tcfConsentVersion == 2 + !privacy.resolvedPrivacy?.tcf?.inEea + + privacy.privacyActionsPerBidder[BidderName.GENERIC].isEmpty() + + privacy.errors == ["Amp request parameter consent_string or gdpr_consent have invalid format:" + + " $invalidConsentString" as String] + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy new file mode 100644 index 00000000000..d02bc0cd040 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy @@ -0,0 +1,60 @@ +package org.prebid.server.functional.tests.privacy + +import org.prebid.server.functional.model.request.amp.AmpRequest +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.Device +import org.prebid.server.functional.model.request.auction.Geo +import org.prebid.server.functional.model.request.auction.RegsExt +import org.prebid.server.functional.model.request.auction.User +import org.prebid.server.functional.model.request.auction.UserExt +import org.prebid.server.functional.testcontainers.PBSTest +import org.prebid.server.functional.tests.BaseSpec +import org.prebid.server.functional.util.PBSUtils +import org.prebid.server.functional.util.privacy.ConsentString + +@PBSTest +abstract class PrivacyBaseSpec extends BaseSpec { + + private static final int GEO_PRECISION = 2 + + protected static BidRequest getBidRequestWithGeo() { + BidRequest.defaultBidRequest.tap { + device = new Device(geo: new Geo(lat: PBSUtils.getFractionalRandomNumber(0, 90), lon: PBSUtils.getFractionalRandomNumber(0, 90))) + } + } + + protected static BidRequest getCcpaBidRequest(ConsentString consentString) { + bidRequestWithGeo.tap { + regs.ext = new RegsExt(usPrivacy: consentString) + } + } + + protected static AmpRequest getCcpaAmpRequest(ConsentString consentString) { + AmpRequest.defaultAmpRequest.tap { + gdprConsent = consentString + consentType = 3 + } + } + + protected static BidRequest getGdprBidRequest(ConsentString consentString) { + bidRequestWithGeo.tap { + regs.ext = new RegsExt(gdpr: 1) + user = new User(ext: new UserExt(consent: consentString)) + } + } + + protected static AmpRequest getGdprAmpRequest(ConsentString consentString) { + AmpRequest.defaultAmpRequest.tap { + gdprConsent = consentString + consentType = 2 + gdprApplies = true + } + } + + protected static Geo maskGeo(BidRequest bidRequest, int precision = GEO_PRECISION) { + def geo = bidRequest.device.geo.clone() + geo.lat = PBSUtils.getRoundedFractionalNumber(bidRequest.device.geo.lat, precision) + geo.lon = PBSUtils.getRoundedFractionalNumber(bidRequest.device.geo.lon, precision) + geo + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy index 4020a66e203..5e9cd3075d1 100644 --- a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy @@ -22,7 +22,7 @@ class PBSUtils { new Random().nextFloat() * (max - min) + min } - static float getRoundFractionalNumber(float number, int numberDecimalPlaces) { + static float getRoundedFractionalNumber(float number, int numberDecimalPlaces) { def stringBuilder = new StringBuilder().append("##.") IntStream.range(0, numberDecimalPlaces).forEach { index -> stringBuilder.append("#") } def format = new DecimalFormat(stringBuilder.toString()) diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/BogusConsent.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/BogusConsent.groovy new file mode 100644 index 00000000000..786359582f6 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/BogusConsent.groovy @@ -0,0 +1,18 @@ +package org.prebid.server.functional.util.privacy + +import org.prebid.server.functional.util.PBSUtils + +class BogusConsent implements ConsentString { + + private static final String BOGUS_CONSENT_STRING = PBSUtils.randomString + + @Override + String getConsentString() { + BOGUS_CONSENT_STRING + } + + @Override + String toString() { + consentString + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/CcpaConsent.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/CcpaConsent.groovy new file mode 100644 index 00000000000..81e594cb01c --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/CcpaConsent.groovy @@ -0,0 +1,34 @@ +package org.prebid.server.functional.util.privacy + +import static org.prebid.server.functional.util.privacy.CcpaConsent.Signal.NOT_DEFINED + +class CcpaConsent implements ConsentString { + + private static final int SPECIFICATION_VERSION = 1 + private Signal explicitNotice = NOT_DEFINED + private Signal optOutSale = NOT_DEFINED + private Signal serviceProviderAgreement = NOT_DEFINED + + @Override + String getConsentString() { + "$SPECIFICATION_VERSION$explicitNotice.value$optOutSale.value$serviceProviderAgreement.value" + } + + @Override + String toString() { + consentString + } + + enum Signal { + + ENFORCED("Y"), + NOT_ENFORCED("N"), + NOT_DEFINED("-") + + final String value + + Signal(String value) { + this.value = value + } + } +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/ConsentString.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/ConsentString.groovy new file mode 100644 index 00000000000..b850a691929 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/ConsentString.groovy @@ -0,0 +1,9 @@ +package org.prebid.server.functional.util.privacy + +import com.fasterxml.jackson.annotation.JsonValue + +interface ConsentString { + + @JsonValue + String getConsentString() +} diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/TcfConsent.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/TcfConsent.groovy new file mode 100644 index 00000000000..5eb0bc4bc51 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/TcfConsent.groovy @@ -0,0 +1,98 @@ +package org.prebid.server.functional.util.privacy + +import com.iabtcf.encoder.TCStringEncoder +import com.iabtcf.utils.BitSetIntIterable + +import static io.restassured.RestAssured.given + +class TcfConsent implements ConsentString { + + private static final String VENDOR_LIST_URL = "https://vendor-list.consensu.org/v2/vendor-list.json" + private static final Integer VENDOR_LIST_VERSION = vendorListVersion + + private final TCStringEncoder.Builder tcStringEncoder + + private TcfConsent(Builder builder) { + this.tcStringEncoder = builder.tcStringEncoder + } + + static Integer getVendorListVersion() { + def vendorListVersion = given().get(VENDOR_LIST_URL).path("vendorListVersion") as Integer + if (!vendorListVersion) { + throw new IllegalStateException("Vendor list version is null") + } else { + return vendorListVersion + } + + } + + @Override + String getConsentString() { + tcStringEncoder.encode() + } + + @Override + String toString() { + consentString + } + + static class Builder { + + private TCStringEncoder.Builder tcStringEncoder + + Builder() { + tcStringEncoder = TCStringEncoder.newBuilder() + setVersion(2) + setTcfPolicyVersion(2) + setVendorListVersion(VENDOR_LIST_VERSION) + } + + Builder setVersion(Integer version) { + tcStringEncoder.version(version) + this + } + + Builder setVendorListVersion(Integer vendorListVersion) { + tcStringEncoder.vendorListVersion(vendorListVersion) + this + } + + Builder setTcfPolicyVersion(Integer tcfPolicyVersion) { + tcStringEncoder.tcfPolicyVersion(tcfPolicyVersion) + this + } + + Builder setPurposesConsent(List purposesConsent) { + tcStringEncoder.addPurposesConsent(BitSetIntIterable.from(purposesConsent)) + this + } + + Builder setVendorConsent(List vendorConsent) { + tcStringEncoder.addVendorConsent(BitSetIntIterable.from(vendorConsent)) + this + } + + Builder setPurposesLITransparency(PurposeId purposesLITransparency) { + tcStringEncoder.addPurposesLITransparency(purposesLITransparency.value) + this + } + + TcfConsent build() { + new TcfConsent(this) + } + } + + enum PurposeId { + + DEVICE_ACCESS(1), + BASIC_ADS(2), + PERSONALIZED_ADS(4), + MEASURE_AD_PERFORMANCE(7) + + final int value + + PurposeId(int value) { + this.value = value + } + } +} From eabf0c43b578c752a9254e548b088538f7c5d631 Mon Sep 17 00:00:00 2001 From: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com> Date: Mon, 29 Nov 2021 13:24:22 +0200 Subject: [PATCH 15/26] Refactor AdfBidder (#1575) --- .../prebid/server/bidder/OpenrtbBidder.java | 2 +- .../prebid/server/bidder/adf/AdfBidder.java | 134 ++++++++++++++---- .../server/bidder/model/ImpWithExt.java | 4 +- .../openrtb/ext/request/adf/ExtImpAdf.java | 9 +- 4 files changed, 115 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/OpenrtbBidder.java b/src/main/java/org/prebid/server/bidder/OpenrtbBidder.java index 1cea93ef1cf..0c3df994fdb 100644 --- a/src/main/java/org/prebid/server/bidder/OpenrtbBidder.java +++ b/src/main/java/org/prebid/server/bidder/OpenrtbBidder.java @@ -59,7 +59,7 @@ public final Result>> makeHttpRequests(BidRequest b try { final T impExt = parseImpExt(imp); final Imp modifiedImp = modifyImp(imp, impExt); - modifiedImpsWithExts.add(new ImpWithExt<>(modifiedImp, impExt)); + modifiedImpsWithExts.add(ImpWithExt.of(modifiedImp, impExt)); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); } diff --git a/src/main/java/org/prebid/server/bidder/adf/AdfBidder.java b/src/main/java/org/prebid/server/bidder/adf/AdfBidder.java index 3f0be1cd72a..8d3cf068abd 100644 --- a/src/main/java/org/prebid/server/bidder/adf/AdfBidder.java +++ b/src/main/java/org/prebid/server/bidder/adf/AdfBidder.java @@ -1,56 +1,142 @@ package org.prebid.server.bidder.adf; -import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; import com.iab.openrtb.response.Bid; -import org.prebid.server.bidder.OpenrtbBidder; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import io.vertx.core.http.HttpMethod; +import org.apache.commons.collections4.CollectionUtils; +import org.prebid.server.bidder.Bidder; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpCall; +import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.Result; import org.prebid.server.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.adf.ExtImpAdf; import org.prebid.server.proto.openrtb.ext.response.BidType; -import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; +import org.prebid.server.util.HttpUtil; +import org.prebid.server.util.ObjectUtil; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; -public class AdfBidder extends OpenrtbBidder { +public class AdfBidder implements Bidder { - private static final String PREBID_EXT = "prebid"; + private static final TypeReference> ADF_EXT_TYPE_REFERENCE = + new TypeReference<>() { + }; + + private final String endpointUrl; + private final JacksonMapper mapper; public AdfBidder(String endpointUrl, JacksonMapper mapper) { - super(endpointUrl, RequestCreationStrategy.SINGLE_REQUEST, ExtImpAdf.class, mapper); + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.mapper = Objects.requireNonNull(mapper); } @Override - protected Imp modifyImp(Imp imp, ExtImpAdf impExt) { - return imp.toBuilder() - .tagid(impExt.getMid()) - .build(); + public Result>> makeHttpRequests(BidRequest bidRequest) { + final List modifiedImps = new ArrayList<>(); + final List errors = new ArrayList<>(); + + for (Imp imp : bidRequest.getImp()) { + try { + final ExtImpAdf adfImp = parseExt(imp); + final Imp modifiedImp = imp.toBuilder().tagid(adfImp.getMid()).build(); + modifiedImps.add(modifiedImp); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + } + } + + if (modifiedImps.isEmpty()) { + return Result.withErrors(errors); + } + + final HttpRequest httpRequest = makeRequest(bidRequest, modifiedImps); + return Result.of(Collections.singletonList(httpRequest), errors); } - @Override - protected BidType getBidType(Bid bid, List imps) { - final String impId = bid.getImpid(); - final BidType bidType = getExtBidPrebidType(bid.getExt()); - if (bidType != null) { - return bidType; + private ExtImpAdf parseExt(Imp imp) throws PreBidException { + try { + return mapper.mapper().convertValue(imp.getExt(), ADF_EXT_TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException(e.getMessage()); } + } + + private HttpRequest makeRequest(BidRequest bidRequest, List imps) { + final BidRequest outgoingRequest = bidRequest.toBuilder().imp(imps).build(); - throw new PreBidException(String.format("Failed to parse bid %s mediatype", impId)); + return HttpRequest.builder() + .method(HttpMethod.POST) + .uri(endpointUrl) + .headers(HttpUtil.headers()) + .body(mapper.encodeToBytes(outgoingRequest)) + .payload(outgoingRequest) + .build(); } - private BidType getExtBidPrebidType(ObjectNode bidExt) { - final ExtBidPrebid extBidPrebid = getExtBidPrebid(bidExt); - return extBidPrebid != null ? extBidPrebid.getType() : null; + @Override + public Result> makeBids(HttpCall httpCall, BidRequest bidRequest) { + try { + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + final List errors = new ArrayList<>(); + final List bidderBids = extractBids(bidResponse, errors); + return Result.of(bidderBids, errors); + } catch (DecodeException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } } - private ExtBidPrebid getExtBidPrebid(ObjectNode bidExt) { - if (bidExt == null || !bidExt.hasNonNull(PREBID_EXT)) { - return null; + private List extractBids(BidResponse bidResponse, List errors) { + if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { + return Collections.emptyList(); } + + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(Objects::nonNull) + .map(bid -> makeBidderBid(bid, bidResponse.getCur(), errors)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private BidderBid makeBidderBid(Bid bid, String bidCurrency, List errors) { + final JsonNode prebidNode = ObjectUtil.getIfNotNull(bid.getExt(), node -> node.get("prebid")); + final JsonNode typeNode = ObjectUtil.getIfNotNull(prebidNode, node -> node.get("type")); + final BidType bidType; try { - return mapper.mapper().convertValue(bidExt.get(PREBID_EXT), ExtBidPrebid.class); + bidType = mapper.mapper().convertValue(typeNode, BidType.class); } catch (IllegalArgumentException e) { + addMediaTypeParseError(errors, bid.getImpid()); return null; } + + if (bidType == null) { + addMediaTypeParseError(errors, bid.getImpid()); + return null; + } + + return BidderBid.of(bid, bidType, bidCurrency); + } + + private void addMediaTypeParseError(List errors, String impId) { + final String errorMessage = String.format("Failed to parse impression %s mediatype", impId); + errors.add(BidderError.badServerResponse(errorMessage)); } } diff --git a/src/main/java/org/prebid/server/bidder/model/ImpWithExt.java b/src/main/java/org/prebid/server/bidder/model/ImpWithExt.java index 40f31b51fd9..f62f435375c 100644 --- a/src/main/java/org/prebid/server/bidder/model/ImpWithExt.java +++ b/src/main/java/org/prebid/server/bidder/model/ImpWithExt.java @@ -1,11 +1,9 @@ package org.prebid.server.bidder.model; import com.iab.openrtb.request.Imp; -import lombok.AllArgsConstructor; import lombok.Value; -@AllArgsConstructor -@Value +@Value(staticConstructor = "of") public class ImpWithExt { Imp imp; diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adf/ExtImpAdf.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/adf/ExtImpAdf.java index cc53f88cad0..7afb5e19e1e 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adf/ExtImpAdf.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/adf/ExtImpAdf.java @@ -1,16 +1,13 @@ package org.prebid.server.proto.openrtb.ext.request.adf; -import lombok.AllArgsConstructor; import lombok.Value; -/** - * Defines the contract for bidrequest.imp[i].ext.adf - */ -@AllArgsConstructor(staticName = "of") -@Value +@Value(staticConstructor = "of") public class ExtImpAdf { String mid; + Integer inv; + String mname; } From 6ce3678a6aa050ceacae477a04ca5aae7042461a Mon Sep 17 00:00:00 2001 From: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com> Date: Mon, 29 Nov 2021 13:28:46 +0200 Subject: [PATCH 16/26] Convert Adform adapter to Adf alias. (#1596) --- .../server/bidder/ViewabilityVendors.java | 1 - .../server/bidder/adform/AdformBidder.java | 322 --------------- .../server/bidder/adform/AdformHttpUtil.java | 173 -------- .../bidder/adform/AdformRequestUtil.java | 64 --- .../server/bidder/adform/model/AdformBid.java | 29 -- .../bidder/adform/model/AdformParams.java | 28 -- .../bidder/adform/model/UrlParameters.java | 43 -- .../ext/request/adform/ExtImpAdform.java | 35 -- .../config/bidder/AdformConfiguration.java | 41 -- src/main/resources/bidder-config/adf.yaml | 6 + src/main/resources/bidder-config/adform.yaml | 17 - src/main/resources/bidder-config/rubicon.yaml | 1 - .../static/bidder-params/adform.json | 50 --- .../server/auction/FpdResolverTest.java | 24 +- .../bidder/adform/AdformBidderTest.java | 385 ------------------ .../bidder/adform/AdformHttpUtilTest.java | 307 -------------- .../bidder/adform/AdformRequestUtilTest.java | 92 ----- .../bidder/rubicon/RubiconBidderTest.java | 4 +- .../java/org/prebid/server/it/AdformTest.java | 36 -- .../org/prebid/server/it/ApplicationTest.java | 9 +- .../validation/BidderParamValidatorTest.java | 31 +- .../adform/test-adform-bid-response-1.json | 10 - .../adform/test-auction-adform-request.json | 29 -- .../adform/test-auction-adform-response.json | 37 -- .../server/it/test-application.properties | 3 +- 25 files changed, 24 insertions(+), 1753 deletions(-) delete mode 100644 src/main/java/org/prebid/server/bidder/adform/AdformBidder.java delete mode 100644 src/main/java/org/prebid/server/bidder/adform/AdformHttpUtil.java delete mode 100644 src/main/java/org/prebid/server/bidder/adform/AdformRequestUtil.java delete mode 100644 src/main/java/org/prebid/server/bidder/adform/model/AdformBid.java delete mode 100644 src/main/java/org/prebid/server/bidder/adform/model/AdformParams.java delete mode 100644 src/main/java/org/prebid/server/bidder/adform/model/UrlParameters.java delete mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/adform/ExtImpAdform.java delete mode 100644 src/main/java/org/prebid/server/spring/config/bidder/AdformConfiguration.java delete mode 100644 src/main/resources/bidder-config/adform.yaml delete mode 100644 src/main/resources/static/bidder-params/adform.json delete mode 100644 src/test/java/org/prebid/server/bidder/adform/AdformBidderTest.java delete mode 100644 src/test/java/org/prebid/server/bidder/adform/AdformHttpUtilTest.java delete mode 100644 src/test/java/org/prebid/server/bidder/adform/AdformRequestUtilTest.java delete mode 100644 src/test/java/org/prebid/server/it/AdformTest.java delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/adform/test-adform-bid-response-1.json delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/adform/test-auction-adform-request.json delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/adform/test-auction-adform-response.json diff --git a/src/main/java/org/prebid/server/bidder/ViewabilityVendors.java b/src/main/java/org/prebid/server/bidder/ViewabilityVendors.java index bb5209964b7..4ddc4b8faa5 100644 --- a/src/main/java/org/prebid/server/bidder/ViewabilityVendors.java +++ b/src/main/java/org/prebid/server/bidder/ViewabilityVendors.java @@ -3,7 +3,6 @@ public enum ViewabilityVendors { activeview("doubleclickbygoogle.com"), - adform("adform.com"), comscore("comscore.com"), doubleverify("doubleverify.com"), integralads("integralads.com"), diff --git a/src/main/java/org/prebid/server/bidder/adform/AdformBidder.java b/src/main/java/org/prebid/server/bidder/adform/AdformBidder.java deleted file mode 100644 index 2494df609c4..00000000000 --- a/src/main/java/org/prebid/server/bidder/adform/AdformBidder.java +++ /dev/null @@ -1,322 +0,0 @@ -package org.prebid.server.bidder.adform; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -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.Source; -import com.iab.openrtb.request.User; -import com.iab.openrtb.response.Bid; -import io.vertx.core.MultiMap; -import io.vertx.core.http.HttpMethod; -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.adform.model.AdformBid; -import org.prebid.server.bidder.adform.model.UrlParameters; -import org.prebid.server.bidder.model.BidderBid; -import org.prebid.server.bidder.model.BidderError; -import org.prebid.server.bidder.model.HttpCall; -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.json.JacksonMapper; -import org.prebid.server.proto.openrtb.ext.ExtPrebid; -import org.prebid.server.proto.openrtb.ext.request.ExtUser; -import org.prebid.server.proto.openrtb.ext.request.adform.ExtImpAdform; -import org.prebid.server.proto.openrtb.ext.response.BidType; -import org.prebid.server.util.HttpUtil; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -public class AdformBidder implements Bidder { - - private static final String VERSION = "0.1.3"; - private static final String BANNER = "banner"; - private static final String DEFAULT_CURRENCY = "USD"; - - private static final TypeReference> ADFORM_EXT_TYPE_REFERENCE = - new TypeReference>() { - }; - - private final String endpointUrl; - private final JacksonMapper mapper; - - private final AdformRequestUtil requestUtil; - private final AdformHttpUtil httpUtil; - - public AdformBidder(String endpointUrl, JacksonMapper mapper) { - this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); - this.mapper = Objects.requireNonNull(mapper); - - this.requestUtil = new AdformRequestUtil(); - this.httpUtil = new AdformHttpUtil(); - } - - @Override - public Result>> makeHttpRequests(BidRequest request) { - final List imps = request.getImp(); - final Result> extImpAdformsResult = getExtImpAdforms(imps); - final List extImpAdforms = extImpAdformsResult.getValue(); - final List errors = extImpAdformsResult.getErrors(); - - if (extImpAdforms.isEmpty()) { - return Result.withErrors(errors); - } - - final String currency = resolveRequestCurrency(request.getCur()); - final Device device = request.getDevice(); - final User user = request.getUser(); - final ExtUser extUser = user != null ? user.getExt() : null; - final String url = httpUtil.buildAdformUrl( - UrlParameters.builder() - .masterTagIds(getMasterTagIds(extImpAdforms)) - .keyValues(getKeyValues(extImpAdforms)) - .keyWords(getKeyWords(extImpAdforms)) - .priceTypes(getPriceType(extImpAdforms)) - .cdims(getCdims(extImpAdforms)) - .minPrices(getMinPrices(extImpAdforms)) - .endpointUrl(endpointUrl) - .tid(getTid(request.getSource())) - .ip(getIp(device)) - .advertisingId(getIfa(device)) - .secure(getSecure(imps)) - .gdprApplies(requestUtil.getGdprApplies(request.getRegs())) - .consent(requestUtil.getConsent(extUser)) - .eids(requestUtil.getEids(extUser, mapper)) - .currency(currency) - .url(getUrl(extImpAdforms)) - .build()); - - final MultiMap headers = httpUtil.buildAdformHeaders( - VERSION, - getUserAgent(device), - getIp(device), - getReferer(request.getSite()), - getUserId(user)); - - return Result.of(Collections.singletonList( - HttpRequest.builder() - .method(HttpMethod.GET) - .uri(url) - .body(null) - .headers(headers) - .payload(null) - .build()), - errors); - } - - private List getKeyValues(List extImpAdforms) { - return extImpAdforms.stream().map(ExtImpAdform::getKeyValues).collect(Collectors.toList()); - } - - private List getKeyWords(List extImpAdforms) { - return extImpAdforms.stream().map(ExtImpAdform::getKeyWords).collect(Collectors.toList()); - } - - /** - * Converts Adform Response format to {@link List} of {@link BidderBid}s with {@link List} of errors. - * Returns empty result {@link List} in case of "No Content" response status. - * Returns empty result {@link List} with errors in case of response status different from "OK" or "No Content". - */ - @Override - public Result> makeBids(HttpCall httpCall, BidRequest bidRequest) { - final HttpResponse httpResponse = httpCall.getResponse(); - - final List adformBids; - try { - adformBids = mapper.mapper().readValue( - httpResponse.getBody(), - mapper.mapper().getTypeFactory().constructCollectionType(List.class, AdformBid.class)); - } catch (JsonProcessingException e) { - return Result.withError(BidderError.badServerResponse(e.getMessage())); - } - return Result.withValues(toBidderBid(adformBids, bidRequest.getImp())); - } - - /** - * Retrieves from {@link Imp} and filter not valid {@link ExtImpAdform} and returns list result with errors. - */ - private Result> getExtImpAdforms(List imps) { - final List errors = new ArrayList<>(); - final List extImpAdforms = new ArrayList<>(); - for (final Imp imp : imps) { - if (imp.getBanner() == null) { - errors.add(BidderError.badInput(String.format( - "Adform adapter supports only banner Imps for now. Ignoring Imp ID=%s", imp.getId()))); - continue; - } - final ExtImpAdform extImpAdform; - try { - extImpAdform = mapper.mapper().convertValue(imp.getExt(), ADFORM_EXT_TYPE_REFERENCE).getBidder(); - } catch (IllegalArgumentException e) { - errors.add(BidderError.badInput(String.format("Error occurred parsing adform parameters %s", - e.getMessage()))); - continue; - } - - final Long mid = extImpAdform.getMasterTagId(); - if (mid == null || mid <= 0) { - errors.add(BidderError.badInput(String.format("master tag(placement) id is invalid=%s", mid))); - continue; - } - extImpAdforms.add(extImpAdform); - } - - return Result.of(extImpAdforms, errors); - } - - /** - * Resolves a currency that should be forwarded to bidder. Default - USD, if request - * doesn't contain USD - select the top level currency (first one); - */ - private static String resolveRequestCurrency(List currencies) { - return CollectionUtils.isNotEmpty(currencies) && !currencies.contains(DEFAULT_CURRENCY) - ? currencies.get(0) - : DEFAULT_CURRENCY; - } - - /** - * Converts {@link ExtImpAdform} {@link List} to master tag {@link List}. - */ - private List getMasterTagIds(List extImpAdforms) { - return extImpAdforms.stream().map(ExtImpAdform::getMasterTagId).collect(Collectors.toList()); - } - - /** - * Converts {@link ExtImpAdform} {@link List} to price types {@link List}. - */ - private List getPriceType(List extImpAdforms) { - return extImpAdforms.stream().map(ExtImpAdform::getPriceType).collect(Collectors.toList()); - } - - /** - * Converts {@link ExtImpAdform} {@link List} to cdims {@link List}. - */ - private List getCdims(List extImpAdforms) { - return extImpAdforms.stream().map(ExtImpAdform::getCdims).collect(Collectors.toList()); - } - - /** - * Converts {@link ExtImpAdform} {@link List} to minPrices {@link List}. - */ - private List getMinPrices(List extImpAdforms) { - return extImpAdforms.stream().map(ExtImpAdform::getMinPrice).collect(Collectors.toList()); - } - - /** - * Retrieves referer from {@link Site}. - */ - private String getReferer(Site site) { - return site != null ? site.getPage() : ""; - } - - /** - * Retrieves userId from {@link User}. - */ - private String getUserId(User user) { - return user != null ? user.getBuyeruid() : ""; - } - - /** - * Defines if request should be secured from {@link List} of {@link Imp}s. - */ - private Boolean getSecure(List imps) { - return imps.stream() - .anyMatch(imp -> Objects.equals(imp.getSecure(), 1)); - } - - /** - * Retrieves userAgent from {@link Device}. - */ - private String getUserAgent(Device device) { - return device != null ? ObjectUtils.defaultIfNull(device.getUa(), "") : ""; - } - - /** - * Retrieves ip from {@link Device}. - */ - private String getIp(Device device) { - return device != null ? ObjectUtils.defaultIfNull(device.getIp(), "") : ""; - } - - /** - * Retrieves ifs from {@link Device}. - */ - private String getIfa(Device device) { - return device != null ? ObjectUtils.defaultIfNull(device.getIfa(), "") : ""; - } - - /** - * Retrieves tid from {@link Source}. - */ - private String getTid(Source source) { - return source != null ? ObjectUtils.defaultIfNull(source.getTid(), "") : ""; - } - - /** - * Finds not blank url from {@link ExtImpAdform}. - */ - private String getUrl(List extImpAdforms) { - return extImpAdforms.stream() - .map(ExtImpAdform::getUrl) - .filter(StringUtils::isNotBlank) - .findFirst() - .orElse(""); - } - - /** - * Converts {@link AdformBid} to {@link List} of {@link BidderBid}. - */ - private List toBidderBid(List adformBids, List imps) { - final List bidderBids = new ArrayList<>(); - - for (int i = 0; i < adformBids.size(); i++) { - final AdformBid adformBid = adformBids.get(i); - final String adm = resolveAdm(adformBid); - if (StringUtils.isBlank(adm)) { - continue; - } - final BidType bidType = resolveBidType(adformBid.getResponse()); - final Imp imp = imps.get(i); - bidderBids.add(BidderBid.of(Bid.builder() - .id(imp.getId()) - .impid(imp.getId()) - .price(adformBid.getWinBid()) - .adm(adm) - .w(adformBid.getWidth()) - .h(adformBid.getHeight()) - .dealid(adformBid.getDealId()) - .crid(adformBid.getWinCrid()) - .build(), - bidType, - adformBid.getWinCur())); - } - - return bidderBids; - } - - private String resolveAdm(AdformBid adformBid) { - if (Objects.equals(adformBid.getResponse(), "banner")) { - return adformBid.getBanner(); - } - - if (Objects.equals(adformBid.getResponse(), "vast_content")) { - return adformBid.getVastContent(); - } - - return ""; - } - - private BidType resolveBidType(String response) { - return Objects.equals(response, BANNER) - ? BidType.banner : BidType.video; - } -} diff --git a/src/main/java/org/prebid/server/bidder/adform/AdformHttpUtil.java b/src/main/java/org/prebid/server/bidder/adform/AdformHttpUtil.java deleted file mode 100644 index 6b53abc1d65..00000000000 --- a/src/main/java/org/prebid/server/bidder/adform/AdformHttpUtil.java +++ /dev/null @@ -1,173 +0,0 @@ -package org.prebid.server.bidder.adform; - -import io.netty.handler.codec.http.HttpHeaderValues; -import io.vertx.core.MultiMap; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.prebid.server.bidder.adform.model.UrlParameters; -import org.prebid.server.util.HttpUtil; - -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; -import java.util.Locale; -import java.util.stream.Collectors; - -/** - * Util class to help {@link org.prebid.server.bidder.adform.AdformBidder} to create adform url and headers - */ -class AdformHttpUtil { - - private static final String PRICE_TYPE_GROSS = "gross"; - private static final String PRICE_TYPE_NET = "net"; - private static final String PRICE_TYPE_GROSS_PARAM = String.format("pt=%s", PRICE_TYPE_GROSS); - private static final String PRICE_TYPE_NET_PARAM = String.format("pt=%s", PRICE_TYPE_NET); - - private static final Locale LOCALE = Locale.US; - - AdformHttpUtil() { - } - - /** - * Creates headers for Adform request - */ - MultiMap buildAdformHeaders(String version, - String userAgent, - String ip, - String referer, - String userId) { - - final MultiMap headers = MultiMap.caseInsensitiveMultiMap() - .add(HttpUtil.CONTENT_TYPE_HEADER, HttpUtil.APPLICATION_JSON_CONTENT_TYPE) - .add(HttpUtil.ACCEPT_HEADER, HttpHeaderValues.APPLICATION_JSON) - .add(HttpUtil.USER_AGENT_HEADER, userAgent) - .add(HttpUtil.X_FORWARDED_FOR_HEADER, ip) - .add(HttpUtil.X_REQUEST_AGENT_HEADER, String.format("PrebidAdapter %s", version)); - - if (StringUtils.isNotEmpty(referer)) { - headers.add(HttpUtil.REFERER_HEADER, referer); - } - final List cookieValues = new ArrayList<>(); - if (StringUtils.isNotEmpty(userId)) { - cookieValues.add(String.format("uid=%s", userId)); - } - - if (CollectionUtils.isNotEmpty(cookieValues)) { - headers.add(HttpUtil.COOKIE_HEADER, String.join(";", cookieValues)); - } - - return headers; - } - - /** - * Creates url with parameters for adform request - */ - String buildAdformUrl(UrlParameters parameters) { - final List params = new ArrayList<>(); - - final String advertisingId = parameters.getAdvertisingId(); - if (StringUtils.isNotEmpty(advertisingId)) { - params.add(String.format("adid=%s", advertisingId)); - } - - params.add("CC=1"); - params.add("rp=4"); - params.add("fd=1"); - params.add("stid=" + parameters.getTid()); - params.add("ip=" + parameters.getIp()); - - final String priceTypeParameter = getValidPriceTypeParameter(parameters.getPriceTypes()); - if (StringUtils.isNotEmpty(priceTypeParameter)) { - params.add(priceTypeParameter); - } - - params.add("gdpr=" + parameters.getGdprApplies()); - params.add("gdpr_consent=" + parameters.getConsent()); - - final String url = parameters.getUrl(); - if (StringUtils.isNotEmpty(url)) { - params.add("url=" + HttpUtil.encodeUrl(url)); - } - - final String eids = parameters.getEids(); - if (StringUtils.isNotEmpty(eids)) { - params.add("eids=" + eids); - } - - final List encodedMids = new ArrayList<>(); - final List masterTagIds = parameters.getMasterTagIds(); - final List keyValues = parameters.getKeyValues(); - final List keyWords = parameters.getKeyWords(); - final List cdims = parameters.getCdims(); - final List minPrices = parameters.getMinPrices(); - for (int i = 0; i < masterTagIds.size(); i++) { - final StringBuilder mid = new StringBuilder( - String.format("mid=%s&rcur=%s", masterTagIds.get(i), parameters.getCurrency())); - - if (CollectionUtils.isNotEmpty(keyValues)) { - final String keyValue = keyValues.get(i); - if (StringUtils.isNotBlank(keyValue)) { - mid.append(String.format("&mkv=%s", keyValue)); - } - } - - if (CollectionUtils.isNotEmpty(keyWords)) { - final String keyWord = keyWords.get(i); - if (StringUtils.isNotBlank(keyWord)) { - mid.append(String.format("&mkw=%s", keyWord)); - } - } - - if (CollectionUtils.isNotEmpty(cdims)) { - final String cdim = cdims.get(i); - if (StringUtils.isNotBlank(cdim)) { - mid.append(String.format("&cdims=%s", cdim)); - } - } - - if (CollectionUtils.isNotEmpty(minPrices)) { - final Double minPrice = minPrices.get(i); - if (minPrice != null && minPrice > 0) { - mid.append(String.format(LOCALE, "&minp=%.2f", minPrice)); - } - } - - encodedMids.add(Base64.getUrlEncoder().withoutPadding().encodeToString(mid.toString().getBytes())); - } - - final String mids = String.join("&", encodedMids); - - final String urlParams = params.stream().sorted().collect(Collectors.joining("&")); - - final String endpointUrl = parameters.getEndpointUrl(); - final String uri = parameters.isSecure() ? endpointUrl.replaceFirst("http://", "https://") : endpointUrl; - - return String.format("%s?%s&%s", uri, urlParams, mids); - } - - /** - * Returns price type parameter if valid is found. Otherwise returns empty string. - */ - private static String getValidPriceTypeParameter(List priceTypes) { - String priceTypeParameter = ""; - for (final String priceType : priceTypes) { - final String lowCasePriceType = priceType != null ? priceType.toLowerCase() : null; - if (isPriceTypeValid(lowCasePriceType)) { - if (lowCasePriceType.equals(PRICE_TYPE_GROSS)) { - priceTypeParameter = PRICE_TYPE_GROSS_PARAM; - break; - } else { - priceTypeParameter = PRICE_TYPE_NET_PARAM; - } - } - } - return priceTypeParameter; - } - - /** - * Checks if price type is valid value - */ - private static boolean isPriceTypeValid(String priceType) { - return priceType != null && (priceType.equals(PRICE_TYPE_GROSS) || priceType.equals(PRICE_TYPE_NET)); - } -} diff --git a/src/main/java/org/prebid/server/bidder/adform/AdformRequestUtil.java b/src/main/java/org/prebid/server/bidder/adform/AdformRequestUtil.java deleted file mode 100644 index 700dcec2161..00000000000 --- a/src/main/java/org/prebid/server/bidder/adform/AdformRequestUtil.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.prebid.server.bidder.adform; - -import com.iab.openrtb.request.Regs; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.ObjectUtils; -import org.prebid.server.json.JacksonMapper; -import org.prebid.server.proto.openrtb.ext.request.ExtRegs; -import org.prebid.server.proto.openrtb.ext.request.ExtUser; -import org.prebid.server.proto.openrtb.ext.request.ExtUserEid; -import org.prebid.server.proto.openrtb.ext.request.ExtUserEidUid; - -import java.util.ArrayList; -import java.util.Base64; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Util class to help {@link org.prebid.server.bidder.adform.AdformBidder} to retrieve data from request. - */ -class AdformRequestUtil { - - /** - * Retrieves gdpr from regs.ext.gdpr and in case of any exception or invalid values returns empty string. - */ - String getGdprApplies(Regs regs) { - final ExtRegs extRegs = regs != null ? regs.getExt() : null; - final String gdpr = extRegs != null ? Integer.toString(extRegs.getGdpr()) : ""; - - return ObjectUtils.notEqual(gdpr, "1") && ObjectUtils.notEqual(gdpr, "0") ? "" : gdpr; - } - - /** - * Retrieves consent from user.ext.consent and in case of any exception or invalid values return empty string. - */ - String getConsent(ExtUser extUser) { - final String gdprConsent = extUser != null ? extUser.getConsent() : ""; - return ObjectUtils.defaultIfNull(gdprConsent, ""); - } - - /** - * Retrieves eids from user.ext.eids and in case of any exception or invalid values return empty collection. - */ - String getEids(ExtUser extUser, JacksonMapper mapper) { - final List eids = extUser != null ? extUser.getEids() : null; - final Map>> eidsMap = new HashMap<>(); - if (CollectionUtils.isNotEmpty(eids)) { - for (ExtUserEid eid : eids) { - final Map> uidMap = eidsMap.computeIfAbsent(eid.getSource(), - ignored -> new HashMap<>()); - for (ExtUserEidUid uid : eid.getUids()) { - uidMap.putIfAbsent(uid.getId(), new ArrayList()); - uidMap.get(uid.getId()).add(uid.getAtype()); - } - } - } - - final String encodedEids = mapper.encodeToString(eidsMap); - - return ObjectUtils - .defaultIfNull(Base64.getUrlEncoder().withoutPadding().encodeToString(encodedEids.getBytes()), - ""); - } -} diff --git a/src/main/java/org/prebid/server/bidder/adform/model/AdformBid.java b/src/main/java/org/prebid/server/bidder/adform/model/AdformBid.java deleted file mode 100644 index 64729679896..00000000000 --- a/src/main/java/org/prebid/server/bidder/adform/model/AdformBid.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.prebid.server.bidder.adform.model; - -import lombok.Builder; -import lombok.Value; - -import java.math.BigDecimal; - -@Builder -@Value -public class AdformBid { - - String response; - - String banner; - - BigDecimal winBid; - - String winCur; - - Integer width; - - Integer height; - - String dealId; - - String winCrid; - - String vastContent; -} diff --git a/src/main/java/org/prebid/server/bidder/adform/model/AdformParams.java b/src/main/java/org/prebid/server/bidder/adform/model/AdformParams.java deleted file mode 100644 index af60338ea7e..00000000000 --- a/src/main/java/org/prebid/server/bidder/adform/model/AdformParams.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.prebid.server.bidder.adform.model; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Value; - -@AllArgsConstructor(staticName = "of") -@Value -public class AdformParams { - - Long mid; - - @JsonProperty("priceType") - String priceType; - - @JsonProperty("mkv") - String keyValues; - - @JsonProperty("mkw") - String keyWords; - - String cdims; - - @JsonProperty("minp") - Double minPrice; - - String url; -} diff --git a/src/main/java/org/prebid/server/bidder/adform/model/UrlParameters.java b/src/main/java/org/prebid/server/bidder/adform/model/UrlParameters.java deleted file mode 100644 index edd7424fdce..00000000000 --- a/src/main/java/org/prebid/server/bidder/adform/model/UrlParameters.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.prebid.server.bidder.adform.model; - -import lombok.Builder; -import lombok.Value; - -import java.util.List; - -@Builder -@Value -public class UrlParameters { - - List masterTagIds; - - List keyValues; - - List keyWords; - - List priceTypes; - - List cdims; - - List minPrices; - - String endpointUrl; - - String tid; - - String ip; - - String advertisingId; - - boolean secure; - - String gdprApplies; - - String consent; - - String currency; - - String eids; - - String url; -} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adform/ExtImpAdform.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/adform/ExtImpAdform.java deleted file mode 100644 index 68f449b4bd4..00000000000 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adform/ExtImpAdform.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.prebid.server.proto.openrtb.ext.request.adform; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Value; - -/** - * Defines the contract for bidrequest.imp[i].ext.adform - */ -@AllArgsConstructor(staticName = "of") -@Value -public class ExtImpAdform { - - /** - * Defines the contract for bidrequest.imp[i].ext.adform.mid - */ - @JsonProperty("mid") - Long masterTagId; - - @JsonProperty("priceType") - String priceType; - - @JsonProperty("mkv") - String keyValues; - - @JsonProperty("mkw") - String keyWords; - - String cdims; - - @JsonProperty("minp") - Double minPrice; - - String url; -} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/AdformConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/AdformConfiguration.java deleted file mode 100644 index 8de313e4308..00000000000 --- a/src/main/java/org/prebid/server/spring/config/bidder/AdformConfiguration.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.prebid.server.spring.config.bidder; - -import org.prebid.server.bidder.BidderDeps; -import org.prebid.server.bidder.adform.AdformBidder; -import org.prebid.server.json.JacksonMapper; -import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties; -import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler; -import org.prebid.server.spring.config.bidder.util.UsersyncerCreator; -import org.prebid.server.spring.env.YamlPropertySourceFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; - -import javax.validation.constraints.NotBlank; - -@Configuration -@PropertySource(value = "classpath:/bidder-config/adform.yaml", factory = YamlPropertySourceFactory.class) -public class AdformConfiguration { - - private static final String BIDDER_NAME = "adform"; - - @Bean("adformConfigurationProperties") - @ConfigurationProperties("adapters.adform") - BidderConfigurationProperties configurationProperties() { - return new BidderConfigurationProperties(); - } - - @Bean - BidderDeps adformBidderDeps(BidderConfigurationProperties adformConfigurationProperties, - @NotBlank @Value("${external-url}") String externalUrl, - JacksonMapper mapper) { - - return BidderDepsAssembler.forBidder(BIDDER_NAME) - .withConfig(adformConfigurationProperties) - .usersyncerCreator(UsersyncerCreator.create(externalUrl)) - .bidderCreator(config -> new AdformBidder(config.getEndpoint(), mapper)) - .assemble(); - } -} diff --git a/src/main/resources/bidder-config/adf.yaml b/src/main/resources/bidder-config/adf.yaml index 25072aa94df..e6bc9c37684 100644 --- a/src/main/resources/bidder-config/adf.yaml +++ b/src/main/resources/bidder-config/adf.yaml @@ -1,6 +1,12 @@ adapters: adf: endpoint: https://adx.adform.net/adx/openrtb + aliases: + adform: + usersync: + url: https://cm.adform.net/cookie?redirect_url= + redirect-url: /setuid?bidder=adform&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&uid=$UID + cookie-family-name: adform meta-info: maintainer-email: scope.sspp@adform.com app-media-types: diff --git a/src/main/resources/bidder-config/adform.yaml b/src/main/resources/bidder-config/adform.yaml deleted file mode 100644 index 987c2e752eb..00000000000 --- a/src/main/resources/bidder-config/adform.yaml +++ /dev/null @@ -1,17 +0,0 @@ -adapters: - adform: - endpoint: http://adx.adform.net/adx - meta-info: - maintainer-email: scope.sspp@adform.com - app-media-types: - - banner - site-media-types: - - banner - supported-vendors: - vendor-id: 50 - usersync: - url: https://cm.adform.net/cookie?redirect_url= - redirect-url: /setuid?bidder=adform&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&uid=$UID - cookie-family-name: adform - type: redirect - support-cors: false diff --git a/src/main/resources/bidder-config/rubicon.yaml b/src/main/resources/bidder-config/rubicon.yaml index 1d38ff201c5..232b9ad10c1 100644 --- a/src/main/resources/bidder-config/rubicon.yaml +++ b/src/main/resources/bidder-config/rubicon.yaml @@ -10,7 +10,6 @@ adapters: - video supported-vendors: - activeview - - adform - comscore - doubleverify - integralads diff --git a/src/main/resources/static/bidder-params/adform.json b/src/main/resources/static/bidder-params/adform.json deleted file mode 100644 index 0f4ad83bfdf..00000000000 --- a/src/main/resources/static/bidder-params/adform.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Adform Adapter Params", - "description": "A schema which validates params accepted by the Adform adapter", - "type": "object", - "properties": { - "mid": { - "type": [ - "integer", - "string" - ], - "description": "An ID which identifies the placement selling the impression" - }, - "priceType": { - "type": "string", - "enum": [ - "gross", - "net" - ], - "description": "An expected price type (net or gross) of bids." - }, - "mkv": { - "type": "string", - "description": "Comma-separated key-value pairs. Forbidden symbols: &. Example: mkv='color:blue,length:350'", - "pattern": "^(\\s*|((\\s*[^,:&\\s]+\\s*:[^,:&]*)(,\\s*[^,:&\\s]+\\s*:[^,:&]*)*))$" - }, - "mkw": { - "type": "string", - "description": "Comma-separated keywords. Forbidden symbols: &.", - "pattern": "^[^&]*$" - }, - "cdims": { - "type": "string", - "description": "Comma-separated creative dimensions.", - "pattern": "(^\\d+x\\d+)(,\\d+x\\d+)*$" - }, - "minp": { - "type": "number", - "description": "The minimum CPM price.", - "minimum": 0 - }, - "url": { - "type": "string", - "description": "Custom URL for targeting." - } - }, - "required": [ - "mid" - ] -} diff --git a/src/test/java/org/prebid/server/auction/FpdResolverTest.java b/src/test/java/org/prebid/server/auction/FpdResolverTest.java index f0662a88399..0c93e232858 100644 --- a/src/test/java/org/prebid/server/auction/FpdResolverTest.java +++ b/src/test/java/org/prebid/server/auction/FpdResolverTest.java @@ -745,20 +745,20 @@ public void resolveBidRequestExtShouldMergeBidders() { // when final ExtRequest result = fpdResolver.resolveBidRequestExt(givenExtRequest, - Targeting.of(Arrays.asList("rubicon", "adform"), null, null)); + Targeting.of(Arrays.asList("rubicon", "between"), null, null)); // then - assertThat(result.getPrebid().getData().getBidders()).contains("rubicon", "appnexus", "adform"); + assertThat(result.getPrebid().getData().getBidders()).contains("rubicon", "appnexus", "between"); } @Test public void resolveBidRequestExtShouldAddBiddersIfExtIsNull() { // when final ExtRequest result = fpdResolver.resolveBidRequestExt(null, - Targeting.of(Arrays.asList("rubicon", "adform"), null, null)); + Targeting.of(Arrays.asList("rubicon", "appnexus"), null, null)); // then - assertThat(result.getPrebid().getData().getBidders()).contains("rubicon", "adform"); + assertThat(result.getPrebid().getData().getBidders()).contains("rubicon", "appnexus"); } @Test @@ -768,10 +768,10 @@ public void resolveBidRequestExtShouldAddBiddersIfExtPrebidIsNull() { // when final ExtRequest result = fpdResolver.resolveBidRequestExt(givenExtRequest, - Targeting.of(Arrays.asList("rubicon", "adform"), null, null)); + Targeting.of(Arrays.asList("rubicon", "appnexus"), null, null)); // then - assertThat(result.getPrebid().getData().getBidders()).contains("rubicon", "adform"); + assertThat(result.getPrebid().getData().getBidders()).contains("rubicon", "appnexus"); } @Test @@ -781,11 +781,11 @@ public void resolveBidRequestExtShouldAddBiddersIfExtPrebidDataIsNullKeepingOthe // when final ExtRequest result = fpdResolver.resolveBidRequestExt(givenExtRequest, - Targeting.of(Arrays.asList("rubicon", "adform"), null, null)); + Targeting.of(Arrays.asList("rubicon", "appnexus"), null, null)); // then assertThat(result).isEqualTo(ExtRequest.of(ExtRequestPrebid.builder().debug(1) - .data(ExtRequestPrebidData.of(Arrays.asList("rubicon", "adform"), null)).build())); + .data(ExtRequestPrebidData.of(Arrays.asList("rubicon", "appnexus"), null)).build())); } @Test @@ -796,11 +796,11 @@ public void resolveBidRequestExtShouldAddBiddersIfExtPrebidDataBiddersIsNull() { // when final ExtRequest result = fpdResolver.resolveBidRequestExt(givenExtRequest, - Targeting.of(Arrays.asList("rubicon", "adform"), null, null)); + Targeting.of(Arrays.asList("rubicon", "appnexus"), null, null)); // then assertThat(result).isEqualTo(ExtRequest.of(ExtRequestPrebid.builder().debug(1) - .data(ExtRequestPrebidData.of(Arrays.asList("rubicon", "adform"), null)).build())); + .data(ExtRequestPrebidData.of(Arrays.asList("rubicon", "appnexus"), null)).build())); } @Test @@ -841,7 +841,7 @@ public void resolveBidRequestExtShouldNotAddBidderConfigWhenUserAndSiteIsNull() public void resolveBidRequestExtShoulUpdateBidderConfigAndData() { // given final ExtRequest givenExtRequest = ExtRequest.of(null); - final Targeting targeting = Targeting.of(Arrays.asList("rubicon", "adform"), + final Targeting targeting = Targeting.of(Arrays.asList("rubicon", "appnexus"), mapper.valueToTree(Site.builder().id("id").build()), mapper.valueToTree(User.builder().id("id").build())); @@ -850,7 +850,7 @@ public void resolveBidRequestExtShoulUpdateBidderConfigAndData() { // then assertThat(result).isEqualTo(ExtRequest.of(ExtRequestPrebid.builder() - .data(ExtRequestPrebidData.of(Arrays.asList("rubicon", "adform"), null)) + .data(ExtRequestPrebidData.of(Arrays.asList("rubicon", "appnexus"), null)) .bidderconfig(Collections.singletonList(ExtRequestPrebidBidderConfig.of( Collections.singletonList("*"), ExtBidderConfig.of(null, ExtBidderConfigOrtb.of( mapper.valueToTree(Site.builder().id("id").build()), null, diff --git a/src/test/java/org/prebid/server/bidder/adform/AdformBidderTest.java b/src/test/java/org/prebid/server/bidder/adform/AdformBidderTest.java deleted file mode 100644 index cf86cb8aa5d..00000000000 --- a/src/test/java/org/prebid/server/bidder/adform/AdformBidderTest.java +++ /dev/null @@ -1,385 +0,0 @@ -package org.prebid.server.bidder.adform; - -import com.fasterxml.jackson.core.JsonProcessingException; -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.Regs; -import com.iab.openrtb.request.Site; -import com.iab.openrtb.request.Source; -import com.iab.openrtb.request.User; -import com.iab.openrtb.response.Bid; -import io.netty.handler.codec.http.HttpHeaderValues; -import io.vertx.core.http.HttpMethod; -import org.junit.Before; -import org.junit.Test; -import org.prebid.server.VertxTest; -import org.prebid.server.bidder.adform.model.AdformBid; -import org.prebid.server.bidder.model.BidderBid; -import org.prebid.server.bidder.model.BidderError; -import org.prebid.server.bidder.model.HttpCall; -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.proto.openrtb.ext.ExtPrebid; -import org.prebid.server.proto.openrtb.ext.request.ExtRegs; -import org.prebid.server.proto.openrtb.ext.request.ExtUser; -import org.prebid.server.proto.openrtb.ext.request.ExtUserEid; -import org.prebid.server.proto.openrtb.ext.request.ExtUserEidUid; -import org.prebid.server.proto.openrtb.ext.request.adform.ExtImpAdform; -import org.prebid.server.proto.openrtb.ext.response.BidType; -import org.prebid.server.util.HttpUtil; - -import java.util.Arrays; -import java.util.Base64; -import java.util.List; -import java.util.Map; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; - -public class AdformBidderTest extends VertxTest { - - private static final String ENDPOINT_URL = "http://adform.com/openrtb2d"; - - private AdformBidder adformBidder; - - @Before - public void setUp() { - adformBidder = new AdformBidder(ENDPOINT_URL, jacksonMapper); - } - - @Test - public void makeHttpRequestsShouldReturnHttpRequestWithoutErrors() { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(Imp.builder() - .banner(Banner.builder().build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdform.of(15L, "gross", "color:red", "red", "300X60", 2.5, - "https://adform.com?a=b")))) - .build())) - .site(Site.builder().page("www.example.com").build()) - .regs(Regs.of(null, ExtRegs.of(1, null))) - .user(User.builder() - .buyeruid("buyeruid") - .ext(ExtUser.builder() - .consent("consent") - .eids(singletonList(ExtUserEid.of("test.com", "some_user_id", - singletonList(ExtUserEidUid.of("uId", 1, null)), null))) - .build()) - .build()) - .device(Device.builder().ua("ua").ip("ip").ifa("ifaId").build()) - .source(Source.builder().tid("tid").build()) - .build(); - - // when - final Result>> result = adformBidder.makeHttpRequests(bidRequest); - - // then - final String expectedEncodedPart = Base64.getUrlEncoder().withoutPadding() - .encodeToString("mid=15&rcur=USD&mkv=color:red&mkw=red&cdims=300X60&minp=2.50".getBytes()); - - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getUri) - .containsExactly( - "http://adform.com/openrtb2d?CC=1&adid=ifaId&eids=eyJ0ZXN0LmNvbSI6eyJ1SWQiOlsxXX19&fd=1&gdpr=1" - + "&gdpr_consent=consent&ip=ip&pt=gross&rp=4&stid=tid" - + "&url=https%3A%2F%2Fadform.com%3Fa%3Db" - + "&" + expectedEncodedPart); - assertThat(result.getValue()).extracting(HttpRequest::getMethod).containsExactly(HttpMethod.GET); - - assertThat(result.getValue()) - .flatExtracting(res -> res.getHeaders().entries()) - .extracting(Map.Entry::getKey, Map.Entry::getValue) - .containsOnly(tuple(HttpUtil.CONTENT_TYPE_HEADER.toString(), HttpUtil.APPLICATION_JSON_CONTENT_TYPE), - tuple(HttpUtil.ACCEPT_HEADER.toString(), HttpHeaderValues.APPLICATION_JSON.toString()), - tuple(HttpUtil.USER_AGENT_HEADER.toString(), "ua"), - tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "ip"), - tuple(HttpUtil.X_REQUEST_AGENT_HEADER.toString(), "PrebidAdapter 0.1.3"), - tuple(HttpUtil.REFERER_HEADER.toString(), "www.example.com"), - // Base64 encoded {"id":"id","version":1,"keyv":123,"privacy":{"optout":true}} - tuple(HttpUtil.COOKIE_HEADER.toString(), "uid=buyeruid")); - } - - @Test - public void makeHttpRequestsShouldReturnErrorIfMediaTypeIsNotBanner() { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("Imp12") - .build())) - .build(); - - // when - final Result>> result = adformBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badInput( - "Adform adapter supports only banner Imps for now. Ignoring Imp ID=Imp12")); - } - - @Test - public void makeHttpRequestsShouldReturnErrorIfMasterTagIsLessThanOne() { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("Imp12") - .banner(Banner.builder().build()) - .ext(mapper.valueToTree(ExtPrebid.of( - null, ExtImpAdform.of(0L, null, null, null, null, null, null)))) - .build())) - .build(); - - // when - final Result>> result = adformBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()).hasSize(1) - .contains(BidderError.badInput("master tag(placement) id is invalid=0")); - } - - @Test - public void makeHttpRequestsShouldReturnHttpRequestsWithErrors() { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(asList(Imp.builder() - .banner(Banner.builder().build()) - .ext(mapper.valueToTree(ExtPrebid.of( - null, ExtImpAdform.of(15L, null, null, null, null, null, null)))) - .build(), - Imp.builder().build())) - .build(); - - // when - final Result>> result = adformBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1); - assertThat(result.getErrors()).hasSize(1); - } - - @Test - public void makeHttpRequestsShouldReturnHttpsUrlIfAtLeastOneImpIsSecured() { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(asList(Imp.builder() - .banner(Banner.builder().build()) - .secure(1) - .ext(mapper.valueToTree(ExtPrebid.of( - null, ExtImpAdform.of(15L, null, null, null, null, null, null)))) - .build(), - Imp.builder().build())) - .source(Source.builder().tid("tid").build()) - .user(User.builder() - .buyeruid("buyeruid") - .ext(ExtUser.builder() - .consent("consent") - .eids(singletonList(ExtUserEid.of("test.com", "some_user_id", - singletonList(ExtUserEidUid.of("uId", 1, null)), null))) - .build()) - .build()) - .build(); - - // when - final Result>> result = adformBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getUri) - .containsExactly("https://adform.com/openrtb2d?CC=1&eids=eyJ0ZXN0LmNvbSI6eyJ1SWQiOlsxXX19&fd=1&gdpr=" - + "&gdpr_consent=consent&ip=&rp=4&stid=tid&bWlkPTE1JnJjdXI9VVNE"); - } - - @Test - public void makeBidsShouldReturnEmptyBidderWithErrorWhenResponseCantBeParsed() { - // given - final HttpCall httpCall = givenHttpCall("{"); - - // when - final Result> result = adformBidder.makeBids(httpCall, null); - - // then - assertThat(result.getErrors().get(0).getMessage()).startsWith( - "Unexpected end-of-input: expected close marker for Object"); - assertThat(result.getValue()).isEmpty(); - } - - @Test - public void makeBidsShouldReturnEmptyListIfResponseBannerFieldIsEmpty() throws JsonProcessingException { - // given - final String adformResponse = mapper.writeValueAsString(AdformBid.builder().build()); - final HttpCall httpCall = givenHttpCall(adformResponse); - final BidRequest bidRequest = BidRequest.builder().imp(singletonList(Imp.builder().build())).build(); - - // when - final Result> result = adformBidder.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); - } - - @Test - public void makeBidsShouldReturnEmptyListIfResponseTypeIsNotBanner() throws JsonProcessingException { - // given - final String adformResponse = mapper.writeValueAsString(AdformBid.builder().banner("someBanner") - .response("notBanner").build()); - final HttpCall httpCall = givenHttpCall(adformResponse); - final BidRequest bidRequest = BidRequest.builder().imp(singletonList(Imp.builder().build())).build(); - - // when - final Result> result = adformBidder.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); - } - - @Test - public void makeBidsShouldReturnBidderBidIfResponseIsBannerAndBannerIsPresent() throws JsonProcessingException { - // given - final String adformResponse = mapper.writeValueAsString(AdformBid.builder().banner("admBanner") - .response("banner").winCur("currency").build()); - - final HttpCall httpCall = givenHttpCall(adformResponse); - final BidRequest bidRequest = BidRequest.builder().imp(singletonList(Imp.builder().id("id").build())).build(); - - // when - final Result> result = adformBidder.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1).containsOnly(BidderBid.of( - Bid.builder().id("id").impid("id").adm("admBanner").build(), BidType.banner, "currency")); - } - - @Test - public void makeBidsShouldReturnBidderBidsWithCurrencyFromEveryAdformResponse() throws JsonProcessingException { - // given - final AdformBid firstAdformResponse = AdformBid.builder().banner("admBanner") - .response("banner").winCur("EUR").build(); - final AdformBid secondAdformResponse = AdformBid.builder().banner("admBanner") - .response("banner").winCur("USD").build(); - final String adformBidResponse = - mapper.writeValueAsString(Arrays.asList(firstAdformResponse, secondAdformResponse)); - - final HttpCall httpCall = givenHttpCall(adformBidResponse); - final BidRequest bidRequest = BidRequest.builder() - .imp(asList(Imp.builder().id("firstId").build(), Imp.builder().id("secondId").build())) - .build(); - - // when - final Result> result = adformBidder.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(2).containsExactly( - BidderBid.of(Bid.builder().id("firstId").impid("firstId").adm("admBanner").build(), - BidType.banner, - "EUR"), - BidderBid.of(Bid.builder().id("secondId").impid("secondId").adm("admBanner").build(), - BidType.banner, - "USD")); - } - - @Test - public void makeBidsShouldReturnVideoBidIfResponseEqualsVastContent() throws JsonProcessingException { - // given - final String adformResponse = mapper.writeValueAsString( - AdformBid.builder().winCur("currency").vastContent("admVastContent").response("vast_content").build()); - - final HttpCall httpCall = givenHttpCall(adformResponse); - final BidRequest bidRequest = BidRequest.builder().imp(singletonList(Imp.builder().id("id").build())).build(); - - // when - final Result> result = adformBidder.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1).containsOnly(BidderBid.of(Bid.builder().id("id").impid("id") - .adm("admVastContent").build(), BidType.video, "currency")); - } - - @Test - public void makeBidsShouldReturnEmptyResultIfVastContentOrBannerIsNotPresent() throws JsonProcessingException { - // given - final String adformResponse = mapper.writeValueAsString(AdformBid.builder().build()); - - final HttpCall httpCall = givenHttpCall(adformResponse); - final BidRequest bidRequest = BidRequest.builder().imp(singletonList(Imp.builder().id("id").build())).build(); - - // when - final Result> result = adformBidder.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); - } - - @Test - public void makeBidsShouldSkipInvalidBidAndReturnValidBid() throws JsonProcessingException { - // given - final String adformResponse = mapper.writeValueAsString(asList(AdformBid.builder().banner("admBanner") - .response("banner").build(), AdformBid.builder().build())); - - final HttpCall httpCall = givenHttpCall(adformResponse); - final BidRequest bidRequest = BidRequest.builder().imp(asList(Imp.builder().id("id1").build(), - Imp.builder().id("id2").build())).build(); - - // when - final Result> result = adformBidder.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1).doesNotContainNull().element(0).isEqualTo(BidderBid.of( - Bid.builder().id("id1").impid("id1").adm("admBanner").build(), BidType.banner, null)); - } - - @Test - public void makeHttpRequestsShouldPassMultipleUserIds() { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(asList(Imp.builder() - .banner(Banner.builder().build()) - .secure(1) - .ext(mapper.valueToTree(ExtPrebid.of( - null, ExtImpAdform.of(15L, null, null, null, null, null, null)))) - .build(), - Imp.builder().build())) - .source(Source.builder().tid("tid").build()) - .user(User.builder() - .buyeruid("buyeruid") - .ext(ExtUser.builder() - .consent("consent") - .eids(asList(ExtUserEid.of("test.com", "some_user_id", - singletonList(ExtUserEidUid.of("uId", 1, null)), null), - ExtUserEid.of("test.com", "some_user_id", - singletonList(ExtUserEidUid.of("uId", 2, null)), null), - ExtUserEid.of("test.net", "some_user_id", - singletonList(ExtUserEidUid.of("id_some_user", 3, null)), null))) - .build()) - .build()) - .build(); - - // when - final Result>> result = adformBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getUri) - .containsExactly( - "https://adform.com/openrtb2d?CC=1&eids=eyJ0ZXN0LmNvbSI6eyJ1SWQiOlsxLDJdfSwidGVzdC5uZXQiOnsiaWRfc29tZV91c2VyIjpbM119fQ&fd=1&gdpr=" - + "&gdpr_consent=consent&ip=&rp=4&stid=tid&bWlkPTE1JnJjdXI9VVNE"); - } - - private static HttpCall givenHttpCall(String body) { - return HttpCall.success(null, HttpResponse.of(200, null, body), null); - } -} diff --git a/src/test/java/org/prebid/server/bidder/adform/AdformHttpUtilTest.java b/src/test/java/org/prebid/server/bidder/adform/AdformHttpUtilTest.java deleted file mode 100644 index 3b5c320b764..00000000000 --- a/src/test/java/org/prebid/server/bidder/adform/AdformHttpUtilTest.java +++ /dev/null @@ -1,307 +0,0 @@ -package org.prebid.server.bidder.adform; - -import io.netty.handler.codec.http.HttpHeaderValues; -import io.vertx.core.MultiMap; -import org.junit.Before; -import org.junit.Test; -import org.prebid.server.VertxTest; -import org.prebid.server.bidder.adform.model.UrlParameters; -import org.prebid.server.util.HttpUtil; - -import java.util.Base64; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; -import static org.assertj.core.util.Lists.emptyList; - -public class AdformHttpUtilTest extends VertxTest { - - private AdformHttpUtil httpUtil; - - @Before - public void setUp() { - httpUtil = new AdformHttpUtil(); - } - - @Test - public void buildAdformHeadersShouldReturnAllHeaders() { - // when - final MultiMap headers = httpUtil.buildAdformHeaders( - "0.1.0", - "userAgent", - "ip", - "www.example.com", - "buyeruid"); - - // then - assertThat(headers).hasSize(7) - .extracting(Map.Entry::getKey, Map.Entry::getValue) - .containsOnly(tuple(HttpUtil.CONTENT_TYPE_HEADER.toString(), HttpUtil.APPLICATION_JSON_CONTENT_TYPE), - tuple(HttpUtil.ACCEPT_HEADER.toString(), HttpHeaderValues.APPLICATION_JSON.toString()), - tuple(HttpUtil.USER_AGENT_HEADER.toString(), "userAgent"), - tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "ip"), - tuple(HttpUtil.X_REQUEST_AGENT_HEADER.toString(), "PrebidAdapter 0.1.0"), - tuple(HttpUtil.REFERER_HEADER.toString(), "www.example.com"), - tuple(HttpUtil.COOKIE_HEADER.toString(), "uid=buyeruid")); - } - - @Test - public void buildAdformHeadersShouldNotContainRefererHeaderIfRefererIsEmpty() { - // when - final MultiMap headers = httpUtil.buildAdformHeaders( - "0.1.0", - "userAgent", - "ip", - "", - "buyeruid"); - - // then - assertThat(headers).extracting(Map.Entry::getKey).doesNotContain(HttpUtil.REFERER_HEADER.toString()); - } - - @Test - public void buildAdformHeadersShouldNotContainCookieHeaderIfUserIdIsEmpty() { - // when - final MultiMap headers = httpUtil.buildAdformHeaders( - "0.1.0", - "userAgent", - "ip", - "referer", - ""); - - // then - assertThat(headers).extracting(Map.Entry::getKey).doesNotContain(HttpUtil.COOKIE_HEADER.toString()); - } - - @Test - public void buildAdformHeaderShouldContainCookieHeaderOnlyWithUserIdIfUserIdPresent() { - // when - final MultiMap headers = httpUtil.buildAdformHeaders( - "0.1.0", - "userAgent", - "ip", - "referer", - "buyeruid"); - - // then - assertThat(headers).extracting(Map.Entry::getKey, Map.Entry::getValue) - .contains(tuple(HttpUtil.COOKIE_HEADER.toString(), "uid=buyeruid")); - } - - @Test - public void buildAdformUrlShouldReturnCorrectUrl() { - // when - final String url = httpUtil.buildAdformUrl( - UrlParameters.builder() - .masterTagIds(asList(15L, 16L)) - .keyValues(asList("color:red", "age:30-40")) - .keyWords(asList("red", "blue")) - .priceTypes(singletonList("gross")) - .cdims(asList("300x300,400x200", "300x200")) - .minPrices(asList(23.1, null)) - .endpointUrl("http://adx.adform.net/adx") - .tid("tid") - .ip("ip") - .advertisingId("adId") - .gdprApplies("1") - .consent("consent") - .secure(false) - .currency("USD") - .eids("eyJ0ZXN0LmNvbSI6eyJvdGh") - .url("https://adform.com?a=b") - .build()); - - // then - final String expectedEncodedPart = Stream.of( - "mid=15&rcur=USD&mkv=color:red&mkw=red&cdims=300x300,400x200&minp=23.10", - "mid=16&rcur=USD&mkv=age:30-40&mkw=blue&cdims=300x200") - .map(s -> Base64.getUrlEncoder().withoutPadding().encodeToString(s.getBytes())) - .collect(Collectors.joining("&")); - - assertThat(url).isEqualTo( - "http://adx.adform.net/adx?CC=1&adid=adId&eids=eyJ0ZXN0LmNvbSI6eyJvdGh&" - + "fd=1&gdpr=1&gdpr_consent=consent&ip=ip&pt=gross&rp=4" - + "&stid=tid&url=https%3A%2F%2Fadform.com%3Fa%3Db" - + "&" + expectedEncodedPart); - } - - @Test - public void buildAdformUrlShouldReturnHttpsProtocolIfSecureIsTrue() { - // when - final String url = httpUtil.buildAdformUrl(UrlParameters.builder() - .masterTagIds(asList(15L, 16L)) - .priceTypes(singletonList("gross")) - .endpointUrl("http://adx.adform.net/adx") - .tid("tid") - .ip("ip") - .advertisingId("adId") - .gdprApplies("") - .consent("") - .secure(true) - .currency("USD") - .build()); - - // then - // bWlkPTE1 is Base64 encoded mid=15 and bWlkPTE2 encoded mid=16, so bWlkPTE1&bWlkPTE2 = mid=15&mid=16 - assertThat(url).isEqualTo( - "https://adx.adform.net/adx?CC=1&adid=adId&fd=1&gdpr=&gdpr_consent=&ip=ip&pt=gross&rp=4" - + "&stid=tid&bWlkPTE1JnJjdXI9VVNE&bWlkPTE2JnJjdXI9VVNE"); - } - - @Test - public void buildAdformUrlShouldNotContainAdidParamIfAdvertisingIdIsMissed() { - // when - final String url = httpUtil.buildAdformUrl(UrlParameters.builder() - .masterTagIds(asList(15L, 16L)) - .priceTypes(singletonList("gross")) - .endpointUrl("http://adx.adform.net/adx") - .tid("tid") - .ip("ip") - .advertisingId(null) - .gdprApplies("") - .consent("") - .currency("USD") - .build()); - - // then - // bWlkPTE1 is Base64 encoded mid=15 and bWlkPTE2 encoded mid=16, so bWlkPTE1&bWlkPTE2 = mid=15&mid=16 - assertThat(url) - .isEqualTo("http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=ip&pt=gross&rp=4&" - + "stid=tid&bWlkPTE1JnJjdXI9VVNE&bWlkPTE2JnJjdXI9VVNE"); - } - - @Test - public void buildAdformUrlShouldNotContainPtParamIfPriceTypesListIsEmpty() { - // when - final String url = httpUtil.buildAdformUrl(UrlParameters.builder() - .masterTagIds(asList(15L, 16L)) - .priceTypes(emptyList()) - .endpointUrl("http://adx.adform.net/adx") - .tid("tid") - .ip("ip") - .consent("") - .gdprApplies("") - .currency("USD") - .build()); - - // then - // bWlkPTE1 is Base64 encoded mid=15 and bWlkPTE2 encoded mid=16, so bWlkPTE1&bWlkPTE2 = mid=15&mid=16 - assertThat(url).isEqualTo("http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=ip&rp=4&" - + "stid=tid&bWlkPTE1JnJjdXI9VVNE&bWlkPTE2JnJjdXI9VVNE"); - } - - @Test - public void buildAdformUrlShouldNotContainPtParamIfNoValidPriceTypes() { - // when - final String url = httpUtil.buildAdformUrl(UrlParameters.builder() - .masterTagIds(asList(15L, 16L)) - .priceTypes(singletonList("notValid")) - .endpointUrl("http://adx.adform.net/adx") - .tid("tid") - .ip("ip") - .gdprApplies("") - .consent("") - .currency("USD") - .build()); - - // then - // bWlkPTE1 is Base64 encoded mid=15 and bWlkPTE2 encoded mid=16, so bWlkPTE1&bWlkPTE2 = mid=15&mid=16 - assertThat(url).isEqualTo("http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=ip&rp=4&" - + "stid=tid&bWlkPTE1JnJjdXI9VVNE&bWlkPTE2JnJjdXI9VVNE"); - } - - @Test - public void buildAdformUrlShouldHasNetPtParamIfOnlyNetIsInPriceTypesList() { - // when - final String url = httpUtil.buildAdformUrl(UrlParameters.builder() - .masterTagIds(asList(15L, 16L)) - .priceTypes(singletonList("Net")) - .endpointUrl("http://adx.adform.net/adx") - .tid("tid") - .ip("ip") - .gdprApplies("") - .consent("") - .currency("USD") - .build()); - - // then - // bWlkPTE1 is Base64 encoded mid=15 and bWlkPTE2 encoded mid=16, so bWlkPTE1&bWlkPTE2 = mid=15&mid=16 - assertThat(url).isEqualTo("http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=ip&pt=net&rp=4&" - + "stid=tid&bWlkPTE1JnJjdXI9VVNE&bWlkPTE2JnJjdXI9VVNE"); - } - - @Test - public void buildAdformUrlShouldHasGrossPtParamIfOnlyGrossIsInPriceTypesList() { - // when - final String url = httpUtil.buildAdformUrl(UrlParameters.builder() - .priceTypes(singletonList("Gross")) - .masterTagIds(asList(15L, 16L)) - .endpointUrl("http://adx.adform.net/adx") - .tid("tid") - .ip("ip") - .gdprApplies("") - .consent("") - .currency("USD") - .build()); - - // then - // bWlkPTE1 is Base64 encoded mid=15 and bWlkPTE2 encoded mid=16, so bWlkPTE1&bWlkPTE2 = mid=15&mid=16 - assertThat(url) - .isEqualTo("http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=ip&pt=gross&rp=4" - + "&stid=tid&bWlkPTE1JnJjdXI9VVNE&bWlkPTE2JnJjdXI9VVNE"); - } - - @Test - public void buildAdformUrlShouldHasGrossPtParamIfGrossAndNetAndNotValidPriceTypesAreInList() { - // when - final String url = httpUtil.buildAdformUrl(UrlParameters.builder() - .priceTypes(asList("Net", "Gross", "NotValid")) - .masterTagIds(asList(15L, 16L)) - .endpointUrl("http://adx.adform.net/adx") - .tid("tid") - .ip("ip") - .gdprApplies("") - .consent("") - .currency("USD") - .build()); - - // then - // bWlkPTE1 is Base64 encoded mid=15 and bWlkPTE2 encoded mid=16, so bWlkPTE1&bWlkPTE2 = mid=15&mid=16 - assertThat(url) - .isEqualTo("http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=ip&pt=gross&rp=4" - + "&stid=tid&bWlkPTE1JnJjdXI9VVNE&bWlkPTE2JnJjdXI9VVNE"); - } - - @Test - public void buildAdformUrlShouldNotContainEidsParamIfEmptyEids() { - // when - final String url = httpUtil.buildAdformUrl( - UrlParameters.builder() - .masterTagIds(asList(15L, 16L)) - .keyValues(asList("color:red", "age:30-40")) - .keyWords(asList("red", "blue")) - .priceTypes(singletonList("gross")) - .endpointUrl("http://adx.adform.net/adx") - .tid("tid") - .ip("ip") - .advertisingId("adId") - .gdprApplies("1") - .consent("consent") - .secure(false) - .currency("USD") - .eids(null) - .build()); - - // then - // bWlkPTE1 is Base64 encoded mid=15 and bWlkPTE2 encoded mid=16, so bWlkPTE1&bWlkPTE2 = mid=15&mid=16 - assertThat(url).isEqualTo( - "http://adx.adform.net/adx?CC=1&adid=adId&fd=1&gdpr=1&gdpr_consent=consent&ip=ip&pt=gross&rp=4" - + "&stid=tid&bWlkPTE1JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQmbWt3PXJlZA" - + "&bWlkPTE2JnJjdXI9VVNEJm1rdj1hZ2U6MzAtNDAmbWt3PWJsdWU"); - } -} diff --git a/src/test/java/org/prebid/server/bidder/adform/AdformRequestUtilTest.java b/src/test/java/org/prebid/server/bidder/adform/AdformRequestUtilTest.java deleted file mode 100644 index 46afb44585d..00000000000 --- a/src/test/java/org/prebid/server/bidder/adform/AdformRequestUtilTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.prebid.server.bidder.adform; - -import com.iab.openrtb.request.Regs; -import org.junit.Before; -import org.junit.Test; -import org.prebid.server.VertxTest; -import org.prebid.server.proto.openrtb.ext.request.ExtRegs; -import org.prebid.server.proto.openrtb.ext.request.ExtUser; - -import static org.assertj.core.api.Assertions.assertThat; - -public class AdformRequestUtilTest extends VertxTest { - - private AdformRequestUtil requestUtil; - - @Before - public void setUp() { - requestUtil = new AdformRequestUtil(); - } - - @Test - public void getGdprAppliesShouldReturnEmptyValueWhenRegsIsNull() { - // given and when - final String gdpr = requestUtil.getGdprApplies(null); - - // then - assertThat(gdpr).isEmpty(); - } - - @Test - public void getGdprAppliesShouldReturnEmptyValueWhenRegsExtIsNull() { - // given and when - final String gdpr = requestUtil.getGdprApplies(Regs.of(null, null)); - - // then - assertThat(gdpr).isEmpty(); - } - - @Test - public void getGdprAppliesShouldReturnEmptyValueWhenRegsExtGdprIsNoEqualsToOneOrZero() { - // given and when - final String gdpr = requestUtil.getGdprApplies(Regs.of(null, ExtRegs.of(2, null))); - - // then - assertThat(gdpr).isEmpty(); - } - - @Test - public void getGdprAppliesShouldReturnOne() { - // given and when - final String gdpr = requestUtil.getGdprApplies(Regs.of(null, ExtRegs.of(1, null))); - - // then - assertThat(gdpr).isEqualTo("1"); - } - - @Test - public void getGdprAppliesShouldReturnZero() { - // given and when - final String gdpr = requestUtil.getGdprApplies(Regs.of(null, ExtRegs.of(0, null))); - - // then - assertThat(gdpr).isEqualTo("0"); - } - - @Test - public void getConsentShouldReturnEmptyValueWhenExtUserIsNull() { - // given and when - final String consent = requestUtil.getConsent(null); - - // then - assertThat(consent).isEmpty(); - } - - @Test - public void getConsentShouldReturnEmptyValueWhenConsentIsNull() { - // given and when - final String consent = requestUtil.getConsent(ExtUser.builder().build()); - - // then - assertThat(consent).isEmpty(); - } - - @Test - public void getConsentShouldReturnConsent() { - // given and when - final String consent = requestUtil.getConsent(ExtUser.builder().consent("consent").build()); - - // then - assertThat(consent).isEqualTo("consent"); - } -} diff --git a/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java b/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java index 84007b523d0..f1c015170e8 100644 --- a/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java @@ -122,8 +122,8 @@ public class RubiconBidderTest extends VertxTest { private static final String ENDPOINT_URL = "http://rubiconproject.com/exchange.json?tk_xint=prebid"; private static final String USERNAME = "username"; private static final String PASSWORD = "password"; - private static final List SUPPORTED_VENDORS = Arrays.asList("activeview", "adform", - "comscore", "doubleverify", "integralads", "moat", "sizmek", "whiteops"); + private static final List SUPPORTED_VENDORS = Arrays.asList("activeview", "comscore", + "doubleverify", "integralads", "moat", "sizmek", "whiteops"); @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); diff --git a/src/test/java/org/prebid/server/it/AdformTest.java b/src/test/java/org/prebid/server/it/AdformTest.java deleted file mode 100644 index 29615cb0b81..00000000000 --- a/src/test/java/org/prebid/server/it/AdformTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.prebid.server.it; - -import io.restassured.response.Response; -import org.json.JSONException; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.prebid.server.model.Endpoint; -import org.springframework.test.context.junit4.SpringRunner; - -import java.io.IOException; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.absent; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static java.util.Collections.singletonList; - -@RunWith(SpringRunner.class) -public class AdformTest extends IntegrationTest { - - @Test - public void openrtb2AuctionShouldRespondWithBidsFromAdform() throws IOException, JSONException { - // given - WIRE_MOCK_RULE.stubFor(get(urlPathEqualTo("/adform-exchange")) - .withRequestBody(absent()) - .willReturn(aResponse().withBody(jsonFrom("openrtb2/adform/test-adform-bid-response-1.json")))); - - // when - final Response response = responseFor("openrtb2/adform/test-auction-adform-request.json", - Endpoint.openrtb2_auction); - - // then - assertJsonEquals("openrtb2/adform/test-auction-adform-response.json", response, - singletonList("adform")); - } -} diff --git a/src/test/java/org/prebid/server/it/ApplicationTest.java b/src/test/java/org/prebid/server/it/ApplicationTest.java index ec62bf14827..61bd1d571a7 100644 --- a/src/test/java/org/prebid/server/it/ApplicationTest.java +++ b/src/test/java/org/prebid/server/it/ApplicationTest.java @@ -62,7 +62,6 @@ @RunWith(SpringRunner.class) public class ApplicationTest extends IntegrationTest { - private static final String ADFORM = "adform"; private static final String APPNEXUS = "appnexus"; private static final String APPNEXUS_ALIAS = "appnexusAlias"; private static final String RUBICON = "rubicon"; @@ -349,7 +348,7 @@ public void cookieSyncShouldReturnBidderStatusWithExpectedUsersyncInfo() { final CookieSyncResponse cookieSyncResponse = given(SPEC) .cookies("host-cookie-name", "host-cookie-uid") .body(CookieSyncRequest.builder() - .bidders(asList(RUBICON, APPNEXUS, ADFORM)) + .bidders(asList(RUBICON, APPNEXUS)) .gdpr(1) .gdprConsent(gdprConsent) .usPrivacy("1YNN") @@ -365,7 +364,7 @@ public void cookieSyncShouldReturnBidderStatusWithExpectedUsersyncInfo() { // then assertThat(cookieSyncResponse.getStatus()).isEqualTo("ok"); assertThat(cookieSyncResponse.getBidderStatus()) - .hasSize(3) + .hasSize(2) .containsOnly(BidderUsersyncStatus.builder() .bidder(RUBICON) .noCookie(true) @@ -387,10 +386,6 @@ public void cookieSyncShouldReturnBidderStatusWithExpectedUsersyncInfo() { + "%26f%3Di" + "%26uid%3D%24UID", "redirect", false)) - .build(), - BidderUsersyncStatus.builder() - .bidder(ADFORM) - .error("Rejected by TCF") .build()); } diff --git a/src/test/java/org/prebid/server/validation/BidderParamValidatorTest.java b/src/test/java/org/prebid/server/validation/BidderParamValidatorTest.java index 3639b434bce..87164a7d8ec 100644 --- a/src/test/java/org/prebid/server/validation/BidderParamValidatorTest.java +++ b/src/test/java/org/prebid/server/validation/BidderParamValidatorTest.java @@ -9,7 +9,7 @@ import org.mockito.junit.MockitoRule; import org.prebid.server.VertxTest; import org.prebid.server.bidder.BidderCatalog; -import org.prebid.server.proto.openrtb.ext.request.adform.ExtImpAdform; +import org.prebid.server.bidder.BidderInfo; import org.prebid.server.proto.openrtb.ext.request.adtelligent.ExtImpAdtelligent; import org.prebid.server.proto.openrtb.ext.request.appnexus.ExtImpAppnexus; import org.prebid.server.proto.openrtb.ext.request.beachfront.ExtImpBeachfront; @@ -20,7 +20,6 @@ import org.prebid.server.proto.openrtb.ext.request.rubicon.ExtImpRubicon; import org.prebid.server.proto.openrtb.ext.request.somoaudience.ExtImpSomoaudience; import org.prebid.server.proto.openrtb.ext.request.sovrn.ExtImpSovrn; -import org.prebid.server.bidder.BidderInfo; import org.prebid.server.util.ResourceUtil; import java.io.IOException; @@ -41,7 +40,6 @@ public class BidderParamValidatorTest extends VertxTest { private static final String RUBICON = "rubicon"; private static final String APPNEXUS = "appnexus"; private static final String APPNEXUS_ALIAS = "appnexusAlias"; - private static final String ADFORM = "adform"; private static final String BRIGHTROLL = "brightroll"; private static final String SOVRN = "sovrn"; private static final String ADTELLIGENT = "adtelligent"; @@ -65,7 +63,6 @@ public void setUp() { RUBICON, APPNEXUS, APPNEXUS_ALIAS, - ADFORM, BRIGHTROLL, SOVRN, ADTELLIGENT, @@ -182,32 +179,6 @@ public void validateShouldNotReturnValidationMessagesWhenAppnexusAliasImpExtIsOk assertThat(messages).isEmpty(); } - @Test - public void validateShouldNotReturnValidationMessagesWhenAdformImpExtIsOk() { - // given - final ExtImpAdform ext = ExtImpAdform.of(15L, "gross", null, null, null, null, null); - - final JsonNode node = mapper.convertValue(ext, JsonNode.class); - - // when - final Set messages = bidderParamValidator.validate(ADFORM, node); - - // then - assertThat(messages).isEmpty(); - } - - @Test - public void validateShouldReturnValidationMessagesWhenAdformImpExtNotValid() { - // given - final JsonNode node = mapper.createObjectNode(); - - // when - final Set messages = bidderParamValidator.validate(ADFORM, node); - - // then - assertThat(messages.size()).isEqualTo(1); - } - @Test public void validateShouldNotReturnValidationMessagesWhenBrightrollImpExtIsOk() { // given diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adform/test-adform-bid-response-1.json b/src/test/resources/org/prebid/server/it/openrtb2/adform/test-adform-bid-response-1.json deleted file mode 100644 index 6432182febc..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/adform/test-adform-bid-response-1.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "response": "banner", - "banner": "banner", - "win_bid": "0.5", - "win_cur": "USD", - "width": 400, - "height": 300, - "dealId": "dealId", - "win_crid": "crid12" -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adform/test-auction-adform-request.json b/src/test/resources/org/prebid/server/it/openrtb2/adform/test-auction-adform-request.json deleted file mode 100644 index 517e1f0d469..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/adform/test-auction-adform-request.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "request_id", - "imp": [ - { - "id": "imp_id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "adform": { - "mid": 15, - "priceType": "gross", - "mkv": "color:red", - "mkw": "red", - "cdims": "300x600", - "minp": 2.5, - "url": "https://adform.com?a=b" - } - } - } - ], - "tmax": 5000, - "regs": { - "ext": { - "gdpr": 0 - } - } -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adform/test-auction-adform-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adform/test-auction-adform-response.json deleted file mode 100644 index 3ebea4cb55d..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/adform/test-auction-adform-response.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "id": "request_id", - "seatbid": [ - { - "bid": [ - { - "id": "imp_id", - "impid": "imp_id", - "price": 0.5, - "adm": "banner", - "crid": "crid12", - "w": 400, - "h": 300, - "ext": { - "prebid": { - "type": "banner" - }, - "origbidcpm": 0.5, - "origbidcur": "USD" - } - } - ], - "seat": "adform", - "group": 0 - } - ], - "cur": "USD", - "ext": { - "responsetimemillis": { - "adform": "{{ adform.response_time_ms }}" - }, - "prebid": { - "auctiontimestamp": 0 - }, - "tmaxrequest": 5000 - } -} diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index acbea9d6d73..3bbfffea128 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -8,8 +8,7 @@ adapters.adagio.enabled=true adapters.adagio.endpoint=http://localhost:8090/adagio-exchange adapters.adf.enabled=true adapters.adf.endpoint=http://localhost:8090/adf-exchange -adapters.adform.enabled=true -adapters.adform.endpoint=http://localhost:8090/adform-exchange +adapters.adf.aliases.adform.enabled=true adapters.adgeneration.enabled=true adapters.adgeneration.endpoint=http://localhost:8090/adgeneration-exchange adapters.adhese.enabled=true From 87a7e8e624f8004cda877a3ecfbc70b24f1da970 Mon Sep 17 00:00:00 2001 From: Yevhenii Viktorov <91950229+yevhenii-viktorov@users.noreply.github.com> Date: Mon, 29 Nov 2021 13:35:29 +0200 Subject: [PATCH 17/26] Change url in usersync, add intl field to json and update smaato.json (#1600) --- src/main/resources/bidder-config/smaato.yaml | 2 +- .../static/bidder-params/smaato.json | 20 ++++++++++++++++++- .../smaato/test-auction-smaato-request.json | 3 ++- .../smaato/test-smaato-bid-request.json | 3 ++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/resources/bidder-config/smaato.yaml b/src/main/resources/bidder-config/smaato.yaml index 9ead8427574..4506a0d3765 100644 --- a/src/main/resources/bidder-config/smaato.yaml +++ b/src/main/resources/bidder-config/smaato.yaml @@ -12,7 +12,7 @@ adapters: supported-vendors: vendor-id: 82 usersync: - url: https://s.ad.smaato.net/c/?adExInit=n&redir= + url: https://s.ad.smaato.net/c/?adExInit=p&redir= redirect-url: /setuid?bidder=smaato&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&uid=$UID cookie-family-name: smaato type: redirect diff --git a/src/main/resources/static/bidder-params/smaato.json b/src/main/resources/static/bidder-params/smaato.json index 9fa134d603b..30b59b0d1be 100644 --- a/src/main/resources/static/bidder-params/smaato.json +++ b/src/main/resources/static/bidder-params/smaato.json @@ -11,7 +11,25 @@ "adspaceId": { "type": "string", "description": "Identifier for specific ad placement is SOMA `adspaceId`" + }, + "adbreakId": { + "type": "string", + "description": "Identifier for specific adpod placement is SOMA `adbreakId`" } }, - "required": ["publisherId","adspaceId"] + "required": [ + "publisherId" + ], + "anyOf": [ + { + "required": [ + "adspaceId" + ] + }, + { + "required": [ + "adbreakId" + ] + } + ] } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-auction-smaato-request.json b/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-auction-smaato-request.json index a6264d12e6e..a2fa2aac8db 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-auction-smaato-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-auction-smaato-request.json @@ -8,6 +8,7 @@ "w": 300, "h": 250 }, + "instl": 1, "ext": { "smaato": { "publisherId": "11000", @@ -22,4 +23,4 @@ "gdpr": 0 } } -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-smaato-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-smaato-bid-request.json index e69898c274e..13ab6c50027 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-smaato-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/smaato/test-smaato-bid-request.json @@ -3,6 +3,7 @@ "imp": [ { "id": "imp_id", + "instl": 1, "banner": { "w": 300, "h": 250 @@ -32,4 +33,4 @@ "ext": { "client": "prebid_server_0.4" } -} \ No newline at end of file +} From b9cac43438e713bdb909f4c131574889bb35b08e Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 29 Nov 2021 16:03:47 +0200 Subject: [PATCH 18/26] Rubicon adapter fpd updates (#1583) --- .../server/bidder/rubicon/RubiconBidder.java | 56 +++++-- .../rubicon/proto/request/RubiconImpExt.java | 4 +- .../bidder/rubicon/RubiconBidderTest.java | 137 +++++++++++------- 3 files changed, 131 insertions(+), 66 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/rubicon/RubiconBidder.java b/src/main/java/org/prebid/server/bidder/rubicon/RubiconBidder.java index b01f16b8d16..d3a55e56906 100644 --- a/src/main/java/org/prebid/server/bidder/rubicon/RubiconBidder.java +++ b/src/main/java/org/prebid/server/bidder/rubicon/RubiconBidder.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.IntNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; @@ -92,6 +93,7 @@ import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; import org.prebid.server.util.BidderUtil; import org.prebid.server.util.HttpUtil; +import org.prebid.server.util.ObjectUtil; import java.math.BigDecimal; import java.net.URISyntaxException; @@ -126,6 +128,7 @@ public class RubiconBidder implements Bidder { private static final String LIVEINTENT_EID = "liveintent.com"; private static final String LIVERAMP_EID = "liveramp.com"; + private static final String FPD_GPID_FIELD = "gpid"; private static final String FPD_SECTIONCAT_FIELD = "sectioncat"; private static final String FPD_PAGECAT_FIELD = "pagecat"; private static final String FPD_PAGE_FIELD = "page"; @@ -133,9 +136,11 @@ public class RubiconBidder implements Bidder { private static final String FPD_SEARCH_FIELD = "search"; private static final String FPD_CONTEXT_FIELD = "context"; private static final String FPD_DATA_FIELD = "data"; + private static final String FPD_DATA_PBADSLOT_FIELD = "pbadslot"; private static final String FPD_ADSERVER_FIELD = "adserver"; private static final String FPD_ADSERVER_NAME_GAM = "gam"; private static final String FPD_KEYWORDS_FIELD = "keywords"; + private static final String DFP_ADUNIT_CODE_FIELD = "dfp_ad_unit_code"; private static final String PREBID_EXT = "prebid"; private static final String PPUID_STYPE = "ppuid"; @@ -505,6 +510,7 @@ private RubiconImpExt makeImpExt(Imp imp, Site site, App app, ExtRequest extRequest) { + final ExtImpContext context = extImpContext(imp); return RubiconImpExt.of( RubiconImpExtRp.of( @@ -513,7 +519,7 @@ private RubiconImpExt makeImpExt(Imp imp, RubiconImpExtRpTrack.of("", "")), mapVendorsNamesToUrls(imp.getMetric()), getMaxBids(extRequest), - getAdSlot(imp, context)); + getGpid(imp.getExt())); } private JsonNode makeTarget(Imp imp, ExtImpRubicon rubiconImpExt, Site site, App app, ExtImpContext context) { @@ -576,7 +582,6 @@ private void mergeFirstPartyDataFromImp(Imp imp, ExtImpRubicon rubiconImpExt, ExtImpContext context, ObjectNode result) { - final JsonNode data = imp.getExt().get(FPD_DATA_FIELD); mergeFirstPartyDataFromData(imp, context, result); mergeFirstPartyDataKeywords(imp, context, result); @@ -591,22 +596,36 @@ private void mergeFirstPartyDataFromImp(Imp imp, // merge OPENRTB.imp[].ext.data.search to XAPI.imp[].ext.rp.target.search mergeStringAttributeIntoArray( result, - data, + imp.getExt().get(FPD_DATA_FIELD), node -> getTextValueFromNodeByPath(node, FPD_SEARCH_FIELD), FPD_SEARCH_FIELD); } private void mergeFirstPartyDataFromData(Imp imp, ExtImpContext context, ObjectNode result) { + final ObjectNode contextDataNode = toObjectNode( + ObjectUtil.getIfNotNull(context, ExtImpContext::getData)); // merge OPENRTB.imp[].ext.context.data.* to XAPI.imp[].ext.rp.target.* - final ObjectNode contextDataNode = context != null ? context.getData() : null; - if (contextDataNode != null) { - populateFirstPartyDataAttributes(contextDataNode, result); - } + populateFirstPartyDataAttributes(contextDataNode, result); + final ObjectNode dataNode = toObjectNode(imp.getExt().get(FPD_DATA_FIELD)); // merge OPENRTB.imp[].ext.data.* to XAPI.imp[].ext.rp.target.* - final JsonNode dataNode = imp.getExt().get(FPD_DATA_FIELD); - if (dataNode != null && dataNode.isObject()) { - populateFirstPartyDataAttributes((ObjectNode) dataNode, result); + populateFirstPartyDataAttributes(dataNode, result); + + // override XAPI.imp[].ext.rp.target.* with OPENRTB.imp[].ext.data.* + overrideFirstPartyDataAttributes(contextDataNode, dataNode, result); + } + + private void overrideFirstPartyDataAttributes(ObjectNode contextDataNode, ObjectNode dataNode, ObjectNode result) { + final JsonNode pbadslotNode = dataNode.get(FPD_DATA_PBADSLOT_FIELD); + if (pbadslotNode != null && pbadslotNode.isTextual()) { + // copy imp[].ext.data.pbadslot to XAPI.imp[].ext.rp.target.pbadslot + result.set(FPD_DATA_PBADSLOT_FIELD, pbadslotNode); + } else { + // copy adserver.adslot value to XAPI field imp[].ext.rp.target.dfp_ad_unit_code + final String resolvedDfpAdUnitCode = getAdSlot(contextDataNode, dataNode); + if (resolvedDfpAdUnitCode != null) { + result.set(DFP_ADUNIT_CODE_FIELD, TextNode.valueOf(resolvedDfpAdUnitCode)); + } } } @@ -744,10 +763,19 @@ private Integer getMaxBids(ExtRequest extRequest) { return multibidMaxBids != null ? multibidMaxBids : 1; } + private String getGpid(ObjectNode impExt) { + final JsonNode gpidNode = impExt.get(FPD_GPID_FIELD); + return gpidNode != null && gpidNode.isTextual() ? gpidNode.asText() : null; + } + private String getAdSlot(Imp imp, ExtImpContext context) { final ObjectNode contextDataNode = context != null ? context.getData() : null; - final JsonNode dataNode = imp.getExt().get(FPD_DATA_FIELD); + final ObjectNode dataNode = toObjectNode(imp.getExt().get(FPD_DATA_FIELD)); + return getAdSlot(contextDataNode, dataNode); + } + + private String getAdSlot(ObjectNode contextDataNode, ObjectNode dataNode) { return ObjectUtils.firstNonNull( // or imp[].ext.context.data.adserver.adslot getAdSlotFromAdServer(contextDataNode), @@ -1426,4 +1454,10 @@ private Float cpmOverrideFromImp(Imp imp) { private static BidType bidType(BidRequest bidRequest) { return isVideo(bidRequest.getImp().get(0)) ? BidType.video : BidType.banner; } + + private ObjectNode toObjectNode(JsonNode node) { + return node != null && node.isObject() + ? (ObjectNode) node + : mapper.mapper().createObjectNode(); + } } diff --git a/src/main/java/org/prebid/server/bidder/rubicon/proto/request/RubiconImpExt.java b/src/main/java/org/prebid/server/bidder/rubicon/proto/request/RubiconImpExt.java index 28b96274ebf..201c4631b00 100644 --- a/src/main/java/org/prebid/server/bidder/rubicon/proto/request/RubiconImpExt.java +++ b/src/main/java/org/prebid/server/bidder/rubicon/proto/request/RubiconImpExt.java @@ -1,12 +1,10 @@ package org.prebid.server.bidder.rubicon.proto.request; -import lombok.AllArgsConstructor; import lombok.Value; import java.util.List; -@AllArgsConstructor(staticName = "of") -@Value +@Value(staticConstructor = "of") public class RubiconImpExt { RubiconImpExtRp rp; diff --git a/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java b/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java index f1c015170e8..f106bb11376 100644 --- a/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/rubicon/RubiconBidderTest.java @@ -1803,57 +1803,12 @@ public void makeHttpRequestsShouldCopyAndModifyImpExtContextDataAndDataFieldsToR } @Test - public void makeHttpRequestsShouldPreferContextDataGamAdSlot() { + public void makeHttpRequestsShouldPassThroughImpExtGpid() { // given final BidRequest bidRequest = givenBidRequest( - identity(), - impBuilder -> impBuilder.video(Video.builder().build()), - identity()); - - bidRequest.getImp().get(0).getExt() - .set("context", mapper.createObjectNode() - .set("data", mapper.createObjectNode() - .put("pbadslot", "/test-pbadslot-context-data") - .set("adserver", mapper.createObjectNode() - .put("name", "gam") - .put("adslot", "/test-adserver-context-data")))) - .set("data", mapper.createObjectNode() - .put("pbadslot", "/test-pbadslot-data") - .set("adserver", mapper.createObjectNode() - .put("name", "gam") - .put("adslot", "/test-adserver-data"))); - - // when - final Result>> result = rubiconBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getExt) - .extracting(objectNode -> mapper.convertValue(objectNode, RubiconImpExt.class)) - .extracting(RubiconImpExt::getGpid) - .containsOnly("/test-adserver-context-data"); - } + identity(), impBuilder -> impBuilder.video(Video.builder().build()), identity()); - @Test - public void makeHttpRequestsShouldPreferDataGamAdSlot() { - // given - final BidRequest bidRequest = givenBidRequest( - identity(), - impBuilder -> impBuilder.video(Video.builder().build()), - identity()); - - bidRequest.getImp().get(0).getExt() - .set("context", mapper.createObjectNode() - .set("data", mapper.createObjectNode() - .put("pbadslot", "/test-pbadslot-context-data"))) - .set("data", mapper.createObjectNode() - .put("pbadslot", "/test-pbadslot-data") - .set("adserver", mapper.createObjectNode() - .put("name", "gam") - .put("adslot", "test-adserver-data"))); + bidRequest.getImp().get(0).getExt().set("gpid", TextNode.valueOf("gpidvalue")); // when final Result>> result = rubiconBidder.makeHttpRequests(bidRequest); @@ -1861,12 +1816,11 @@ public void makeHttpRequestsShouldPreferDataGamAdSlot() { // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .extracting(HttpRequest::getPayload) .flatExtracting(BidRequest::getImp) .extracting(Imp::getExt) - .extracting(objectNode -> mapper.convertValue(objectNode, RubiconImpExt.class)) - .extracting(RubiconImpExt::getGpid) - .containsOnly("test-adserver-data"); + .extracting(ext -> ext.get("gpid")) + .containsExactly(TextNode.valueOf("gpidvalue")); } @Test @@ -2224,6 +2178,85 @@ public void makeHttpRequestsShouldMergeImpExtContextDataAndSiteAttributesAndCopy .add("imp ext search"))); } + @Test + public void makeHttpRequestsShouldOverridePbadslotIfPresentInRequestImpExt() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), impBuilder -> impBuilder.video(Video.builder().build()), identity()); + + final ObjectNode dataNode = mapper.createObjectNode() + .set("pbadslot", TextNode.valueOf("pbadslotvalue")); + + bidRequest.getImp().get(0).getExt().set("data", dataNode); + + // when + final Result>> result = rubiconBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .extracting(ext -> ext.at("/rp/target/pbadslot")) + .containsExactly(TextNode.valueOf("pbadslotvalue")); + } + + @Test + public void makeHttpRequestsShouldOverrideDfpAdunitCodeIfAdslotPresentInImpExtDataAndAndAdserverNameIsGam() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), impBuilder -> impBuilder.video(Video.builder().build()), identity()); + + final ObjectNode dataNode = + mapper.createObjectNode() + .set("adserver", mapper.createObjectNode() + .put("adslot", "adslotvalue") + .put("name", "gam")); + + bidRequest.getImp().get(0).getExt().set("data", dataNode); + + // when + final Result>> result = rubiconBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .extracting(ext -> ext.at("/rp/target/dfp_ad_unit_code")) + .containsExactly(TextNode.valueOf("adslotvalue")); + } + + @Test + public void makeHttpRequestsShouldOverrideDfpAdunitCodeIfAdslotPresentInImpExtContextDataAndAndAdserverNameIsGam() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), impBuilder -> impBuilder.video(Video.builder().build()), identity()); + + final ObjectNode dataNode = + mapper.createObjectNode() + .set("adserver", mapper.createObjectNode() + .put("adslot", "adslotvalue") + .put("name", "gam")); + + bidRequest.getImp().get(0).getExt() + .set("context", mapper.createObjectNode().set("data", dataNode)); + + // when + final Result>> result = rubiconBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .extracting(ext -> ext.at("/rp/target/dfp_ad_unit_code")) + .containsExactly(TextNode.valueOf("adslotvalue")); + } + @Test public void makeHttpRequestsShouldMergeImpExtContextDataAndAppAttributesAndCopyToRubiconImpExtRpTarget() { // given From 6773fa316abbc5966b4bf7dd4a9cac131c0517ca Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 29 Nov 2021 17:36:47 +0200 Subject: [PATCH 19/26] Add alpha-2 to alpha-3(and inverse) country code mapping (#1601) --- .../auction/PrivacyEnforcementService.java | 24 +- .../requestfactory/Ortb2RequestFactory.java | 18 +- .../server/geolocation/CountryCodeMapper.java | 45 ++++ .../config/GeoLocationConfiguration.java | 23 ++ .../spring/config/ServiceConfiguration.java | 5 + src/main/resources/country-codes.csv | 243 ++++++++++++++++++ .../PrivacyEnforcementServiceTest.java | 29 ++- .../Ortb2RequestFactoryTest.java | 13 +- .../geolocation/CountryCodeMapperTest.java | 59 +++++ 9 files changed, 435 insertions(+), 24 deletions(-) create mode 100644 src/main/java/org/prebid/server/geolocation/CountryCodeMapper.java create mode 100644 src/main/resources/country-codes.csv create mode 100644 src/test/java/org/prebid/server/geolocation/CountryCodeMapperTest.java diff --git a/src/main/java/org/prebid/server/auction/PrivacyEnforcementService.java b/src/main/java/org/prebid/server/auction/PrivacyEnforcementService.java index 22b07524e66..6afa3bdb1ba 100644 --- a/src/main/java/org/prebid/server/auction/PrivacyEnforcementService.java +++ b/src/main/java/org/prebid/server/auction/PrivacyEnforcementService.java @@ -17,6 +17,7 @@ import org.prebid.server.auction.model.IpAddress; import org.prebid.server.bidder.BidderCatalog; import org.prebid.server.execution.Timeout; +import org.prebid.server.geolocation.CountryCodeMapper; import org.prebid.server.metric.MetricName; import org.prebid.server.metric.Metrics; import org.prebid.server.privacy.PrivacyExtractor; @@ -70,6 +71,7 @@ public class PrivacyEnforcementService { private final ImplicitParametersExtractor implicitParametersExtractor; private final IpAddressHelper ipAddressHelper; private final Metrics metrics; + private final CountryCodeMapper countryCodeMapper; private final boolean ccpaEnforce; private final boolean lmtEnforce; @@ -79,6 +81,7 @@ public PrivacyEnforcementService(BidderCatalog bidderCatalog, ImplicitParametersExtractor implicitParametersExtractor, IpAddressHelper ipAddressHelper, Metrics metrics, + CountryCodeMapper countryCodeMapper, boolean ccpaEnforce, boolean lmtEnforce) { @@ -88,6 +91,7 @@ public PrivacyEnforcementService(BidderCatalog bidderCatalog, this.implicitParametersExtractor = Objects.requireNonNull(implicitParametersExtractor); this.ipAddressHelper = Objects.requireNonNull(ipAddressHelper); this.metrics = Objects.requireNonNull(metrics); + this.countryCodeMapper = Objects.requireNonNull(countryCodeMapper); this.ccpaEnforce = ccpaEnforce; this.lmtEnforce = lmtEnforce; } @@ -103,10 +107,7 @@ public Future contextFromBidRequest(AuctionContext auctionContex final Privacy privacy = privacyExtractor.validPrivacyFrom(bidRequest, errors); final Device device = bidRequest.getDevice(); - - final Geo geo = device != null ? device.getGeo() : null; - final String country = geo != null ? geo.getCountry() : null; - + final String alpha2CountryCode = resolveAlpha2CountryCode(device); final String effectiveIpAddress = resolveIpAddress(device, privacy); final AccountGdprConfig accountGdpr = accountGdprConfig(account); @@ -114,11 +115,24 @@ public Future contextFromBidRequest(AuctionContext auctionContex final RequestLogInfo requestLogInfo = requestLogInfo(requestType, bidRequest, accountId); return tcfDefinerService.resolveTcfContext( - privacy, country, effectiveIpAddress, accountGdpr, requestType, requestLogInfo, timeout, + privacy, + alpha2CountryCode, + effectiveIpAddress, + accountGdpr, + requestType, + requestLogInfo, + timeout, debugWarnings) .map(tcfContext -> PrivacyContext.of(privacy, tcfContext, tcfContext.getIpAddress())); } + private String resolveAlpha2CountryCode(Device device) { + final Geo geo = device != null ? device.getGeo() : null; + final String alpha3CountryCode = geo != null ? geo.getCountry() : null; + + return countryCodeMapper.mapToAlpha2(alpha3CountryCode); + } + private String resolveIpAddress(Device device, Privacy privacy) { final boolean shouldBeMasked = isCoppaMaskingRequired(privacy) || isLmtEnabled(device); diff --git a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java index 89111798a0d..85179100704 100644 --- a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java +++ b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java @@ -30,6 +30,7 @@ import org.prebid.server.exception.UnauthorizedAccountException; import org.prebid.server.execution.Timeout; import org.prebid.server.execution.TimeoutFactory; +import org.prebid.server.geolocation.CountryCodeMapper; import org.prebid.server.geolocation.model.GeoInfo; import org.prebid.server.hooks.execution.HookStageExecutor; import org.prebid.server.hooks.execution.model.HookExecutionContext; @@ -80,6 +81,7 @@ public class Ortb2RequestFactory { private final DealsProcessor dealsProcessor; private final IpAddressHelper ipAddressHelper; private final HookStageExecutor hookStageExecutor; + private final CountryCodeMapper countryCodeMapper; private final Clock clock; public Ortb2RequestFactory(boolean enforceValidAccount, @@ -93,6 +95,7 @@ public Ortb2RequestFactory(boolean enforceValidAccount, IpAddressHelper ipAddressHelper, HookStageExecutor hookStageExecutor, DealsProcessor dealsProcessor, + CountryCodeMapper countryCodeMapper, Clock clock) { this.enforceValidAccount = enforceValidAccount; @@ -106,6 +109,7 @@ public Ortb2RequestFactory(boolean enforceValidAccount, this.ipAddressHelper = Objects.requireNonNull(ipAddressHelper); this.hookStageExecutor = Objects.requireNonNull(hookStageExecutor); this.dealsProcessor = dealsProcessor; + this.countryCodeMapper = Objects.requireNonNull(countryCodeMapper); this.clock = Objects.requireNonNull(clock); } @@ -403,9 +407,8 @@ private Device enrichDevice(Device device, PrivacyContext privacyContext) { final Geo geo = ObjectUtil.getIfNotNull(device, Device::getGeo); final String countryInRequest = ObjectUtil.getIfNotNull(geo, Geo::getCountry); - final String country = ObjectUtil.getIfNotNull(privacyContext.getTcfContext().getGeoInfo(), - GeoInfo::getCountry); - final boolean shouldUpdateCountry = country != null && !Objects.equals(countryInRequest, country); + final String alpha3CountryCode = resolveAlpha3CountryCode(privacyContext); + final boolean shouldUpdateCountry = alpha3CountryCode != null && !alpha3CountryCode.equals(countryInRequest); if (shouldUpdateIpV4 || shouldUpdateIpV6 || shouldUpdateCountry) { final Device.DeviceBuilder deviceBuilder = device != null ? device.toBuilder() : Device.builder(); @@ -420,7 +423,7 @@ private Device enrichDevice(Device device, PrivacyContext privacyContext) { if (shouldUpdateCountry) { final Geo.GeoBuilder geoBuilder = geo != null ? geo.toBuilder() : Geo.builder(); - geoBuilder.country(country); + geoBuilder.country(alpha3CountryCode); deviceBuilder.geo(geoBuilder.build()); } @@ -430,6 +433,13 @@ private Device enrichDevice(Device device, PrivacyContext privacyContext) { return null; } + private String resolveAlpha3CountryCode(PrivacyContext privacyContext) { + final String alpha2CountryCode = ObjectUtil.getIfNotNull( + privacyContext.getTcfContext().getGeoInfo(), GeoInfo::getCountry); + + return countryCodeMapper.mapToAlpha3(alpha2CountryCode); + } + private static String accountDefaultIntegration(Account account) { final AccountAuctionConfig accountAuctionConfig = account.getAuction(); diff --git a/src/main/java/org/prebid/server/geolocation/CountryCodeMapper.java b/src/main/java/org/prebid/server/geolocation/CountryCodeMapper.java new file mode 100644 index 00000000000..6967086a063 --- /dev/null +++ b/src/main/java/org/prebid/server/geolocation/CountryCodeMapper.java @@ -0,0 +1,45 @@ +package org.prebid.server.geolocation; + +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.bidimap.DualHashBidiMap; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +public class CountryCodeMapper { + + private final BidiMap alpha2ToAlpha3CountryCodes; + + public CountryCodeMapper(String resource) { + alpha2ToAlpha3CountryCodes = new DualHashBidiMap<>(populateAlpha2ToAlpha3Mapping(resource)); + } + + public String mapToAlpha3(String alpha2Code) { + return alpha2ToAlpha3CountryCodes.get(StringUtils.upperCase(alpha2Code)); + } + + public String mapToAlpha2(String alpha3Code) { + return alpha2ToAlpha3CountryCodes.inverseBidiMap().get(StringUtils.upperCase(alpha3Code)); + } + + private Map populateAlpha2ToAlpha3Mapping(String countryCodesCsvAsString) { + return Arrays.stream(countryCodesCsvAsString.split("\n")) + .map(CountryCodeMapper::parseCountryCodesCsvRow) + .collect(Collectors.toMap(Pair::getKey, Pair::getValue, (o1, o2) -> o1)); + } + + private static Pair parseCountryCodesCsvRow(String row) { + final String[] subTokens = row.replaceAll("[^a-zA-Z,]", "").split(","); + if (subTokens.length != 2 || subTokens[0].length() != 2 || subTokens[1].length() != 3) { + throw new IllegalArgumentException( + String.format( + "Invalid csv file format: row \"%s\" contains more than 2 entries or tokens are invalid", + row)); + } + + return Pair.of(subTokens[0].toUpperCase(), subTokens[1].toUpperCase()); + } +} diff --git a/src/main/java/org/prebid/server/spring/config/GeoLocationConfiguration.java b/src/main/java/org/prebid/server/spring/config/GeoLocationConfiguration.java index 1f649b097e7..7edd378c565 100644 --- a/src/main/java/org/prebid/server/spring/config/GeoLocationConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/GeoLocationConfiguration.java @@ -4,6 +4,7 @@ import io.vertx.core.http.HttpClientOptions; import org.prebid.server.execution.RemoteFileSyncer; import org.prebid.server.geolocation.CircuitBreakerSecuredGeoLocationService; +import org.prebid.server.geolocation.CountryCodeMapper; import org.prebid.server.geolocation.GeoLocationService; import org.prebid.server.geolocation.MaxMindGeoLocationService; import org.prebid.server.metric.Metrics; @@ -11,12 +12,19 @@ import org.prebid.server.spring.config.model.HttpClientProperties; import org.prebid.server.spring.config.model.RemoteFileSyncerProperties; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.util.FileCopyUtils; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.time.Clock; public class GeoLocationConfiguration { @@ -81,4 +89,19 @@ private GeoLocationService createGeoLocationService(RemoteFileSyncerProperties f return maxMindGeoLocationService; } } + + @Configuration + static class CountryCodeMapperConfiguration { + + @Bean + public CountryCodeMapper countryCodeMapper( + @Value("classpath:country-codes.csv") Resource resource) throws IOException { + + final Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8); + final String countryCodesCsvAsString = FileCopyUtils.copyToString(reader); + reader.close(); + + return new CountryCodeMapper(countryCodesCsvAsString); + } + } } diff --git a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java index 34713f239e8..9725561c92a 100644 --- a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java @@ -46,6 +46,7 @@ import org.prebid.server.deals.events.ApplicationEventService; import org.prebid.server.events.EventsService; import org.prebid.server.execution.TimeoutFactory; +import org.prebid.server.geolocation.CountryCodeMapper; import org.prebid.server.hooks.execution.HookStageExecutor; import org.prebid.server.identity.IdGenerator; import org.prebid.server.identity.NoneIdGenerator; @@ -236,6 +237,7 @@ Ortb2RequestFactory openRtb2RequestFactory( IpAddressHelper ipAddressHelper, HookStageExecutor hookStageExecutor, @Autowired(required = false) DealsProcessor dealsProcessor, + CountryCodeMapper countryCodeMapper, Clock clock) { final List blacklistedAccounts = splitToList(blacklistedAccountsString); @@ -252,6 +254,7 @@ Ortb2RequestFactory openRtb2RequestFactory( ipAddressHelper, hookStageExecutor, dealsProcessor, + countryCodeMapper, clock); } @@ -663,6 +666,7 @@ PrivacyEnforcementService privacyEnforcementService( ImplicitParametersExtractor implicitParametersExtractor, IpAddressHelper ipAddressHelper, Metrics metrics, + CountryCodeMapper countryCodeMapper, @Value("${ccpa.enforce}") boolean ccpaEnforce, @Value("${lmt.enforce}") boolean lmtEnforce) { @@ -673,6 +677,7 @@ PrivacyEnforcementService privacyEnforcementService( implicitParametersExtractor, ipAddressHelper, metrics, + countryCodeMapper, ccpaEnforce, lmtEnforce); } diff --git a/src/main/resources/country-codes.csv b/src/main/resources/country-codes.csv new file mode 100644 index 00000000000..7f68a310ba0 --- /dev/null +++ b/src/main/resources/country-codes.csv @@ -0,0 +1,243 @@ +AF,AFG +AL,ALB +DZ,DZA +AS,ASM +AD,AND +AO,AGO +AI,AIA +AQ,ATA +AG,ATG +AR,ARG +AM,ARM +AW,ABW +AU,AUS +AT,AUT +AZ,AZE +BS,BHS +BH,BHR +BD,BGD +BB,BRB +BY,BLR +BE,BEL +BZ,BLZ +BJ,BEN +BM,BMU +BT,BTN +BO,BOL +BA,BIH +BW,BWA +BV,BVT +BR,BRA +IO,IOT +BN,BRN +BG,BGR +BF,BFA +BI,BDI +KH,KHM +CM,CMR +CA,CAN +CV,CPV +KY,CYM +CF,CAF +TD,TCD +CL,CHL +CN,CHN +CX,CXR +CC,CCK +CO,COL +KM,COM +CG,COG +CD,COD +CK,COK +CR,CRI +CI,CIV +HR,HRV +CU,CUB +CY,CYP +CZ,CZE +DK,DNK +DJ,DJI +DM,DMA +DO,DOM +EC,ECU +EG,EGY +SV,SLV +GQ,GNQ +ER,ERI +EE,EST +ET,ETH +FK,FLK +FO,FRO +FJ,FJI +FI,FIN +FR,FRA +GF,GUF +PF,PYF +TF,ATF +GA,GAB +GM,GMB +GE,GEO +DE,DEU +GH,GHA +GI,GIB +GR,GRC +GL,GRL +GD,GRD +GP,GLP +GU,GUM +GT,GTM +GG,GGY +GN,GIN +GW,GNB +GY,GUY +HT,HTI +HM,HMD +VA,VAT +HN,HND +HK,HKG +HU,HUN +IS,ISL +IN,IND +ID,IDN +IR,IRN +IQ,IRQ +IE,IRL +IM,IMN +IL,ISR +IT,ITA +JM,JAM +JP,JPN +JE,JEY +JO,JOR +KZ,KAZ +KE,KEN +KI,KIR +KP,PRK +KR,KOR +KW,KWT +KG,KGZ +LA,LAO +LV,LVA +LB,LBN +LS,LSO +LR,LBR +LY,LBY +LI,LIE +LT,LTU +LU,LUX +MO,MAC +MK,MKD +MG,MDG +MW,MWI +MY,MYS +MV,MDV +ML,MLI +MT,MLT +MH,MHL +MQ,MTQ +MR,MRT +MU,MUS +YT,MYT +MX,MEX +FM,FSM +MD,MDA +MC,MCO +MN,MNG +ME,MNE +MS,MSR +MA,MAR +MZ,MOZ +MM,MMR +NA,NAM +NR,NRU +NP,NPL +NL,NLD +AN,ANT +NC,NCL +NZ,NZL +NI,NIC +NE,NER +NG,NGA +NU,NIU +NF,NFK +MP,MNP +NO,NOR +OM,OMN +PK,PAK +PW,PLW +PS,PSE +PA,PAN +PG,PNG +PY,PRY +PE,PER +PH,PHL +PN,PCN +PL,POL +PT,PRT +PR,PRI +QA,QAT +RE,REU +RO,ROU +RU,RUS +RW,RWA +SH,SHN +KN,KNA +LC,LCA +PM,SPM +VC,VCT +WS,WSM +SM,SMR +ST,STP +SA,SAU +SN,SEN +RS,SRB +SC,SYC +SL,SLE +SG,SGP +SK,SVK +SI,SVN +SB,SLB +SO,SOM +ZA,ZAF +GS,SGS +ES,ESP +LK,LKA +SD,SDN +SR,SUR +SJ,SJM +SZ,SWZ +SE,SWE +CH,CHE +SY,SYR +TW,TWN +TJ,TJK +TZ,TZA +TH,THA +TL,TLS +TG,TGO +TK,TKL +TO,TON +TT,TTO +TN,TUN +TR,TUR +TM,TKM +TC,TCA +TV,TUV +UG,UGA +UA,UKR +AE,ARE +GB,GBR +US,USA +UM,UMI +UY,URY +UZ,UZB +VU,VUT +VE,VEN +VN,VNM +VG,VGB +VI,VIR +WF,WLF +EH,ESH +YE,YEM +ZM,ZMB +ZW,ZWE diff --git a/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java b/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java index 9bec2a50064..c1abb772a92 100644 --- a/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java +++ b/src/test/java/org/prebid/server/auction/PrivacyEnforcementServiceTest.java @@ -27,6 +27,7 @@ import org.prebid.server.exception.InvalidRequestException; import org.prebid.server.execution.Timeout; import org.prebid.server.execution.TimeoutFactory; +import org.prebid.server.geolocation.CountryCodeMapper; import org.prebid.server.geolocation.model.GeoInfo; import org.prebid.server.metric.MetricName; import org.prebid.server.metric.Metrics; @@ -83,8 +84,8 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; public class PrivacyEnforcementServiceTest extends VertxTest { @@ -105,6 +106,8 @@ public class PrivacyEnforcementServiceTest extends VertxTest { private IpAddressHelper ipAddressHelper; @Mock private Metrics metrics; + @Mock + private CountryCodeMapper countryCodeMapper; private PrivacyEnforcementService privacyEnforcementService; @@ -128,7 +131,7 @@ public void setUp() { privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, false, false); + metrics, countryCodeMapper, false, false); } @Test @@ -415,7 +418,7 @@ public void shouldMaskForCcpaWhenUsPolicyIsValidAndCoppaIsZero() { // given privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, true, false); + metrics, countryCodeMapper, true, false); given(tcfDefinerService.resultForBidderNames(anySet(), any(), any(), any())) .willReturn(Future.succeededFuture(TcfResponse.of(true, emptyMap(), null))); @@ -455,7 +458,7 @@ public void shouldMaskForCcpaWhenAccountHasCppaConfigEnabledForRequestType() { // given privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, false, true); + metrics, countryCodeMapper, false, true); given(tcfDefinerService.resultForBidderNames(anySet(), any(), any(), any())) .willReturn(Future.succeededFuture(TcfResponse.of(true, emptyMap(), null))); @@ -507,7 +510,7 @@ public void shouldMaskForCcpaWhenAccountHasCppaEnforcedTrue() { // given privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, false, true); + metrics, countryCodeMapper, false, true); given(tcfDefinerService.resultForBidderNames(anySet(), any(), any(), any())) .willReturn(Future.succeededFuture(TcfResponse.of(true, emptyMap(), null))); @@ -555,7 +558,7 @@ public void shouldMaskForCcpaWhenAccountHasCcpaConfigEnabled() { // given privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, false, true); + metrics, countryCodeMapper, false, true); given(tcfDefinerService.resultForBidderNames(anySet(), any(), any(), any())) .willReturn(Future.succeededFuture(TcfResponse.of(true, emptyMap(), null))); @@ -703,7 +706,7 @@ public void shouldMaskForTcfWhenTcfServiceAllowAllAndDeviceLmtIsOneAndLmtIsEnfor // given privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, false, true); + metrics, countryCodeMapper, false, true); given(tcfDefinerService.resultForBidderNames(any(), any(), any(), any())) .willReturn(Future.succeededFuture( @@ -1333,7 +1336,7 @@ public void shouldMaskForCcpaAndTcfWhenUsPolicyIsValidAndGdprIsEnforcedAndCOPPAI // given privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, true, false); + metrics, countryCodeMapper, true, false); final String bidder1Name = "bidder1Name"; final String bidder2Name = "bidder2Name"; @@ -1404,7 +1407,7 @@ public void shouldNotMaskForCcpaWhenCatchAllWildcardIsPresentInNosaleList() { // given privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, true, false); + metrics, countryCodeMapper, true, false); given(tcfDefinerService.resultForBidderNames(anySet(), any(), any(), any())) .willReturn(Future.succeededFuture( @@ -1459,7 +1462,7 @@ public void isCcpaEnforcedShouldReturnFalseWhenEnforcedPropertyIsTrueInConfigura // given privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, true, false); + metrics, countryCodeMapper, true, false); final Ccpa ccpa = Ccpa.of("1YYY"); final Account account = Account.builder() @@ -1475,7 +1478,7 @@ public void isCcpaEnforcedShouldReturnFalseWhenEnforcedPropertyIsTrue() { // given privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, true, false); + metrics, countryCodeMapper, true, false); final Ccpa ccpa = Ccpa.of("1YNY"); final Account account = Account.builder().build(); @@ -1489,7 +1492,7 @@ public void isCcpaEnforcedShouldReturnFalseWhenAccountCcpaConfigHasEnabledTrue() // given privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, false, true); + metrics, countryCodeMapper, false, true); final Ccpa ccpa = Ccpa.of("1YYY"); final Account account = Account.builder() @@ -1507,7 +1510,7 @@ public void isCcpaEnforcedShouldReturnTrueWhenEnforcedPropertyIsTrueAndCcpaRetur // given privacyEnforcementService = new PrivacyEnforcementService( bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper, - metrics, true, false); + metrics, countryCodeMapper, true, false); final Ccpa ccpa = Ccpa.of("1YYY"); final Account account = Account.builder().build(); diff --git a/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java b/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java index 1219fa7ddb8..fa38051130b 100644 --- a/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java +++ b/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java @@ -37,6 +37,7 @@ import org.prebid.server.exception.UnauthorizedAccountException; import org.prebid.server.execution.Timeout; import org.prebid.server.execution.TimeoutFactory; +import org.prebid.server.geolocation.CountryCodeMapper; import org.prebid.server.geolocation.model.GeoInfo; import org.prebid.server.hooks.execution.HookStageExecutor; import org.prebid.server.hooks.execution.model.HookExecutionContext; @@ -112,6 +113,8 @@ public class Ortb2RequestFactoryTest extends VertxTest { private HookStageExecutor hookStageExecutor; @Mock private DealsProcessor dealsProcessor; + @Mock + private CountryCodeMapper countryCodeMapper; private final Clock clock = Clock.systemDefaultZone(); @@ -169,6 +172,7 @@ public void setUp() { ipAddressHelper, hookStageExecutor, dealsProcessor, + countryCodeMapper, clock); } @@ -187,6 +191,7 @@ public void fetchAccountShouldReturnFailedFutureIfAccountIsEnforcedAndIdIsNotPro ipAddressHelper, hookStageExecutor, dealsProcessor, + countryCodeMapper, clock); given(storedRequestProcessor.processStoredRequests(any(), any())) @@ -223,6 +228,7 @@ public void fetchAccountShouldReturnFailedFutureIfAccountIsEnforcedAndFailedGetA ipAddressHelper, hookStageExecutor, dealsProcessor, + countryCodeMapper, clock); given(applicationSettings.getAccountById(any(), any())) @@ -564,6 +570,7 @@ public void shouldFetchAccountFromStoredAndReturnFailedFutureIfValidIsEnforcedAn ipAddressHelper, hookStageExecutor, dealsProcessor, + countryCodeMapper, clock); final BidRequest receivedBidRequest = givenBidRequest(identity()); @@ -815,6 +822,8 @@ public void enrichBidRequestWithAccountAndPrivacyDataShouldReturnIntegrationFrom @Test public void enrichBidRequestWithAccountAndPrivacyDataShouldAddCountryFromPrivacy() { // given + given(countryCodeMapper.mapToAlpha3("ua")).willReturn("UKR"); + final BidRequest bidRequest = givenBidRequest(identity()); final PrivacyContext privacyContext = PrivacyContext.of( Privacy.of("", "", Ccpa.EMPTY, 0), @@ -835,11 +844,11 @@ public void enrichBidRequestWithAccountAndPrivacyDataShouldAddCountryFromPrivacy final BidRequest result = target.enrichBidRequestWithAccountAndPrivacyData(auctionContext); // then - assertThat(Collections.singleton(result)) + assertThat(List.of(result)) .extracting(BidRequest::getDevice) .extracting(Device::getGeo) .extracting(Geo::getCountry) - .containsOnly("ua"); + .containsExactly("UKR"); } @Test diff --git a/src/test/java/org/prebid/server/geolocation/CountryCodeMapperTest.java b/src/test/java/org/prebid/server/geolocation/CountryCodeMapperTest.java new file mode 100644 index 00000000000..679c8fadc63 --- /dev/null +++ b/src/test/java/org/prebid/server/geolocation/CountryCodeMapperTest.java @@ -0,0 +1,59 @@ +package org.prebid.server.geolocation; + +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +public class CountryCodeMapperTest { + + private CountryCodeMapper countryCodeMapper; + + @Before + public void setUp() { + countryCodeMapper = new CountryCodeMapper("UA, UKR"); + } + + @Test + public void creationShouldThrowErrorInvalidResourceFile() { + // when and then + assertThatIllegalArgumentException().isThrownBy(() -> new CountryCodeMapper("invalid_resouce")); + } + + @Test + public void mapToAlpha3ShouldCorrectlyMapAlpha2Code() { + // when and then + assertThat(countryCodeMapper.mapToAlpha3("UA")).isEqualTo("UKR"); + } + + @Test + public void mapToAlpha3ShouldTolerateInvalidCaseAlpha2Code() { + // when and then + assertThat(countryCodeMapper.mapToAlpha3("uA")).isEqualTo("UKR"); + } + + @Test + public void mapToAlpha3ShouldReturnNullOnEmptyAlpha2Code() { + // when and then + assertThat(countryCodeMapper.mapToAlpha3("")).isNull(); + } + + @Test + public void mapToAlpha2ShouldCorrectlyMapAlpha3Code() { + // when and then + assertThat(countryCodeMapper.mapToAlpha2("UKR")).isEqualTo("UA"); + } + + @Test + public void mapToAlpha2ShouldTolerateInvalidCaseAlpha3Code() { + // when and then + assertThat(countryCodeMapper.mapToAlpha2("uKr")).isEqualTo("UA"); + } + + @Test + public void mapToAlpha2ShouldReturnNullOnEmptyAlpha3Code() { + // when and then + assertThat(countryCodeMapper.mapToAlpha2("")).isNull(); + } +} From 4d87933fa64d1367ef540c4e0e05405e5480ffe2 Mon Sep 17 00:00:00 2001 From: Yevhenii Viktorov <91950229+yevhenii-viktorov@users.noreply.github.com> Date: Mon, 29 Nov 2021 18:04:27 +0200 Subject: [PATCH 20/26] Add new bidder: Impactify (#1553) --- .../bidder/impactify/ImpactifyBidder.java | 201 ++++++++++ .../request/impactify/ExtImpImpactify.java | 15 + .../config/bidder/ImpactifyConfiguration.java | 43 ++ .../resources/bidder-config/impactify.yaml | 17 + .../static/bidder-params/impactify.json | 28 ++ .../bidder/impactify/ImpactifyBidderTest.java | 375 ++++++++++++++++++ .../org/prebid/server/it/ImpactifyTest.java | 34 ++ .../test-auction-impactify-request.json | 27 ++ .../test-auction-impactify-response.json | 35 ++ .../impactify/test-impactify-bid-request.json | 45 +++ .../test-impactify-bid-response.json | 18 + .../server/it/test-application.properties | 2 + 12 files changed, 840 insertions(+) create mode 100644 src/main/java/org/prebid/server/bidder/impactify/ImpactifyBidder.java create mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/impactify/ExtImpImpactify.java create mode 100644 src/main/java/org/prebid/server/spring/config/bidder/ImpactifyConfiguration.java create mode 100644 src/main/resources/bidder-config/impactify.yaml create mode 100644 src/main/resources/static/bidder-params/impactify.json create mode 100644 src/test/java/org/prebid/server/bidder/impactify/ImpactifyBidderTest.java create mode 100644 src/test/java/org/prebid/server/it/ImpactifyTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/impactify/test-impactify-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/impactify/test-impactify-bid-response.json diff --git a/src/main/java/org/prebid/server/bidder/impactify/ImpactifyBidder.java b/src/main/java/org/prebid/server/bidder/impactify/ImpactifyBidder.java new file mode 100644 index 00000000000..95e1a8d9126 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/impactify/ImpactifyBidder.java @@ -0,0 +1,201 @@ +package org.prebid.server.bidder.impactify; + +import com.fasterxml.jackson.core.type.TypeReference; +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.User; +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.prebid.server.bidder.Bidder; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpCall; +import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.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.impactify.ExtImpImpactify; +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.Collectors; + +public class ImpactifyBidder implements Bidder { + + private static final TypeReference> IMPACTIFY_EXT_TYPE_REFERENCE = + new TypeReference>() { + }; + private static final String X_OPENRTB_VERSION = "2.5"; + private static final String BIDDER_CURRENCY = "USD"; + + private final String endpointUrl; + private final JacksonMapper mapper; + private final CurrencyConversionService currencyConversionService; + + public ImpactifyBidder(String endpointUrl, JacksonMapper mapper, CurrencyConversionService conversionService) { + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.mapper = Objects.requireNonNull(mapper); + this.currencyConversionService = Objects.requireNonNull(conversionService); + } + + @Override + public Result>> makeHttpRequests(BidRequest request) { + final List updatedImps = new ArrayList<>(); + + for (Imp imp : request.getImp()) { + try { + final BigDecimal resolvedBidFloor = resolveBidFloor(request, imp); + updatedImps.add(updateImp(imp, resolvedBidFloor)); + } catch (PreBidException e) { + return Result.withError(BidderError.badInput(e.getMessage())); + } + } + + final BidRequest updatedBidRequest = updateBidRequest(request, updatedImps); + + return Result.withValue(HttpRequest.builder() + .method(HttpMethod.POST) + .uri(endpointUrl) + .headers(constructHeaders(updatedBidRequest)) + .body(mapper.encodeToBytes(updatedBidRequest)) + .payload(updatedBidRequest) + .build()); + } + + private BigDecimal resolveBidFloor(BidRequest request, Imp imp) { + return shouldConvertBidFloor(imp.getBidfloor(), imp.getBidfloorcur()) + ? convertBidFloorCurrency(imp.getBidfloor(), request, imp.getId(), imp.getBidfloorcur()) + : imp.getBidfloor(); + } + + private static boolean shouldConvertBidFloor(BigDecimal bidFloor, String bidFloorCur) { + return BidderUtil.isValidPrice(bidFloor) && !StringUtils.equalsIgnoreCase(bidFloorCur, BIDDER_CURRENCY); + } + + private BigDecimal convertBidFloorCurrency(BigDecimal bidFloor, + BidRequest bidRequest, + String impId, + String bidFloorCur) { + try { + return currencyConversionService + .convertCurrency(bidFloor, bidRequest, bidFloorCur, BIDDER_CURRENCY); + } catch (PreBidException e) { + throw new PreBidException(String.format( + "Unable to convert provided bid floor currency from %s to %s for imp `%s`", + bidFloorCur, BIDDER_CURRENCY, impId)); + } + } + + private Imp updateImp(Imp imp, BigDecimal bidFloor) { + return imp.toBuilder() + .bidfloorcur(BIDDER_CURRENCY) + .bidfloor(bidFloor) + .ext(mapper.mapper().createObjectNode() + .set("impactify", mapper.mapper().valueToTree(parseExtImp(imp)))) + .build(); + } + + private ExtImpImpactify parseExtImp(Imp imp) { + try { + return mapper.mapper() + .convertValue(imp.getExt(), IMPACTIFY_EXT_TYPE_REFERENCE) + .getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException(String.format("Unable to decode the impression ext for id: %s", imp.getId())); + } + } + + private static BidRequest updateBidRequest(BidRequest request, List updatedImps) { + return request.toBuilder() + .imp(updatedImps) + .cur(Collections.singletonList(BIDDER_CURRENCY)) + .build(); + } + + private static MultiMap constructHeaders(BidRequest bidRequest) { + final MultiMap headers = HttpUtil.headers().set(HttpUtil.X_OPENRTB_VERSION_HEADER, X_OPENRTB_VERSION); + + final Device device = bidRequest.getDevice(); + if (device != null) { + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, device.getUa()); + + final String deviceIp = device.getIp(); + if (deviceIp != null) { + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, deviceIp); + } else { + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6()); + } + } + + final Site site = bidRequest.getSite(); + if (site != null) { + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, site.getPage()); + } + + final User user = bidRequest.getUser(); + final String userUid = user != null ? user.getBuyeruid() : null; + if (StringUtils.isNotBlank(userUid)) { + headers.set(HttpUtil.COOKIE_HEADER, "uids=" + userUid); + } + + return headers; + } + + @Override + public Result> makeBids(HttpCall httpCall, BidRequest bidRequest) { + try { + final List errors = new ArrayList<>(); + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + return Result.of(extractBids(bidRequest, bidResponse), errors); + } catch (DecodeException | PreBidException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + + private List extractBids(BidRequest bidRequest, BidResponse bidResponse) { + if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { + return Collections.emptyList(); + } + return bidsFromResponse(bidResponse, bidRequest); + } + + private List bidsFromResponse(BidResponse bidResponse, BidRequest bidRequest) { + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .flatMap(Collection::stream) + .map(bid -> BidderBid.of(bid, getBidType(bid.getImpid(), bidRequest.getImp()), bidResponse.getCur())) + .collect(Collectors.toList()); + } + + private static BidType getBidType(String impId, List imps) { + for (Imp imp : imps) { + if (imp.getId().equals(impId)) { + if (imp.getBanner() != null) { + return BidType.banner; + } + if (imp.getVideo() != null) { + return BidType.video; + } + } + } + throw new PreBidException( + String.format("Failed to find a supported media type impression with ID: '%s'", impId)); + } +} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/impactify/ExtImpImpactify.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/impactify/ExtImpImpactify.java new file mode 100644 index 00000000000..d562605a312 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/impactify/ExtImpImpactify.java @@ -0,0 +1,15 @@ +package org.prebid.server.proto.openrtb.ext.request.impactify; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class ExtImpImpactify { + + @JsonProperty("appId") + String appId; + + String format; + + String style; +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ImpactifyConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ImpactifyConfiguration.java new file mode 100644 index 00000000000..6daed5621b3 --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/ImpactifyConfiguration.java @@ -0,0 +1,43 @@ +package org.prebid.server.spring.config.bidder; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.impactify.ImpactifyBidder; +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; +import org.prebid.server.spring.config.bidder.util.UsersyncerCreator; +import org.prebid.server.spring.env.YamlPropertySourceFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import javax.validation.constraints.NotBlank; + +@Configuration +@PropertySource(value = "classpath:/bidder-config/impactify.yaml", factory = YamlPropertySourceFactory.class) +public class ImpactifyConfiguration { + + private static final String BIDDER_NAME = "impactify"; + + @Bean("impactifyConfigurationProperties") + @ConfigurationProperties("adapters.impactify") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + @Bean + BidderDeps impactifyBidderDeps(BidderConfigurationProperties impactifyConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + CurrencyConversionService currencyConversionService, + JacksonMapper mapper) { + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(impactifyConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new ImpactifyBidder(config.getEndpoint(), mapper, currencyConversionService)) + .assemble(); + } +} diff --git a/src/main/resources/bidder-config/impactify.yaml b/src/main/resources/bidder-config/impactify.yaml new file mode 100644 index 00000000000..162d0d36ddf --- /dev/null +++ b/src/main/resources/bidder-config/impactify.yaml @@ -0,0 +1,17 @@ +adapters: + impactify: + endpoint: https://sonic.impactify.media/bidder + meta-info: + maintainer-email: support@impactify.io + app-media-types: + site-media-types: + - banner + - video + supported-vendors: + vendor-id: 606 + usersync: + url: https://sonic.impactify.media/static/cookie_sync.html?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect_url= + redirect-url: /setuid?bidder=impactify&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&uid={IMPACTIFY_UID} + cookie-family-name: impactify + type: iframe + support-cors: false diff --git a/src/main/resources/static/bidder-params/impactify.json b/src/main/resources/static/bidder-params/impactify.json new file mode 100644 index 00000000000..1b0bbf4596a --- /dev/null +++ b/src/main/resources/static/bidder-params/impactify.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Impactify Adapter Params", + "description": "A schema which validates params accepted by the Impactify adapter", + "type": "object", + "properties": { + "appId": { + "type": "string", + "description": "The appId of your website", + "minLength": 1 + }, + "format": { + "type": "string", + "description": "The format of the ad", + "minLength": 1 + }, + "style": { + "type": "string", + "description": "The style of the ad", + "minLength": 1 + } + }, + "required": [ + "appId", + "format", + "style" + ] +} diff --git a/src/test/java/org/prebid/server/bidder/impactify/ImpactifyBidderTest.java b/src/test/java/org/prebid/server/bidder/impactify/ImpactifyBidderTest.java new file mode 100644 index 00000000000..871080fb89b --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/impactify/ImpactifyBidderTest.java @@ -0,0 +1,375 @@ +package org.prebid.server.bidder.impactify; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; +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.User; +import com.iab.openrtb.request.Video; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.vertx.core.MultiMap; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.prebid.server.VertxTest; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpCall; +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.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.impactify.ExtImpImpactify; +import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.HttpUtil; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.function.UnaryOperator; + +import static java.util.function.UnaryOperator.identity; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; + +public class ImpactifyBidderTest extends VertxTest { + + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + private ImpactifyBidder impactifyBidder; + + @Mock + private CurrencyConversionService currencyConversionService; + + @Before + public void setUp() { + impactifyBidder = + new ImpactifyBidder("https://test.endpoint.com", jacksonMapper, currencyConversionService); + } + + @Test + public void createBidderWithWrongEndpointShouldThrowException() { + assertThatIllegalArgumentException().isThrownBy(() -> new ImpactifyBidder("incorrect.endpoint", + jacksonMapper, currencyConversionService)); + } + + @Test + public void makeHttpRequestsShouldConvertCurrencyIfNotDefault() { + // given + given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) + .willReturn(BigDecimal.TEN); + + final BidRequest bidRequest = givenBidRequest( + impCustomizer -> impCustomizer.bidfloor(BigDecimal.ONE).bidfloorcur("EUR")); + + // when + Result>> result = impactifyBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBidfloor, Imp::getBidfloorcur) + .containsExactly(tuple(BigDecimal.TEN, "USD")); + } + + @Test + public void makeHttpRequestsShouldReturnErrorMessageOnFailedCurrencyConversion() { + // given + given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) + .willThrow(PreBidException.class); + + final BidRequest bidRequest = givenBidRequest( + impCustomizer -> impCustomizer.bidfloor(BigDecimal.ONE).bidfloorcur("EUR")); + + // when + Result>> result = impactifyBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1) + .extracting(BidderError::getMessage) + .containsExactly("Unable to convert provided bid floor currency from EUR to USD for imp `123`"); + + } + + @Test + public void makeHttpRequestsShouldReturnValidBidResponseWithAllHeadersExceptIpv6() { + // given + final BidRequest bidRequest = givenBidRequest( + bidRequestCustomizer -> bidRequestCustomizer + .device(givenDevice(deviceCustomizer -> deviceCustomizer.ua("ua").ip("ip").ipv6("ipv6"))) + .site(Site.builder().page("https://proper.web.site").build()) + .user(User.builder().buyeruid("buyer_user_uid").build()), + impCustomizer -> impCustomizer + .bidfloorcur("USD") + .bidfloor(BigDecimal.ONE)); + + // when + final Result>> result = impactifyBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getHeaders) + .flatExtracting(MultiMap::entries) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsExactlyInAnyOrder( + tuple(HttpUtil.CONTENT_TYPE_HEADER.toString(), HttpUtil.APPLICATION_JSON_CONTENT_TYPE), + tuple(HttpUtil.ACCEPT_HEADER.toString(), HttpHeaderValues.APPLICATION_JSON.toString()), + tuple(HttpUtil.USER_AGENT_HEADER.toString(), "ua"), + tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "ip"), + tuple(HttpUtil.X_OPENRTB_VERSION_HEADER.toString(), "2.5"), + tuple(HttpUtil.REFERER_HEADER.toString(), "https://proper.web.site"), + tuple(HttpUtil.COOKIE_HEADER.toString(), "uids=buyer_user_uid") + ); + } + + @Test + public void makeHttpRequestsWithValidDataShouldContainCorrectExt() { + // given + final BidRequest bidRequest = givenBidRequest( + bidRequestCustomizer -> bidRequestCustomizer + .site(Site.builder().page("https://proper.web.site").build()) + .user(User.builder().buyeruid("buyer_user_uid").build()), + impCustomizer -> impCustomizer + .bidfloorcur("USD") + .bidfloor(BigDecimal.ONE)); + + // when + final Result>> result = impactifyBidder.makeHttpRequests(bidRequest); + + // then + final ObjectNode expectedExtNode = mapper.createObjectNode() + .set("impactify", mapper.valueToTree(ExtImpImpactify.of("appId", "format", "style"))); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .containsExactly(expectedExtNode); + } + + @Test + public void makeHttpRequestsShouldReturnValidBidResponseWithAllHeadersExceptIpv4() { + // given + final BidRequest bidRequest = givenBidRequest( + bidRequestCustomizer -> bidRequestCustomizer + .device(givenDevice(deviceCustomizer -> deviceCustomizer.ua("ua").ipv6("ipv6"))) + .site(Site.builder().page("https://proper.web.site").build()) + .user(User.builder().buyeruid("buyer_user_uid").build()), + impCustomizer -> impCustomizer + .bidfloorcur("USD") + .bidfloor(BigDecimal.ONE)); + + // when + final Result>> result = impactifyBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getHeaders) + .flatExtracting(MultiMap::entries) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsExactlyInAnyOrder( + tuple(HttpUtil.CONTENT_TYPE_HEADER.toString(), HttpUtil.APPLICATION_JSON_CONTENT_TYPE), + tuple(HttpUtil.ACCEPT_HEADER.toString(), HttpHeaderValues.APPLICATION_JSON.toString()), + tuple(HttpUtil.USER_AGENT_HEADER.toString(), "ua"), + tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "ipv6"), + tuple(HttpUtil.X_OPENRTB_VERSION_HEADER.toString(), "2.5"), + tuple(HttpUtil.REFERER_HEADER.toString(), "https://proper.web.site"), + tuple(HttpUtil.COOKIE_HEADER.toString(), "uids=buyer_user_uid") + ); + } + + @Test + public void makeHttpRequestsShouldCheckIfValidDataInImpressionHasCorrectBidFloorAndBidFloorCur() { + // given + final BidRequest bidRequest = givenBidRequest( + impCustomizer -> impCustomizer.bidfloor(BigDecimal.ONE).bidfloorcur("USD")); + + // when + Result>> result = impactifyBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBidfloor, Imp::getBidfloorcur) + .containsExactly(tuple(BigDecimal.ONE, "USD")); + } + + @Test + public void makeHttpRequestsWithInvalidImpressionExtShouldReturnUnableToDecodeError() { + // given + final BidRequest bidRequest = givenBidRequest(impCustomizer -> impCustomizer + .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + + // when + Result>> result = impactifyBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1) + .extracting(BidderError::getMessage) + .containsExactly("Unable to decode the impression ext for id: 123"); + } + + @Test + public void makeBidsShouldReturnValidBidResponseWithBanner() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(impCustomizer -> impCustomizer.banner(Banner.builder().build())); + + final HttpCall httpCall = givenHttpCall( + bidRequest, + mapper.writeValueAsString( + givenBidResponse(identity()))); + + // when + final Result> result = impactifyBidder.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getBid) + .containsOnly(Bid.builder() + .impid("123") + .build()); + + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .containsExactly(BidType.banner); + } + + @Test + public void makeBidsWithInvalidBodyShouldResultInError() { + // given + final HttpCall httpCall = givenHttpCall(null, "invalid"); + + // when + final Result> result = impactifyBidder.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().get(0).getMessage()).startsWith("Failed to decode: Unrecognized token"); + } + + @Test + public void makeBidsReturnEmptyListsResultWhenEmptySeatBidInBidResponse() throws JsonProcessingException { + // given + final BidRequest bidRequest = BidRequest.builder().build(); + + final HttpCall httpCall = givenHttpCall( + bidRequest, + mapper.writeValueAsString(BidResponse.builder().build())); + + // when + final Result> result = impactifyBidder.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnValidBidResponseWithVideo() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(impCustomizer -> impCustomizer.video(Video.builder().build())); + + final HttpCall httpCall = givenHttpCall( + bidRequest, + mapper.writeValueAsString( + givenBidResponse(identity()))); + + // when + final Result> result = impactifyBidder.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getBid) + .containsOnly(Bid.builder() + .impid("123") + .build()); + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .containsExactly(BidType.video); + } + + @Test + public void makeBidsShouldReturnErrorWhenBidResponseImpIdIsNotSameAsBidRequestImpId() + throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(identity()); + + final HttpCall httpCall = givenHttpCall( + bidRequest, + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("321")))); + + // when + final Result> result = impactifyBidder.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors()) + .extracting(BidderError::getMessage) + .containsExactly("Failed to find a supported media type impression with ID: '321'"); + } + + private static BidRequest givenBidRequest( + UnaryOperator bidRequestCustomizer, + UnaryOperator impCustomizer) { + + return bidRequestCustomizer.apply(BidRequest.builder() + .imp(List.of(givenImp(impCustomizer)))) + .build(); + } + + private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { + return givenBidRequest(identity(), impCustomizer); + } + + private static Device givenDevice(UnaryOperator deviceCustomizer) { + return deviceCustomizer.apply(Device.builder()).build(); + } + + private static Imp givenImp(UnaryOperator impCustomizer) { + return impCustomizer.apply(Imp.builder() + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpImpactify.of("appId", "format", "style"))))) + .build(); + } + + private static BidResponse givenBidResponse(UnaryOperator bidCustomizer) { + return BidResponse.builder() + .seatbid(List.of(SeatBid.builder() + .bid(List.of(bidCustomizer.apply(Bid.builder().impid("123")).build())) + .build())) + .build(); + } + + private static HttpCall givenHttpCall(BidRequest bidRequest, String body) { + return HttpCall.success( + HttpRequest.builder().payload(bidRequest).build(), + HttpResponse.of(200, null, body), + null); + } +} diff --git a/src/test/java/org/prebid/server/it/ImpactifyTest.java b/src/test/java/org/prebid/server/it/ImpactifyTest.java new file mode 100644 index 00000000000..d4e58997f55 --- /dev/null +++ b/src/test/java/org/prebid/server/it/ImpactifyTest.java @@ -0,0 +1,34 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.prebid.server.model.Endpoint; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static java.util.Collections.singletonList; + +@RunWith(SpringRunner.class) +public class ImpactifyTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromImpactify() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/impactify-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/impactify/test-impactify-bid-request.json"))) + .willReturn(aResponse().withBody(jsonFrom("openrtb2/impactify/test-impactify-bid-response.json")))); + // when + final Response response = responseFor("openrtb2/impactify/test-auction-impactify-request.json", + Endpoint.openrtb2_auction); + // then + assertJsonEquals("openrtb2/impactify/test-auction-impactify-response.json", + response, singletonList("impactify")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-request.json b/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-request.json new file mode 100644 index 00000000000..4006623abaf --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-request.json @@ -0,0 +1,27 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 320, + "h": 250 + }, + "bidfloor": 10, + "bidfloorcur": "USD", + "ext": { + "impactify": { + "appId": "appId", + "format": "format", + "style": "style" + } + } + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-response.json b/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-response.json new file mode 100644 index 00000000000..4bfd0bcee03 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-auction-impactify-response.json @@ -0,0 +1,35 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 0.01, + "adid": "2068416", + "cid": "8048", + "crid": "24080", + "ext": { + "prebid": { + "type": "banner" + }, + "origbidcpm": 0.01 + } + } + ], + "seat": "impactify", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "impactify": "{{ impactify.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-impactify-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-impactify-bid-request.json new file mode 100644 index 00000000000..08b94025306 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-impactify-bid-request.json @@ -0,0 +1,45 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 320, + "h": 250 + }, + "bidfloor": 10, + "bidfloorcur": "USD", + "ext": { + "impactify": { + "appId": "appId", + "format": "format", + "style": "style" + } + } + } + ], + "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": 5000, + "cur": [ + "USD" + ], + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-impactify-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-impactify-bid-response.json new file mode 100644 index 00000000000..2dd18dff6c6 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/impactify/test-impactify-bid-response.json @@ -0,0 +1,18 @@ +{ + "id": "tid", + "seatbid": [ + { + "bid": [ + { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "bid_id", + "impid": "imp_id", + "cid": "8048" + } + ], + "type": "banner" + } + ] +} diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index 3bbfffea128..4db090739d6 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -117,6 +117,8 @@ adapters.grid.enabled=true adapters.grid.endpoint=http://localhost:8090/grid-exchange adapters.gumgum.enabled=true adapters.gumgum.endpoint=http://localhost:8090/gumgum-exchange +adapters.impactify.enabled=true +adapters.impactify.endpoint=http://localhost:8090/impactify-exchange adapters.improvedigital.enabled=true adapters.improvedigital.endpoint=http://localhost:8090/improvedigital-exchange adapters.iqzone.enabled=true From 8a1154f2206ef567482b9684d7d978c078941bc9 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Tue, 30 Nov 2021 14:19:36 +0200 Subject: [PATCH 21/26] Refactored SetuidHandler (#1607) --- .../prebid/server/handler/SetuidHandler.java | 92 ++++++++++++------- 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/prebid/server/handler/SetuidHandler.java b/src/main/java/org/prebid/server/handler/SetuidHandler.java index 79808537ddd..570eab328fe 100644 --- a/src/main/java/org/prebid/server/handler/SetuidHandler.java +++ b/src/main/java/org/prebid/server/handler/SetuidHandler.java @@ -7,9 +7,11 @@ import io.vertx.core.http.Cookie; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpServerResponse; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import io.vertx.ext.web.RoutingContext; +import lombok.Value; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.prebid.server.analytics.AnalyticsReporterDelegator; @@ -39,6 +41,7 @@ import java.util.Collections; import java.util.Map; import java.util.Objects; +import java.util.function.Consumer; import java.util.stream.Collectors; public class SetuidHandler implements Handler { @@ -239,49 +242,52 @@ private void respondByTcfResponse(AsyncResult hostTcfResp private void respondWithCookie(SetuidContext setuidContext) { final RoutingContext routingContext = setuidContext.getRoutingContext(); final String uid = routingContext.request().getParam(UID_PARAM); - final UidsCookie updatedUidsCookie; - boolean successfullyUpdated = false; - final String bidder = setuidContext.getCookieName(); - final UidsCookie uidsCookie = setuidContext.getUidsCookie(); - if (StringUtils.isBlank(uid)) { - updatedUidsCookie = uidsCookie.deleteUid(bidder); - } else if (UidsCookie.isFacebookSentinel(bidder, uid)) { - // At the moment, Facebook calls /setuid with a UID of 0 if the user isn't logged into Facebook. - // They shouldn't be sending us a sentinel value... but since they are, we're refusing to save that ID. - updatedUidsCookie = uidsCookie; - } else { - updatedUidsCookie = uidsCookie.updateUid(bidder, uid); - successfullyUpdated = true; - metrics.updateUserSyncSetsMetric(bidder); - } - final Cookie cookie = uidsCookieService.toCookie(updatedUidsCookie); - addCookie(routingContext, cookie); + final UidsCookieUpdateResult uidsCookieUpdateResult = updateUidsCookie( + setuidContext.getUidsCookie(), bidder, uid); - final HttpResponseStatus status = HttpResponseStatus.OK; + final Cookie updatedUidsCookie = uidsCookieService.toCookie(uidsCookieUpdateResult.getUidsCookie()); + addCookie(routingContext, updatedUidsCookie); - final String format = routingContext.request().getParam(UsersyncUtil.FORMAT_PARAMETER); - if (shouldRespondWithPixel(format, setuidContext.getSyncType())) { - HttpUtil.executeSafely(routingContext, Endpoint.setuid, - response -> response - .sendFile(PIXEL_FILE_PATH)); - } else { - HttpUtil.executeSafely(routingContext, Endpoint.setuid, - response -> response - .setStatusCode(status.code()) - .putHeader(HttpHeaders.CONTENT_LENGTH, "0") - .putHeader(HttpHeaders.CONTENT_TYPE, HttpHeaders.TEXT_HTML) - .end()); - } + final int statusCode = HttpResponseStatus.OK.code(); + HttpUtil.executeSafely(routingContext, Endpoint.setuid, buildCookieResponseConsumer(setuidContext, statusCode)); final TcfContext tcfContext = setuidContext.getPrivacyContext().getTcfContext(); - analyticsDelegator.processEvent(SetuidEvent.builder() - .status(status.code()) + final SetuidEvent setuidEvent = SetuidEvent.builder() + .status(statusCode) .bidder(bidder) .uid(uid) - .success(successfullyUpdated) - .build(), tcfContext); + .success(uidsCookieUpdateResult.isSuccessfullyUpdated()) + .build(); + analyticsDelegator.processEvent(setuidEvent, tcfContext); + } + + private UidsCookieUpdateResult updateUidsCookie(UidsCookie uidsCookie, String bidderName, String uid) { + if (StringUtils.isBlank(uid)) { + return UidsCookieUpdateResult.unaltered(uidsCookie.deleteUid(bidderName)); + } else if (UidsCookie.isFacebookSentinel(bidderName, uid)) { + // At the moment, Facebook calls /setuid with a UID of 0 if the user isn't logged into Facebook. + // They shouldn't be sending us a sentinel value... but since they are, we're refusing to save that ID. + return UidsCookieUpdateResult.unaltered(uidsCookie); + } else { + final UidsCookie updatedCookie = uidsCookie.updateUid(bidderName, uid); + metrics.updateUserSyncSetsMetric(bidderName); + return UidsCookieUpdateResult.updated(updatedCookie); + } + } + + private Consumer buildCookieResponseConsumer(SetuidContext setuidContext, + int responseStatusCode) { + + final String format = setuidContext.getRoutingContext().request().getParam(UsersyncUtil.FORMAT_PARAMETER); + return shouldRespondWithPixel(format, setuidContext.getSyncType()) + ? response -> response.sendFile(PIXEL_FILE_PATH) + : response -> response + .setStatusCode(responseStatusCode) + .putHeader(HttpHeaders.CONTENT_LENGTH, "0") + .putHeader(HttpHeaders.CONTENT_TYPE, HttpHeaders.TEXT_HTML) + .end(); } private boolean shouldRespondWithPixel(String format, String syncType) { @@ -324,4 +330,20 @@ private void handleErrors(Throwable error, RoutingContext routingContext, TcfCon private void addCookie(RoutingContext routingContext, Cookie cookie) { routingContext.response().headers().add(HttpUtil.SET_COOKIE_HEADER, HttpUtil.toSetCookieHeaderValue(cookie)); } + + @Value(staticConstructor = "of") + private static class UidsCookieUpdateResult { + + private static UidsCookieUpdateResult updated(UidsCookie uidsCookie) { + return of(true, uidsCookie); + } + + private static UidsCookieUpdateResult unaltered(UidsCookie uidsCookie) { + return of(false, uidsCookie); + } + + boolean successfullyUpdated; + + UidsCookie uidsCookie; + } } From a39fde922eb99020c7c0ed127a75d7061f4381cb Mon Sep 17 00:00:00 2001 From: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com> Date: Tue, 30 Nov 2021 16:08:24 +0200 Subject: [PATCH 22/26] Add currency stopgap for Beachfront bidder. (#1599) --- .../bidder/beachfront/BeachfrontBidder.java | 24 ++++++++++++++----- .../beachfront/BeachfrontBidderTest.java | 20 ++++++++++++---- .../test-auction-beachfront-request.json | 2 +- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/beachfront/BeachfrontBidder.java b/src/main/java/org/prebid/server/bidder/beachfront/BeachfrontBidder.java index 59bfa224c39..36f2fb81f2c 100644 --- a/src/main/java/org/prebid/server/bidder/beachfront/BeachfrontBidder.java +++ b/src/main/java/org/prebid/server/bidder/beachfront/BeachfrontBidder.java @@ -157,15 +157,19 @@ private BeachfrontBannerRequest getBannerRequest(BidRequest bidRequest, List slots = new ArrayList<>(); for (Imp imp : bannerImps) { + final ExtImpBeachfront extImpBeachfront; + final String appId; try { - final ExtImpBeachfront extImpBeachfront = parseImpExt(imp); - final String appId = getAppId(extImpBeachfront, true); - - slots.add(BeachfrontSlot.of(imp.getId(), appId, getBidFloor( - extImpBeachfront.getBidfloor(), imp.getBidfloor()), makeBeachfrontSizes(imp.getBanner()))); + validateImp(imp); + extImpBeachfront = parseImpExt(imp); + appId = getAppId(extImpBeachfront, true); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); + continue; } + + final BigDecimal bidFloor = getBidFloor(extImpBeachfront.getBidfloor(), imp.getBidfloor()); + slots.add(BeachfrontSlot.of(imp.getId(), appId, bidFloor, makeBeachfrontSizes(imp.getBanner()))); } if (slots.isEmpty()) { return null; @@ -217,6 +221,14 @@ private BeachfrontBannerRequest getBannerRequest(BidRequest bidRequest, List getVideoRequests(BidRequest bidRequest, Lis for (Imp imp : videoImps) { final ExtImpBeachfront extImpBeachfront; final String appId; - try { + validateImp(imp); extImpBeachfront = parseImpExt(imp); appId = getAppId(extImpBeachfront, false); } catch (PreBidException e) { diff --git a/src/test/java/org/prebid/server/bidder/beachfront/BeachfrontBidderTest.java b/src/test/java/org/prebid/server/bidder/beachfront/BeachfrontBidderTest.java index 981b50e3c84..b0bcc5b6fe3 100644 --- a/src/test/java/org/prebid/server/bidder/beachfront/BeachfrontBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/beachfront/BeachfrontBidderTest.java @@ -406,6 +406,19 @@ public void makeHttpRequestsShouldReturnExpectedBidFloorFromBidRequest() { .containsExactly(BigDecimal.ONE); } + @Test + public void makeHttpRequestsShouldReturnErrorIfImpBidFloorCurIsUnsupported() { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.bidfloorcur("unsupported")); + + // when + final Result>> result = beachfrontBidder.makeHttpRequests(bidRequest); + + // then + final String errorMessage = "Unsupported bid currency, unsupported. bids are currently accepted in USD only."; + assertThat(result.getErrors()).hasSize(1).containsExactly(BidderError.badInput(errorMessage)); + } + @Test public void makeHttpRequestsShouldUseDefaultBidFloorIfNoInRequest() { // given @@ -458,7 +471,7 @@ public void makeHttpRequestsShouldUseImpBidFloor() { } @Test - public void makeBidsShouldReturnEmptyResultWhenResponseBodyHasEmptyArray() throws JsonProcessingException { + public void makeBidsShouldReturnEmptyResultWhenResponseBodyHasEmptyArray() { // given final HttpCall httpCall = givenHttpCall(null, "[]"); @@ -471,7 +484,7 @@ public void makeBidsShouldReturnEmptyResultWhenResponseBodyHasEmptyArray() throw } @Test - public void makeBidsShouldReturnErrorWhenResponseBodyIsInvalid() throws JsonProcessingException { + public void makeBidsShouldReturnErrorWhenResponseBodyIsInvalid() { // given final HttpCall httpCall = givenHttpCall(null, "invalid"); @@ -819,8 +832,7 @@ private static HttpCall givenHttpCall(BeachfrontVideoRequest beachfrontVid return givenHttpCall(mapper.writeValueAsBytes(beachfrontVideoRequest), mapper.writeValueAsString(bidResponse)); } - private static HttpCall givenHttpCall(byte[] requestBody, String responseBody) - throws JsonProcessingException { + private static HttpCall givenHttpCall(byte[] requestBody, String responseBody) { return HttpCall.success( HttpRequest.builder().body(requestBody).uri("url").build(), HttpResponse.of(200, null, responseBody), null); diff --git a/src/test/resources/org/prebid/server/it/openrtb2/beachfront/test-auction-beachfront-request.json b/src/test/resources/org/prebid/server/it/openrtb2/beachfront/test-auction-beachfront-request.json index 8aed8adf217..472044307d5 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/beachfront/test-auction-beachfront-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/beachfront/test-auction-beachfront-request.json @@ -25,4 +25,4 @@ "gdpr": 0 } } -} \ No newline at end of file +} From 973d8669581ae3cfaf529d30b424b260edc13762 Mon Sep 17 00:00:00 2001 From: Yevhenii Viktorov <91950229+yevhenii-viktorov@users.noreply.github.com> Date: Thu, 2 Dec 2021 13:07:17 +0200 Subject: [PATCH 23/26] Add new bid parameters to Adprime (#1569) --- .../server/bidder/adprime/AdprimeBidder.java | 177 ++++++++++++- .../ext/request/adprime/ExtImpAdprime.java | 6 + src/main/resources/bidder-config/adprime.yaml | 4 +- .../static/bidder-params/adprime.json | 19 +- .../bidder/adprime/AdprimeBidderTest.java | 238 ++++++++++++++---- .../adprime/test-adprime-bid-request.json | 7 +- .../adprime/test-auction-adprime-request.json | 8 +- 7 files changed, 405 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/adprime/AdprimeBidder.java b/src/main/java/org/prebid/server/bidder/adprime/AdprimeBidder.java index 901282cb9f1..e95d0699d45 100644 --- a/src/main/java/org/prebid/server/bidder/adprime/AdprimeBidder.java +++ b/src/main/java/org/prebid/server/bidder/adprime/AdprimeBidder.java @@ -1,20 +1,187 @@ package org.prebid.server.bidder.adprime; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; -import org.prebid.server.bidder.OpenrtbBidder; +import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.User; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import io.vertx.core.http.HttpMethod; +import org.apache.commons.collections4.CollectionUtils; +import org.prebid.server.bidder.Bidder; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpCall; +import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.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.adprime.ExtImpAdprime; +import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.HttpUtil; -public class AdprimeBidder extends OpenrtbBidder { +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class AdprimeBidder implements Bidder { + + private static final TypeReference> ADPRIME_EXT_TYPE_REFERENCE = + new TypeReference>() { + }; + + private final String endpointUrl; + private final JacksonMapper mapper; public AdprimeBidder(String endpointUrl, JacksonMapper mapper) { - super(endpointUrl, RequestCreationStrategy.REQUEST_PER_IMP, ExtImpAdprime.class, mapper); + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.mapper = Objects.requireNonNull(mapper); } @Override - protected Imp modifyImp(Imp imp, ExtImpAdprime impExt) { + public final Result>> makeHttpRequests(BidRequest bidRequest) { + final List> httpRequests = new ArrayList<>(); + final List imps = bidRequest.getImp(); + + for (Imp imp : imps) { + try { + final ExtImpAdprime extImpAdprime = mapper.mapper() + .convertValue(imp.getExt(), ADPRIME_EXT_TYPE_REFERENCE) + .getBidder(); + + httpRequests.add(makeHttpRequest( + modifyBidRequest(bidRequest, extImpAdprime, modifyImp(imp, extImpAdprime)))); + } catch (IllegalArgumentException e) { + return Result.withError(BidderError.badInput( + String.format("Unable to decode the impression ext for id: '%s'", imp.getId()))); + } + } + + return Result.of(httpRequests, Collections.emptyList()); + } + + private Imp modifyImp(Imp imp, ExtImpAdprime impExt) { + final String tagId = impExt.getTagId(); + final ObjectNode modifiedImpExtBidder = mapper.mapper().createObjectNode(); + + modifiedImpExtBidder.set("TagID", TextNode.valueOf(tagId)); + modifiedImpExtBidder.set("placementId", TextNode.valueOf(tagId)); + return imp.toBuilder() - .tagid(impExt.getTagId()) + .tagid(tagId) + .ext(mapper.mapper().createObjectNode().set("bidder", modifiedImpExtBidder)) + .build(); + } + + private BidRequest modifyBidRequest(BidRequest bidRequest, ExtImpAdprime extImpAdprime, Imp imp) { + return bidRequest.toBuilder() + .user(resolveUser(bidRequest, extImpAdprime)) + .site(resolveSite(bidRequest, extImpAdprime)) + .imp(List.of(imp)) + .build(); + } + + private User resolveUser(BidRequest bidRequest, ExtImpAdprime extImpAdprime) { + final User user = bidRequest.getUser(); + final List audiences = extImpAdprime.getAudiences(); + + if (Objects.isNull(bidRequest.getSite()) || CollectionUtils.isEmpty(audiences)) { + return user; + } + + final User.UserBuilder userBuilder = user != null ? user.toBuilder() : User.builder(); + + return userBuilder.customdata(String.join(",", audiences)).build(); + } + + private Site resolveSite(BidRequest bidRequest, ExtImpAdprime extImpAdprime) { + final Site site = bidRequest.getSite(); + final List keywords = extImpAdprime.getKeywords(); + + return Objects.nonNull(site) && CollectionUtils.isNotEmpty(keywords) + ? site.toBuilder() + .keywords(String.join(",", keywords)) + .build() + : site; + } + + private HttpRequest makeHttpRequest(BidRequest bidRequest) { + return HttpRequest.builder() + .method(HttpMethod.POST) + .uri(endpointUrl) + .headers(HttpUtil.headers()) + .payload(bidRequest) + .body(mapper.encodeToBytes(bidRequest)) .build(); } + + @Override + public final Result> makeBids(HttpCall httpCall, BidRequest bidRequest) { + try { + final List errors = new ArrayList<>(); + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + return Result.of(extractBids(httpCall.getRequest().getPayload(), bidResponse, errors), errors); + } catch (DecodeException | PreBidException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + + private static List extractBids(BidRequest bidRequest, BidResponse bidResponse, + List errors) { + if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { + return Collections.emptyList(); + } + return bidsFromResponse(bidRequest, bidResponse, errors); + } + + private static List bidsFromResponse(BidRequest bidRequest, BidResponse bidResponse, + List errors) { + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .map(bid -> resolveBidderBid(bid, bidResponse.getCur(), bidRequest.getImp(), errors)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private static BidderBid resolveBidderBid(Bid bid, String currency, List imps, List errors) { + final BidType bidType; + try { + bidType = getBidType(bid.getImpid(), imps); + } catch (PreBidException e) { + errors.add(BidderError.badServerResponse(e.getMessage())); + return null; + } + return BidderBid.of(bid, bidType, currency); + } + + private static BidType getBidType(String impId, List imps) { + for (Imp imp : imps) { + if (imp.getId().equals(impId)) { + if (imp.getBanner() != null) { + return BidType.banner; + } + if (imp.getVideo() != null) { + return BidType.video; + } + if (imp.getXNative() != null) { + return BidType.xNative; + } + throw new PreBidException(String.format("Unknown impression type for ID: '%s'", impId)); + } + } + throw new PreBidException(String.format("Failed to find impression for ID: '%s'", impId)); + } + } diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adprime/ExtImpAdprime.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/adprime/ExtImpAdprime.java index 9d625de5ec1..8a5c2947648 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adprime/ExtImpAdprime.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/adprime/ExtImpAdprime.java @@ -4,6 +4,8 @@ import lombok.AllArgsConstructor; import lombok.Value; +import java.util.List; + /** * Defines the contract for bidRequest.imp[i].ext.adprime */ @@ -13,4 +15,8 @@ public class ExtImpAdprime { @JsonProperty("TagID") String tagId; + + List keywords; + + List audiences; } diff --git a/src/main/resources/bidder-config/adprime.yaml b/src/main/resources/bidder-config/adprime.yaml index 6e27742a957..0658911fa22 100644 --- a/src/main/resources/bidder-config/adprime.yaml +++ b/src/main/resources/bidder-config/adprime.yaml @@ -1,14 +1,16 @@ adapters: adprime: - endpoint: http://delta.adprime.com/?c=o&m=ortb + endpoint: http://delta.adprime.com/pserver meta-info: maintainer-email: prebid@adprime.com app-media-types: - banner - video + - native site-media-types: - banner - video + - native supported-vendors: vendor-id: 0 usersync: diff --git a/src/main/resources/static/bidder-params/adprime.json b/src/main/resources/static/bidder-params/adprime.json index 31a83842e9a..f48fc97ac66 100644 --- a/src/main/resources/static/bidder-params/adprime.json +++ b/src/main/resources/static/bidder-params/adprime.json @@ -2,13 +2,28 @@ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Adprime Adapter Params", "description": "A schema which validates params accepted by the Adprime adapter", - "type": "object", "properties": { "TagID": { "type": "string", "description": "An ID which identifies the adprime ad tag" + }, + "keywords": { + "type": "array", + "description": "page context keywords", + "items": { + "type": "string" + } + }, + "audiences": { + "type": "array", + "description": "publisher audiences", + "items": { + "type": "string" + } } }, - "required" : [ "TagID" ] + "required": [ + "TagID" + ] } diff --git a/src/test/java/org/prebid/server/bidder/adprime/AdprimeBidderTest.java b/src/test/java/org/prebid/server/bidder/adprime/AdprimeBidderTest.java index 4d08ae06325..e046c6bd499 100644 --- a/src/test/java/org/prebid/server/bidder/adprime/AdprimeBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/adprime/AdprimeBidderTest.java @@ -1,9 +1,14 @@ package org.prebid.server.bidder.adprime; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Native; +import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.User; import com.iab.openrtb.request.Video; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; @@ -20,16 +25,19 @@ import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.adprime.ExtImpAdprime; -import java.util.Arrays; import java.util.List; import java.util.function.Function; +import java.util.stream.Collectors; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.function.Function.identity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; 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.proto.openrtb.ext.response.BidType.xNative; public class AdprimeBidderTest extends VertxTest { @@ -50,50 +58,143 @@ public void creationShouldFailOnInvalidEndpointUrl() { @Test public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(Imp.builder() - .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()))) - .build())) - .build(); + final BidRequest bidRequest = givenBidRequestWithDefaultBidRequest(impCustomizer -> impCustomizer + .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); // when final Result>> result = adprimeBidder.makeHttpRequests(bidRequest); // then assertThat(result.getErrors()).hasSize(1); - assertThat(result.getErrors().get(0).getMessage()).startsWith("Cannot deserialize value"); + assertThat(result.getErrors().get(0).getMessage()).startsWith("Unable to decode the impression ext for id"); assertThat(result.getValue()).isEmpty(); } + @Test + public void makeHttpRequestsWithNoKeywordsAndAudiencesReturnsNullSiteAndUser() { + // given + final BidRequest bidRequest = givenBidRequestWithDefaultBidRequest(impCustomizer -> impCustomizer + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpAdprime.of("otherTagId", + null, + null))))); + + // when + final Result>> result = adprimeBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()).hasSize(1) + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .flatExtracting(BidRequest::getSite) + .containsNull(); + assertThat(result.getValue()).hasSize(1) + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .flatExtracting(BidRequest::getUser) + .containsNull(); + } + + @Test + public void makeHttpRequestsWithSiteUserAndAudiencesShouldReturnUserWithCustomData() { + // given + final BidRequest bidRequest = givenBidRequest( + bidRequestCustomizer -> bidRequestCustomizer + .site(Site.builder().build()) + .user(User.builder().build()), + singletonList(impCustomizer -> impCustomizer.ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpAdprime.of("otherTagId", + null, + List.of("audience1"))))))); + + // when + final Result>> result = adprimeBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()).hasSize(1) + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .extracting(BidRequest::getUser) + .extracting(User::getCustomdata) + .containsExactly("audience1"); + } + + @Test + public void makeHttpRequestsWithNoUserShouldReturnNewUserWithAudiences() { + // given + final BidRequest bidRequest = givenBidRequest( + bidRequestCustomizer -> bidRequestCustomizer + .site(Site.builder().build()), + singletonList(impCustomizer -> impCustomizer.ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpAdprime.of("otherTagId", + null, + List.of("audience1"))))))); + + // when + final Result>> result = adprimeBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()).hasSize(1) + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .extracting(BidRequest::getUser) + .extracting(User::getCustomdata) + .containsExactly("audience1"); + } + + @Test + public void makeHttpRequestsWithSiteAndKeywordsShouldReturnSiteWithKeywords() { + // given + final BidRequest bidRequest = givenBidRequest(bidRequestCustomizer -> bidRequestCustomizer + .site(Site.builder().build()), + singletonList(impCustomizer -> impCustomizer.ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpAdprime.of("otherTagId", + List.of("keyword1"), + null)))))); + + // when + final Result>> result = adprimeBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(0); + assertThat(result.getValue()).hasSize(1) + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .extracting(BidRequest::getSite) + .extracting(Site::getKeywords) + .containsExactly("keyword1"); + } + @Test public void makeHttpRequestsShouldReturnExpectedBidRequest() { // given - final BidRequest bidRequest = givenBidRequest(identity()); + final BidRequest bidRequest = givenBidRequest(bidRequestCustomizer -> bidRequestCustomizer + .site(Site.builder().keywords("keyword1,keyword2").build()) + .user(User.builder().customdata("audience1,audience2").build()), + singletonList(impCustomizer -> impCustomizer.tagid("someTagId"))); + final ObjectNode modifiedImpExtBidder = createModifiedImpExtBidder(); // when final Result>> result = adprimeBidder.makeHttpRequests(bidRequest); // then - final BidRequest expectedRequest = bidRequest.toBuilder() - .imp(singletonList(bidRequest.getImp().get(0).toBuilder() - .tagid("tagidString").build())) - .build(); + final BidRequest expectedRequest = givenBidRequest(bidRequestCustomizer -> bidRequestCustomizer + .site(Site.builder().keywords("keyword1,keyword2").build()) + .user(User.builder().customdata("audience1,audience2").build()), + singletonList(impCustomizer -> impCustomizer + .tagid("someTagId") + .ext(mapper.createObjectNode().set("bidder", modifiedImpExtBidder)))); + assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1) .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) - .containsOnly(expectedRequest); + .containsExactly(expectedRequest); } @Test public void makeHttpRequestsShouldMakeOneRequestPerImp() { // given - final BidRequest bidRequest = givenBidRequest( - identity(), - requestBuilder -> requestBuilder.imp(Arrays.asList( - givenImp(identity()), - Imp.builder() - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpAdprime.of("otherTagId")))) - .build()))); + final BidRequest bidRequest = givenBidRequestWithDefaultBidRequest(identity(), impCustomizer -> impCustomizer + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpAdprime.of("otherTagId", emptyList(), emptyList()))))); // when final Result>> result = adprimeBidder.makeHttpRequests(bidRequest); @@ -104,7 +205,7 @@ public void makeHttpRequestsShouldMakeOneRequestPerImp() { .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) .flatExtracting(BidRequest::getImp).hasSize(2) .extracting(Imp::getTagid) - .containsOnly("tagidString", "otherTagId"); + .containsExactly("someTagId", "otherTagId"); } @Test @@ -150,73 +251,115 @@ public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws Jso } @Test - public void makeBidsShouldReturnBannerBidByDefault() throws JsonProcessingException { + public void makeBidsShouldReturnErrorIfNoBidTypeIsPresent() throws JsonProcessingException { // given final HttpCall httpCall = givenHttpCall( - BidRequest.builder().imp(singletonList(Imp.builder().id("123").build())).build(), + givenBidRequestWithDefaultBidRequest(impCustomizer -> impCustomizer.id("123")), mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); // when final Result> result = adprimeBidder.makeBids(httpCall, null); // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(Bid.builder().impid("123").build(), banner, "USD")); + assertThat(result.getErrors().get(0).getMessage()).startsWith("Unknown impression type for ID:"); + } @Test public void makeBidsShouldReturnVideoBidIfNoBannerAndHasVideo() throws JsonProcessingException { // given final HttpCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder().video(Video.builder().build()).id("123").build())) - .build(), + givenBidRequestWithDefaultBidRequest(impCustomizer -> impCustomizer + .id("123").video(Video.builder().build())), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); + + // when + final Result> result = adprimeBidder.makeBids(httpCall, null); + + // then + final BidderBid expectedBidderBid = BidderBid.of(Bid.builder().impid("123").build(), video, "USD"); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsExactly(expectedBidderBid); + } + + @Test + public void makeBidsShouldReturnNativeBidIfHasNativeInImpression() throws JsonProcessingException { + // given + final HttpCall httpCall = givenHttpCall( + givenBidRequestWithDefaultBidRequest(impCustomizer -> impCustomizer + .id("123").xNative(Native.builder().build())), mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); // when final Result> result = adprimeBidder.makeBids(httpCall, null); // then + final BidderBid expectedBidderBid = BidderBid.of(Bid.builder().impid("123").build(), xNative, "USD"); + assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(Bid.builder().impid("123").build(), video, "USD")); + assertThat(result.getValue()).containsExactly(expectedBidderBid); } @Test public void makeBidsShouldReturnBannerBidIfHasBothBannerAndVideo() throws JsonProcessingException { // given final HttpCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(givenImp(identity()))) - .build(), + givenBidRequest(identity(), + singletonList(impCustomizer -> impCustomizer + .banner(Banner.builder().build()) + .video(Video.builder().build()))), mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); // when final Result> result = adprimeBidder.makeBids(httpCall, null); // then + final BidderBid expectedBidderBid = BidderBid.of(Bid.builder().impid("123").build(), banner, "USD"); + assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(Bid.builder().impid("123").build(), banner, "USD")); + assertThat(result.getValue()).containsExactly(expectedBidderBid); + } + + @Test + public void makeBidsShouldThrowExcceptionIsNoImpressionWithIdFound() throws JsonProcessingException { + // given + final HttpCall httpCall = givenHttpCall( + givenBidRequestWithDefaultBidRequest(identity(), identity()), + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("321")))); + + // when + final Result> result = adprimeBidder.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().get(0).getMessage()).startsWith("Failed to find impression"); } private static BidRequest givenBidRequest( - Function impCustomizer, - Function requestCustomizer) { - return requestCustomizer.apply(BidRequest.builder() - .imp(singletonList(givenImp(impCustomizer)))) + Function bidRequestCustomizer, + List> impCustomizers) { + + return bidRequestCustomizer.apply(BidRequest.builder() + .imp(impCustomizers.stream() + .map(AdprimeBidderTest::givenImp) + .collect(Collectors.toList()))) .build(); } - private static BidRequest givenBidRequest( - Function impCustomizer) { - return givenBidRequest(impCustomizer, identity()); + @SafeVarargs + private static BidRequest givenBidRequestWithDefaultBidRequest(Function... impCustomizers) { + return givenBidRequest(identity(), asList(impCustomizers)); } private static Imp givenImp(Function impCustomizer) { return impCustomizer.apply(Imp.builder() - .id("123")) - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpAdprime.of("tagidString")))) + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpAdprime.of("someTagId", + List.of("keyword1", "keyword2"), + List.of("audience1", "audience2")))))) .build(); } @@ -234,4 +377,11 @@ private static HttpCall givenHttpCall(BidRequest bidRequest, String return HttpCall.success(HttpRequest.builder().payload(bidRequest).build(), HttpResponse.of(200, null, body), null); } + + private ObjectNode createModifiedImpExtBidder() { + final ObjectNode modifiedImpExtBidder = mapper.createObjectNode(); + modifiedImpExtBidder.set("TagID", TextNode.valueOf("someTagId")); + modifiedImpExtBidder.set("placementId", TextNode.valueOf("someTagId")); + return modifiedImpExtBidder; + } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-adprime-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-adprime-bid-request.json index ce248831c1f..71a72df899c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-adprime-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-adprime-bid-request.json @@ -10,7 +10,8 @@ "tagid": "tag_id", "ext": { "bidder": { - "TagID": "tag_id" + "TagID": "tag_id", + "placementId": "tag_id" } } } @@ -21,6 +22,7 @@ "publisher": { "domain": "example.com" }, + "keywords": "keyword1", "ext": { "amp": 0 } @@ -29,6 +31,9 @@ "ua": "userAgent", "ip": "193.168.244.1" }, + "user": { + "customdata": "audience1" + }, "at": 1, "tmax": 5000, "cur": [ diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-auction-adprime-request.json b/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-auction-adprime-request.json index da97797b1e7..9ea234c92dd 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-auction-adprime-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/adprime/test-auction-adprime-request.json @@ -9,7 +9,13 @@ }, "ext": { "adprime": { - "TagID": "tag_id" + "TagID": "tag_id", + "keywords": [ + "keyword1" + ], + "audiences": [ + "audience1" + ] } } } From 2a3d0c9b314c7dd5504a1b559c5940c0a8f6b934 Mon Sep 17 00:00:00 2001 From: Yevhenii Viktorov <91950229+yevhenii-viktorov@users.noreply.github.com> Date: Thu, 2 Dec 2021 14:00:07 +0200 Subject: [PATCH 24/26] Add NextMillenium bidder (#1572) --- .../nextmillenium/NextMilleniumBidder.java | 126 +++++++++++ .../nextmillenium/ExtImpNextMillenium.java | 9 + .../bidder/NextMilleniumConfiguration.java | 41 ++++ .../bidder-config/nextmillenium.yaml | 17 ++ .../static/bidder-params/nextmillenium.json | 16 ++ .../NextMilleniumBidderTest.java | 201 ++++++++++++++++++ .../prebid/server/it/NextMilleniumTest.java | 38 ++++ .../test-auction-nextmillenium-request.json | 23 ++ .../test-auction-nextmillenium-response.json | 35 +++ .../test-nextmillenium-bid-request.json | 10 + .../test-nextmillenium-bid-response.json | 18 ++ .../server/it/test-application.properties | 2 + 12 files changed, 536 insertions(+) create mode 100644 src/main/java/org/prebid/server/bidder/nextmillenium/NextMilleniumBidder.java create mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/nextmillenium/ExtImpNextMillenium.java create mode 100644 src/main/java/org/prebid/server/spring/config/bidder/NextMilleniumConfiguration.java create mode 100644 src/main/resources/bidder-config/nextmillenium.yaml create mode 100644 src/main/resources/static/bidder-params/nextmillenium.json create mode 100644 src/test/java/org/prebid/server/bidder/nextmillenium/NextMilleniumBidderTest.java create mode 100644 src/test/java/org/prebid/server/it/NextMilleniumTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-auction-nextmillenium-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-auction-nextmillenium-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-nextmillenium-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-nextmillenium-bid-response.json diff --git a/src/main/java/org/prebid/server/bidder/nextmillenium/NextMilleniumBidder.java b/src/main/java/org/prebid/server/bidder/nextmillenium/NextMilleniumBidder.java new file mode 100644 index 00000000000..3a55a15c192 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/nextmillenium/NextMilleniumBidder.java @@ -0,0 +1,126 @@ +package org.prebid.server.bidder.nextmillenium; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import io.vertx.core.MultiMap; +import io.vertx.core.http.HttpMethod; +import org.apache.commons.collections4.CollectionUtils; +import org.prebid.server.bidder.Bidder; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpCall; +import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.json.DecodeException; +import org.prebid.server.json.JacksonMapper; +import org.prebid.server.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtStoredRequest; +import org.prebid.server.proto.openrtb.ext.request.nextmillenium.ExtImpNextMillenium; +import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.HttpUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class NextMilleniumBidder implements Bidder { + + private static final TypeReference> NEXTMILLENIUM_EXT_TYPE_REFERENCE = + new TypeReference>() { + }; + + private final String endpointUrl; + private final JacksonMapper mapper; + + public NextMilleniumBidder(String endpointUrl, JacksonMapper mapper) { + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.mapper = Objects.requireNonNull(mapper); + } + + @Override + public final Result>> makeHttpRequests(BidRequest bidRequest) { + final List errors = new ArrayList<>(); + final List impExts = getImpExts(bidRequest, errors); + + return errors.isEmpty() + ? Result.withValues(makeRequests(bidRequest, impExts)) + : Result.withErrors(errors); + } + + private List getImpExts(BidRequest bidRequest, List errors) { + final List impExts = new ArrayList<>(); + + for (Imp imp : bidRequest.getImp()) { + try { + impExts.add(mapper.mapper() + .convertValue(imp.getExt(), NEXTMILLENIUM_EXT_TYPE_REFERENCE) + .getBidder()); + } catch (IllegalArgumentException e) { + errors.add(BidderError.badInput(e.getMessage())); + } + } + return impExts; + } + + private List> makeRequests(BidRequest bidRequest, List extImps) { + return extImps.stream() + .map(extImp -> makeHttpRequest(updateBidRequest(bidRequest, extImp))) + .collect(Collectors.toList()); + } + + private static BidRequest updateBidRequest(BidRequest bidRequest, ExtImpNextMillenium ext) { + return BidRequest.builder() + .id(bidRequest.getId()) + .test(bidRequest.getTest()) + .ext(ExtRequest.of( + ExtRequestPrebid.builder() + .storedrequest(ExtStoredRequest.of(ext.getPlacementId())) + .build())) + .build(); + } + + private HttpRequest makeHttpRequest(BidRequest bidRequest) { + return HttpRequest.builder() + .method(HttpMethod.POST) + .uri(endpointUrl) + .headers(headers()) + .payload(bidRequest) + .body(mapper.encodeToBytes(bidRequest)) + .build(); + } + + private static MultiMap headers() { + return HttpUtil.headers() + .add(HttpUtil.X_OPENRTB_VERSION_HEADER, "2.5"); + } + + @Override + public final Result> makeBids(HttpCall httpCall, BidRequest bidRequest) { + try { + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + if (CollectionUtils.isEmpty(bidResponse.getSeatbid())) { + return Result.empty(); + } + return Result.withValues(bidsFromResponse(bidResponse)); + } catch (DecodeException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + + private static List bidsFromResponse(BidResponse bidResponse) { + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .map(bid -> BidderBid.of(bid, BidType.banner, bidResponse.getCur())) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/nextmillenium/ExtImpNextMillenium.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/nextmillenium/ExtImpNextMillenium.java new file mode 100644 index 00000000000..cbc17a59f95 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/nextmillenium/ExtImpNextMillenium.java @@ -0,0 +1,9 @@ +package org.prebid.server.proto.openrtb.ext.request.nextmillenium; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class ExtImpNextMillenium { + + String placementId; +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/NextMilleniumConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/NextMilleniumConfiguration.java new file mode 100644 index 00000000000..1c0c302cd7c --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/NextMilleniumConfiguration.java @@ -0,0 +1,41 @@ +package org.prebid.server.spring.config.bidder; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.nextmillenium.NextMilleniumBidder; +import org.prebid.server.json.JacksonMapper; +import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties; +import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler; +import org.prebid.server.spring.config.bidder.util.UsersyncerCreator; +import org.prebid.server.spring.env.YamlPropertySourceFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import javax.validation.constraints.NotBlank; + +@Configuration +@PropertySource(value = "classpath:/bidder-config/nextmillenium.yaml", factory = YamlPropertySourceFactory.class) +public class NextMilleniumConfiguration { + + private static final String BIDDER_NAME = "nextmillenium"; + + @Bean("nextmilleniumConfigurationProperties") + @ConfigurationProperties("adapters.nextmillenium") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + @Bean + BidderDeps nextmilleniumBidderDeps(BidderConfigurationProperties nextmilleniumConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + JacksonMapper mapper) { + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(nextmilleniumConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new NextMilleniumBidder(config.getEndpoint(), mapper)) + .assemble(); + } +} diff --git a/src/main/resources/bidder-config/nextmillenium.yaml b/src/main/resources/bidder-config/nextmillenium.yaml new file mode 100644 index 00000000000..c52a6db52c5 --- /dev/null +++ b/src/main/resources/bidder-config/nextmillenium.yaml @@ -0,0 +1,17 @@ +adapters: + nextmillenium: + endpoint: https://pbs.nextmillmedia.com/openrtb2/auction + meta-info: + maintainer-email: appdevs@nextmillennium.io + app-media-types: + - banner + site-media-types: + - banner + supported-vendors: + vendor-id: 1060 + usersync: + url: + redirect-url: + cookie-family-name: nextmillennium + type: redirect + support-cors: false diff --git a/src/main/resources/static/bidder-params/nextmillenium.json b/src/main/resources/static/bidder-params/nextmillenium.json new file mode 100644 index 00000000000..7db57b6b9c1 --- /dev/null +++ b/src/main/resources/static/bidder-params/nextmillenium.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "NextMillennium Adapter Params", + "description": "A schema which validates params accepted by the NextMillennium adapter", + "type": "object", + "properties": { + "placement_id": { + "type": "string", + "minLength": 1, + "description": "An id used to identify NextMillennium placement" + } + }, + "required": [ + "placement_id" + ] +} diff --git a/src/test/java/org/prebid/server/bidder/nextmillenium/NextMilleniumBidderTest.java b/src/test/java/org/prebid/server/bidder/nextmillenium/NextMilleniumBidderTest.java new file mode 100644 index 00000000000..18881e0214e --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/nextmillenium/NextMilleniumBidderTest.java @@ -0,0 +1,201 @@ +package org.prebid.server.bidder.nextmillenium; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import org.junit.Before; +import org.junit.Test; +import org.prebid.server.VertxTest; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpCall; +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.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtStoredRequest; +import org.prebid.server.proto.openrtb.ext.request.nextmillenium.ExtImpNextMillenium; +import org.prebid.server.proto.openrtb.ext.response.BidType; + +import java.util.Collections; +import java.util.List; +import java.util.function.UnaryOperator; + +import static java.util.function.UnaryOperator.identity; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +public class NextMilleniumBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "https://test-url.com/"; + + private NextMilleniumBidder nextMilleniumBidder; + + @Before + public void setUp() { + nextMilleniumBidder = new NextMilleniumBidder(ENDPOINT_URL, jacksonMapper); + } + + @Test + public void creationShouldFailOnInvalidEndpointUrl() { + assertThatIllegalArgumentException().isThrownBy(() -> new NextMilleniumBidder("invalid_url", jacksonMapper)); + } + + @Test + public void makeHttpRequestsShouldReturnCorrectResult() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(identity()); + + // when + final Result>> result = nextMilleniumBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getBody) + .containsExactly(mapper.writeValueAsBytes( + BidRequest.builder() + .id(bidRequest.getId()) + .test(bidRequest.getTest()) + .ext(ExtRequest.of(ExtRequestPrebid.builder() + .storedrequest(ExtStoredRequest.of("placement_id")) + .build())) + .build())); + } + + @Test + public void makeHttpRequestsWithProperDataShouldContainCorrectPayload() { + // given + final BidRequest bidRequest = givenBidRequest(identity()); + + // when + final Result>> result = nextMilleniumBidder.makeHttpRequests(bidRequest); + + // then + final BidRequest expectedPayload = BidRequest.builder() + .id(bidRequest.getId()) + .test(bidRequest.getTest()) + .ext(ExtRequest.of(ExtRequestPrebid.builder() + .storedrequest(ExtStoredRequest.of("placement_id")) + .build())) + .build(); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .containsExactly(expectedPayload); + } + + @Test + public void makeHttpRequestsWithInvalidImpsShouldReturnError() { + // given + final BidRequest bidRequest = givenBidRequest(impCustomizer -> impCustomizer + .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + + // when + final Result>> result = nextMilleniumBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isNotEmpty() + .allSatisfy(bidderError -> { + assertThat(bidderError.getType()).isEqualTo(BidderError.Type.bad_input); + assertThat(bidderError.getMessage()).startsWith("Cannot deserialize value of type"); + }); + } + + @Test + public void makeBidsShouldReturnBannerBidByDefault() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(identity()); + final HttpCall httpCall = givenHttpCall(bidRequest, + mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); + + // when + final Result> result = nextMilleniumBidder.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsExactly(BidderBid.of(Bid.builder().impid("123").build(), BidType.banner, "USD")); + } + + @Test + public void makeBidsWithZeroSeatBidsShouldReturnNoErrorsAndNoValues() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(identity()); + final HttpCall httpCall = givenHttpCall(bidRequest, + mapper.writeValueAsString(BidResponse.builder() + .seatbid(Collections.emptyList()) + .build())); + + // when + final Result> result = nextMilleniumBidder.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsWithUnparsableBidResponseShouldReturnError() { + // given + final BidRequest bidRequest = givenBidRequest(identity()); + final HttpCall httpCall = givenHttpCall(bidRequest, + mapper.createArrayNode().toString()); + + // when + final Result> result = nextMilleniumBidder.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isNotEmpty() + .allSatisfy(bidderError -> { + assertThat(bidderError.getType()).isEqualTo(BidderError.Type.bad_server_response); + assertThat(bidderError.getMessage()).startsWith("Failed to decode:"); + }); + + } + + private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { + return givenBidRequest(identity(), impCustomizer); + } + + private static BidRequest givenBidRequest( + UnaryOperator bidRequestCustomizer, + UnaryOperator impCustomizer) { + + return bidRequestCustomizer.apply(BidRequest.builder() + .id("1500") + .test(100) + .imp(Collections.singletonList(givenImp(impCustomizer)))) + .build(); + } + + private static Imp givenImp(UnaryOperator impCustomizer) { + return impCustomizer.apply(Imp.builder() + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpNextMillenium.of("placement_id"))))) + .build(); + } + + private static BidResponse givenBidResponse(UnaryOperator bidCustomizer) { + return BidResponse.builder() + .seatbid(Collections.singletonList(SeatBid.builder() + .bid(Collections.singletonList(bidCustomizer.apply(Bid.builder()).build())) + .build())) + .cur("USD") + .build(); + } + + private static HttpCall givenHttpCall(BidRequest bidRequest, String body) { + return HttpCall.success( + HttpRequest.builder().payload(bidRequest).build(), + HttpResponse.of(200, null, body), + null); + } +} diff --git a/src/test/java/org/prebid/server/it/NextMilleniumTest.java b/src/test/java/org/prebid/server/it/NextMilleniumTest.java new file mode 100644 index 00000000000..77e2813b73f --- /dev/null +++ b/src/test/java/org/prebid/server/it/NextMilleniumTest.java @@ -0,0 +1,38 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.prebid.server.model.Endpoint; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static java.util.Collections.singletonList; + +@RunWith(SpringRunner.class) +public class NextMilleniumTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromNextMillenium() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/nextmillenium-exchange")) + .withRequestBody(equalToJson( + jsonFrom("openrtb2/nextmillenium/test-nextmillenium-bid-request.json"))) + .willReturn(aResponse().withBody( + jsonFrom("openrtb2/nextmillenium/test-nextmillenium-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/nextmillenium/test-auction-nextmillenium-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/nextmillenium/test-auction-nextmillenium-response.json", response, + singletonList("nextmillenium")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-auction-nextmillenium-request.json b/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-auction-nextmillenium-request.json new file mode 100644 index 00000000000..f98aa54019f --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-auction-nextmillenium-request.json @@ -0,0 +1,23 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "nextmillenium": { + "placement_id": "placement_id" + } + } + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-auction-nextmillenium-response.json b/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-auction-nextmillenium-response.json new file mode 100644 index 00000000000..229af32f363 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-auction-nextmillenium-response.json @@ -0,0 +1,35 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 0.01, + "adid": "adid001", + "cid": "cid001", + "crid": "crid001", + "ext": { + "prebid": { + "type": "banner" + }, + "origbidcpm": 0.01 + } + } + ], + "seat": "nextmillenium", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "nextmillenium": "{{ nextmillenium.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-nextmillenium-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-nextmillenium-bid-request.json new file mode 100644 index 00000000000..52c381510b5 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-nextmillenium-bid-request.json @@ -0,0 +1,10 @@ +{ + "id": "request_id", + "ext": { + "prebid": { + "storedrequest": { + "id": "placement_id" + } + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-nextmillenium-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-nextmillenium-bid-response.json new file mode 100644 index 00000000000..dede1455066 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/nextmillenium/test-nextmillenium-bid-response.json @@ -0,0 +1,18 @@ +{ + "id": "tid", + "seatbid": [ + { + "bid": [ + { + "crid": "crid001", + "adid": "adid001", + "price": 0.01, + "id": "bid_id", + "impid": "imp_id", + "cid": "cid001" + } + ], + "type": "banner" + } + ] +} diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index 4db090739d6..fb5d193596a 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -159,6 +159,8 @@ adapters.mobilefuse.enabled=true adapters.mobilefuse.endpoint=http://localhost:8090/mobilefuse-exchange/ adapters.nanointeractive.enabled=true adapters.nanointeractive.endpoint=http://localhost:8090/nanointeractive-exchange/ +adapters.nextmillenium.enabled=true +adapters.nextmillenium.endpoint=http://localhost:8090/nextmillenium-exchange adapters.ninthdecimal.enabled=true adapters.ninthdecimal.endpoint=http://localhost:8090/ninthdecimal-exchange?pubid= adapters.nobid.enabled=true From 9276331956ae071630402f7e77066874aa43fddb Mon Sep 17 00:00:00 2001 From: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com> Date: Thu, 2 Dec 2021 14:35:26 +0200 Subject: [PATCH 25/26] 33Across: Enable support for SRA requests (#1604) --- .../prebid/server/bidder/ttx/TtxBidder.java | 28 ++++++++++++++----- src/main/resources/bidder-config/ttx.yaml | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/ttx/TtxBidder.java b/src/main/java/org/prebid/server/bidder/ttx/TtxBidder.java index ec295e5f517..fc4d30a901a 100644 --- a/src/main/java/org/prebid/server/bidder/ttx/TtxBidder.java +++ b/src/main/java/org/prebid/server/bidder/ttx/TtxBidder.java @@ -1,6 +1,7 @@ package org.prebid.server.bidder.ttx; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; @@ -32,7 +33,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -53,6 +56,7 @@ public TtxBidder(String endpointUrl, JacksonMapper mapper) { @Override public Result>> makeHttpRequests(BidRequest request) { final List errors = new ArrayList<>(); + final Map> impsMap = new HashMap<>(); final List> requests = new ArrayList<>(); for (Imp imp : request.getImp()) { @@ -60,22 +64,27 @@ public Result>> makeHttpRequests(BidRequest request validateImp(imp); final ExtImpTtx extImpTtx = parseImpExt(imp); final Imp updatedImp = updateImp(imp, extImpTtx); - requests.add(createRequest(request, updatedImp)); + impsMap.computeIfAbsent(computeKey(updatedImp), k -> new ArrayList<>()).add(updatedImp); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); } } + + for (List imps : impsMap.values()) { + requests.add(createRequest(request, imps)); + } + return Result.of(requests, errors); } - private void validateImp(Imp imp) { + private void validateImp(Imp imp) throws PreBidException { if (imp.getBanner() == null && imp.getVideo() == null) { throw new PreBidException( String.format("Imp ID %s must have at least one of [Banner, Video] defined", imp.getId())); } } - private ExtImpTtx parseImpExt(Imp imp) { + private ExtImpTtx parseImpExt(Imp imp) throws PreBidException { try { return mapper.mapper().convertValue(imp.getExt(), TTX_EXT_TYPE_REFERENCE).getBidder(); } catch (IllegalArgumentException e) { @@ -83,7 +92,7 @@ private ExtImpTtx parseImpExt(Imp imp) { } } - private Imp updateImp(Imp imp, ExtImpTtx extImpTtx) { + private Imp updateImp(Imp imp, ExtImpTtx extImpTtx) throws PreBidException { final String productId = extImpTtx.getProductId(); return imp.toBuilder() .video(updatedVideo(imp.getVideo(), productId)) @@ -91,7 +100,12 @@ private Imp updateImp(Imp imp, ExtImpTtx extImpTtx) { .build(); } - private static Video updatedVideo(Video video, String productId) { + private String computeKey(Imp imp) { + final JsonNode ttx = imp.getExt().get("ttx"); + return ttx.get("prod").asText() + ttx.get("zoneid").asText(); + } + + private static Video updatedVideo(Video video, String productId) throws PreBidException { if (video == null) { return null; } @@ -135,9 +149,9 @@ private ObjectNode createImpExt(String productId, String zoneId, String siteId) return mapper.mapper().valueToTree(ttxImpExt); } - private HttpRequest createRequest(BidRequest request, Imp requestImp) { + private HttpRequest createRequest(BidRequest request, List requestImps) { final BidRequest modifiedRequest = request.toBuilder() - .imp(Collections.singletonList(requestImp)) + .imp(requestImps) .build(); return HttpRequest.builder() diff --git a/src/main/resources/bidder-config/ttx.yaml b/src/main/resources/bidder-config/ttx.yaml index fbfc24299cf..7dc3142bf5b 100644 --- a/src/main/resources/bidder-config/ttx.yaml +++ b/src/main/resources/bidder-config/ttx.yaml @@ -13,7 +13,7 @@ adapters: supported-vendors: vendor-id: 58 usersync: - url: https://ic.tynt.com/r/d?m=xch&rt=html&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&id=zzz000000000002zzz&ru= + url: https://ssc-cms.33across.com/ps/?m=xch&rt=html&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&id=zzz000000000002zzz&ru= redirect-url: /setuid?bidder=33across&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&uid=33XUSERID33X cookie-family-name: 33across type: iframe From 7f27cf3c9195b45cace63daa18cf4297b0a1b60d Mon Sep 17 00:00:00 2001 From: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com> Date: Thu, 2 Dec 2021 14:37:50 +0200 Subject: [PATCH 26/26] Fix typo in redirect-url for Tripleliftnative bidder (#1608) --- src/main/resources/bidder-config/tripleliftnative.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/bidder-config/tripleliftnative.yaml b/src/main/resources/bidder-config/tripleliftnative.yaml index 1c51220f4f8..6990a0322ff 100644 --- a/src/main/resources/bidder-config/tripleliftnative.yaml +++ b/src/main/resources/bidder-config/tripleliftnative.yaml @@ -11,7 +11,7 @@ adapters: vendor-id: 28 usersync: url: https://eb2.3lift.com/getuid?gdpr={{gdpr}}&cmp_cs={{gdpr_consent}}&us_privacy={{us_privacy}}&redir= - redirect-url: /setuid?bidder=triplelift_native&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&uid=$UID" + redirect-url: /setuid?bidder=triplelift_native&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&uid=$UID cookie-family-name: triplelift_native type: redirect support-cors: false