diff --git a/docs/bidders/audienceNetwork.md b/docs/bidders/audienceNetwork.md index 6b52caadd59..e0929922204 100644 --- a/docs/bidders/audienceNetwork.md +++ b/docs/bidders/audienceNetwork.md @@ -1,5 +1,7 @@ # Audience Network Bidder +Audience Network bidder requires `placementId` attribute to be sent in the `ext` object of impressions. + ## Mobile Bids Audience Network will not bid on requests made from device simulators. diff --git a/src/main/java/com/iab/openrtb/request/Publisher.java b/src/main/java/com/iab/openrtb/request/Publisher.java index 4d05dc6c43d..55707c38068 100644 --- a/src/main/java/com/iab/openrtb/request/Publisher.java +++ b/src/main/java/com/iab/openrtb/request/Publisher.java @@ -10,7 +10,7 @@ * This object describes the publisher of the media in which the ad will be * displayed. The publisher is typically the seller in an OpenRTB transaction. */ -@Builder +@Builder(toBuilder = true) @Value public class Publisher { diff --git a/src/main/java/org/prebid/server/bidder/facebook/FacebookBidder.java b/src/main/java/org/prebid/server/bidder/facebook/FacebookBidder.java index dc07bb02586..35278fd3f44 100644 --- a/src/main/java/org/prebid/server/bidder/facebook/FacebookBidder.java +++ b/src/main/java/org/prebid/server/bidder/facebook/FacebookBidder.java @@ -1,34 +1,254 @@ package org.prebid.server.bidder.facebook; +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.Imp; +import com.iab.openrtb.request.Publisher; +import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.Video; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.json.Json; +import io.vertx.core.logging.Logger; +import io.vertx.core.logging.LoggerFactory; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.prebid.server.bidder.Bidder; +import org.prebid.server.bidder.BidderUtil; +import org.prebid.server.bidder.facebook.proto.ExtImpFacebook; +import org.prebid.server.bidder.facebook.proto.FacebookExt; 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.proto.openrtb.ext.ExtPrebid; +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.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.stream.Collectors; /** * Facebook {@link Bidder} implementation. */ -public class FacebookBidder implements Bidder { +public class FacebookBidder implements Bidder { + + private static final Logger logger = LoggerFactory.getLogger(FacebookBidder.class); + + private static final Random RANDOM = new Random(); + private static final TypeReference> FACEBOOK_EXT_TYPE_REFERENCE = new + TypeReference>() { + }; + private static final Integer LEGACY_BANNER_WIDTH = 320; + private static final Integer LEGACY_BANNER_HEIGHT = 50; + + private final String endpointUrl; + private final String nonSecureEndpointUrl; + private final ObjectNode platformJson; + + public FacebookBidder(String endpointUrl, String nonSecureEndpointUrl, String platformId) { + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.nonSecureEndpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(nonSecureEndpointUrl)); + platformJson = createPlatformJson(Objects.requireNonNull(platformId)); + } @Override - public Result> makeHttpRequests(BidRequest bidRequest) { - throw new UnsupportedOperationException(); + public Result>> makeHttpRequests(BidRequest bidRequest) { + if (CollectionUtils.isEmpty(bidRequest.getImp())) { + return Result.of(Collections.emptyList(), Collections.emptyList()); + } + + final List errors = new ArrayList<>(); + final List processedImps = new ArrayList<>(); + final String placementId; + String pubId = null; + + try { + placementId = parseExtImpFacebook(bidRequest.getImp().get(0)).getPlacementId(); + pubId = extractPubId(placementId); + + for (final Imp imp : bidRequest.getImp()) { + processedImps.add(makeImp(imp, placementId)); + } + + } catch (PreBidException e) { + errors.add(e.getMessage()); + } + + final BidRequest outgoingRequest = bidRequest.toBuilder() + .imp(processedImps) + .site(makeSite(bidRequest, pubId)) + .app(makeApp(bidRequest, pubId)) + .build(); + final String body = Json.encode(outgoingRequest); + + return Result.of(Collections.singletonList( + HttpRequest.of(HttpMethod.POST, endpointUrl(), body, BidderUtil.headers(), outgoingRequest)), + BidderUtil.errors(errors)); } @Override public Result> makeBids(HttpCall httpCall, BidRequest bidRequest) { - throw new UnsupportedOperationException(); + try { + return Result.of(extractBids(BidderUtil.parseResponse(httpCall.getResponse())), Collections.emptyList()); + } catch (PreBidException e) { + return Result.of(Collections.emptyList(), Collections.singletonList(BidderError.create(e.getMessage()))); + } } @Override public Map extractTargeting(ObjectNode ext) { return Collections.emptyMap(); } + + private static ObjectNode createPlatformJson(String platformId) { + final Integer platformIdAsInt; + try { + platformIdAsInt = Integer.valueOf(platformId); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(String.format("Platform ID is not valid number: '%s'", platformId), e); + } + return Json.mapper.valueToTree(FacebookExt.of(platformIdAsInt)); + } + + private ExtImpFacebook parseExtImpFacebook(Imp imp) { + if (imp.getExt() == null) { + throw new PreBidException("audienceNetwork parameters section is missing"); + } + + try { + return Json.mapper.>convertValue(imp.getExt(), FACEBOOK_EXT_TYPE_REFERENCE) + .getBidder(); + } catch (IllegalArgumentException e) { + logger.warn("Error occurred parsing audienceNetwork parameters", e); + throw new PreBidException(e.getMessage(), e); + } + } + + private String extractPubId(String placementId) { + if (StringUtils.isBlank(placementId)) { + throw new PreBidException("Missing placementId param"); + } + + final String[] placementIdSplit = placementId.split("_"); + if (placementIdSplit.length != 2) { + throw new PreBidException(String.format("Invalid placementId param '%s'", placementId)); + } + return placementIdSplit[0]; + } + + private Imp makeImp(Imp imp, String placementId) { + validateMediaImpMediaTypes(imp); + return imp.toBuilder() + .tagid(placementId) + .banner(makeBanner(imp)) + .ext(platformJson) + .build(); + } + + private static Site makeSite(BidRequest bidRequest, String pubId) { + final Site site = bidRequest.getSite(); + if (site == null) { + return null; + } + + final Publisher publisher = site.getPublisher(); + if (publisher != null && StringUtils.isNotBlank(publisher.getId())) { + return site; + } + + return site.toBuilder() + .publisher(makePublisher(publisher, pubId)) + .build(); + } + + private static App makeApp(BidRequest bidRequest, String pubId) { + final App app = bidRequest.getApp(); + if (app == null) { + return null; + } + + final Publisher publisher = app.getPublisher(); + if (publisher != null && StringUtils.isNotBlank(publisher.getId())) { + return app; + } + + return app.toBuilder() + .publisher(makePublisher(publisher, pubId)) + .build(); + } + + private static Publisher makePublisher(Publisher publisher, String pubId) { + final Publisher.PublisherBuilder publisherBuilder = publisher == null + ? Publisher.builder() + : publisher.toBuilder(); + return publisherBuilder + .id(pubId) + .build(); + } + + private static void validateMediaImpMediaTypes(Imp imp) { + if (imp.getXNative() != null || imp.getAudio() != null) { + throw new PreBidException( + String.format("audienceNetwork doesn't support native or audio Imps. Ignoring Imp ID=%s", + imp.getId())); + } + + final Video video = imp.getVideo(); + if (imp.getVideo() != null) { + if (CollectionUtils.isEmpty(video.getMimes())) { + throw new PreBidException("audienceNetwork doesn't support video type with no video data"); + } + } + } + + private static Banner makeBanner(Imp imp) { + final Banner banner = imp.getBanner(); + if (banner == null) { + return null; + } + + final Integer h = banner.getH(); + final Integer w = banner.getW(); + + if (Objects.equals(w, LEGACY_BANNER_WIDTH) && Objects.equals(h, LEGACY_BANNER_HEIGHT)) { + // do not send legacy 320x50 size to facebook, instead use 0x50 + return banner.toBuilder().w(0).build(); + } + return banner; + } + + private String endpointUrl() { + // 50% of traffic to non-secure endpoint + return RANDOM.nextBoolean() ? endpointUrl : nonSecureEndpointUrl; + } + + private static List extractBids(BidResponse bidResponse) { + return bidResponse == null || bidResponse.getSeatbid() == null + ? Collections.emptyList() + : bidsFromResponse(bidResponse); + } + + 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)) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/org/prebid/server/bidder/facebook/proto/ExtImpFacebook.java b/src/main/java/org/prebid/server/bidder/facebook/proto/ExtImpFacebook.java new file mode 100644 index 00000000000..1fc81425393 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/facebook/proto/ExtImpFacebook.java @@ -0,0 +1,13 @@ +package org.prebid.server.bidder.facebook.proto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Value; + +@Value +@AllArgsConstructor(staticName = "of") +public class ExtImpFacebook { + + @JsonProperty("placementId") + String placementId; +} 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 9c4d4b8447f..abc212705af 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 @@ -1,15 +1,13 @@ package org.prebid.server.spring.config.bidder; import io.vertx.core.http.HttpClient; -import io.vertx.core.logging.Logger; -import io.vertx.core.logging.LoggerFactory; import org.apache.commons.lang3.StringUtils; import org.prebid.server.bidder.Adapter; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.BidderDeps; import org.prebid.server.bidder.BidderRequester; import org.prebid.server.bidder.HttpAdapterConnector; -import org.prebid.server.bidder.HttpAdapterRequester; +import org.prebid.server.bidder.HttpBidderRequester; import org.prebid.server.bidder.MetaInfo; import org.prebid.server.bidder.Usersyncer; import org.prebid.server.bidder.facebook.FacebookAdapter; @@ -25,8 +23,6 @@ @Configuration public class FacebookConfiguration extends BidderConfiguration { - private static final Logger logger = LoggerFactory.getLogger(FacebookConfiguration.class); - private static final String BIDDER_NAME = "audienceNetwork"; @Value("${adapters.facebook.enabled}") @@ -72,7 +68,7 @@ protected Usersyncer createUsersyncer() { @Override protected Bidder createBidder(MetaInfo metaInfo) { - return new FacebookBidder(); + return new FacebookBidder(endpoint, nonSecureEndpoint, platformId); } @Override @@ -83,6 +79,6 @@ protected Bidder createBidder(MetaInfo metaInfo) { @Override protected BidderRequester createBidderRequester(HttpClient httpClient, Bidder bidder, Adapter adapter, Usersyncer usersyncer, HttpAdapterConnector httpAdapterConnector) { - return new HttpAdapterRequester(BIDDER_NAME, adapter, usersyncer, httpAdapterConnector); + return new HttpBidderRequester<>(bidder, httpClient); } } 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 944558ac1d2..74e830cffba 100644 --- a/src/test/java/org/prebid/server/bidder/appnexus/AppnexusBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/appnexus/AppnexusBidderTest.java @@ -85,7 +85,7 @@ public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestContainsNotSupporte .flatExtracting(BidRequest::getImp) .isEmpty(); assertThat(result.getErrors()).hasSize(1) - .element(0).extracting(BidderError::getMessage) + .extracting(BidderError::getMessage) .containsExactly("Appnexus doesn't support audio Imps. Ignoring Imp ID=23"); } @@ -532,7 +532,7 @@ public void makeBidsShouldReturnErrorIfBidTypeValueFromResponseIsNotValid() thro final Result> result = appnexusBidder.makeBids(httpCall, bidRequest); // then - assertThat(result.getErrors()).hasSize(1).element(0) + assertThat(result.getErrors()).hasSize(1) .extracting(BidderError::getMessage) .containsOnly("Unrecognized bid_ad_type in response from appnexus: 42"); assertThat(result.getValue()).isEmpty(); @@ -554,7 +554,7 @@ public void makeBidsShouldReturnErrorIfBidExtNotDefined() throws IOException { final Result> result = appnexusBidder.makeBids(httpCall, bidRequest); // then - assertThat(result.getErrors()).hasSize(1).element(0) + assertThat(result.getErrors()).hasSize(1) .extracting(BidderError::getMessage) .containsOnly("bidResponse.bid.ext should be defined for appnexus"); assertThat(result.getValue()).isEmpty(); @@ -576,7 +576,7 @@ public void makeBidsShouldReturnErrorIfBidExtAppnexusNotDefined() throws IOExcep final Result> result = appnexusBidder.makeBids(httpCall, bidRequest); // then - assertThat(result.getErrors()).hasSize(1).element(0) + assertThat(result.getErrors()).hasSize(1) .extracting(BidderError::getMessage) .containsOnly("bidResponse.bid.ext.appnexus should be defined"); assertThat(result.getValue()).isEmpty(); @@ -598,7 +598,7 @@ public void makeBidsShouldReturnErrorIfBidExtAppnexusBidTypeNotDefined() throws final Result> result = appnexusBidder.makeBids(httpCall, bidRequest); // then - assertThat(result.getErrors()).hasSize(1).element(0) + assertThat(result.getErrors()).hasSize(1) .extracting(BidderError::getMessage) .containsOnly("bidResponse.bid.ext.appnexus.bid_ad_type should be defined"); assertThat(result.getValue()).isEmpty(); diff --git a/src/test/java/org/prebid/server/bidder/facebook/FacebookBidderTest.java b/src/test/java/org/prebid/server/bidder/facebook/FacebookBidderTest.java new file mode 100644 index 00000000000..554c61e7805 --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/facebook/FacebookBidderTest.java @@ -0,0 +1,482 @@ +package org.prebid.server.bidder.facebook; + +import com.fasterxml.jackson.core.JsonProcessingException; +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.Imp; +import com.iab.openrtb.request.Native; +import com.iab.openrtb.request.Publisher; +import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.Video; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import org.junit.Before; +import org.junit.Test; +import org.prebid.server.VertxTest; +import org.prebid.server.bidder.facebook.proto.ExtImpFacebook; +import org.prebid.server.bidder.facebook.proto.FacebookExt; +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.response.BidType; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.*; + +public class FacebookBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "https://facebook.com/openrtb2d"; + private static final String NON_SECURED_ENDPOINT_URL = "http://facebook.com/openrtb2d"; + private static final String PLATFORM_ID = "101"; + + private FacebookBidder facebookBidder; + + @Before + public void setUp() { + facebookBidder = new FacebookBidder(ENDPOINT_URL, NON_SECURED_ENDPOINT_URL, PLATFORM_ID); + } + + @Test + public void creationShouldFailOnNullArguments() { + assertThatNullPointerException().isThrownBy( + () -> new FacebookBidder(null, NON_SECURED_ENDPOINT_URL, PLATFORM_ID)); + assertThatNullPointerException().isThrownBy( + () -> new FacebookBidder(ENDPOINT_URL, null, PLATFORM_ID)); + assertThatNullPointerException().isThrownBy( + () -> new FacebookBidder(ENDPOINT_URL, NON_SECURED_ENDPOINT_URL, null)); + } + + @Test + public void creationShouldFailOnInvalidEndpoints() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new FacebookBidder("invalid_url", NON_SECURED_ENDPOINT_URL, PLATFORM_ID)) + .withMessage("URL supplied is not valid: invalid_url"); + + assertThatIllegalArgumentException() + .isThrownBy(() -> new FacebookBidder(ENDPOINT_URL, "invalid_url", PLATFORM_ID)) + .withMessage("URL supplied is not valid: invalid_url"); + } + + @Test + public void creationShouldFailOnInvalidPlatformId() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new FacebookBidder(ENDPOINT_URL, NON_SECURED_ENDPOINT_URL, "non-number")) + .withMessage("Platform ID is not valid number: 'non-number'"); + } + + @Test + public void makeHttpRequestsShouldReturnRequestsWithExpectedHeaders() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder().build())) + .build(); + + // when + final Result>> result = facebookBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).hasSize(1) + .flatExtracting(r -> r.getHeaders().entries()) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly(tuple("Content-Type", "application/json;charset=utf-8"), + tuple("Accept", "application/json")); + } + + @Test + public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestContainsNotSupportedAudioMediaType() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .audio(Audio.builder().build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpFacebook.of("pub1_place1")))) + .build())) + .build(); + + // when + final Result>> result = facebookBidder.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) + .extracting(BidderError::getMessage) + .containsExactly("audienceNetwork doesn't support native or audio Imps. Ignoring Imp ID=impId"); + } + + @Test + public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestContainsNotSupportedNativeMediaType() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .xNative(Native.builder().build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpFacebook.of("pub1_place1")))) + .build())) + .build(); + + // when + final Result>> result = facebookBidder.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) + .extracting(BidderError::getMessage) + .containsExactly("audienceNetwork doesn't support native or audio Imps. Ignoring Imp ID=impId"); + } + + @Test + public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestContainsInvalidVideoCreative() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .video(Video.builder().build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpFacebook.of("pub1_place1")))) + .build())) + .build(); + + // when + final Result>> result = facebookBidder.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) + .extracting(BidderError::getMessage) + .containsExactly("audienceNetwork doesn't support video type with no video data"); + } + + @Test + public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestDoesNotContainImpExt() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .banner(Banner.builder() + .w(0) + .h(50) + .build()) + .build())) + .build(); + + // when + final Result>> result = facebookBidder.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) + .extracting(BidderError::getMessage) + .containsExactly("audienceNetwork parameters section is missing"); + } + + @Test + public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestImpExtFacebookPlacementIdEmpty() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .banner(Banner.builder() + .w(0) + .h(50) + .build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpFacebook.of("")))) + .build())) + .build(); + + // when + final Result>> result = facebookBidder.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) + .extracting(BidderError::getMessage) + .containsExactly("Missing placementId param"); + } + + @Test + public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestImpExtFacebookPlacementIdNull() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .banner(Banner.builder() + .w(0) + .h(50) + .build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpFacebook.of(null)))) + .build())) + .build(); + + // when + final Result>> result = facebookBidder.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) + .extracting(BidderError::getMessage) + .containsExactly("Missing placementId param"); + } + + @Test + public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestImpExtFacebookPlacementIdIsMalformed() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .banner(Banner.builder() + .w(0) + .h(50) + .build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpFacebook.of("~malformed")))) + .build())) + .build(); + + // when + final Result>> result = facebookBidder.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) + .extracting(BidderError::getMessage) + .containsExactly("Invalid placementId param '~malformed'"); + } + + @Test + public void makeHttpRequestsShouldReturnResultWithHttpRequestContainingExpectedFieldsInBidRequestWithSite() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .banner(Banner.builder() + .w(0) + .h(50) + .build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpFacebook.of("pub1_placement1")))) + .build())) + .site(Site.builder() + .publisher(Publisher.builder().build()) + .build()) + .build(); + + // when + final Result>> result = facebookBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getBody) + .extracting(body -> mapper.readValue(body, BidRequest.class)) + .containsExactly(BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .ext(mapper.valueToTree(FacebookExt.of(101))) + .banner(Banner.builder() + .w(0) + .h(50) + .build()) + .tagid("pub1_placement1") + .build())) + .site(Site.builder() + .publisher(Publisher.builder() + .id("pub1") + .build()) + .build()) + .build()); + } + + @Test + public void makeHttpRequestsShouldReturnResultWithHttpRequestContainingExpectedFieldsInBidRequestWithApp() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .banner(Banner.builder() + .w(0) + .h(50) + .build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpFacebook.of("pub1_placement1")))) + .build())) + .app(App.builder() + .publisher(Publisher.builder().build()) + .build()) + .build(); + + // when + final Result>> result = facebookBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getBody) + .extracting(body -> mapper.readValue(body, BidRequest.class)) + .containsExactly(BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .ext(mapper.valueToTree(FacebookExt.of(101))) + .banner(Banner.builder() + .w(0) + .h(50) + .build()) + .tagid("pub1_placement1") + .build())) + .app(App.builder() + .publisher(Publisher.builder() + .id("pub1") + .build()) + .build()) + .build()); + } + + @Test + public void makeHttpRequestsShouldReturnResultWithRequestContainingExpectedFieldsAndUpdatedLegacyBannerSizes() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .banner(Banner.builder() + .w(320) + .h(50) + .build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpFacebook.of("pub1_placement1")))) + .build())) + .app(App.builder() + .publisher(Publisher.builder().build()) + .build()) + .build(); + + // when + final Result>> result = facebookBidder.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getBody) + .extracting(body -> mapper.readValue(body, BidRequest.class)) + .containsExactly(BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("impId") + .ext(mapper.valueToTree(FacebookExt.of(101))) + .banner(Banner.builder() + .w(0) + .h(50) + .build()) + .tagid("pub1_placement1") + .build())) + .app(App.builder() + .publisher(Publisher.builder() + .id("pub1") + .build()) + .build()) + .build()); + } + + @Test + public void makeBidsShouldReturnEmptyResultIfResponseStatusIs204() { + // given + final HttpCall httpCall = givenHttpCall(204, null); + + // when + final Result> result = facebookBidder.makeBids(httpCall, BidRequest.builder().build()); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnErrorIfResponseStatusIsNot200Or204() { + // given + final HttpCall httpCall = givenHttpCall(302, null); + + // when + final Result> result = facebookBidder.makeBids(httpCall, BidRequest.builder().build()); + + // then + assertThat(result.getErrors()) + .extracting(BidderError::getMessage) + .containsOnly("Unexpected status code: 302. Run with request.test = 1 for more info"); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { + // given + final HttpCall httpCall = givenHttpCall(200, "invalid"); + + // when + final Result> result = facebookBidder.makeBids(httpCall, BidRequest.builder().build()); + + // then + assertThat(result.getErrors()).hasSize(1).extracting(BidderError::getMessage).containsOnly( + "Unrecognized token 'invalid': was expecting ('true', 'false' or 'null')\n" + + " at [Source: (String)\"invalid\"; line: 1, column: 15]"); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnResultWithExpectedFields() throws JsonProcessingException { + // given + final HttpCall httpCall = givenHttpCall(200, mapper.writeValueAsString(BidResponse.builder() + .seatbid(singletonList(SeatBid.builder() + .bid(singletonList(Bid.builder() + .w(200) + .h(150) + .price(BigDecimal.ONE) + .impid("impid") + .dealid("dealid") + .adm("
This is an Ad
") + .build())) + .build())) + .build())); + + // when + final Result> result = facebookBidder.makeBids(httpCall, BidRequest.builder().build()); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .containsOnly(BidderBid.of( + Bid.builder() + .impid("impid") + .price(BigDecimal.ONE) + .dealid("dealid") + .w(200) + .h(150) + .adm("
This is an Ad
") + .build(), + BidType.banner)); + } + + private static HttpCall givenHttpCall(int statusCode, String body) { + return HttpCall.full(null, HttpResponse.of(statusCode, null, body), null); + } + +} \ No newline at end of file diff --git a/src/test/java/org/prebid/server/bidder/sovrn/SovrnBidderTest.java b/src/test/java/org/prebid/server/bidder/sovrn/SovrnBidderTest.java index 927d197ba80..5696015c2c0 100644 --- a/src/test/java/org/prebid/server/bidder/sovrn/SovrnBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/sovrn/SovrnBidderTest.java @@ -64,7 +64,7 @@ public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestContainsNotSupporte .flatExtracting(BidRequest::getImp) .isEmpty(); assertThat(result.getErrors()).hasSize(1) - .element(0).extracting(BidderError::getMessage) + .extracting(BidderError::getMessage) .containsExactly("Sovrn doesn't support audio, video, or native Imps. Ignoring Imp ID=impId"); } @@ -85,7 +85,7 @@ public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestContainsNotSupporte .flatExtracting(BidRequest::getImp) .isEmpty(); assertThat(result.getErrors()).hasSize(1) - .element(0).extracting(BidderError::getMessage) + .extracting(BidderError::getMessage) .containsExactly("Sovrn doesn't support audio, video, or native Imps. Ignoring Imp ID=impId"); } @@ -106,7 +106,7 @@ public void makeHttpRequestsShouldSkipImpAndAddErrorIfRequestContainsNotSupporte .flatExtracting(BidRequest::getImp) .isEmpty(); assertThat(result.getErrors()).hasSize(1) - .element(0).extracting(BidderError::getMessage) + .extracting(BidderError::getMessage) .containsExactly("Sovrn doesn't support audio, video, or native Imps. Ignoring Imp ID=23"); } @@ -126,7 +126,7 @@ public void makeHttpRequestsShouldReturnResultWithErrorSayingAboutMissingSovrnPa .flatExtracting(BidRequest::getImp) .isEmpty(); assertThat(result.getErrors()).hasSize(1) - .element(0).extracting(BidderError::getMessage) + .extracting(BidderError::getMessage) .containsExactly("Sovrn parameters section is missing"); } diff --git a/src/test/java/org/prebid/server/validation/BidderParamValidatorTest.java b/src/test/java/org/prebid/server/validation/BidderParamValidatorTest.java index 50b102122a2..e078ade1253 100644 --- a/src/test/java/org/prebid/server/validation/BidderParamValidatorTest.java +++ b/src/test/java/org/prebid/server/validation/BidderParamValidatorTest.java @@ -9,6 +9,7 @@ import org.mockito.junit.MockitoRule; import org.prebid.server.VertxTest; import org.prebid.server.bidder.BidderCatalog; +import org.prebid.server.bidder.facebook.proto.ExtImpFacebook; import org.prebid.server.bidder.sovrn.proto.ExtImpSovrn; import org.prebid.server.proto.openrtb.ext.request.adform.ExtImpAdform; import org.prebid.server.proto.openrtb.ext.request.adtelligent.ExtImpAdtelligent; @@ -31,6 +32,7 @@ public class BidderParamValidatorTest extends VertxTest { private static final String ADFORM = "adform"; private static final String SOVRN = "sovrn"; private static final String ADTELLIGENT = "adtelligent"; + private static final String FACEBOOK = "audienceNetwork"; @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); @@ -42,7 +44,8 @@ public class BidderParamValidatorTest extends VertxTest { @Before public void setUp() { - given(bidderCatalog.names()).willReturn(new HashSet<>(asList(RUBICON, APPNEXUS, ADFORM, SOVRN, ADTELLIGENT))); + given(bidderCatalog.names()).willReturn(new HashSet<>( + asList(RUBICON, APPNEXUS, ADFORM, SOVRN, ADTELLIGENT, FACEBOOK))); bidderParamValidator = BidderParamValidator.create(bidderCatalog, "static/bidder-params"); } @@ -166,7 +169,7 @@ public void validateShouldNotReturnValidationMessagesWhenSovrnImpExtIsOk() { } @Test - public void validateShouldReturnValidationMessagesWhenAdformSovrnExtNotValid() { + public void validateShouldReturnValidationMessagesWhenSovrnExtNotValid() { // given final JsonNode node = mapper.createObjectNode(); @@ -203,6 +206,32 @@ public void validateShouldReturnValidationMessagesWhenAdtelligentImpExtNotValid( assertThat(messages.size()).isEqualTo(1); } + @Test + public void validateShouldNotReturnValidationMessagesWhenFacebookImpExtIsOk() { + // given + final ExtImpFacebook ext = ExtImpFacebook.of("placementId"); + + final JsonNode node = mapper.convertValue(ext, JsonNode.class); + + // when + final Set messages = bidderParamValidator.validate(FACEBOOK, node); + + // then + assertThat(messages).isEmpty(); + } + + @Test + public void validateShouldReturnValidationMessagesWhenFacebookExtNotValid() { + // given + final JsonNode node = mapper.createObjectNode(); + + // when + final Set messages = bidderParamValidator.validate(FACEBOOK, node); + + // then + assertThat(messages.size()).isEqualTo(1); + } + @Test public void schemaShouldReturnSchemasString() throws IOException { // given diff --git a/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-auction-request.json b/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-auction-request.json index 8afef1bacdb..23f7f875173 100644 --- a/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-auction-request.json +++ b/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-auction-request.json @@ -83,17 +83,16 @@ "banner": { "format": [ { - "w": 300, - "h": 600 + "w": 320, + "h": 50 } ], - "w": 300, - "h": 600 + "w": 320, + "h": 50 }, "ext": { "audienceNetwork": { - "placementId": "pub1_place1", - "site_id": "siteId" + "placementId": "pub1_place1" } } }, diff --git a/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-auction-response.json b/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-auction-response.json index 94f01f25fa1..258808344b0 100644 --- a/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-auction-response.json +++ b/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-auction-response.json @@ -4,35 +4,75 @@ { "bid": [ { - "id": "620160380", - "impid": "impId14", + "id": "880290288", + "impid": "impId1", "price": 8.43, - "adm": "adm14", - "crid": "crid14", - "dealid": "dealId14", + "adm": "adm1", + "crid": "crid1", + "dealid": "dealId1", "w": 300, "h": 250, "ext": { "prebid": { - "type": "banner", + "type": "video", "targeting": { "hb_pb": "8.40", - "hb_bidder_adtelligent": "adtelligent", - "hb_size_adtelligent": "300x250", + "hb_pb_rubicon": "8.40", + "hb_cache_id_rubicon": "92c39b22-48d8-43e6-bd79-ffea57ce0196", + "hb_deal_rubicon": "dealId1", "hb_size": "300x250", - "hb_pb_adtelligent": "8.40", - "hb_bidder": "adtelligent", - "hb_cache_id": "117431c9-807a-41e1-82a7-dcd8f8875493", - "hb_cache_id_adtelligent": "117431c9-807a-41e1-82a7-dcd8f8875493", + "hb_bidder": "rubicon", + "hb_size_rubicon": "300x250", + "hb_bidder_rubicon": "rubicon", + "hb_cache_id": "92c39b22-48d8-43e6-bd79-ffea57ce0196", "hb_creative_loadtype": "html", - "hb_deal_adtelligent": "dealId14", - "hb_deal": "dealId14" + "hb_deal": "dealId1" + } + }, + "bidder": { + "rp": { + "targeting": [ + { + "key": "rpfl_1001", + "values": [ + "2_tier0100" + ] + } + ] + } + } + } + }, + { + "id": "466223845", + "impid": "impId2", + "price": 4.26, + "adm": "adm2", + "crid": "crid2", + "dealid": "dealId2", + "w": 300, + "h": 600, + "ext": { + "prebid": { + "type": "banner", + "targeting": { + "hb_pb": "4.20", + "hb_pb_rubicon": "4.20", + "hb_cache_id_rubicon": "e3c40044-003e-4d90-927b-7be6b622cd6f", + "hb_deal_rubicon": "dealId2", + "hb_size": "300x600", + "hb_bidder": "rubicon", + "hb_size_rubicon": "300x600", + "hb_bidder_rubicon": "rubicon", + "hb_cache_id": "e3c40044-003e-4d90-927b-7be6b622cd6f", + "hb_creative_loadtype": "html", + "hb_deal": "dealId2" } } } } ], - "seat": "adtelligent", + "seat": "rubicon", "group": 0 }, { @@ -72,25 +112,111 @@ { "bid": [ { - "id": "impId5", + "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": { + "prebid": { + "type": "banner", + "targeting": { + "hb_pb": "5.50", + "hb_pb_appnexus": "5.50", + "hb_size": "300x250", + "hb_bidder_appnexus": "appnexus", + "hb_bidder": "appnexus", + "hb_cache_id": "e81ad329-bf26-4f03-b249-5916abf8daa3", + "hb_creative_loadtype": "html", + "hb_size_appnexus": "300x250", + "hb_cache_id_appnexus": "e81ad329-bf26-4f03-b249-5916abf8daa3" + } + }, + "bidder": { + "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, + "adm": "{\"assets\":[{\"id\":0,\"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\":1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"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": [ + "IAB3-1" + ], + "ext": { + "prebid": { + "type": "native", + "targeting": { + "hb_pb": "1.00", + "hb_pb_appnexus": "1.00", + "hb_bidder_appnexus": "appnexus", + "hb_bidder": "appnexus", + "hb_cache_id": "e3c415a7-700e-4d90-927b-7be6b622cd6f", + "hb_creative_loadtype": "html", + "hb_cache_id_appnexus": "e3c415a7-700e-4d90-927b-7be6b622cd6f" + } + }, + "bidder": { + "appnexus": { + "brand_id": 350, + "auction_id": 5607483846416358664, + "bidder_id": 2, + "bid_ad_type": 3 + } + } + } + } + ], + "seat": "appnexus", + "group": 0 + }, + { + "bid": [ + { + "id": "347088686", "impid": "impId5", "price": 9.0, "adm": "adm5", - "w": 300, - "h": 600, + "crid": "crid5", + "dealid": "dealId5", + "w": 0, + "h": 50, "ext": { "prebid": { "type": "banner", "targeting": { "hb_pb": "9.00", "hb_bidder_audienceNetwork": "audienceNetwork", - "hb_size": "300x600", - "hb_cache_id_audienceNetwork": "e3c415a7-700e-4d90-927b-7be6b622cd6f", + "hb_deal_audienceNetwork": "dealId5", + "hb_cache_id_audienceNetwork": "a93c0531-d0e9-43f8-9b98-b60116c8dc84", "hb_bidder": "audienceNetwork", - "hb_size_audienceNetwork": "300x600", - "hb_cache_id": "e3c415a7-700e-4d90-927b-7be6b622cd6f", + "hb_cache_id": "a93c0531-d0e9-43f8-9b98-b60116c8dc84", "hb_pb_audienceNetwork": "9.00", - "hb_creative_loadtype": "demand_sdk" + "hb_creative_loadtype": "demand_sdk", + "hb_deal": "dealId5" } } } @@ -99,6 +225,31 @@ "seat": "audienceNetwork", "group": 0 }, + { + "bid": [ + { + "id": "impId4", + "impid": "impId4", + "price": 5.0, + "adm": "adm4", + "crid": "crid4", + "w": 300, + "h": 600, + "ext": { + "prebid": { + "type": "banner", + "targeting": { + "hb_bidder_conversantAlias": "conversantAlias", + "hb_pb_conversantAlias": "5.00", + "hb_size_conversantAlias": "300x600" + } + } + } + } + ], + "seat": "conversantAlias", + "group": 0 + }, { "bid": [ { @@ -114,12 +265,12 @@ "type": "banner", "targeting": { "hb_pb": "6.00", - "hb_cache_id_conversant": "81bc3e9d-5b73-4b34-9626-59f352514167", + "hb_cache_id_conversant": "683fe79f-6df7-4971-ac70-820e0486992d", "hb_size": "300x600", "hb_pb_conversant": "6.00", "hb_bidder_conversant": "conversant", "hb_bidder": "conversant", - "hb_cache_id": "81bc3e9d-5b73-4b34-9626-59f352514167", + "hb_cache_id": "683fe79f-6df7-4971-ac70-820e0486992d", "hb_creative_loadtype": "html", "hb_size_conversant": "300x600" } @@ -158,140 +309,35 @@ { "bid": [ { - "id": "impId12", - "impid": "impId12", - "price": 10.5, - "adm": "banner", - "w": 400, - "h": 300, - "ext": { - "prebid": { - "type": "banner", - "targeting": { - "hb_pb": "10.50", - "hb_size_adform": "400x300", - "hb_size": "400x300", - "hb_bidder": "adform", - "hb_cache_id_adform": "a93c0531-d0e9-43f8-9b98-b60116c8dc84", - "hb_cache_id": "a93c0531-d0e9-43f8-9b98-b60116c8dc84", - "hb_bidder_adform": "adform", - "hb_creative_loadtype": "html", - "hb_pb_adform": "10.50" - } - } - } - } - ], - "seat": "adform", - "group": 0 - }, - { - "bid": [ - { - "id": "impId4", - "impid": "impId4", - "price": 5.0, - "adm": "adm4", - "crid": "crid4", - "w": 300, - "h": 600, - "ext": { - "prebid": { - "type": "banner", - "targeting": { - "hb_bidder_conversantAlias": "conversantAlias", - "hb_pb_conversantAlias": "5.00", - "hb_size_conversantAlias": "300x600" - } - } - } - } - ], - "seat": "conversantAlias", - "group": 0 - }, - { - "bid": [ - { - "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", + "id": "620160380", + "impid": "impId14", + "price": 8.43, + "adm": "adm14", + "crid": "crid14", + "dealid": "dealId14", "w": 300, "h": 250, "ext": { "prebid": { "type": "banner", "targeting": { - "hb_pb": "5.50", - "hb_pb_appnexus": "5.50", + "hb_pb": "8.40", + "hb_bidder_adtelligent": "adtelligent", + "hb_size_adtelligent": "300x250", "hb_size": "300x250", - "hb_bidder_appnexus": "appnexus", - "hb_bidder": "appnexus", - "hb_cache_id": "e81ad329-bf26-4f03-b249-5916abf8daa3", - "hb_creative_loadtype": "html", - "hb_size_appnexus": "300x250", - "hb_cache_id_appnexus": "e81ad329-bf26-4f03-b249-5916abf8daa3" - } - }, - "bidder": { - "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, - "adm": "{\"assets\":[{\"id\":0,\"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\":1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"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": [ - "IAB3-1" - ], - "ext": { - "prebid": { - "type": "native", - "targeting": { - "hb_pb": "1.00", - "hb_pb_appnexus": "1.00", - "hb_bidder_appnexus": "appnexus", - "hb_bidder": "appnexus", - "hb_cache_id": "683fe79f-6df7-4971-ac70-820e0486992d", + "hb_pb_adtelligent": "8.40", + "hb_bidder": "adtelligent", + "hb_cache_id": "117431c9-807a-41e1-82a7-dcd8f8875493", + "hb_cache_id_adtelligent": "117431c9-807a-41e1-82a7-dcd8f8875493", "hb_creative_loadtype": "html", - "hb_cache_id_appnexus": "683fe79f-6df7-4971-ac70-820e0486992d" - } - }, - "bidder": { - "appnexus": { - "brand_id": 350, - "auction_id": 5607483846416358664, - "bidder_id": 2, - "bid_ad_type": 3 + "hb_deal_adtelligent": "dealId14", + "hb_deal": "dealId14" } } } } ], - "seat": "appnexus", + "seat": "adtelligent", "group": 0 }, { @@ -337,75 +383,31 @@ { "bid": [ { - "id": "880290288", - "impid": "impId1", - "price": 8.43, - "adm": "adm1", - "crid": "crid1", - "dealid": "dealId1", - "w": 300, - "h": 250, - "ext": { - "prebid": { - "type": "video", - "targeting": { - "hb_pb": "8.40", - "hb_pb_rubicon": "8.40", - "hb_cache_id_rubicon": "92c39b22-48d8-43e6-bd79-ffea57ce0196", - "hb_deal_rubicon": "dealId1", - "hb_size": "300x250", - "hb_bidder": "rubicon", - "hb_size_rubicon": "300x250", - "hb_bidder_rubicon": "rubicon", - "hb_cache_id": "92c39b22-48d8-43e6-bd79-ffea57ce0196", - "hb_creative_loadtype": "html", - "hb_deal": "dealId1" - } - }, - "bidder": { - "rp": { - "targeting": [ - { - "key": "rpfl_1001", - "values": [ - "2_tier0100" - ] - } - ] - } - } - } - }, - { - "id": "466223845", - "impid": "impId2", - "price": 4.26, - "adm": "adm2", - "crid": "crid2", - "dealid": "dealId2", - "w": 300, - "h": 600, + "id": "impId12", + "impid": "impId12", + "price": 10.5, + "adm": "banner", + "w": 400, + "h": 300, "ext": { "prebid": { "type": "banner", "targeting": { - "hb_pb": "4.20", - "hb_pb_rubicon": "4.20", - "hb_cache_id_rubicon": "e3c40044-003e-4d90-927b-7be6b622cd6f", - "hb_deal_rubicon": "dealId2", - "hb_size": "300x600", - "hb_bidder": "rubicon", - "hb_size_rubicon": "300x600", - "hb_bidder_rubicon": "rubicon", - "hb_cache_id": "e3c40044-003e-4d90-927b-7be6b622cd6f", + "hb_pb": "10.50", + "hb_size_adform": "400x300", + "hb_size": "400x300", + "hb_bidder": "adform", + "hb_cache_id_adform": "81bc3e9d-5b73-4b34-9626-59f352514167", + "hb_cache_id": "81bc3e9d-5b73-4b34-9626-59f352514167", + "hb_bidder_adform": "adform", "hb_creative_loadtype": "html", - "hb_deal": "dealId2" + "hb_pb_adform": "10.50" } } } } ], - "seat": "rubicon", + "seat": "adform", "group": 0 } ], @@ -460,8 +462,8 @@ "audienceNetwork": [ { "uri": "{{ audienceNetwork.exchange_uri }}", - "requestbody": "{\"id\":\"tid\",\"imp\":[{\"id\":\"impId5\",\"banner\":{\"format\":[{\"w\":300,\"h\":600}],\"w\":300,\"h\":600,\"topframe\":0},\"tagid\":\"pub1_place1\"}],\"site\":{\"domain\":\"example.com\",\"page\":\"http://www.example.com\",\"publisher\":{\"id\":\"pub1\"}},\"device\":{\"dnt\":2,\"pxratio\":4.2,\"language\":\"en\",\"ifa\":\"ifaId\"},\"user\":{\"buyeruid\":\"FB-UID\"},\"at\":1,\"tmax\":1000,\"source\":{\"fd\":1,\"tid\":\"tid\"},\"ext\":{\"platformid\":101}}", - "responsebody": "{\"id\":\"bidResponseId5\",\"seatbid\":[{\"bid\":[{\"id\":\"347088686\",\"impid\":\"impId5\",\"price\":9.0,\"adm\":\"adm5\",\"crid\":\"crid5\",\"dealid\":\"dealId5\",\"w\":300,\"h\":250}],\"seat\":\"seatId5\",\"group\":0}]}", + "requestbody": "{\"id\":\"tid\",\"imp\":[{\"id\":\"impId5\",\"banner\":{\"format\":[{\"w\":320,\"h\":50}],\"w\":0,\"h\":50},\"tagid\":\"pub1_place1\",\"ext\":{\"platformid\":101}}],\"site\":{\"domain\":\"example.com\",\"page\":\"http:\/\/www.example.com\",\"publisher\":{\"id\":\"publisherId\"}},\"device\":{\"ua\":\"userAgent\",\"dnt\":2,\"ip\":\"192.168.244.1\",\"pxratio\":4.2,\"language\":\"en\",\"ifa\":\"ifaId\"},\"user\":{\"buyeruid\":\"FB-UID\",\"ext\":{\"digitrust\":{\"id\":\"id\",\"keyv\":123,\"pref\":0}}},\"test\":1,\"at\":1,\"tmax\":1000,\"source\":{\"fd\":1,\"tid\":\"tid\"},\"ext\":{\"prebid\":{\"aliases\":{\"appnexusAlias\":\"appnexus\",\"conversantAlias\":\"conversant\"},\"targeting\":{\"pricegranularity\":\"med\"},\"cache\":{\"bids\":[]}}}}", + "responsebody": "{\"id\":\"bidResponseId5\",\"seatbid\":[{\"bid\":[{\"id\":\"347088686\",\"impid\":\"impId5\",\"price\":9.0,\"adm\":\"adm5\",\"crid\":\"crid5\",\"dealid\":\"dealId5\",\"w\":0,\"h\":50}],\"seat\":\"seatId5\",\"group\":0}]}", "status": 200 } ], @@ -615,17 +617,16 @@ "banner": { "format": [ { - "w": 300, - "h": 600 + "w": 320, + "h": 50 } ], - "w": 300, - "h": 600 + "w": 320, + "h": 50 }, "ext": { "audienceNetwork": { - "placementId": "pub1_place1", - "site_id": "siteId" + "placementId": "pub1_place1" } } }, diff --git a/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-cache-request.json b/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-cache-request.json index 41b932d362a..09fd2ebc8c3 100644 --- a/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-cache-request.json +++ b/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-cache-request.json @@ -51,17 +51,6 @@ "h": 250 } }, - { - "type": "json", - "value": { - "id": "impId5", - "impid": "impId5", - "price": 9, - "adm": "adm5", - "w": 300, - "h": 600 - } - }, { "type": "json", "value": { @@ -112,6 +101,19 @@ "h": 300 } }, + { + "type": "json", + "value": { + "id": "347088686", + "impid": "impId5", + "price": 9, + "adm": "adm5", + "crid": "crid5", + "dealid": "dealId5", + "w": 0, + "h": 50 + } + }, { "type": "json", "value": { diff --git a/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-facebook-bid-request-1.json b/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-facebook-bid-request-1.json index 52c0c68d374..9079ee91f74 100644 --- a/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-facebook-bid-request-1.json +++ b/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-facebook-bid-request-1.json @@ -6,33 +6,45 @@ "banner": { "format": [ { - "w": 300, - "h": 600 + "w": 320, + "h": 50 } ], - "w": 300, - "h": 600, - "topframe": 0 + "w": 0, + "h": 50 }, - "tagid": "pub1_place1" + "tagid": "pub1_place1", + "ext": { + "platformid": 101 + } } ], "site": { "domain": "example.com", "page": "http://www.example.com", "publisher": { - "id": "pub1" + "id": "publisherId" } }, "device": { + "ua": "userAgent", "dnt": 2, + "ip": "192.168.244.1", "pxratio": 4.2, "language": "en", "ifa": "ifaId" }, "user": { - "buyeruid": "FB-UID" + "buyeruid": "FB-UID", + "ext": { + "digitrust": { + "id": "id", + "keyv": 123, + "pref": 0 + } + } }, + "test": 1, "at": 1, "tmax": 1000, "source": { @@ -40,6 +52,17 @@ "tid": "tid" }, "ext": { - "platformid": 101 + "prebid": { + "aliases": { + "appnexusAlias": "appnexus", + "conversantAlias": "conversant" + }, + "targeting": { + "pricegranularity": "med" + }, + "cache": { + "bids": [] + } + } } } \ No newline at end of file diff --git a/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-facebook-bid-response-1.json b/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-facebook-bid-response-1.json index ea8f1424f7f..fe15befc5a8 100644 --- a/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-facebook-bid-response-1.json +++ b/src/test/resources/org/prebid/server/ApplicationTest/openrtb2/test-facebook-bid-response-1.json @@ -10,8 +10,8 @@ "adm": "adm5", "crid": "crid5", "dealid": "dealId5", - "w": 300, - "h": 250 + "w": 0, + "h": 50 } ], "seat": "seatId5",