diff --git a/.github/workflows/scripts/codepath-notification b/.github/workflows/scripts/codepath-notification index 001ccbebc1a..371c86fc652 100644 --- a/.github/workflows/scripts/codepath-notification +++ b/.github/workflows/scripts/codepath-notification @@ -23,3 +23,4 @@ openx|OpenX: prebid@openx.com medianet|Medianet: prebid@media.net thetradedesk|TheTradeDesk: Prebid-Maintainers@thetradedesk.com gumgum|GumGum: prebid@gumgum.com +kargo|Kargo: kraken@kargo.com diff --git a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/model/OptableAttributes.java b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/model/OptableAttributes.java index 5498457dcbb..9dacd6de322 100644 --- a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/model/OptableAttributes.java +++ b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/model/OptableAttributes.java @@ -20,5 +20,7 @@ public class OptableAttributes { List ips; + String userAgent; + Long timeout; } diff --git a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/model/config/OptableTargetingProperties.java b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/model/config/OptableTargetingProperties.java index c13bf132ca9..7f0598da83e 100644 --- a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/model/config/OptableTargetingProperties.java +++ b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/model/config/OptableTargetingProperties.java @@ -5,6 +5,7 @@ import lombok.NoArgsConstructor; import java.util.Map; +import java.util.Set; @Data @NoArgsConstructor @@ -31,5 +32,14 @@ public final class OptableTargetingProperties { @JsonProperty("id-prefix-order") String idPrefixOrder; + @JsonProperty("optable-inserter-eids-merge") + Set optableInserterEidsMerge = Set.of(); + + @JsonProperty("optable-inserter-eids-replace") + Set optableInserterEidsReplace = Set.of(); + + @JsonProperty("optable-inserter-eids-ignore") + Set optableInserterEidsIgnore = Set.of(); + CacheProperties cache = new CacheProperties(); } diff --git a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/OptableTargetingProcessedAuctionRequestHook.java b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/OptableTargetingProcessedAuctionRequestHook.java index 2984d3c90b7..a5ad2559d40 100644 --- a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/OptableTargetingProcessedAuctionRequestHook.java +++ b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/OptableTargetingProcessedAuctionRequestHook.java @@ -89,7 +89,7 @@ public Future> call(AuctionRequestPayloa .compose(targetingResult -> { moduleContext.setOptableTargetingExecutionTime( System.currentTimeMillis() - callTargetingAPITimestamp); - return enrichedPayload(targetingResult, moduleContext); + return enrichedPayload(targetingResult, moduleContext, properties); }) .recover(throwable -> { moduleContext.setOptableTargetingExecutionTime( @@ -143,13 +143,14 @@ private Timeout getHookTimeout(AuctionInvocationContext invocationContext) { } private Future> enrichedPayload(TargetingResult targetingResult, - ModuleContext moduleContext) { + ModuleContext moduleContext, + OptableTargetingProperties properties) { moduleContext.setTargeting(targetingResult.getAudience()); moduleContext.setEnrichRequestStatus(EnrichmentStatus.success()); return update( BidRequestCleaner.instance() - .andThen(BidRequestEnricher.of(targetingResult)) + .andThen(BidRequestEnricher.of(targetingResult, properties)) ::apply, moduleContext); } diff --git a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/BidRequestEnricher.java b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/BidRequestEnricher.java index b3ea3e6dce9..2a60389802c 100644 --- a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/BidRequestEnricher.java +++ b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/BidRequestEnricher.java @@ -4,8 +4,11 @@ import com.iab.openrtb.request.Data; import com.iab.openrtb.request.Eid; import com.iab.openrtb.request.Segment; +import com.iab.openrtb.request.Uid; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl; +import org.prebid.server.hooks.modules.optable.targeting.model.config.OptableTargetingProperties; import org.prebid.server.hooks.modules.optable.targeting.model.openrtb.Ortb2; import org.prebid.server.hooks.modules.optable.targeting.model.openrtb.TargetingResult; import org.prebid.server.hooks.modules.optable.targeting.model.openrtb.User; @@ -23,14 +26,18 @@ public class BidRequestEnricher implements PayloadUpdate { + private static final String OPTABLE_CO_INSERTER = "optable.co"; + private final TargetingResult targetingResult; + private final OptableTargetingProperties targetingProperties; - private BidRequestEnricher(TargetingResult targetingResult) { + private BidRequestEnricher(TargetingResult targetingResult, OptableTargetingProperties targetingProperties) { this.targetingResult = targetingResult; + this.targetingProperties = targetingProperties; } - public static BidRequestEnricher of(TargetingResult targetingResult) { - return new BidRequestEnricher(targetingResult); + public static BidRequestEnricher of(TargetingResult targetingResult, OptableTargetingProperties properties) { + return new BidRequestEnricher(targetingResult, properties); } @Override @@ -60,18 +67,88 @@ private BidRequest enrichBidRequest(BidRequest bidRequest) { .build(); } - private static com.iab.openrtb.request.User mergeUserData(com.iab.openrtb.request.User user, User optableUser) { + private com.iab.openrtb.request.User mergeUserData(com.iab.openrtb.request.User user, User optableUser) { return user.toBuilder() - .eids(mergeEids(user.getEids(), optableUser.getEids())) + .eids(filterOptableEids(mergeEids(user.getEids(), optableUser.getEids()))) .data(mergeData(user.getData(), optableUser.getData())) .build(); } - private static List mergeEids(List destination, List source) { - return merge( - destination, - source, - Eid::getSource); + private List mergeEids(List destination, List source) { + if (CollectionUtils.isEmpty(destination)) { + return source; + } + + if (CollectionUtils.isEmpty(source)) { + return destination; + } + + final Map idToSourceEid = source.stream().collect(Collectors.toMap( + BidRequestEnricher::eidIdExtractor, + Function.identity(), + (a, b) -> b, + HashMap::new)); + + final Set sourceToReplace = targetingProperties.getOptableInserterEidsReplace(); + final Set sourceToMerge = targetingProperties.getOptableInserterEidsMerge() + .stream() + .filter(it -> !sourceToReplace.contains(it)).collect(Collectors.toSet()); + + final List mergedEid = destination.stream() + .map(destinationEid -> idToSourceEid.containsKey(eidIdExtractor(destinationEid)) + && OPTABLE_CO_INSERTER.equals(destinationEid.getInserter()) + ? resolveEidConflict( + destinationEid, + idToSourceEid.get(eidIdExtractor(destinationEid)), + sourceToMerge, + sourceToReplace) + : destinationEid) + .toList(); + + return merge(mergedEid, source, BidRequestEnricher::eidIdExtractor); + } + + private List filterOptableEids(List eids) { + if (CollectionUtils.isEmpty(eids)) { + return eids; + } + + final Set optableIdsToIgnore = targetingProperties.getOptableInserterEidsIgnore(); + if (CollectionUtils.isEmpty(optableIdsToIgnore)) { + return eids; + } + + return eids.stream() + .filter(eid -> !OPTABLE_CO_INSERTER.equals(eid.getInserter()) + || !optableIdsToIgnore.contains(eid.getSource())) + .toList(); + } + + private static Eid resolveEidConflict(Eid destinationEid, + Eid sourceEid, + Set sourceToMerge, + Set sourceToReplace) { + + final String eidSource = sourceEid.getSource(); + + if (sourceToReplace.contains(eidSource)) { + return sourceEid; + } + if (sourceToMerge.contains(eidSource)) { + return mergeEid(destinationEid, sourceEid); + } + + return destinationEid; + } + + private static Eid mergeEid(Eid destinationEid, Eid sourceEid) { + return destinationEid.toBuilder() + .uids(merge(destinationEid.getUids(), sourceEid.getUids(), Uid::getId)) + .build(); + } + + private static String eidIdExtractor(Eid eid) { + return "%s_%s".formatted(StringUtils.defaultString(eid.getInserter()), eid.getSource()); } private static List mergeData(List destination, List source) { diff --git a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableAttributesResolver.java b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableAttributesResolver.java index 6fda659278f..7f2aad0657d 100644 --- a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableAttributesResolver.java +++ b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableAttributesResolver.java @@ -22,6 +22,7 @@ public static OptableAttributes resolveAttributes(AuctionContext auctionContext, final OptableAttributes.OptableAttributesBuilder builder = OptableAttributes.builder() .ips(resolveIp(auctionContext)) + .userAgent(resolveUserAgent(auctionContext)) .timeout(timeout); if (tcfContext.isConsentValid()) { @@ -39,7 +40,12 @@ public static OptableAttributes resolveAttributes(AuctionContext auctionContext, return builder.build(); } - public static List resolveIp(AuctionContext auctionContext) { + public static String resolveUserAgent(AuctionContext auctionContext) { + final Device device = auctionContext.getBidRequest().getDevice(); + return device != null ? device.getUa() : null; + } + + private static List resolveIp(AuctionContext auctionContext) { final List result = new ArrayList<>(); final Optional deviceOpt = Optional.ofNullable(auctionContext.getBidRequest().getDevice()); diff --git a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableTargeting.java b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableTargeting.java index c45ce8b6f9f..0cb16d9c456 100644 --- a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableTargeting.java +++ b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableTargeting.java @@ -34,6 +34,6 @@ public Future getTargeting(OptableTargetingProperties propertie return Future.failedFuture("Can't get targeting"); } - return apiClient.getTargeting(properties, query, attributes.getIps(), timeout); + return apiClient.getTargeting(properties, query, attributes.getIps(), attributes.getUserAgent(), timeout); } } diff --git a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/QueryBuilder.java b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/QueryBuilder.java index 2cf964bf63b..613286f4b92 100644 --- a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/QueryBuilder.java +++ b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/QueryBuilder.java @@ -21,6 +21,8 @@ public class QueryBuilder { + private static final String REQUEST_SOURCE = "prebid-server"; + private QueryBuilder() { } @@ -81,6 +83,8 @@ private static String buildAttributesString(OptableAttributes optableAttributes) Optional.ofNullable(optableAttributes.getTimeout()) .ifPresent(timeout -> sb.append("&timeout=").append(timeout).append("ms")); + sb.append("&osdk=").append(REQUEST_SOURCE); + return sb.toString(); } } diff --git a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClient.java b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClient.java index 9e0a4f3db17..ecb96d39cda 100644 --- a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClient.java +++ b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClient.java @@ -13,5 +13,6 @@ public interface APIClient { Future getTargeting(OptableTargetingProperties properties, Query query, List ips, + String userAgent, Timeout timeout); } diff --git a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClientImpl.java b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClientImpl.java index 436ad729375..d31929f111f 100644 --- a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClientImpl.java +++ b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClientImpl.java @@ -49,11 +49,12 @@ public APIClientImpl(String endpoint, public Future getTargeting(OptableTargetingProperties properties, Query query, List ips, + String userAgent, Timeout timeout) { final String uri = resolveEndpoint(properties.getTenant(), properties.getOrigin()); final String queryAsString = query.toQueryString(); - final MultiMap headers = headers(properties, ips); + final MultiMap headers = headers(properties, ips, userAgent); return httpClient.get(uri + queryAsString, headers, timeout.remaining()) .compose(this::validateResponse) @@ -67,10 +68,13 @@ private String resolveEndpoint(String tenant, String origin) { .replace(ORIGIN, origin); } - private static MultiMap headers(OptableTargetingProperties properties, List ips) { + private static MultiMap headers(OptableTargetingProperties properties, List ips, String userAgent) { final MultiMap headers = HeadersMultiMap.headers() .add(HttpUtil.ACCEPT_HEADER, "application/json"); + if (userAgent != null) { + headers.add(HttpUtil.USER_AGENT_HEADER, userAgent); + } final String apiKey = properties.getApiKey(); if (StringUtils.isNotEmpty(apiKey)) { headers.add(HttpUtil.AUTHORIZATION_HEADER, "Bearer %s".formatted(apiKey)); diff --git a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/CachedAPIClient.java b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/CachedAPIClient.java index 73ade87e5e8..e7e8bc3e452 100644 --- a/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/CachedAPIClient.java +++ b/extra/modules/optable-targeting/src/main/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/CachedAPIClient.java @@ -28,18 +28,19 @@ public CachedAPIClient(APIClient apiClient, Cache cache, boolean isCircuitBreake public Future getTargeting(OptableTargetingProperties properties, Query query, List ips, + String userAgent, Timeout timeout) { final CacheProperties cacheProperties = properties.getCache(); if (!cacheProperties.isEnabled()) { - return apiClient.getTargeting(properties, query, ips, timeout); + return apiClient.getTargeting(properties, query, ips, userAgent, timeout); } final String tenant = properties.getTenant(); final String origin = properties.getOrigin(); return cache.get(createCachingKey(tenant, origin, ips, query, true)) - .recover(ignore -> apiClient.getTargeting(properties, query, ips, timeout) + .recover(ignore -> apiClient.getTargeting(properties, query, ips, userAgent, timeout) .recover(throwable -> isCircuitBreakerEnabled ? Future.succeededFuture(new TargetingResult(null, null)) : Future.failedFuture(throwable)) diff --git a/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/BidRequestEnricherTest.java b/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/BidRequestEnricherTest.java index 038a0958acc..71ee889008f 100644 --- a/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/BidRequestEnricherTest.java +++ b/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/BidRequestEnricherTest.java @@ -7,26 +7,32 @@ import com.iab.openrtb.request.Segment; import com.iab.openrtb.request.Uid; import com.iab.openrtb.request.User; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl; +import org.prebid.server.hooks.modules.optable.targeting.model.config.OptableTargetingProperties; import org.prebid.server.hooks.modules.optable.targeting.model.openrtb.TargetingResult; import org.prebid.server.hooks.modules.optable.targeting.v1.BaseOptableTest; import org.prebid.server.hooks.v1.auction.AuctionRequestPayload; import java.util.Collections; import java.util.List; +import java.util.Set; +import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; public class BidRequestEnricherTest extends BaseOptableTest { + private final OptableTargetingProperties targetingProperties = new OptableTargetingProperties(); + @Test public void shouldReturnOriginBidRequestWhenNoTargetingResults() { // given final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(givenBidRequest()); // when - final AuctionRequestPayload result = BidRequestEnricher.of(null) + final AuctionRequestPayload result = BidRequestEnricher.of(null, targetingProperties) .apply(auctionRequestPayload); // then @@ -44,7 +50,7 @@ public void shouldNotFailIfBidRequestIsNull() { final TargetingResult targetingResult = givenTargetingResult(); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -58,7 +64,7 @@ public void shouldReturnEnrichedBidRequestWhenTargetingResultsIsPresent() { final TargetingResult targetingResult = givenTargetingResult(); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -73,15 +79,15 @@ public void shouldReturnEnrichedBidRequestWhenTargetingResultsIsPresent() { public void shouldNotAddEidWhenSourceAlreadyPresent() { // given final TargetingResult targetingResult = givenTargetingResultWithEids(List.of( - givenEid("source", List.of(givenUid("id2", 3, null)), null))); + givenEid("inserter", "source", List.of(givenUid("id2", 3, null)), null))); final BidRequest bidRequest = givenBidRequestWithUserEids(List.of( - givenEid("source", List.of(givenUid("id", null, null)), null), - givenEid("source1", List.of(givenUid("id", null, null)), null))); + givenEid("inserter", "source", List.of(givenUid("id", null, null)), null), + givenEid("inserter", "source1", List.of(givenUid("id", null, null)), null))); final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(bidRequest); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -95,15 +101,15 @@ public void shouldNotAddEidWhenSourceAlreadyPresent() { public void shouldAddEidWhenSourceIsNotAlreadyPresent() { // given final TargetingResult targetingResult = givenTargetingResultWithEids(List.of( - givenEid("source3", List.of(givenUid("id2", 3, null)), null))); + givenEid("inserter", "source3", List.of(givenUid("id2", 3, null)), null))); final BidRequest bidRequest = givenBidRequestWithUserEids(List.of( - givenEid("source1", List.of(givenUid("id", null, null)), null), - givenEid("source2", List.of(givenUid("id", null, null)), null))); + givenEid("inserter", "source1", List.of(givenUid("id", null, null)), null), + givenEid("inserter", "source2", List.of(givenUid("id", null, null)), null))); final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(bidRequest); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -113,19 +119,185 @@ public void shouldAddEidWhenSourceIsNotAlreadyPresent() { assertThat(eids.stream()).extracting(Eid::getSource).containsExactly("source1", "source2", "source3"); } + @Test + public void shouldSkipEidWhenOptableSourceIsAlreadyPresent() { + // given + final TargetingResult targetingResult = givenTargetingResultWithEids(List.of( + givenEid("optable.co", "source2", List.of(givenUid("id2", 3, null)), null))); + + final BidRequest bidRequest = givenBidRequestWithUserEids(List.of( + givenEid("optable.co", "source1", List.of(givenUid("id", null, null)), null), + givenEid("optable.co", "source2", List.of(givenUid("id", null, null)), null))); + final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(bidRequest); + + // when + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) + .apply(auctionRequestPayload); + + // then + assertThat(result.bidRequest()).isNotNull(); + final List eids = result.bidRequest().getUser().getEids(); + assertThat(eids.size()).isEqualTo(2); + assertThat(eids.stream()).extracting(Eid::getSource).containsExactly("source1", "source2"); + assertThat(eids) + .filteredOn(eid -> "source2".equals(eid.getSource())) + .singleElement() + .extracting(Eid::getUids, as(InstanceOfAssertFactories.list(Uid.class))) + .extracting(Uid::getId) + .containsExactly("id"); + } + + @Test + public void shouldMergeEidWhenOptableSourceIsAlreadyPresent() { + // given + final TargetingResult targetingResult = givenTargetingResultWithEids(List.of( + givenEid("optable.co", "source2", List.of(givenUid("id2", 3, null)), null))); + + final BidRequest bidRequest = givenBidRequestWithUserEids(List.of( + givenEid("optable.co", "source1", List.of(givenUid("id", null, null)), null), + givenEid("optable.co", "source2", List.of(givenUid("id", null, null)), null))); + final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(bidRequest); + final OptableTargetingProperties properties = new OptableTargetingProperties(); + properties.setOptableInserterEidsMerge(Set.of("source2")); + + // when + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, properties) + .apply(auctionRequestPayload); + + // then + assertThat(result.bidRequest()).isNotNull(); + final List eids = result.bidRequest().getUser().getEids(); + assertThat(eids.size()).isEqualTo(2); + assertThat(eids.stream()).extracting(Eid::getSource).containsExactly("source1", "source2"); + assertThat(eids) + .filteredOn(eid -> "source2".equals(eid.getSource())) + .singleElement() + .extracting(Eid::getUids, as(InstanceOfAssertFactories.list(Uid.class))) + .extracting(Uid::getId) + .contains("id", "id2"); + } + + @Test + public void shouldRemoveEidWhenOptableSourceIsAlreadyPresent() { + // given + final TargetingResult targetingResult = givenTargetingResultWithEids(List.of( + givenEid("optable.co", "source2", List.of(givenUid("id2", 3, null)), null))); + + final BidRequest bidRequest = givenBidRequestWithUserEids(List.of( + givenEid("optable.co", "source1", List.of(givenUid("id", null, null)), null), + givenEid("optable.co", "source2", List.of(givenUid("id", null, null)), null))); + final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(bidRequest); + final OptableTargetingProperties properties = new OptableTargetingProperties(); + properties.setOptableInserterEidsIgnore(Set.of("source2")); + + // when + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, properties) + .apply(auctionRequestPayload); + + // then + assertThat(result.bidRequest()).isNotNull(); + final List eids = result.bidRequest().getUser().getEids(); + assertThat(eids.size()).isEqualTo(1); + assertThat(eids.stream()).extracting(Eid::getSource).containsExactly("source1"); + } + + @Test + public void shouldRemoveEidWhenOptableSourceIsAlreadyPresentAndEmptyTargeting() { + // given + final TargetingResult targetingResult = givenTargetingResultWithEids(List.of()); + + final BidRequest bidRequest = givenBidRequestWithUserEids(List.of( + givenEid("optable.co", "source1", List.of(givenUid("id", null, null)), null), + givenEid("optable.co", "source2", List.of(givenUid("id", null, null)), null))); + final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(bidRequest); + final OptableTargetingProperties properties = new OptableTargetingProperties(); + properties.setOptableInserterEidsIgnore(Set.of("source2")); + + // when + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, properties) + .apply(auctionRequestPayload); + + // then + assertThat(result.bidRequest()).isNotNull(); + final List eids = result.bidRequest().getUser().getEids(); + assertThat(eids.size()).isEqualTo(1); + assertThat(eids.stream()).extracting(Eid::getSource).containsExactly("source1"); + } + + @Test + public void shouldReplaceEidWhenOptableSourceIsAlreadyPresent() { + // given + final TargetingResult targetingResult = givenTargetingResultWithEids(List.of( + givenEid("optable.co", "source2", List.of(givenUid("id2", 3, null)), null))); + + final BidRequest bidRequest = givenBidRequestWithUserEids(List.of( + givenEid("optable.co", "source1", List.of(givenUid("id", null, null)), null), + givenEid("optable.co", "source2", List.of(givenUid("id", null, null)), null))); + final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(bidRequest); + final OptableTargetingProperties properties = new OptableTargetingProperties(); + properties.setOptableInserterEidsReplace(Set.of("source2")); + + // when + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, properties) + .apply(auctionRequestPayload); + + // then + assertThat(result.bidRequest()).isNotNull(); + final List eids = result.bidRequest().getUser().getEids(); + assertThat(eids.size()).isEqualTo(2); + assertThat(eids.stream()).extracting(Eid::getSource).containsExactly("source1", "source2"); + assertThat(eids) + .filteredOn(eid -> "source2".equals(eid.getSource())) + .singleElement() + .extracting(Eid::getUids, as(InstanceOfAssertFactories.list(Uid.class))) + .extracting(Uid::getId) + .containsExactly("id2"); + } + + @Test + public void shouldReplaceEidWhenOptableSourceIsPresentInBothMergeAndReplaceLists() { + // given + final TargetingResult targetingResult = givenTargetingResultWithEids(List.of( + givenEid("optable.co", "source2", List.of(givenUid("id2", 3, null)), null))); + + final BidRequest bidRequest = givenBidRequestWithUserEids(List.of( + givenEid("optable.co", "source1", List.of(givenUid("id", null, null)), null), + givenEid("optable.co", "source2", List.of(givenUid("id", null, null)), null))); + final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(bidRequest); + final OptableTargetingProperties properties = new OptableTargetingProperties(); + properties.setOptableInserterEidsReplace(Set.of("source2")); + properties.setOptableInserterEidsMerge(Set.of("source2")); + + // when + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, properties) + .apply(auctionRequestPayload); + + // then + assertThat(result.bidRequest()).isNotNull(); + final List eids = result.bidRequest().getUser().getEids(); + assertThat(eids.size()).isEqualTo(2); + assertThat(eids.stream()).extracting(Eid::getSource).containsExactly("source1", "source2"); + assertThat(eids) + .filteredOn(eid -> "source2".equals(eid.getSource())) + .singleElement() + .extracting(Eid::getUids, as(InstanceOfAssertFactories.list(Uid.class))) + .extracting(Uid::getId) + .containsExactly("id2"); + } + @Test public void shouldNotMergeOriginEidsWithTheSameSource() { // given final TargetingResult targetingResult = givenTargetingResultWithEids(List.of( - givenEid("source3", List.of(givenUid("id2", 3, null)), null))); + givenEid("inserter", "source3", List.of(givenUid("id2", 3, null)), null))); final BidRequest bidRequest = givenBidRequestWithUserEids(List.of( - givenEid("source", List.of(givenUid("id", null, null)), null), - givenEid("source", List.of(givenUid("id", null, null)), null))); + givenEid("inserter", "source", List.of(givenUid("id", null, null)), null), + givenEid("inserter", "source", List.of(givenUid("id", null, null)), null))); final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(bidRequest); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -139,13 +311,13 @@ public void shouldNotMergeOriginEidsWithTheSameSource() { public void shouldApplyOriginEidsWhenTargetingIsEmpty() { // given final TargetingResult targetingResult = givenTargetingResultWithEids(List.of( - givenEid("source3", List.of(givenUid("id2", 3, null)), null))); + givenEid("inserter", "source3", List.of(givenUid("id2", 3, null)), null))); final BidRequest bidRequest = givenBidRequestWithUserEids(Collections.emptyList()); final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(bidRequest); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -161,12 +333,12 @@ public void shouldApplyTargetingEidsWhenOriginListIsEmpty() { final TargetingResult targetingResult = givenTargetingResultWithEids(Collections.emptyList()); final BidRequest bidRequest = givenBidRequestWithUserEids(List.of( - givenEid("source", List.of(givenUid("id", null, null)), null), - givenEid("source1", List.of(givenUid("id", null, null)), null))); + givenEid("inserter", "source", List.of(givenUid("id", null, null)), null), + givenEid("inserter", "source1", List.of(givenUid("id", null, null)), null))); final AuctionRequestPayload auctionRequestPayload = AuctionRequestPayloadImpl.of(bidRequest); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -184,7 +356,7 @@ public void shouldNotApplyEidsWhenOriginAndTargetingEidsAreEmpty() { final TargetingResult targetingResult = givenTargetingResultWithEids(Collections.emptyList()); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -203,7 +375,7 @@ public void shouldMergeDataWithTheSameId() { givenData("id", List.of(givenSegment("id3", "value3"))))); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -228,7 +400,7 @@ public void shouldMergeDistinctSegmentsWithinTheSameData() { givenData("id", List.of(givenSegment("id4", "value4"))))); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -254,7 +426,7 @@ public void shouldAppendDataWithNewId() { givenData("id1", List.of(givenSegment("id3", "value3"))))); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -278,7 +450,7 @@ public void shouldApplyOriginDataWhenTargetingIsEmpty() { final TargetingResult targetingResult = givenTargetingResultWithData(Collections.emptyList()); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -299,7 +471,7 @@ public void shouldApplyTargetingDataWhenOriginIsEmpty() { givenData("id", List.of(givenSegment("id1", "value1"))))); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -316,7 +488,7 @@ public void shouldApplyNothingWhenOriginAndTargetingDataAreEmpty() { final TargetingResult targetingResult = givenTargetingResultWithData(Collections.emptyList()); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -331,7 +503,7 @@ public void shouldReturnOriginBidRequestWhenTargetingResultsIsEmpty() { final TargetingResult targetingResult = givenEmptyTargetingResult(); // when - final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult) + final AuctionRequestPayload result = BidRequestEnricher.of(targetingResult, targetingProperties) .apply(auctionRequestPayload); // then @@ -342,8 +514,9 @@ public void shouldReturnOriginBidRequestWhenTargetingResultsIsEmpty() { assertThat(user.getData()).isNull(); } - private Eid givenEid(String source, List uids, ObjectNode ext) { + private Eid givenEid(String inserter, String source, List uids, ObjectNode ext) { return Eid.builder() + .inserter(inserter) .source(source) .uids(uids) .ext(ext) diff --git a/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableTargetingTest.java b/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableTargetingTest.java index 9f793605f12..4533ef073ae 100644 --- a/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableTargetingTest.java +++ b/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/OptableTargetingTest.java @@ -51,7 +51,7 @@ public void setUp() { public void shouldCallNonCachedAPIClient() { // given when(idsMapper.toIds(any(), any())).thenReturn(List.of(Id.of(Id.ID5, "id"))); - when(apiClient.getTargeting(any(), any(), any(), any())) + when(apiClient.getTargeting(any(), any(), any(), any(), any())) .thenReturn(Future.succeededFuture(givenTargetingResult())); final BidRequest bidRequest = givenBidRequest(); @@ -64,7 +64,7 @@ public void shouldCallNonCachedAPIClient() { // then assertThat(targetingResult.result()).isNotNull(); - verify(apiClient).getTargeting(any(), any(), any(), any()); + verify(apiClient).getTargeting(any(), any(), any(), any(), any()); } @Test @@ -72,7 +72,7 @@ public void shouldUseCachedAPIClient() { // given when(idsMapper.toIds(any(), any())).thenReturn(List.of(Id.of(Id.ID5, "id"))); when(cache.get(any())).thenReturn(Future.failedFuture(new NullPointerException())); - when(apiClient.getTargeting(any(), any(), any(), any())) + when(apiClient.getTargeting(any(), any(), any(), any(), any())) .thenReturn(Future.succeededFuture(givenTargetingResult())); final BidRequest bidRequest = givenBidRequest(); @@ -84,7 +84,7 @@ public void shouldUseCachedAPIClient() { // then verify(cache).get(any()); - verify(apiClient).getTargeting(any(), any(), any(), any()); + verify(apiClient).getTargeting(any(), any(), any(), any(), any()); } private OptableAttributes givenOptableAttributes() { diff --git a/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/QueryBuilderTest.java b/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/QueryBuilderTest.java index 606c76db4a2..0359578ee75 100644 --- a/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/QueryBuilderTest.java +++ b/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/core/QueryBuilderTest.java @@ -25,7 +25,7 @@ public void shouldSeparateAttributesFromIds() { // then assertThat(query.getIds()).isEqualTo("&id=e%3Aemail&id=p%3A123"); - assertThat(query.getAttributes()).isEqualTo("&gdpr_consent=tcf&gdpr=1&timeout=100ms"); + assertThat(query.getAttributes()).isEqualTo("&gdpr_consent=tcf&gdpr=1&timeout=100ms&osdk=prebid-server"); } @Test @@ -38,9 +38,9 @@ public void shouldBuildFullQueryString() { // then assertThat(query.getIds()).isEqualTo("&id=e%3Aemail&id=p%3A123"); - assertThat(query.getAttributes()).isEqualTo("&gdpr_consent=tcf&gdpr=1&timeout=100ms"); + assertThat(query.getAttributes()).isEqualTo("&gdpr_consent=tcf&gdpr=1&timeout=100ms&osdk=prebid-server"); assertThat(query.toQueryString()) - .isEqualTo("&id=e%3Aemail&id=p%3A123&gdpr_consent=tcf&gdpr=1&timeout=100ms"); + .isEqualTo("&id=e%3Aemail&id=p%3A123&gdpr_consent=tcf&gdpr=1&timeout=100ms&osdk=prebid-server"); } @Test @@ -96,7 +96,7 @@ public void shouldBuildQueryStringWhenIdsListIsEmptyAndIpIsPresent() { // then assertThat(query).isNotNull(); - assertThat(query.toQueryString()).isEqualTo("&gdpr=0"); + assertThat(query.toQueryString()).isEqualTo("&gdpr=0&osdk=prebid-server"); } @Test diff --git a/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClientImplTest.java b/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClientImplTest.java index 64c23c541fc..d147d7d4294 100644 --- a/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClientImplTest.java +++ b/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/APIClientImplTest.java @@ -54,6 +54,7 @@ public void shouldReturnTargetingResult() { givenOptableTargetingProperties(false), givenQuery(), List.of("8.8.8.8"), + "user agent", timeout); // then @@ -75,6 +76,7 @@ public void shouldReturnNullWhenEndpointRespondsWithError() { givenOptableTargetingProperties(false), givenQuery(), List.of("8.8.8.8"), + "user agent", timeout); // then @@ -92,6 +94,7 @@ public void shouldNotFailWhenEndpointRespondsWithWrongData() { givenOptableTargetingProperties(false), givenQuery(), List.of("8.8.8.8"), + "user agent", timeout); // then @@ -109,6 +112,7 @@ public void shouldNotFailWhenHttpClientIsCrashed() { givenOptableTargetingProperties(false), givenQuery(), List.of("8.8.8.8"), + "user agent", timeout); // then @@ -126,6 +130,7 @@ public void shouldNotFailWhenInternalErrorOccurs() { givenOptableTargetingProperties(false), givenQuery(), List.of("8.8.8.8"), + "user agent", timeout); // then @@ -143,7 +148,7 @@ public void shouldUseAuthorizationHeaderIfApiKeyIsPresent() { // when final Future result = target.getTargeting(givenOptableTargetingProperties(false), - givenQuery(), List.of("8.8.8.8"), timeout); + givenQuery(), List.of("8.8.8.8"), "user agent", timeout); // then final ArgumentCaptor headersCaptor = ArgumentCaptor.forClass(MultiMap.class); @@ -168,7 +173,7 @@ public void shouldBuildApiUrlByReplacingTenantAndOriginMacros() { // when final Future result = target.getTargeting(givenOptableTargetingProperties(false), - givenQuery(), List.of("8.8.8.8"), timeout); + givenQuery(), List.of("8.8.8.8"), "user agent", timeout); // then final ArgumentCaptor endpointCaptor = ArgumentCaptor.forClass(String.class); @@ -189,6 +194,7 @@ public void shouldNotUseAuthorizationHeaderIfApiKeyIsAbsent() { givenOptableTargetingProperties(null, false), givenQuery(), List.of("8.8.8.8"), + "user agent", timeout); // then @@ -200,7 +206,7 @@ public void shouldNotUseAuthorizationHeaderIfApiKeyIsAbsent() { } @Test - public void shouldPassThroughIpAddresses() { + public void shouldPassThroughIpAddressesAndUserAgent() { // given when(httpClient.get(any(), any(), anyLong())).thenReturn(Future.succeededFuture( givenFailHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "plain_text_response.json"))); @@ -210,18 +216,21 @@ public void shouldPassThroughIpAddresses() { givenOptableTargetingProperties(false), givenQuery(), List.of("8.8.8.8", "2001:4860:4860::8888"), + "user agent", timeout); // then final ArgumentCaptor headersCaptor = ArgumentCaptor.forClass(MultiMap.class); verify(httpClient).get(any(), headersCaptor.capture(), anyLong()); - assertThat(headersCaptor.getValue().getAll(HttpUtil.X_FORWARDED_FOR_HEADER)) + final MultiMap headers = headersCaptor.getValue(); + assertThat(headers.getAll(HttpUtil.X_FORWARDED_FOR_HEADER)) .contains("8.8.8.8", "2001:4860:4860::8888"); + assertThat(headers.get(HttpUtil.USER_AGENT_HEADER)).isEqualTo("user agent"); assertThat(result.result()).isNull(); } @Test - public void shouldNotPassThroughIpAddressWhenNotSpecified() { + public void shouldNotPassThroughIpAddressAndUserAgentWhenNotSpecified() { // given when(httpClient.get(any(), any(), anyLong())).thenReturn(Future.succeededFuture( givenFailHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "plain_text_response.json"))); @@ -231,12 +240,15 @@ public void shouldNotPassThroughIpAddressWhenNotSpecified() { givenOptableTargetingProperties(false), givenQuery(), null, + null, timeout); // then final ArgumentCaptor headersCaptor = ArgumentCaptor.forClass(MultiMap.class); verify(httpClient).get(any(), headersCaptor.capture(), anyLong()); - assertThat(headersCaptor.getValue().get(HttpUtil.X_FORWARDED_FOR_HEADER)).isNull(); + final MultiMap headers = headersCaptor.getValue(); + assertThat(headers.get(HttpUtil.X_FORWARDED_FOR_HEADER)).isNull(); + assertThat(headers.get(HttpUtil.USER_AGENT_HEADER)).isNull(); assertThat(result.result()).isNull(); } } diff --git a/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/CachedAPIClientTest.java b/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/CachedAPIClientTest.java index 6c365f2c2c0..e624fa56a8c 100644 --- a/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/CachedAPIClientTest.java +++ b/extra/modules/optable-targeting/src/test/java/org/prebid/server/hooks/modules/optable/targeting/v1/net/CachedAPIClientTest.java @@ -49,7 +49,7 @@ public void shouldCallAPIAndAddTargetingResultsToCache() { when(cache.get(any())).thenReturn(Future.failedFuture("error")); when(cache.put(any(), any(), anyInt())).thenReturn(Future.succeededFuture()); final Query query = givenQuery(); - when(apiClient.getTargeting(any(), any(), any(), any())) + when(apiClient.getTargeting(any(), any(), any(), any(), any())) .thenReturn(Future.succeededFuture(givenTargetingResult())); // when @@ -57,6 +57,7 @@ public void shouldCallAPIAndAddTargetingResultsToCache() { givenOptableTargetingProperties(true), query, List.of("8.8.8.8"), + "user agent", timeout); // then @@ -75,7 +76,7 @@ public void shouldCallAPIAndAddTargetingResultsToCacheWhenCacheReturnsFailure() when(cache.get(any())).thenReturn(Future.failedFuture(new IllegalArgumentException("message"))); when(cache.put(any(), any(), anyInt())).thenReturn(Future.succeededFuture()); final Query query = givenQuery(); - when(apiClient.getTargeting(any(), any(), any(), any())) + when(apiClient.getTargeting(any(), any(), any(), any(), any())) .thenReturn(Future.succeededFuture(givenTargetingResult())); // when @@ -83,6 +84,7 @@ public void shouldCallAPIAndAddTargetingResultsToCacheWhenCacheReturnsFailure() givenOptableTargetingProperties(true), query, List.of("8.8.8.8"), + "user agent", timeout); // then @@ -92,7 +94,7 @@ public void shouldCallAPIAndAddTargetingResultsToCacheWhenCacheReturnsFailure() .returns("id", it -> it.getEids().getFirst().getUids().getFirst().getId()) .returns("id", it -> it.getData().getFirst().getId()) .returns("id", it -> it.getData().getFirst().getSegment().getFirst().getId()); - verify(apiClient, times(1)).getTargeting(any(), any(), any(), any()); + verify(apiClient, times(1)).getTargeting(any(), any(), any(), any(), any()); verify(cache).put(any(), eq(targetingResult.result()), anyInt()); } @@ -107,6 +109,7 @@ public void shouldUseCachedResult() { givenOptableTargetingProperties(true), query, List.of("8.8.8.8"), + "user agent", timeout); // then @@ -117,7 +120,7 @@ public void shouldUseCachedResult() { .returns("id", it -> it.getData().getFirst().getId()) .returns("id", it -> it.getData().getFirst().getSegment().getFirst().getId()); verify(cache, times(1)).get(any()); - verify(apiClient, times(0)).getTargeting(any(), any(), any(), any()); + verify(apiClient, times(0)).getTargeting(any(), any(), any(), any(), any()); verify(cache, times(0)).put(any(), eq(targetingResult.result()), anyInt()); } @@ -126,7 +129,7 @@ public void shouldNotFailWhenApiClientIsFailed() { // given final Query query = givenQuery(); when(cache.get(any())).thenReturn(Future.failedFuture("empty")); - when(apiClient.getTargeting(any(), any(), any(), any())) + when(apiClient.getTargeting(any(), any(), any(), any(), any())) .thenReturn(Future.failedFuture(new NullPointerException())); // when @@ -134,6 +137,7 @@ public void shouldNotFailWhenApiClientIsFailed() { givenOptableTargetingProperties(true), query, List.of("8.8.8.8"), + "user agent", timeout); // then @@ -146,7 +150,7 @@ public void shouldCacheEmptyResultWhenCircuitBreakerIsOn() { // given final Query query = givenQuery(); when(cache.get(any())).thenReturn(Future.failedFuture("empty")); - when(apiClient.getTargeting(any(), any(), any(), any())) + when(apiClient.getTargeting(any(), any(), any(), any(), any())) .thenReturn(Future.failedFuture(new NullPointerException())); when(cache.put(any(), any(), anyInt())).thenReturn(Future.succeededFuture()); @@ -156,6 +160,7 @@ public void shouldCacheEmptyResultWhenCircuitBreakerIsOn() { givenOptableTargetingProperties(true), query, List.of("8.8.8.8"), + "user agent", timeout); // then diff --git a/src/main/java/org/prebid/server/bidder/contxtful/ContxtfulBidder.java b/src/main/java/org/prebid/server/bidder/contxtful/ContxtfulBidder.java new file mode 100644 index 00000000000..cc05acadcd4 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/contxtful/ContxtfulBidder.java @@ -0,0 +1,224 @@ +package org.prebid.server.bidder.contxtful; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.User; +import com.iab.openrtb.response.Bid; +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.contxtful.request.ContxtfulBidRequest; +import org.prebid.server.bidder.contxtful.request.ContxtfulBidRequestParams; +import org.prebid.server.bidder.contxtful.request.ContxtfulBidderRequest; +import org.prebid.server.bidder.contxtful.request.ContxtfulCompositeRequest; +import org.prebid.server.bidder.contxtful.request.ContxtfulConfig; +import org.prebid.server.bidder.contxtful.request.ContxtfulConfigDetails; +import org.prebid.server.bidder.contxtful.response.ContxtfulBid; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderCall; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.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.ExtUser; +import org.prebid.server.proto.openrtb.ext.request.ExtUserPrebid; +import org.prebid.server.proto.openrtb.ext.request.contxtful.ExtImpContxtful; +import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.BidderUtil; +import org.prebid.server.util.HttpUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ContxtfulBidder implements Bidder { + + private static final TypeReference> TYPE_REFERENCE = new TypeReference<>() { + }; + private static final String ACCOUNT_ID_MACRO = "{{AccountId}}"; + private static final String BIDDER_NAME = "contxtful"; + private static final String DEFAULT_ADAPTER_VERSION = "v1"; + private static final String DEFAULT_CURRENCY = "USD"; + + private final String endpointUrl; + private final JacksonMapper mapper; + + public ContxtfulBidder(String endpointUrl, JacksonMapper mapper) { + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.mapper = Objects.requireNonNull(mapper); + } + + @Override + public Result>> makeHttpRequests(BidRequest request) { + final List errors = new ArrayList<>(); + final List bidRequests = new ArrayList<>(); + String customerId = null; + + for (Imp imp : request.getImp()) { + try { + final ExtImpContxtful extImp = parseImpExt(imp); + if (customerId == null) { + customerId = extImp.getCustomerId(); + } + bidRequests.add(ContxtfulBidRequest.of( + BIDDER_NAME, + ContxtfulBidRequestParams.of(extImp.getPlacementId()), imp.getId())); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + } + } + + if (CollectionUtils.isEmpty(bidRequests)) { + return Result.withErrors(errors); + } + + final ContxtfulCompositeRequest outgoingRequest = ContxtfulCompositeRequest.builder() + .ortb2Request(request.toBuilder().user(modifyUser(request.getUser())).build()) + .bidRequests(bidRequests) + .bidderRequest(ContxtfulBidderRequest.of(BIDDER_NAME)) + .config(ContxtfulConfig.of(ContxtfulConfigDetails.of(DEFAULT_ADAPTER_VERSION, customerId))) + .build(); + + final HttpRequest httpRequest = HttpRequest.builder() + .method(HttpMethod.POST) + .uri(makeUrl(customerId)) + .headers(HttpUtil.headers()) + .body(mapper.encodeToBytes(outgoingRequest)) + .payload(outgoingRequest) + .impIds(BidderUtil.impIds(request)) + .build(); + + return Result.of(Collections.singletonList(httpRequest), errors); + } + + private ExtImpContxtful parseImpExt(Imp imp) { + try { + return mapper.mapper().convertValue(imp.getExt(), TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException("Error parsing imp.ext for impression " + imp.getId()); + } + } + + private static User modifyUser(User user) { + if (user == null) { + return null; + } + + final String buyerUid = user.getBuyeruid(); + if (StringUtils.isNotBlank(buyerUid)) { + return user; + } + + return Optional.ofNullable(user.getExt()) + .map(ExtUser::getPrebid) + .map(ExtUserPrebid::getBuyeruids) + .map(buyerUids -> buyerUids.get(BIDDER_NAME)) + .filter(StringUtils::isNotBlank) + .map(uid -> user.toBuilder().buyeruid(uid).build()) + .orElse(user); + } + + private String makeUrl(String customerId) { + return endpointUrl.replace(ACCOUNT_ID_MACRO, HttpUtil.encodeUrl(customerId)); + } + + @Override + public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + final List errors = new ArrayList<>(); + try { + final List responseBids = mapper.decodeValue( + httpCall.getResponse().getBody(), + new TypeReference<>() { + }); + return Result.of(extractBids(bidRequest, responseBids, errors), errors); + } catch (DecodeException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + + private static List extractBids(BidRequest bidRequest, + List responseBids, + List errors) { + + if (CollectionUtils.isEmpty(responseBids)) { + return Collections.emptyList(); + } + return bidsFromResponse(bidRequest, responseBids, errors); + } + + private static List bidsFromResponse(BidRequest bidRequest, + List responseBids, + List errors) { + + final Map impsMap = bidRequest.getImp().stream() + .collect(Collectors.toMap(Imp::getId, Function.identity())); + + return responseBids.stream() + .filter(Objects::nonNull) + .map(responseBid -> makeBidderBid(responseBid, impsMap, errors)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private static BidderBid makeBidderBid(ContxtfulBid responseBid, + Map impsMap, + List errors) { + + final String impId = responseBid.getRequestId(); + if (responseBid.getCpm() == null || impId == null) { + return null; + } + + if (StringUtils.isBlank(responseBid.getMediaType())) { + errors.add(BidderError.badServerResponse("bid %s has no ad media type".formatted(impId))); + return null; + } + + if (StringUtils.isBlank(responseBid.getAdm())) { + errors.add(BidderError.badServerResponse("bid %s has no ad markup".formatted(impId))); + return null; + } + + final Bid bid = Bid.builder() + .id(BIDDER_NAME + "-" + impId) + .impid(impId) + .price(responseBid.getCpm()) + .adm(responseBid.getAdm()) + .w(responseBid.getWidth()) + .h(responseBid.getHeight()) + .crid(responseBid.getCreativeId()) + .nurl(responseBid.getNurl()) + .burl(responseBid.getBurl()) + .lurl(responseBid.getLurl()) + .ext(responseBid.getExt()) + .build(); + + final String currency = Objects.toString(responseBid.getCurrency(), DEFAULT_CURRENCY); + return BidderBid.of(bid, getBidType(impsMap.get(impId)), currency); + } + + private static BidType getBidType(Imp imp) { + if (imp == null) { + return BidType.banner; + } + + if (imp.getVideo() != null) { + return BidType.video; + } else if (imp.getXNative() != null) { + return BidType.xNative; + } else { + return BidType.banner; + } + } +} + diff --git a/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulBidRequest.java b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulBidRequest.java new file mode 100644 index 00000000000..82d011c01f8 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulBidRequest.java @@ -0,0 +1,16 @@ +package org.prebid.server.bidder.contxtful.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class ContxtfulBidRequest { + + String bidder; + + ContxtfulBidRequestParams params; + + @JsonProperty("bidId") + String bidId; + +} diff --git a/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulBidRequestParams.java b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulBidRequestParams.java new file mode 100644 index 00000000000..e4898fa5064 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulBidRequestParams.java @@ -0,0 +1,11 @@ +package org.prebid.server.bidder.contxtful.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class ContxtfulBidRequestParams { + + @JsonProperty("placementId") + String placementId; +} diff --git a/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulBidderRequest.java b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulBidderRequest.java new file mode 100644 index 00000000000..3e3a8e7e254 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulBidderRequest.java @@ -0,0 +1,11 @@ +package org.prebid.server.bidder.contxtful.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class ContxtfulBidderRequest { + + @JsonProperty("bidderCode") + String bidderCode; +} diff --git a/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulCompositeRequest.java b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulCompositeRequest.java new file mode 100644 index 00000000000..dc34fc90c62 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulCompositeRequest.java @@ -0,0 +1,24 @@ +package org.prebid.server.bidder.contxtful.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.iab.openrtb.request.BidRequest; +import lombok.Builder; +import lombok.Value; + +import java.util.List; + +@Builder +@Value(staticConstructor = "of") +public class ContxtfulCompositeRequest { + + @JsonProperty("ortb2") + BidRequest ortb2Request; + + @JsonProperty("bidRequests") + List bidRequests; + + @JsonProperty("bidderRequest") + ContxtfulBidderRequest bidderRequest; + + ContxtfulConfig config; +} diff --git a/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulConfig.java b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulConfig.java new file mode 100644 index 00000000000..05e61089eea --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulConfig.java @@ -0,0 +1,11 @@ +package org.prebid.server.bidder.contxtful.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class ContxtfulConfig { + + @JsonProperty("contxtful") + ContxtfulConfigDetails details; +} diff --git a/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulConfigDetails.java b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulConfigDetails.java new file mode 100644 index 00000000000..0e144646431 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/contxtful/request/ContxtfulConfigDetails.java @@ -0,0 +1,11 @@ +package org.prebid.server.bidder.contxtful.request; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class ContxtfulConfigDetails { + + String version; + + String customer; +} diff --git a/src/main/java/org/prebid/server/bidder/contxtful/response/ContxtfulBid.java b/src/main/java/org/prebid/server/bidder/contxtful/response/ContxtfulBid.java new file mode 100644 index 00000000000..d7eafc62b5a --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/contxtful/response/ContxtfulBid.java @@ -0,0 +1,56 @@ +package org.prebid.server.bidder.contxtful.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Builder; +import lombok.Value; + +import java.math.BigDecimal; + +@Builder +@Value(staticConstructor = "of") +public class ContxtfulBid { + + @JsonProperty("requestId") + String requestId; + + BigDecimal cpm; + + String currency; + + Integer width; + + Integer height; + + @JsonProperty("creativeId") + String creativeId; + + String adm; + + Integer ttl; + + @JsonProperty("netRevenue") + Boolean netRevenue; + + @JsonProperty("mediaType") + String mediaType; + + @JsonProperty("bidderCode") + String bidderCode; + + @JsonProperty("placementId") + String placementId; + + @JsonProperty("traceId") + String traceId; + + BigDecimal random; + + String nurl; + + String burl; + + String lurl; + + ObjectNode ext; +} diff --git a/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java b/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java index 09c607d7451..99beb0538bf 100644 --- a/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java +++ b/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java @@ -396,6 +396,7 @@ private static BidderBid mapToBidderBid(HbResponseSpace hbResponseSpace, HbRespo .price(new BigDecimal(hbResponseAd.getPrice())) .adm(hbResponseAd.getAdM()) .crid(hbResponseAd.getCrId()) + .adomain(Collections.singletonList(hbResponseAd.getAdom())) .w(hbResponseAd.getWidth()) .h(hbResponseAd.getHeight()) .build(), diff --git a/src/main/java/org/prebid/server/bidder/eplanning/model/HbResponseAd.java b/src/main/java/org/prebid/server/bidder/eplanning/model/HbResponseAd.java index 2d5cee971b0..d951070a537 100644 --- a/src/main/java/org/prebid/server/bidder/eplanning/model/HbResponseAd.java +++ b/src/main/java/org/prebid/server/bidder/eplanning/model/HbResponseAd.java @@ -23,6 +23,8 @@ public class HbResponseAd { @JsonProperty("crid") String crId; + String adom; + @JsonProperty("w") Integer width; diff --git a/src/main/java/org/prebid/server/bidder/mobkoi/MobkoiBidder.java b/src/main/java/org/prebid/server/bidder/mobkoi/MobkoiBidder.java index 97cd481b235..dbf82da1b4f 100644 --- a/src/main/java/org/prebid/server/bidder/mobkoi/MobkoiBidder.java +++ b/src/main/java/org/prebid/server/bidder/mobkoi/MobkoiBidder.java @@ -24,8 +24,6 @@ import org.prebid.server.util.BidderUtil; import org.prebid.server.util.HttpUtil; -import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -60,11 +58,9 @@ public Result>> makeHttpRequests(BidRequest bidRequ return Result.withError(BidderError.badInput(e.getMessage())); } - final String selectedEndpointUrl = resolveEndpoint(extImpMobkoi.getAdServerBaseUrl()); - return Result.withValue(BidderUtil.defaultRequest( modifyBidRequest(bidRequest, modifiedFirstImp), - selectedEndpointUrl, + endpointUrl, mapper)); } @@ -91,19 +87,6 @@ private Imp modifyImp(Imp firstImp, ExtImpMobkoi extImpMobkoi) { + "req.imp[0].ext.Bidder.placementId"); } - // url is already validated with `bidder-params` json schema - private String resolveEndpoint(String customUri) { - if (customUri == null) { - return endpointUrl; - } - try { - final URI uri = new URI(customUri); - return uri.resolve("/bid").toString(); - } catch (IllegalArgumentException | URISyntaxException e) { - return endpointUrl; - } - } - private static BidRequest modifyBidRequest(BidRequest bidRequest, Imp modifiedFirstImp) { final User user = modifyUser(bidRequest.getUser()); final List imps = updateFirstImpWith(bidRequest.getImp(), modifiedFirstImp); diff --git a/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java new file mode 100644 index 00000000000..b212950f322 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java @@ -0,0 +1,218 @@ +package org.prebid.server.bidder.showheroes; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.App; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.Source; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +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.BidderCall; +import org.prebid.server.bidder.model.BidderError; +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.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidChannel; +import org.prebid.server.proto.openrtb.ext.request.ExtSource; +import org.prebid.server.proto.openrtb.ext.request.showheroes.ExtImpShowheroes; +import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.BidderUtil; +import org.prebid.server.util.HttpUtil; +import org.prebid.server.version.PrebidVersionProvider; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class ShowheroesBidder implements Bidder { + + private static final String BID_CURRENCY = "EUR"; + private static final String PBSP_JAVA = "java"; + private static final TypeReference> SHOWHEROES_EXT_TYPE_REFERENCE = + new TypeReference<>() { + }; + + private final String endpointUrl; + private final CurrencyConversionService currencyConversionService; + private final JacksonMapper mapper; + private final String pbsVersion; + + public ShowheroesBidder(String endpointUrl, + CurrencyConversionService currencyConversionService, + PrebidVersionProvider prebidVersionProvider, + JacksonMapper mapper) { + + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.currencyConversionService = Objects.requireNonNull(currencyConversionService); + this.mapper = Objects.requireNonNull(mapper); + + this.pbsVersion = prebidVersionProvider.getNameVersionRecord(); + } + + @Override + public Result>> makeHttpRequests(BidRequest request) { + final BidderError validationError = validate(request.getSite(), request.getApp()); + if (validationError != null) { + return Result.withError(validationError); + } + + final List errors = new ArrayList<>(); + + final ExtRequestPrebidChannel prebidChannel = getPrebidChannel(request); + final List modifiedImps = new ArrayList<>(request.getImp().size()); + + for (Imp impression : request.getImp()) { + try { + modifiedImps.add(modifyImp(request, impression, prebidChannel)); + } catch (Exception e) { + errors.add(BidderError.badInput(e.getMessage())); + } + } + + if (modifiedImps.isEmpty()) { + return Result.withErrors(errors); + } + + final Source source = modifySource(request); + final BidRequest modifiedRequest = request.toBuilder().imp(modifiedImps).source(source).build(); + final HttpRequest httpRequest = BidderUtil.defaultRequest(modifiedRequest, endpointUrl, mapper); + + return Result.of(Collections.singletonList(httpRequest), errors); + } + + private static BidderError validate(Site site, App app) { + if (site == null && app == null) { + return BidderError.badInput("BidRequest must contain one of site or app"); + } + if (site != null && site.getPage() == null) { + return BidderError.badInput("BidRequest.site.page is required"); + } + if (app != null && app.getBundle() == null) { + return BidderError.badInput("BidRequest.app.bundle is required"); + } + return null; + } + + private static ExtRequestPrebidChannel getPrebidChannel(BidRequest bidRequest) { + return Optional.ofNullable(bidRequest.getExt()) + .map(ExtRequest::getPrebid) + .map(ExtRequestPrebid::getChannel) + .orElse(null); + } + + private Imp modifyImp(BidRequest bidRequest, Imp imp, ExtRequestPrebidChannel prebidChannel) { + final ExtImpShowheroes extImpShowheroes = parseImpExt(imp); + + final boolean shouldSetDisplayManager = prebidChannel != null && imp.getDisplaymanager() == null; + final boolean shouldConvertFloor = shouldConvertFloor(imp); + + return imp.toBuilder() + .displaymanager(shouldSetDisplayManager ? prebidChannel.getName() : imp.getDisplaymanager()) + .displaymanagerver(shouldSetDisplayManager ? prebidChannel.getVersion() : imp.getDisplaymanagerver()) + .bidfloorcur(shouldConvertFloor ? BID_CURRENCY : imp.getBidfloorcur()) + .bidfloor(shouldConvertFloor ? resolveBidFloor(bidRequest, imp) : imp.getBidfloor()) + .ext(modifyImpExt(imp.getExt(), extImpShowheroes)) + .build(); + } + + private ExtImpShowheroes parseImpExt(Imp imp) { + try { + return mapper.mapper().convertValue(imp.getExt(), SHOWHEROES_EXT_TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException(e.getMessage()); + } + } + + private ObjectNode modifyImpExt(ObjectNode impExt, ExtImpShowheroes shImpExt) { + impExt.set("params", mapper.mapper().createObjectNode().put("unitId", shImpExt.getUnitId())); + return impExt; + } + + private static boolean shouldConvertFloor(Imp imp) { + return BidderUtil.isValidPrice(imp.getBidfloor()) + && !StringUtils.equalsIgnoreCase(imp.getBidfloorcur(), BID_CURRENCY); + } + + private BigDecimal resolveBidFloor(BidRequest bidRequest, Imp imp) { + return currencyConversionService.convertCurrency( + imp.getBidfloor(), bidRequest, imp.getBidfloorcur(), BID_CURRENCY); + } + + private Source modifySource(BidRequest bidRequest) { + if (pbsVersion == null) { + return bidRequest.getSource(); + } + + final Source source = bidRequest.getSource(); + + final ExtSource extSource = Optional.ofNullable(source) + .map(Source::getExt) + .orElse(ExtSource.of(null)); + final ObjectNode prebidExtSource = Optional.ofNullable(extSource.getProperty("pbs")) + .filter(JsonNode::isObject) + .map(ObjectNode.class::cast) + .orElseGet(mapper.mapper()::createObjectNode) + .put("pbsv", pbsVersion) + .put("pbsp", PBSP_JAVA); + extSource.addProperty("pbs", prebidExtSource); + + return Optional.ofNullable(source) + .map(Source::toBuilder) + .orElseGet(Source::builder) + .ext(extSource) + .build(); + } + + @Override + public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + try { + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + return Result.of(extractBids(bidResponse), Collections.emptyList()); + } catch (DecodeException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + + } + + private List extractBids(BidResponse bidResponse) { + 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 -> BidderBid.of(bid, getBidType(bid), bidResponse.getCur())) + .filter(Objects::nonNull) + .toList(); + } + + private static BidType getBidType(Bid bid) { + return switch (bid.getMtype()) { + case 1 -> BidType.banner; + case 2 -> BidType.video; + case null, default -> BidType.video; + }; + } +} diff --git a/src/main/java/org/prebid/server/bidder/smilewanted/SmileWantedBidder.java b/src/main/java/org/prebid/server/bidder/smilewanted/SmileWantedBidder.java index a36057f2c33..67bfec26571 100644 --- a/src/main/java/org/prebid/server/bidder/smilewanted/SmileWantedBidder.java +++ b/src/main/java/org/prebid/server/bidder/smilewanted/SmileWantedBidder.java @@ -1,11 +1,11 @@ package org.prebid.server.bidder.smilewanted; +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; @@ -13,9 +13,13 @@ import org.prebid.server.bidder.model.BidderError; 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.smilewanted.ExtImpSmilewanted; import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.BidderUtil; import org.prebid.server.util.HttpUtil; import java.util.Collections; @@ -27,6 +31,11 @@ public class SmileWantedBidder implements Bidder { private static final String SW_INTEGRATION_TYPE = "prebid_server"; private static final String X_OPENRTB_VERSION = "2.5"; private static final int DEFAULT_AT = 1; + private static final String ZONE_ID_MACRO = "{{ZoneId}}"; + + private static final TypeReference> SMILEWANTED_EXT_TYPE_REFERENCE = + new TypeReference<>() { + }; private final String endpointUrl; private final JacksonMapper mapper; @@ -38,15 +47,30 @@ public SmileWantedBidder(String endpointUrl, JacksonMapper mapper) { @Override public Result>> makeHttpRequests(BidRequest request) { + final ExtImpSmilewanted extImpSmilewanted; + + try { + extImpSmilewanted = parseImpExt(request.getImp().getFirst()); + } catch (PreBidException e) { + return Result.withError(BidderError.badInput(e.getMessage())); + } + final BidRequest outgoingRequest = request.toBuilder().at(DEFAULT_AT).build(); + final String url = endpointUrl.replace(ZONE_ID_MACRO, HttpUtil.encodeUrl(extImpSmilewanted.getZoneId())); - return Result.withValue(HttpRequest.builder() - .method(HttpMethod.POST) - .uri(endpointUrl) - .headers(createHeaders()) - .payload(outgoingRequest) - .body(mapper.encodeToBytes(outgoingRequest)) - .build()); + return Result.withValue(BidderUtil.defaultRequest( + outgoingRequest, + createHeaders(), + url, + mapper)); + } + + private ExtImpSmilewanted parseImpExt(Imp imp) { + try { + return mapper.mapper().convertValue(imp.getExt(), SMILEWANTED_EXT_TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException("Missing bidder ext in impression with id: " + imp.getId()); + } } private static MultiMap createHeaders() { diff --git a/src/main/java/org/prebid/server/bidder/sovrn/SovrnBidder.java b/src/main/java/org/prebid/server/bidder/sovrn/SovrnBidder.java index ac5e2268500..ee79f854777 100644 --- a/src/main/java/org/prebid/server/bidder/sovrn/SovrnBidder.java +++ b/src/main/java/org/prebid/server/bidder/sovrn/SovrnBidder.java @@ -41,7 +41,6 @@ public class SovrnBidder implements Bidder { private static final String LJT_READER_COOKIE_NAME = "ljt_reader"; - private static final String EXT_AD_UNIT_CODE_PARAM = "adunitcode"; private static final TypeReference> SOVRN_EXT_TYPE_REFERENCE = new TypeReference<>() { @@ -91,7 +90,6 @@ private Imp makeImp(Imp imp) { return imp.toBuilder() .bidfloor(resolveBidFloor(imp.getBidfloor(), sovrnExt.getBidfloor())) .tagid(resolveTagId(sovrnExt)) - .ext(resolveImpExt(sovrnExt, impExt)) .build(); } @@ -117,13 +115,6 @@ private String resolveTagId(ExtImpSovrn sovrnExt) { return tagId; } - private ObjectNode resolveImpExt(ExtImpSovrn sovrnExt, ObjectNode impExt) { - final ObjectNode sovrnImpExt = impExt.deepCopy(); - return StringUtils.isNotBlank(sovrnExt.getAdunitcode()) - ? sovrnImpExt.putPOJO(EXT_AD_UNIT_CODE_PARAM, sovrnExt.getAdunitcode()) - : sovrnImpExt; - } - private Result>> makeHttpRequest(BidRequest bidRequest, List errors) { diff --git a/src/main/java/org/prebid/server/bidder/yandex/YandexBidder.java b/src/main/java/org/prebid/server/bidder/yandex/YandexBidder.java index 46ed20a06f1..d3851455d1d 100644 --- a/src/main/java/org/prebid/server/bidder/yandex/YandexBidder.java +++ b/src/main/java/org/prebid/server/bidder/yandex/YandexBidder.java @@ -8,6 +8,7 @@ import com.iab.openrtb.request.Format; import com.iab.openrtb.request.Imp; 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.MultiMap; @@ -46,6 +47,8 @@ public class YandexBidder implements Bidder { private static final String PAGE_ID_MACRO = "{{PageId}}"; private static final String IMP_ID_MACRO = "{{ImpId}}"; + private static final String DISPLAY_MANAGER = "prebid.java"; + private static final String DISPLAY_MANAGER_VERSION = "1.1"; private final String endpointUrl; private final JacksonMapper mapper; @@ -110,17 +113,25 @@ private ExtImpYandex parseAndValidateImpExt(ObjectNode impExtNode, final String } private static Imp modifyImp(Imp imp) { - if (imp.getBanner() != null) { - return imp.toBuilder().banner(modifyBanner(imp.getBanner())).build(); - } - if (imp.getXNative() != null) { - return imp; + if (imp.getBanner() == null && imp.getVideo() == null && imp.getXNative() == null) { + throw new PreBidException("Imp #%s must contain at least one valid format (banner, video, or native)" + .formatted(imp.getId())); } - throw new PreBidException("Yandex only supports banner and native types. Ignoring imp id #%s" - .formatted(imp.getId())); + + return imp.toBuilder() + .displaymanager(DISPLAY_MANAGER) + .displaymanagerver(DISPLAY_MANAGER_VERSION) + .banner(modifyBanner(imp.getBanner())) + .video(modifyVideo(imp.getVideo())) + .xNative(imp.getXNative()) + .build(); } private static Banner modifyBanner(Banner banner) { + if (banner == null) { + return null; + } + final Integer weight = banner.getW(); final Integer height = banner.getH(); final List format = banner.getFormat(); @@ -134,6 +145,31 @@ private static Banner modifyBanner(Banner banner) { return banner; } + private static Video modifyVideo(Video video) { + if (video == null) { + return null; + } + + final Integer width = video.getW(); + final Integer height = video.getH(); + if (width == null || height == null || width == 0 || height == 0) { + throw new PreBidException("Invalid sizes provided for Video %sx%s".formatted(width, height)); + } + + final Video.VideoBuilder videoBuilder = video.toBuilder(); + if (video.getMinduration() == null || video.getMinduration() == 0) { + videoBuilder.minduration(1); + } + if (video.getMaxduration() == null || video.getMaxduration() == 0) { + videoBuilder.maxduration(120); + } + if (CollectionUtils.isEmpty(video.getProtocols())) { + videoBuilder.protocols(Collections.singletonList(3)); + } + + return videoBuilder.build(); + } + private String modifyUrl(ExtImpYandex extImpYandex, String referer, String currency) { final String resolvedUrl = endpointUrl .replace(PAGE_ID_MACRO, HttpUtil.encodeUrl(extImpYandex.getPageId().toString())) @@ -167,6 +203,10 @@ private HttpRequest buildHttpRequest(BidRequest outgoingRequest, Str private static MultiMap headers(BidRequest bidRequest) { final MultiMap headers = HttpUtil.headers(); + + headers.add(HttpUtil.X_OPENRTB_VERSION_HEADER, "2.5"); + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, getReferer(bidRequest)); + final Device device = bidRequest.getDevice(); if (device != null) { HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.ACCEPT_LANGUAGE_HEADER, device.getLanguage()); @@ -217,18 +257,15 @@ private static BidType getBidType(String bidImpId, List imps) { } private static BidType resolveImpType(Imp imp) { + if (imp.getVideo() != null) { + return BidType.video; + } if (imp.getXNative() != null) { return BidType.xNative; } if (imp.getBanner() != null) { return BidType.banner; } - if (imp.getVideo() != null) { - return BidType.video; - } - if (imp.getAudio() != null) { - return BidType.audio; - } throw new PreBidException("Processing an invalid impression; cannot resolve impression type for imp #%s" .formatted(imp.getId())); } diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/contxtful/ExtImpContxtful.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/contxtful/ExtImpContxtful.java new file mode 100644 index 00000000000..14e38b06e9c --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/contxtful/ExtImpContxtful.java @@ -0,0 +1,14 @@ +package org.prebid.server.proto.openrtb.ext.request.contxtful; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class ExtImpContxtful { + + @JsonProperty("placementId") + String placementId; + + @JsonProperty("customerId") + String customerId; +} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/mobkoi/ExtImpMobkoi.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/mobkoi/ExtImpMobkoi.java index 0faa3d1efc8..ac8009a76d2 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/mobkoi/ExtImpMobkoi.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/mobkoi/ExtImpMobkoi.java @@ -8,7 +8,4 @@ public class ExtImpMobkoi { @JsonProperty("placementId") String placementId; - - @JsonProperty("adServerBaseUrl") - String adServerBaseUrl; } diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/showheroes/ExtImpShowheroes.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/showheroes/ExtImpShowheroes.java new file mode 100644 index 00000000000..77b65257fb0 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/showheroes/ExtImpShowheroes.java @@ -0,0 +1,11 @@ +package org.prebid.server.proto.openrtb.ext.request.showheroes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class ExtImpShowheroes { + + @JsonProperty("unitId") + String unitId; +} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/smilewanted/ExtImpSmilewanted.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/smilewanted/ExtImpSmilewanted.java new file mode 100644 index 00000000000..ec08ec3a957 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/smilewanted/ExtImpSmilewanted.java @@ -0,0 +1,11 @@ +package org.prebid.server.proto.openrtb.ext.request.smilewanted; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class ExtImpSmilewanted { + + @JsonProperty("zoneId") + String zoneId; +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ContxtfulConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ContxtfulConfiguration.java new file mode 100644 index 00000000000..0759f38bc17 --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/ContxtfulConfiguration.java @@ -0,0 +1,41 @@ +package org.prebid.server.spring.config.bidder; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.contxtful.ContxtfulBidder; +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 jakarta.validation.constraints.NotBlank; + +@Configuration +@PropertySource(value = "classpath:/bidder-config/contxtful.yaml", factory = YamlPropertySourceFactory.class) +public class ContxtfulConfiguration { + + private static final String BIDDER_NAME = "contxtful"; + + @Bean("contxtfulConfigurationProperties") + @ConfigurationProperties("adapters.contxtful") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + @Bean + BidderDeps contxtfulBidderDeps(BidderConfigurationProperties contxtfulConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + JacksonMapper mapper) { + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(contxtfulConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new ContxtfulBidder(config.getEndpoint(), mapper)) + .assemble(); + } +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ShowheroesConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ShowheroesConfiguration.java new file mode 100644 index 00000000000..260d8376675 --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/ShowheroesConfiguration.java @@ -0,0 +1,49 @@ +package org.prebid.server.spring.config.bidder; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.showheroes.ShowheroesBidder; +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.prebid.server.version.PrebidVersionProvider; +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 jakarta.validation.constraints.NotBlank; + +@Configuration +@PropertySource(value = "classpath:/bidder-config/showheroes.yaml", factory = YamlPropertySourceFactory.class) +public class ShowheroesConfiguration { + + private static final String BIDDER_NAME = "showheroes"; + + @Bean("showheroesConfigurationProperties") + @ConfigurationProperties("adapters.showheroes") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + @Bean + BidderDeps showheroesBidderDeps(BidderConfigurationProperties showheroesConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + CurrencyConversionService currencyConversionService, + PrebidVersionProvider prebidVersionProvider, + JacksonMapper mapper) { + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(showheroesConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new ShowheroesBidder( + config.getEndpoint(), + currencyConversionService, + prebidVersionProvider, + mapper)) + .assemble(); + } +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/SimpleWantedConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/SmileWantedConfiguration.java similarity index 97% rename from src/main/java/org/prebid/server/spring/config/bidder/SimpleWantedConfiguration.java rename to src/main/java/org/prebid/server/spring/config/bidder/SmileWantedConfiguration.java index 0b72987e6f9..eb3ae5bb83c 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/SimpleWantedConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/SmileWantedConfiguration.java @@ -17,7 +17,7 @@ @Configuration @PropertySource(value = "classpath:/bidder-config/smilewanted.yaml", factory = YamlPropertySourceFactory.class) -public class SimpleWantedConfiguration { +public class SmileWantedConfiguration { private static final String BIDDER_NAME = "smilewanted"; diff --git a/src/main/resources/bidder-config/adkernel.yaml b/src/main/resources/bidder-config/adkernel.yaml index 2e521696c36..dbc1afb26a7 100644 --- a/src/main/resources/bidder-config/adkernel.yaml +++ b/src/main/resources/bidder-config/adkernel.yaml @@ -5,6 +5,20 @@ adapters: aliases: rxnetwork: ~ 152media: ~ + xapads: + enabled: false + vendor-id: 1320 + usersync: + enabled: true + cookie-family-name: xapads + redirect: + url: https://sync.adkernel.com/user-sync?t=image&zone=284803&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&r={{redirect_url}} + support-cors: false + uid-macro: '{UID}' + iframe: + url: https://sync.adkernel.com/user-sync?t=iframe&zone=284803&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&r={{redirect_url}} + support-cors: false + uid-macro: '{UID}' meta-info: maintainer-email: prebid-dev@adkernel.com app-media-types: diff --git a/src/main/resources/bidder-config/contxtful.yaml b/src/main/resources/bidder-config/contxtful.yaml new file mode 100644 index 00000000000..086eb92c1dc --- /dev/null +++ b/src/main/resources/bidder-config/contxtful.yaml @@ -0,0 +1,21 @@ +adapters: + contxtful: + endpoint: https://prebid.receptivity.io/v1/pbs/{{AccountId}}/bid + meta-info: + maintainer-email: contact@contxtful.com + app-media-types: + - banner + - video + - native + site-media-types: + - banner + - video + - native + supported-vendors: + vendor-id: 0 + usersync: + cookie-family-name: contxtful + iframe: + url: https://sync.receptivity.io/pbs/iframe?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redirect={{redirect_url}} + support-cors: false + uid-macro: '$UID' diff --git a/src/main/resources/bidder-config/freewheelssp.yaml b/src/main/resources/bidder-config/freewheelssp.yaml index d657e641adc..ff042fe76d2 100644 --- a/src/main/resources/bidder-config/freewheelssp.yaml +++ b/src/main/resources/bidder-config/freewheelssp.yaml @@ -8,6 +8,13 @@ adapters: enabled: false endpoint: "https://prebid.v.fwmrm.net/ortb/ssp" endpoint-compression: gzip + usersync: + enabled: true + cookie-family-name: fwssp + iframe: + url: https://user-sync.fwmrm.net/ad/u?mode=pbs-user-sync&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&pbs_redirect={{redirect_url}} + support-cors: false + uid-macro: '#{user.id}' meta-info: maintainer-email: prebid-maintainer@freewheel.com app-media-types: diff --git a/src/main/resources/bidder-config/improvedigital.yaml b/src/main/resources/bidder-config/improvedigital.yaml index 64af2e0fdc0..41af36899bf 100644 --- a/src/main/resources/bidder-config/improvedigital.yaml +++ b/src/main/resources/bidder-config/improvedigital.yaml @@ -1,6 +1,6 @@ adapters: improvedigital: - endpoint: http://ad.360yield.com/{{PathPrefix}}pbs + endpoint: https://ad.360yield.com/{{PathPrefix}}pbs endpoint-compression: gzip meta-info: maintainer-email: hb@azerion.com diff --git a/src/main/resources/bidder-config/metax.yaml b/src/main/resources/bidder-config/metax.yaml index 7e87526ac0b..493e0640bfd 100644 --- a/src/main/resources/bidder-config/metax.yaml +++ b/src/main/resources/bidder-config/metax.yaml @@ -16,3 +16,9 @@ adapters: - audio supported-vendors: vendor-id: 1301 + usersync: + cookie-family-name: metax + redirect: + url: https://cm.metaxads.com/pixel?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}} + support-cors: false + uid-macro: '[UID]' diff --git a/src/main/resources/bidder-config/showheroes.yaml b/src/main/resources/bidder-config/showheroes.yaml new file mode 100644 index 00000000000..b5fd953ac71 --- /dev/null +++ b/src/main/resources/bidder-config/showheroes.yaml @@ -0,0 +1,17 @@ +adapters: + showheroes: + endpoint: https://ads.viralize.tv/openrtb2/auction/ + aliases: + showheroes-bs: ~ + showheroesBs: ~ + ortb-version: '2.6' + meta-info: + maintainer-email: tech@showheroes.com + app-media-types: + - banner + - video + site-media-types: + - banner + - video + supported-vendors: + vendor-id: 111 diff --git a/src/main/resources/bidder-config/smilewanted.yaml b/src/main/resources/bidder-config/smilewanted.yaml index a4dfdea902e..3cac81ccae3 100644 --- a/src/main/resources/bidder-config/smilewanted.yaml +++ b/src/main/resources/bidder-config/smilewanted.yaml @@ -1,6 +1,6 @@ adapters: smilewanted: - endpoint: https://prebid-server.smilewanted.com + endpoint: https://prebid-server.smilewanted.com/java/{{ZoneId}} meta-info: maintainer-email: tech@smilewanted.com app-media-types: diff --git a/src/main/resources/bidder-config/yandex.yaml b/src/main/resources/bidder-config/yandex.yaml index f980fe3cb5e..11800ddb11a 100644 --- a/src/main/resources/bidder-config/yandex.yaml +++ b/src/main/resources/bidder-config/yandex.yaml @@ -7,11 +7,12 @@ adapters: app-media-types: site-media-types: - banner + - video - native vendor-id: 0 usersync: cookie-family-name: yandex redirect: - url: https://an.yandex.ru/mapuid/yandex/?ssp-id=10500&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&location={{redirect_url}} + url: https://yandex.ru/an/mapuid/yandex/?ssp-id=10500&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&location={{redirect_url}} support-cors: false uid-macro: '{YANDEXUID}' diff --git a/src/main/resources/static/bidder-params/contxtful.json b/src/main/resources/static/bidder-params/contxtful.json new file mode 100644 index 00000000000..18b44b46f58 --- /dev/null +++ b/src/main/resources/static/bidder-params/contxtful.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Contxtful Adapter Params", + "description": "A schema which validates params for the Contxtful adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "description": "Placement ID", + "minLength": 1 + }, + "customerId": { + "type": "string", + "description": "Customer ID used to build the endpoint URL", + "minLength": 1 + } + }, + "required": [ + "placementId", + "customerId" + ] +} diff --git a/src/main/resources/static/bidder-params/kueezrtb.json b/src/main/resources/static/bidder-params/kueezrtb.json index 29685df6f00..e11a79e028d 100644 --- a/src/main/resources/static/bidder-params/kueezrtb.json +++ b/src/main/resources/static/bidder-params/kueezrtb.json @@ -13,6 +13,5 @@ }, "required": [ "cId" - ], - "additionalProperties": false + ] } diff --git a/src/main/resources/static/bidder-params/mobkoi.json b/src/main/resources/static/bidder-params/mobkoi.json index 8286229b035..6858a48a679 100644 --- a/src/main/resources/static/bidder-params/mobkoi.json +++ b/src/main/resources/static/bidder-params/mobkoi.json @@ -7,11 +7,6 @@ "placementId": { "type": "string", "description": "Placement ID" - }, - "adServerBaseUrl": { - "type": "string", - "description": "Mobkoi's ad server url", - "pattern": "^https?://[^.]+\\.mobkoi\\.com$" } } } diff --git a/src/main/resources/static/bidder-params/showheroes.json b/src/main/resources/static/bidder-params/showheroes.json new file mode 100644 index 00000000000..3c269d118d8 --- /dev/null +++ b/src/main/resources/static/bidder-params/showheroes.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Showheroes Adapter Params", + "description": "A schema which validates params accepted by the Showheroes adapter", + "type": "object", + "properties": { + "unitId": { + "type": "string", + "description": "Unit ID", + "minLength": 8 + } + }, + "required": ["unitId"] +} diff --git a/src/test/java/org/prebid/server/bidder/contxtful/ContxtfulBidderTest.java b/src/test/java/org/prebid/server/bidder/contxtful/ContxtfulBidderTest.java new file mode 100644 index 00000000000..1606a8b4a9f --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/contxtful/ContxtfulBidderTest.java @@ -0,0 +1,390 @@ +package org.prebid.server.bidder.contxtful; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Native; +import com.iab.openrtb.request.User; +import com.iab.openrtb.request.Video; +import com.iab.openrtb.response.Bid; +import org.junit.jupiter.api.Test; +import org.prebid.server.VertxTest; +import org.prebid.server.bidder.contxtful.request.ContxtfulBidRequest; +import org.prebid.server.bidder.contxtful.request.ContxtfulBidRequestParams; +import org.prebid.server.bidder.contxtful.request.ContxtfulBidderRequest; +import org.prebid.server.bidder.contxtful.request.ContxtfulCompositeRequest; +import org.prebid.server.bidder.contxtful.request.ContxtfulConfig; +import org.prebid.server.bidder.contxtful.request.ContxtfulConfigDetails; +import org.prebid.server.bidder.contxtful.response.ContxtfulBid; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderCall; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.HttpResponse; +import org.prebid.server.bidder.model.Result; +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.ExtUserPrebid; +import org.prebid.server.proto.openrtb.ext.request.contxtful.ExtImpContxtful; +import org.prebid.server.proto.openrtb.ext.response.BidType; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.prebid.server.bidder.model.BidderError.badServerResponse; +import static org.prebid.server.util.HttpUtil.ACCEPT_HEADER; +import static org.prebid.server.util.HttpUtil.APPLICATION_JSON_CONTENT_TYPE; +import static org.prebid.server.util.HttpUtil.CONTENT_TYPE_HEADER; +import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; + +public class ContxtfulBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "https://test.endpoint.com/?customer={{AccountId}}"; + private static final String BIDDER_NAME = "contxtful"; + + private final ContxtfulBidder target = new ContxtfulBidder(ENDPOINT_URL, jacksonMapper); + + @Test + public void creationShouldFailOnInvalidEndpointUrl() { + assertThatIllegalArgumentException().isThrownBy(() -> new ContxtfulBidder("invalid_url", jacksonMapper)); + } + + @Test + public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { + // given + final BidRequest bidRequest = givenBidRequest( + imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); + assertThat(error.getMessage()).startsWith("Error parsing imp.ext for impression impId"); + }); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldCreateCorrectUri() { + // given + final BidRequest bidRequest = givenBidRequest( + imp -> imp.ext(givenImpExt("customer1", "placement1")), + imp -> imp.ext(givenImpExt("customer2", "placement2"))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1).first() + .extracting(HttpRequest::getUri) + .isEqualTo("https://test.endpoint.com/?customer=customer1"); + } + + @Test + public void makeHttpRequestsShouldSetCorrectHeaders() { + // given + final BidRequest bidRequest = givenBidRequest( + imp -> imp.ext(givenImpExt("customer1", "placement1"))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1).first() + .extracting(HttpRequest::getHeaders) + .satisfies(headers -> { + assertThat(headers.get(CONTENT_TYPE_HEADER)).isEqualTo(APPLICATION_JSON_CONTENT_TYPE); + assertThat(headers.get(ACCEPT_HEADER)).isEqualTo(APPLICATION_JSON_VALUE); + }); + } + + @Test + public void makeHttpRequestsShouldCreateCorrectPayload() { + // given + final BidRequest bidRequest = givenBidRequest( + imp -> imp.id("imp1").ext(givenImpExt("customer1", "placement1")), + imp -> imp.id("imp2").ext(givenImpExt("customer2", "placement2"))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final ContxtfulCompositeRequest expectedPayload = ContxtfulCompositeRequest.builder() + .ortb2Request(bidRequest) + .bidRequests(List.of( + ContxtfulBidRequest.of(BIDDER_NAME, ContxtfulBidRequestParams.of("placement1"), "imp1"), + ContxtfulBidRequest.of(BIDDER_NAME, ContxtfulBidRequestParams.of("placement2"), "imp2"))) + .bidderRequest(ContxtfulBidderRequest.of(BIDDER_NAME)) + .config(ContxtfulConfig.of(ContxtfulConfigDetails.of("v1", "customer1"))) + .build(); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1).first() + .satisfies(httpRequest -> { + assertThat(httpRequest.getPayload()).isEqualTo(expectedPayload); + assertThat(httpRequest.getBody()).isEqualTo(jacksonMapper.encodeToBytes(expectedPayload)); + }); + } + + @Test + public void makeHttpRequestsShouldSendCorrectSetOfImpIds() { + // given + final BidRequest bidRequest = givenBidRequest( + imp -> imp.id("imp1").ext(givenImpExt("customer1", "placement1")), + imp -> imp.id("imp2").ext(givenImpExt("customer2", "placement2"))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final ContxtfulCompositeRequest expectedPayload = ContxtfulCompositeRequest.builder() + .ortb2Request(bidRequest) + .bidRequests(List.of( + ContxtfulBidRequest.of(BIDDER_NAME, ContxtfulBidRequestParams.of("placement1"), "imp1"), + ContxtfulBidRequest.of(BIDDER_NAME, ContxtfulBidRequestParams.of("placement2"), "imp2"))) + .bidderRequest(ContxtfulBidderRequest.of(BIDDER_NAME)) + .config(ContxtfulConfig.of(ContxtfulConfigDetails.of("v1", "customer1"))) + .build(); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1).first() + .extracting(HttpRequest::getImpIds) + .isEqualTo(Set.of("imp1", "imp2")); + } + + @Test + public void makeHttpRequestsShouldModifyUserBuyerUidFromExt() { + // given + final ExtUser extUser = ExtUser.builder() + .prebid(ExtUserPrebid.of(Map.of(BIDDER_NAME, "user-id-from-ext"))) + .build(); + final User user = User.builder().ext(extUser).build(); + final BidRequest bidRequest = givenBidRequest(user, imp -> imp.ext(givenImpExt("customer", "placement"))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1).first() + .extracting(HttpRequest::getPayload) + .extracting(ContxtfulCompositeRequest::getOrtb2Request) + .extracting(BidRequest::getUser) + .isEqualTo(user.toBuilder().buyeruid("user-id-from-ext").build()); + } + + @Test + public void makeBidsShouldReturnErrorWhenResponseBodyCouldNotBeParsed() { + // given + final BidderCall httpCall = BidderCall.succeededHttp( + HttpRequest.builder().build(), + HttpResponse.of(200, null, "invalid_json"), + null); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + assertThat(error.getMessage()).startsWith("Failed to decode: Unrecognized token 'invalid_json'"); + }); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnBannerBid() throws JsonProcessingException { + // given + final ContxtfulBid responseBid = givenContxtfulBid("imp1", "banner", 1); + final BidderCall httpCall = givenHttpCall(singletonList(responseBid)); + final BidRequest bidRequest = BidRequest.builder().imp(singletonList(Imp.builder().id("imp1").build())).build(); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + final Bid expectedBid = Bid.builder() + .id("contxtful-imp1") + .impid("imp1") + .price(BigDecimal.ONE) + .adm("adm") + .w(300) + .h(250) + .crid("crid") + .nurl("nurl") + .burl("burl") + .lurl("lurl") + .ext(mapper.createObjectNode()) + .build(); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, BidType.banner, "USD")); + } + + @Test + public void makeBidsShouldReturnVideoBid() throws JsonProcessingException { + // given + final ContxtfulBid responseBid = givenContxtfulBid("imp1", "video", 2); + final BidderCall httpCall = givenHttpCall(singletonList(responseBid)); + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder().id("imp1").video(Video.builder().build()).build())).build(); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + final Bid expectedBid = Bid.builder() + .id("contxtful-imp1") + .impid("imp1") + .price(BigDecimal.valueOf(2)) + .adm("adm") + .w(300) + .h(250) + .crid("crid") + .nurl("nurl") + .burl("burl") + .lurl("lurl") + .ext(mapper.createObjectNode()) + .build(); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, BidType.video, "USD")); + } + + @Test + public void makeBidsShouldReturnNativeBid() throws JsonProcessingException { + // given + final ContxtfulBid responseBid = givenContxtfulBid("imp1", "native", 2); + final BidderCall httpCall = givenHttpCall(singletonList(responseBid)); + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder().id("imp1").xNative(Native.builder().build()).build())) + .build(); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + final Bid expectedBid = Bid.builder() + .id("contxtful-imp1") + .impid("imp1") + .price(BigDecimal.valueOf(2)) + .adm("adm") + .w(300) + .h(250) + .crid("crid") + .nurl("nurl") + .burl("burl") + .lurl("lurl") + .ext(mapper.createObjectNode()) + .build(); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, BidType.xNative, "USD")); + } + + @Test + public void makeBidsShouldReturnErrorsForInvalidBids() throws JsonProcessingException { + // given + final ContxtfulBid validBid = givenContxtfulBid("imp1", "anyType", 1); + final ContxtfulBid invalidBidNoAdm = ContxtfulBid.builder() + .requestId("imp2") + .mediaType("banner") + .cpm(BigDecimal.ONE) + .adm("") + .build(); + final ContxtfulBid invalidBidNoMediaType = ContxtfulBid.builder() + .requestId("imp3") + .mediaType("") + .cpm(BigDecimal.ONE) + .adm("adm") + .build(); + + final BidderCall httpCall = givenHttpCall( + List.of(validBid, invalidBidNoAdm, invalidBidNoMediaType)); + final BidRequest bidRequest = BidRequest.builder() + .imp(List.of( + Imp.builder().id("imp1").build(), + Imp.builder().id("imp2").build(), + Imp.builder().id("imp3").build())) + .build(); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + final Bid expectedBid = Bid.builder() + .id("contxtful-imp1") + .impid("imp1") + .price(BigDecimal.ONE) + .adm("adm") + .w(300) + .h(250) + .crid("crid") + .nurl("nurl") + .burl("burl") + .lurl("lurl") + .ext(mapper.createObjectNode()) + .build(); + + assertThat(result.getErrors()).containsExactlyInAnyOrder( + badServerResponse("bid imp2 has no ad markup"), + badServerResponse("bid imp3 has no ad media type")); + + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, BidType.banner, "USD")); + } + + private static BidRequest givenBidRequest(UnaryOperator... impCustomizers) { + return givenBidRequest(null, impCustomizers); + } + + private static BidRequest givenBidRequest(User user, UnaryOperator... impCustomizers) { + final List imps = Stream.of(impCustomizers) + .map(customizer -> customizer.apply(Imp.builder().id("impId")).build()) + .toList(); + + return BidRequest.builder().imp(imps).user(user).build(); + } + + private static ObjectNode givenImpExt(String customerId, String placementId) { + return mapper.valueToTree(ExtPrebid.of(null, ExtImpContxtful.of(placementId, customerId))); + } + + private static ContxtfulBid givenContxtfulBid(String requestId, String mediaType, int price) { + return ContxtfulBid.builder() + .requestId(requestId) + .mediaType(mediaType) + .cpm(BigDecimal.valueOf(price)) + .adm("adm") + .width(300) + .height(250) + .creativeId("crid") + .nurl("nurl") + .burl("burl") + .lurl("lurl") + .ext(mapper.createObjectNode()) + .build(); + } + + private static BidderCall givenHttpCall(List bids) + throws JsonProcessingException { + + return BidderCall.succeededHttp( + HttpRequest.builder().build(), + HttpResponse.of(200, null, mapper.writeValueAsString(bids)), + null); + } +} diff --git a/src/test/java/org/prebid/server/bidder/eplanning/EplanningBidderTest.java b/src/test/java/org/prebid/server/bidder/eplanning/EplanningBidderTest.java index 8b94f14c737..6db871bc6bd 100644 --- a/src/test/java/org/prebid/server/bidder/eplanning/EplanningBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/eplanning/EplanningBidderTest.java @@ -615,6 +615,7 @@ public void makeBidsShouldReturnBannerBidWithExpectedFields() throws JsonProcess .price("3.3") .adM("some-adm") .crId("CR-ID") + .adom("test.com") .width(500) .height(300) .build())))))); @@ -632,6 +633,7 @@ public void makeBidsShouldReturnBannerBidWithExpectedFields() throws JsonProcess .price(BigDecimal.valueOf(3.3)) .adm("some-adm") .crid("CR-ID") + .adomain(List.of("test.com")) .w(500) .h(300) .build(); @@ -653,6 +655,7 @@ public void makeBidsShouldReturnBannerBidIfMissingAdunitCode() throws JsonProces .price("3.3") .adM("some-adm") .crId("CR-ID") + .adom("test.com") .width(1) .height(1) .build())))))); @@ -671,6 +674,7 @@ public void makeBidsShouldReturnBannerBidIfMissingAdunitCode() throws JsonProces .price(BigDecimal.valueOf(3.3)) .adm("some-adm") .crid("CR-ID") + .adomain(List.of("test.com")) .w(1) .h(1) .build(); diff --git a/src/test/java/org/prebid/server/bidder/mobkoi/MobkoiBidderTest.java b/src/test/java/org/prebid/server/bidder/mobkoi/MobkoiBidderTest.java index 1c98d21a3ca..7d46ce2a0a8 100644 --- a/src/test/java/org/prebid/server/bidder/mobkoi/MobkoiBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/mobkoi/MobkoiBidderTest.java @@ -63,7 +63,7 @@ public void makeHttpRequestsShouldReturnErrorWhenRequestHasInvalidExtImpression( @Test public void makeHttpRequestsShouldReturnErrorWhenRequestHasMissingTagIdAndPlacementId() { // given - final ObjectNode mobkoiExt = impExt(null, null); + final ObjectNode mobkoiExt = impExt(null); final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(mobkoiExt)); // when @@ -79,7 +79,7 @@ public void makeHttpRequestsShouldReturnErrorWhenRequestHasMissingTagIdAndPlacem @Test public void makeHttpRequestsShouldAddPlacementIdOnlyInFirstImpressionTagId() { // given - final ObjectNode mobkoiExt = impExt("pid", null); + final ObjectNode mobkoiExt = impExt("pid"); final Imp givenImp1 = givenImp(impBuilder -> impBuilder.ext(mobkoiExt)); final Imp givenImp2 = givenImp(identity()); final BidRequest bidRequest = BidRequest.builder().imp(asList(givenImp1, givenImp2)).build(); @@ -95,48 +95,6 @@ public void makeHttpRequestsShouldAddPlacementIdOnlyInFirstImpressionTagId() { .containsExactly("pid", null); } - @Test - public void makeHttpRequestsShouldUseConstructorEndpointWhenNoCustomEndpointIsDefinedInMobkoiExtension() { - // given - final ObjectNode mobkoiExt = impExt("pid", null); - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(mobkoiExt)); - - // when - final Result>> results = target.makeHttpRequests(bidRequest); - - // then - assertThat(results.getValue()).extracting(HttpRequest::getUri).containsExactly("https://test.endpoint.com/bid"); - assertThat(results.getErrors()).isEmpty(); - } - - @Test - public void makeHttpRequestsShouldConstructWithDefaultEndpointWhenTheCustomURLIsInvalidInMobkoiExtension() { - // given - final ObjectNode mobkoiExt = impExt("pid", "invalid URI"); - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(mobkoiExt)); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).extracting(HttpRequest::getUri).containsExactly("https://test.endpoint.com/bid"); - assertThat(result.getErrors()).isEmpty(); - } - - @Test - public void makeHttpRequestsShouldUseCustomEndpointWhenDefinedInMobkoiExtension() { - // given - final ObjectNode mobkoiExt = impExt("pid", "https://custom.endpoint.com"); - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(mobkoiExt)); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).extracting(HttpRequest::getUri).containsExactly("https://custom.endpoint.com/bid"); - assertThat(result.getErrors()).isEmpty(); - } - @Test public void makeHttpRequestsShouldOverrideUserExtAndSetConsent() { // given @@ -235,11 +193,11 @@ private static BidRequest givenBidRequest( } private static Imp givenImp(UnaryOperator impCustomizer) { - return impCustomizer.apply(Imp.builder().id("imp_id").ext(impExt("placementIdValue", null))).build(); + return impCustomizer.apply(Imp.builder().id("imp_id").ext(impExt("placementIdValue"))).build(); } - private static ObjectNode impExt(String placementId, String adServerBaseUrl) { - return mapper.valueToTree(ExtPrebid.of(null, ExtImpMobkoi.of(placementId, adServerBaseUrl))); + private static ObjectNode impExt(String placementId) { + return mapper.valueToTree(ExtPrebid.of(null, ExtImpMobkoi.of(placementId))); } private static BidResponse givenBidResponse(UnaryOperator bidCustomizer) { diff --git a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java new file mode 100644 index 00000000000..acb15b91bb5 --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java @@ -0,0 +1,361 @@ +package org.prebid.server.bidder.showheroes; + +import com.fasterxml.jackson.core.JsonProcessingException; +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.Site; +import com.iab.openrtb.request.Source; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.VertxTest; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderCall; +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.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.showheroes.ExtImpShowheroes; +import org.prebid.server.version.PrebidVersionProvider; + +import java.math.BigDecimal; +import java.util.List; +import java.util.function.Function; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.BDDMockito.given; +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.assertj.core.api.Assertions.tuple; +import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; +import static org.prebid.server.proto.openrtb.ext.response.BidType.video; +import static org.prebid.server.util.HttpUtil.ACCEPT_HEADER; +import static org.prebid.server.util.HttpUtil.APPLICATION_JSON_CONTENT_TYPE; +import static org.prebid.server.util.HttpUtil.CONTENT_TYPE_HEADER; +import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; + +@ExtendWith(MockitoExtension.class) +public class ShowheroesBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "https://ads.showheroes.com/"; + + @Mock(strictness = LENIENT) + private CurrencyConversionService currencyConversionService; + + @Mock(strictness = LENIENT) + private PrebidVersionProvider prebidVersionProvider; + + private ShowheroesBidder target; + + @BeforeEach + public void setUp() { + given(prebidVersionProvider.getNameVersionRecord()).willReturn("test_version"); + target = new ShowheroesBidder(ENDPOINT_URL, currencyConversionService, prebidVersionProvider, jacksonMapper); + } + + @Test + public void creationShouldFailOnInvalidEndpointUrl() { + assertThatIllegalArgumentException().isThrownBy(() -> new ShowheroesBidder("invalid_url", + currencyConversionService, prebidVersionProvider, jacksonMapper)); + } + + @Test + public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { + // given + final BidRequest bidRequest = BidRequest.builder() + .site(Site.builder().page("https://test-example.com").build()) + .imp(singletonList(Imp.builder() + .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()))) + .build())) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().get(0).getMessage()).startsWith("Cannot deserialize value"); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnErrorWhenSitePageIsEmpty() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) + .site(Site.builder().build()) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().get(0).getMessage()).isEqualTo("BidRequest.site.page is required"); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnErrorWhenAppBundleIsEmpty() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) + .app(App.builder().build()) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().get(0).getMessage()).isEqualTo("BidRequest.app.bundle is required"); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldCreateCorrectURL() { + // given + final BidRequest bidRequest = givenBidRequest(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + assertThat(result.getValue().get(0).getUri()).isEqualTo(ENDPOINT_URL); + } + + @Test + public void makeHttpRequestsShouldReturnPbsVersion() { + // given + final BidRequest bidRequest = givenBidRequest(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getSource) + .extracting(Source::getExt) + .extracting(ext -> ext.getProperty("pbs")) + .containsExactly(mapper.createObjectNode() + .put("pbsv", "test_version") + .put("pbsp", "java")); + } + + @Test + public void makeHttpRequestsShouldConvertCurrencyFromUsdToEur() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(List.of(givenImp(impBuilder -> impBuilder.bidfloor(BigDecimal.ONE).bidfloorcur("USD")))) + .app(App.builder().bundle("test_bundle").build()) + .build(); + + given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) + .willReturn(BigDecimal.TEN); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + verify(currencyConversionService).convertCurrency(eq(BigDecimal.ONE), any(), eq("USD"), eq("EUR")); + assertThat(result.getValue()).hasSize(1).doesNotContainNull() + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .flatExtracting(BidRequest::getImp).doesNotContainNull() + .extracting(Imp::getBidfloor, Imp::getBidfloorcur) + .containsOnly(tuple(BigDecimal.TEN, "EUR")); + } + + @Test + public void makeHttpRequestsShouldNotConvertCurrencyEur() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(List.of(givenImp(impBuilder -> impBuilder.bidfloor(BigDecimal.ONE).bidfloorcur("EUR")))) + .app(App.builder().bundle("test_bundle").build()) + .build(); + + given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) + .willReturn(BigDecimal.TEN); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + verify(currencyConversionService, never()).convertCurrency(any(), any(), anyString(), anyString()); + assertThat(result.getValue()).hasSize(1).doesNotContainNull() + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .flatExtracting(BidRequest::getImp).doesNotContainNull() + .extracting(Imp::getBidfloor, Imp::getBidfloorcur) + .containsOnly(tuple(BigDecimal.ONE, "EUR")); + } + + @Test + public void makeHttpRequestsShouldCreateSingleRequestForAllImps() { + // given + final BidRequest bidRequest = BidRequest.builder() + .site(Site.builder().page("https://test-example.com").build()) + .imp(List.of( + givenImp(impBuilder -> impBuilder.id("imp1")), + givenImp(impBuilder -> impBuilder.id("imp2")))) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getId) + .containsExactly("imp1", "imp2"); + } + + @Test + public void makeHttpRequestsShouldSetCorrectHeaders() { + // given + final BidRequest bidRequest = givenBidRequest(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1).first() + .extracting(HttpRequest::getHeaders) + .satisfies(headers -> assertThat(headers.get(CONTENT_TYPE_HEADER)) + .isEqualTo(APPLICATION_JSON_CONTENT_TYPE)) + .satisfies(headers -> assertThat(headers.get(ACCEPT_HEADER)) + .isEqualTo(APPLICATION_JSON_VALUE)); + } + + @Test + public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(BidResponse.builder().build()); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsEmpty() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(BidResponse.builder().seatbid(List.of()).build()); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnVideoBidByDefaultWhenMtypeIsUnknown() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(99))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsOnly(BidderBid.of(Bid.builder().impid("123").mtype(99).build(), video, null)); + } + + @Test + public void makeBidsShouldReturnMultipleBids() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidResponse.builder() + .seatbid(singletonList(SeatBid.builder() + .bid(List.of( + Bid.builder().impid("123").mtype(1).price(BigDecimal.ONE).build(), + Bid.builder().impid("456").mtype(2).price(BigDecimal.TEN).build())) + .build())) + .build()); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(2) + .extracting(BidderBid::getBid, BidderBid::getType) + .containsExactlyInAnyOrder( + tuple(Bid.builder().impid("123").mtype(1).price(BigDecimal.ONE).build(), banner), + tuple(Bid.builder().impid("456").mtype(2).price(BigDecimal.TEN).build(), video)); + } + + private static BidRequest givenBidRequest() { + return BidRequest.builder() + .site(Site.builder().page("https://test-example.com").build()) + .imp(singletonList(givenImp(identity()))) + .build(); + } + + private static Imp givenImp(Function impCustomizer) { + return impCustomizer.apply(Imp.builder() + .id("123") + .banner(Banner.builder().build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpShowheroes.of("unitId"))))) + .build(); + } + + private static BidResponse givenBidResponse(Function bidCustomizer) { + return BidResponse.builder() + .seatbid(singletonList(SeatBid.builder() + .bid(singletonList(bidCustomizer.apply(Bid.builder()).build())) + .build())) + .build(); + } + + private static BidderCall givenHttpCall(BidResponse bidResponse) + throws JsonProcessingException { + + return BidderCall.succeededHttp( + null, + HttpResponse.of(200, null, mapper.writeValueAsString(bidResponse)), + null); + } +} diff --git a/src/test/java/org/prebid/server/bidder/smilewanted/SmileWantedBidderTest.java b/src/test/java/org/prebid/server/bidder/smilewanted/SmileWantedBidderTest.java index 4dabe7e0bfb..26085bff972 100644 --- a/src/test/java/org/prebid/server/bidder/smilewanted/SmileWantedBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smilewanted/SmileWantedBidderTest.java @@ -17,8 +17,12 @@ 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.smilewanted.ExtImpSmilewanted; import org.prebid.server.util.HttpUtil; +import java.math.BigDecimal; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -32,7 +36,7 @@ public class SmileWantedBidderTest extends VertxTest { - private static final String ENDPOINT_URL = "https://{{Host}}/test?param={{PublisherId}}"; + private static final String ENDPOINT_URL = "https://prebid-server.smilewanted.com/java/{{ZoneId}}"; private final SmileWantedBidder target = new SmileWantedBidder(ENDPOINT_URL, jacksonMapper); @@ -41,10 +45,76 @@ public void creationShouldFailOnInvalidEndpointUrl() { assertThatIllegalArgumentException().isThrownBy(() -> new SmileWantedBidder("invalid_url", jacksonMapper)); } + @Test + public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()))) + .build())) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); + assertThat(error.getMessage()).startsWith("Missing bidder ext in impression with id: 123"); + }); + } + + @Test + public void makeHttpRequestsShouldReturnSingleRequest() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(List.of( + givenImp("zone123"), + Imp.builder() + .id("456") + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmilewanted.of("zone123")))) + .build())) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + final HttpRequest httpRequest = result.getValue().get(0); + assertThat(httpRequest.getPayload()).isNotNull(); + assertThat(httpRequest.getPayload().getImp()).hasSize(2); + assertThat(httpRequest.getPayload().getAt()).isEqualTo(1); + assertThat(httpRequest.getImpIds()).containsExactlyInAnyOrder("123", "456"); + } + + @Test + public void makeHttpRequestsShouldBuildCorrectEndpointUrlWithZoneId() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp("zone456"))) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getUri) + .containsExactly("https://prebid-server.smilewanted.com/java/zone456"); + } + @Test public void makeHttpRequestsShouldCorrectlyAddHeaders() { // given - final BidRequest bidRequest = BidRequest.builder().build(); + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp("zone123"))) + .build(); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -65,7 +135,9 @@ public void makeHttpRequestsShouldCorrectlyAddHeaders() { @Test public void makeHttpRequestsShouldSetAtToOne() { // given - final BidRequest bidRequest = BidRequest.builder().build(); + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp("zone123"))) + .build(); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -98,7 +170,7 @@ public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { @Test public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(null)); + final BidderCall httpCall = givenHttpCall(null, (BidResponse) null); // when final Result> result = target.makeBids(httpCall, null); @@ -111,8 +183,7 @@ public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProces @Test public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(BidResponse.builder().build())); + final BidderCall httpCall = givenHttpCall(null, BidResponse.builder().build()); // when final Result> result = target.makeBids(httpCall, null); @@ -130,8 +201,7 @@ public void makeBidsShouldReturnVideoBidIfVideoIsPresentInRequestImpAndCorrespon BidRequest.builder() .imp(singletonList(Imp.builder().id("123").video(Video.builder().build()).build())) .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); + givenBidResponse(bidBuilder -> bidBuilder.impid("123"))); // when final Result> result = target.makeBids(httpCall, null); @@ -148,8 +218,7 @@ public void makeBidsShouldReturnBannerBidIfVideoIsAbsentInRequestImp() throws Js final BidderCall httpCall = givenHttpCall(BidRequest.builder() .imp(singletonList(Imp.builder().id("123").build())) .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); + givenBidResponse(bidBuilder -> bidBuilder.impid("123"))); // when final Result> result = target.makeBids(httpCall, null); @@ -160,6 +229,102 @@ public void makeBidsShouldReturnBannerBidIfVideoIsAbsentInRequestImp() throws Js .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), banner, null)); } + @Test + public void makeBidsShouldReturnMultipleBidsFromSingleSeatBid() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(List.of( + Imp.builder().id("123").build(), + Imp.builder().id("456").video(Video.builder().build()).build())) + .build(), + BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder() + .bid(List.of( + Bid.builder().impid("123").price(BigDecimal.valueOf(1.0)).build(), + Bid.builder().impid("456").price(BigDecimal.valueOf(2.0)).build())) + .build())) + .build()); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(2) + .containsExactlyInAnyOrder( + BidderBid.of(Bid.builder().impid("123").price(BigDecimal.valueOf(1.0)).build(), banner, "USD"), + BidderBid.of(Bid.builder().impid("456").price(BigDecimal.valueOf(2.0)).build(), video, "USD")); + } + + @Test + public void makeBidsShouldFilterNullBids() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").build())) + .build(), + BidResponse.builder() + .seatbid(singletonList(SeatBid.builder() + .bid(Arrays.asList( + Bid.builder().impid("123").build(), + null, + Bid.builder().impid("456").build())) + .build())) + .build()); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(2) + .extracting(BidderBid::getBid) + .extracting(Bid::getImpid) + .containsExactlyInAnyOrder("123", "456"); + } + + @Test + public void makeBidsShouldReturnBidWithCurrency() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").build())) + .build(), + BidResponse.builder() + .cur("EUR") + .seatbid(singletonList(SeatBid.builder() + .bid(singletonList(Bid.builder().impid("123").build())) + .build())) + .build()); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getBidCurrency) + .containsExactly("EUR"); + } + + @Test + public void makeBidsShouldReturnEmptyListIfSeatBidIsEmpty() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, + BidResponse.builder() + .seatbid(singletonList(SeatBid.builder().build())) + .build()); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + private static BidResponse givenBidResponse(Function bidCustomizer) { return BidResponse.builder() .seatbid(singletonList(SeatBid.builder().bid(singletonList(bidCustomizer.apply(Bid.builder()).build())) @@ -173,4 +338,16 @@ private static BidderCall givenHttpCall(BidRequest bidRequest, Strin HttpResponse.of(200, null, body), null); } + + private static BidderCall givenHttpCall(BidRequest bidRequest, BidResponse bidResponse) + throws JsonProcessingException { + return givenHttpCall(bidRequest, mapper.writeValueAsString(bidResponse)); + } + + private static Imp givenImp(String zoneId) { + return Imp.builder() + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmilewanted.of(zoneId)))) + .build(); + } } 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 68d4df4d00d..68f6bd9d0ec 100644 --- a/src/test/java/org/prebid/server/bidder/sovrn/SovrnBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/sovrn/SovrnBidderTest.java @@ -158,26 +158,8 @@ public void makeHttpRequestsShouldReturnResultWithHttpRequestContainingExpectedF .extracting(HttpRequest::getBody) .extracting(SovrnBidderTest::mappedToBidRequest) .flatExtracting(BidRequest::getImp) - .extracting(Imp::getBidfloor, Imp::getTagid, e -> e.getExt().get("adunitcode")) - .containsExactly(tuple(BigDecimal.TEN, "tagid", mapper.valueToTree("sovrn_auc"))); - } - - @Test - public void makeHttpRequestsShouldSetAdUnitCodeFromExtIfPresent() { - // given - final BidRequest bidRequest = givenBidRequest(identity()); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getBody) - .extracting(SovrnBidderTest::mappedToBidRequest) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getExt) - .extracting(e -> e.get("adunitcode")) - .containsExactly(mapper.valueToTree("sovrn_auc")); + .extracting(Imp::getBidfloor, Imp::getTagid, e -> e.getExt().get("bidder").get("adunitcode")) + .containsExactly(tuple(BigDecimal.TEN, "tagid", mapper.valueToTree("sovrn_auc_bidder"))); } @Test @@ -545,7 +527,7 @@ private static Imp givenImp(UnaryOperator impCustomizer) { .protocols(singletonList(1)) .build()) .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSovrn.of("tagid", - "legacyTagId", BigDecimal.TEN, "sovrn_auc"))))) + "legacyTagId", BigDecimal.TEN, "sovrn_auc_bidder"))))) .build(); } diff --git a/src/test/java/org/prebid/server/bidder/yandex/YandexBidderTest.java b/src/test/java/org/prebid/server/bidder/yandex/YandexBidderTest.java index dbc3cf23856..26322943187 100644 --- a/src/test/java/org/prebid/server/bidder/yandex/YandexBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/yandex/YandexBidderTest.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.iab.openrtb.request.Audio; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Device; @@ -36,7 +35,6 @@ 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.prebid.server.proto.openrtb.ext.response.BidType.audio; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.video; import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; @@ -144,7 +142,7 @@ public void makeHttpRequestsShouldCreateARequestForEachImpAndSkipImpsWithNoBanne final Result>> result = target.makeHttpRequests(bidRequest); // then assertThat(result.getErrors()).containsExactly( - BidderError.badInput("Yandex only supports banner and native types. Ignoring imp id #blockA") + BidderError.badInput("Imp #blockA must contain at least one valid format (banner, video, or native)") ); } @@ -184,7 +182,6 @@ public void makeHttpRequestsShouldReturnErrorWhenBannerHasNoFormats() { final BidRequest bidRequest = givenBidRequest( impBuilder -> impBuilder.id("blockA").banner(Banner.builder().build()), identity()); - // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -215,6 +212,208 @@ public void makeHttpRequestsSetFirstImpressionBannerWidthAndHeightWhenFromFirstF .containsOnly(tuple(300, 600)); } + @Test + public void makeHttpRequestsShouldReturnErrorWhenVideoWidthIsZero() { + // given + final BidRequest bidRequest = givenBidRequest( + impBuilder -> impBuilder.id("blockA").banner(null).video(Video.builder().w(0).h(600).build()), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly(BidderError.badInput("Invalid sizes provided for Video 0x600")); + } + + @Test + public void makeHttpRequestsShouldReturnErrorWhenVideoHeightIsZero() { + // given + final BidRequest bidRequest = givenBidRequest( + impBuilder -> impBuilder.id("blockA").banner(null).video(Video.builder().w(300).h(0).build()), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly(BidderError.badInput("Invalid sizes provided for Video 300x0")); + } + + @Test + public void makeHttpRequestsShouldModifyVideoParameters() { + // given + final BidRequest bidRequest = givenBidRequest( + impBuilder -> impBuilder.id("blockA").banner(null) + .video(Video.builder().w(300).h(600).build()), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getVideo) + .extracting(Video::getMinduration, Video::getMaxduration, Video::getProtocols) + .containsOnly(tuple(1, 120, singletonList(3))); + } + + @Test + public void makeHttpRequestsShouldSetExpectedHeaders() { + // given + final BidRequest bidRequest = givenBidRequest(identity(), + requestBuilder -> requestBuilder.site(Site.builder().id("1").page("https://example.com/path?query=value").build()) + .device(Device.builder().ua("UA").language("EN").ip("127.0.0.1").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue().getFirst().getHeaders()) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsOnly(tuple("Accept-Language", "EN"), + tuple("User-Agent", "UA"), + tuple("X-Forwarded-For", "127.0.0.1"), + tuple("X-Real-Ip", "127.0.0.1"), + tuple("Content-Type", "application/json;charset=utf-8"), + tuple("Accept", "application/json"), + tuple("x-openrtb-version", "2.5"), + tuple("Referer", "https://example.com/path?query=value")); + } + + @Test + public void makeHttpRequestsShouldCreateCorrectURL() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(impBuilder -> impBuilder.id("blockA").ext(givenImpExt(1))))) + .site(Site.builder().id("1").page("https://example.com/path?query=value").build()) + .cur(asList("EUR", "USD")) + .build(); + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).extracting(HttpRequest::getUri) + .containsExactly("https://test.endpoint.com/?" + + "target-ref=https%3A%2F%2Fexample.com%2Fpath%3Fquery%3Dvalue&ssp-cur=EUR"); + } + + @Test + public void makeHttpRequestsShouldSupportMultiFormatImpression() { + // given + final BidRequest bidRequest = BidRequest.builder() + .site(Site.builder().id("1").build()) + .imp(singletonList( + Imp.builder().id("multiFormatImp") + .banner(Banner.builder().w(300).h(600).build()) + .video(Video.builder().w(300).h(600).build()) + .xNative(Native.builder().build()) + .ext(givenImpExt(1)) + .build())) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + + final Imp modifiedImp = result.getValue().getFirst().getPayload().getImp().getFirst(); + assertThat(modifiedImp.getBanner()).isNotNull(); + assertThat(modifiedImp.getVideo()).isNotNull(); + assertThat(modifiedImp.getXNative()).isNotNull(); + assertThat(modifiedImp.getDisplaymanager()).isEqualTo("prebid.java"); + assertThat(modifiedImp.getDisplaymanagerver()).isEqualTo("1.1"); + } + + @Test + public void makeHttpRequestsShouldSupportMultiFormatImpressionWithPartialErrors() { + // given + final BidRequest bidRequest = BidRequest.builder() + .site(Site.builder().id("1").build()) + .imp(singletonList( + Imp.builder().id("multiFormatImpWithErrors") + .banner(Banner.builder().w(0).h(0).build()) // Invalid banner + .video(Video.builder().w(300).h(600).build()) // Valid video + .xNative(Native.builder().build()) // Valid native + .ext(givenImpExt(1)) + .build())) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).containsExactly( + BidderError.badInput("Invalid sizes provided for Banner 0x0")); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnErrorWhenNoValidFormats() { + // given + final BidRequest bidRequest = BidRequest.builder() + .site(Site.builder().id("1").build()) + .imp(singletonList( + Imp.builder().id("noValidFormats") + .banner(Banner.builder().w(0).h(0).build()) // Invalid banner + .video(Video.builder().w(0).h(0).build()) // Invalid video + .ext(givenImpExt(1)) + .build())) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()) + .containsExactly(BidderError.badInput("Invalid sizes provided for Banner 0x0")); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldSetDisplayManagerAndVersionForAllImpTypes() { + // given + final BidRequest bidRequest = BidRequest.builder() + .site(Site.builder().id("1").build()) + .imp(asList( + Imp.builder().id("bannerImp") + .banner(Banner.builder().w(300).h(600).build()) + .ext(givenImpExt(1)) + .build(), + Imp.builder().id("videoImp") + .video(Video.builder().w(300).h(600).build()) + .ext(givenImpExt(2)) + .build(), + Imp.builder().id("nativeImp") + .xNative(Native.builder().build()) + .ext(givenImpExt(3)) + .build())) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(3) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getDisplaymanager, Imp::getDisplaymanagerver) + .containsOnly( + tuple("prebid.java", "1.1"), + tuple("prebid.java", "1.1"), + tuple("prebid.java", "1.1")); + } + @Test public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { // given @@ -294,18 +493,16 @@ public void makeBidsShouldReturnErrorWhenBidImpIdIsNotPresent() throws JsonProce } @Test - public void makeBidsShouldReturnBannerAndNative() throws JsonProcessingException { + public void makeBidsShouldReturnVideoBid() throws JsonProcessingException { // given final BidderCall bidderCall = givenBidderCall( BidRequest.builder() - .imp(asList(Imp.builder().id("blockA").xNative(Native.builder().build()).build(), - Imp.builder().id("blockB").banner(Banner.builder().build()).build())) + .imp(singletonList(Imp.builder().id("blockA").video(Video.builder().build()).build())) .build(), mapper.writeValueAsString(BidResponse.builder() .cur("USD") .seatbid(singletonList(SeatBid.builder() - .bid(asList(Bid.builder().impid("blockA").build(), - Bid.builder().impid("blockB").build())) + .bid(singletonList(Bid.builder().impid("blockA").build())) .build())) .build())); @@ -315,23 +512,20 @@ public void makeBidsShouldReturnBannerAndNative() throws JsonProcessingException // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .containsOnly(BidderBid.of(Bid.builder().impid("blockA").build(), xNative, "USD"), - BidderBid.of(Bid.builder().impid("blockB").build(), banner, "USD")); + .containsOnly(BidderBid.of(Bid.builder().impid("blockA").build(), video, "USD")); } @Test - public void makeBidsShouldReturnVideoAndAudio() throws JsonProcessingException { + public void makeBidsShouldReturnBannerBid() throws JsonProcessingException { // given final BidderCall bidderCall = givenBidderCall( BidRequest.builder() - .imp(asList(Imp.builder().id("blockA").video(Video.builder().build()).build(), - Imp.builder().id("blockB").audio(Audio.builder().build()).build())) + .imp(singletonList(Imp.builder().id("blockB").banner(Banner.builder().build()).build())) .build(), mapper.writeValueAsString(BidResponse.builder() .cur("USD") .seatbid(singletonList(SeatBid.builder() - .bid(asList(Bid.builder().impid("blockA").build(), - Bid.builder().impid("blockB").build())) + .bid(singletonList(Bid.builder().impid("blockB").build())) .build())) .build())); @@ -341,21 +535,20 @@ public void makeBidsShouldReturnVideoAndAudio() throws JsonProcessingException { // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .containsOnly(BidderBid.of(Bid.builder().impid("blockA").build(), video, "USD"), - BidderBid.of(Bid.builder().impid("blockB").build(), audio, "USD")); + .containsOnly(BidderBid.of(Bid.builder().impid("blockB").build(), banner, "USD")); } @Test - public void makeBidsShouldReturnError() throws JsonProcessingException { + public void makeBidsShouldReturnNativeBid() throws JsonProcessingException { // given final BidderCall bidderCall = givenBidderCall( BidRequest.builder() - .imp(singletonList(Imp.builder().id("blockA").build())) + .imp(singletonList(Imp.builder().id("blockC").xNative(Native.builder().build()).build())) .build(), mapper.writeValueAsString(BidResponse.builder() .cur("USD") .seatbid(singletonList(SeatBid.builder() - .bid(singletonList(Bid.builder().impid("blockA").build())) + .bid(singletonList(Bid.builder().impid("blockC").build())) .build())) .build())); @@ -363,50 +556,57 @@ public void makeBidsShouldReturnError() throws JsonProcessingException { final Result> result = target.makeBids(bidderCall, null); // then - assertThat(result.getErrors()).containsExactly( - BidderError.badServerResponse( - "Processing an invalid impression; cannot resolve impression type for imp #blockA")); - assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsOnly(BidderBid.of(Bid.builder().impid("blockC").build(), xNative, "USD")); } @Test - public void makeHttpRequestsShouldSetExpectedHeaders() { + public void makeBidsShouldReturnError() throws JsonProcessingException { // given - final BidRequest bidRequest = givenBidRequest(identity(), - requestBuilder -> requestBuilder.site(null) - .device(Device.builder().ua("UA").language("EN").ip("127.0.0.1").build())); + final BidderCall bidderCall = givenBidderCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("blockA").build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder() + .bid(singletonList(Bid.builder().impid("blockA").build())) + .build())) + .build())); // when - final Result>> result = target.makeHttpRequests(bidRequest); + final Result> result = target.makeBids(bidderCall, null); // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue().getFirst().getHeaders()) - .extracting(Map.Entry::getKey, Map.Entry::getValue) - .containsOnly(tuple("Accept-Language", "EN"), - tuple("User-Agent", "UA"), - tuple("X-Forwarded-For", "127.0.0.1"), - tuple("X-Real-Ip", "127.0.0.1"), - tuple("Content-Type", "application/json;charset=utf-8"), - tuple("Accept", "application/json")); + assertThat(result.getErrors()).containsExactly( + BidderError.badServerResponse( + "Processing an invalid impression; cannot resolve impression type for imp #blockA")); + assertThat(result.getValue()).isEmpty(); } @Test - public void makeHttpRequestsShouldCreateCorrectURL() { + public void makeBidsShouldReturnCorrectBidTypeForMultiFormatImpression() throws JsonProcessingException { // given final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(givenImp(impBuilder -> impBuilder.id("blockA").ext(givenImpExt(1))))) - .site(Site.builder().id("1").page("https://domain.com/").build()) - .cur(asList("EUR", "USD")) + .imp(singletonList( + Imp.builder().id("multiFormatImp") + .banner(Banner.builder().w(300).h(600).build()) + .video(Video.builder().w(300).h(600).build()) + .xNative(Native.builder().build()) + .build())) .build(); + + final BidResponse bidResponse = givenBidResponse(bidBuilder -> bidBuilder.impid("multiFormatImp")); + // when - final Result>> result = target.makeHttpRequests(bidRequest); + final Result> result = target.makeBids( + givenBidderCall(bidRequest, mapper.writeValueAsString(bidResponse)), bidRequest); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).extracting(HttpRequest::getUri) - .containsExactly("https://test.endpoint.com/?" - + "target-ref=https%3A%2F%2Fdomain.com%2F&ssp-cur=EUR"); + assertThat(result.getValue()).hasSize(1); + assertThat(result.getValue().getFirst().getType()).isEqualTo(video); // Video has highest priority } private static BidRequest givenBidRequest( diff --git a/src/test/java/org/prebid/server/it/ContxtfulTest.java b/src/test/java/org/prebid/server/it/ContxtfulTest.java new file mode 100644 index 00000000000..2172b904114 --- /dev/null +++ b/src/test/java/org/prebid/server/it/ContxtfulTest.java @@ -0,0 +1,34 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.prebid.server.model.Endpoint; + +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; + +public class ContxtfulTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromContxtful() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/contxtful-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/contxtful/test-contxtful-bid-request.json"))) + .willReturn(aResponse().withBody( + jsonFrom("openrtb2/contxtful/test-contxtful-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/contxtful/test-auction-contxtful-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/contxtful/test-auction-contxtful-response.json", response, + singletonList("contxtful")); + } +} diff --git a/src/test/java/org/prebid/server/it/EplanningTest.java b/src/test/java/org/prebid/server/it/EplanningTest.java index 432456d8526..d8e95e45b2f 100644 --- a/src/test/java/org/prebid/server/it/EplanningTest.java +++ b/src/test/java/org/prebid/server/it/EplanningTest.java @@ -18,7 +18,7 @@ public class EplanningTest extends IntegrationTest { public void openrtb2AuctionShouldRespondWithBidsFromEplanning() throws IOException, JSONException { // given WIRE_MOCK_RULE.stubFor(get(urlPathEqualTo("/eplanning-exchange/12345/1/www.example.com/ROS")) - .willReturn(aResponse().withBody(jsonFrom("openrtb2/eplanning/test-eplanning-bid-response-1.json")))); + .willReturn(aResponse().withBody(jsonFrom("openrtb2/eplanning/test-eplanning-bid-response.json")))); // when final Response response = responseFor("openrtb2/eplanning/test-auction-eplanning-request.json", diff --git a/src/test/java/org/prebid/server/it/OneFiveTwoMediaTest.java b/src/test/java/org/prebid/server/it/OneFiveTwoMediaTest.java index 0a09b348cef..197621cdcc5 100644 --- a/src/test/java/org/prebid/server/it/OneFiveTwoMediaTest.java +++ b/src/test/java/org/prebid/server/it/OneFiveTwoMediaTest.java @@ -18,7 +18,7 @@ public class OneFiveTwoMediaTest extends IntegrationTest { @Test public void openrtb2AuctionShouldRespondWithBidsFrom152Media() throws IOException, JSONException { // given - WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/adkernel-exchange")) + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/152media-exchange")) .withRequestBody(equalToJson(jsonFrom("openrtb2/152media/test-152media-bid-request.json"))) .willReturn(aResponse().withBody(jsonFrom("openrtb2/152media/test-152media-bid-response.json")))); diff --git a/src/test/java/org/prebid/server/it/RxNetworkTest.java b/src/test/java/org/prebid/server/it/RxNetworkTest.java index 139c70d3288..9ba249e73fe 100644 --- a/src/test/java/org/prebid/server/it/RxNetworkTest.java +++ b/src/test/java/org/prebid/server/it/RxNetworkTest.java @@ -18,7 +18,7 @@ public class RxNetworkTest extends IntegrationTest { @Test public void openrtb2AuctionShouldRespondWithBidsFromRxNetwork() throws IOException, JSONException { // given - WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/adkernel-exchange")) + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/rxnetwork-exchange")) .withRequestBody(equalToJson(jsonFrom("openrtb2/rxnetwork/test-rxnetwork-bid-request.json"))) .willReturn(aResponse().withBody(jsonFrom("openrtb2/rxnetwork/test-rxnetwork-bid-response.json")))); diff --git a/src/test/java/org/prebid/server/it/ShowheroesBSTest.java b/src/test/java/org/prebid/server/it/ShowheroesBSTest.java new file mode 100644 index 00000000000..c291bceedb9 --- /dev/null +++ b/src/test/java/org/prebid/server/it/ShowheroesBSTest.java @@ -0,0 +1,40 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.prebid.server.model.Endpoint; +import org.prebid.server.version.PrebidVersionProvider; +import org.springframework.beans.factory.annotation.Autowired; + +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; + +public class ShowheroesBSTest extends IntegrationTest { + + @Autowired + private PrebidVersionProvider prebidVersionProvider; + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromShowheroesBS() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/showheroes-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/showheroesBs/test-showheroes-bid-request.json", + prebidVersionProvider))) + .willReturn( + aResponse().withBody(jsonFrom("openrtb2/showheroesBs/test-showheroes-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/showheroesBs/test-auction-showheroes-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/showheroesBs/test-auction-showheroes-response.json", response, + singletonList("showheroesBs")); + } +} diff --git a/src/test/java/org/prebid/server/it/ShowheroesTest.java b/src/test/java/org/prebid/server/it/ShowheroesTest.java new file mode 100644 index 00000000000..42e6f7a6edb --- /dev/null +++ b/src/test/java/org/prebid/server/it/ShowheroesTest.java @@ -0,0 +1,40 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.prebid.server.model.Endpoint; +import org.prebid.server.version.PrebidVersionProvider; +import org.springframework.beans.factory.annotation.Autowired; + +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; + +public class ShowheroesTest extends IntegrationTest { + + @Autowired + private PrebidVersionProvider prebidVersionProvider; + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromShowheroes() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/showheroes-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/showheroes/test-showheroes-bid-request.json", + prebidVersionProvider))) + .willReturn( + aResponse().withBody(jsonFrom("openrtb2/showheroes/test-showheroes-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/showheroes/test-auction-showheroes-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/showheroes/test-auction-showheroes-response.json", response, + singletonList("showheroes")); + } +} diff --git a/src/test/java/org/prebid/server/it/ShowheroesbsTest.java b/src/test/java/org/prebid/server/it/ShowheroesbsTest.java new file mode 100644 index 00000000000..2d60a348ae2 --- /dev/null +++ b/src/test/java/org/prebid/server/it/ShowheroesbsTest.java @@ -0,0 +1,40 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.prebid.server.model.Endpoint; +import org.prebid.server.version.PrebidVersionProvider; +import org.springframework.beans.factory.annotation.Autowired; + +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; + +public class ShowheroesbsTest extends IntegrationTest { + + @Autowired + private PrebidVersionProvider prebidVersionProvider; + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromShowheroesbs() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/showheroes-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/showheroes_bs/test-showheroes-bid-request.json", + prebidVersionProvider))) + .willReturn( + aResponse().withBody(jsonFrom("openrtb2/showheroes_bs/test-showheroes-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/showheroes_bs/test-auction-showheroes-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/showheroes_bs/test-auction-showheroes-response.json", response, + singletonList("showheroes-bs")); + } +} diff --git a/src/test/java/org/prebid/server/it/SmileWantedTest.java b/src/test/java/org/prebid/server/it/SmileWantedTest.java index 172d2063711..f4d24e0de28 100644 --- a/src/test/java/org/prebid/server/it/SmileWantedTest.java +++ b/src/test/java/org/prebid/server/it/SmileWantedTest.java @@ -18,7 +18,7 @@ public class SmileWantedTest extends IntegrationTest { @Test public void openrtb2AuctionShouldRespondWithBidsFromSmileWanted() throws IOException, JSONException { // given - WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/smilewanted-exchange")) + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/smilewanted-exchange/java/someZoneId")) .withRequestBody(equalToJson(jsonFrom("openrtb2/smilewanted/test-smilewanted-bid-request.json"))) .willReturn(aResponse().withBody(jsonFrom( "openrtb2/smilewanted/test-smilewanted-bid-response.json")))); diff --git a/src/test/java/org/prebid/server/it/XapadsTest.java b/src/test/java/org/prebid/server/it/XapadsTest.java new file mode 100644 index 00000000000..d373fd3bcbc --- /dev/null +++ b/src/test/java/org/prebid/server/it/XapadsTest.java @@ -0,0 +1,33 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.prebid.server.model.Endpoint; + +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; + +public class XapadsTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromXapads() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/xapads-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/xapads/test-xapads-bid-request.json"))) + .willReturn(aResponse().withBody(jsonFrom("openrtb2/xapads/test-xapads-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/xapads/test-auction-xapads-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/xapads/test-auction-xapads-response.json", response, + singletonList("xapads")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-auction-contxtful-request.json b/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-auction-contxtful-request.json new file mode 100644 index 00000000000..b7c25e7357f --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-auction-contxtful-request.json @@ -0,0 +1,44 @@ +{ + "id": "test-request-banner", + "imp": [ + { + "id": "test-imp-banner", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "contxtful": { + "customerId": "DEMO123", + "placementId": "banner-placement-1" + } + } + } + ], + "site": { + "id": "demo-site", + "domain": "example.com", + "page": "https://example.com/demo", + "publisher": { + "id": "demo-publisher" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "ip": "192.0.2.1", + "language": "en", + "dnt": 0 + }, + "user": { + "id": "demo-user" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-auction-contxtful-response.json b/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-auction-contxtful-response.json new file mode 100644 index 00000000000..6817b291056 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-auction-contxtful-response.json @@ -0,0 +1,47 @@ +{ + "id": "test-request-banner", + "seatbid": [ + { + "seat": "contxtful", + "bid": [ + { + "id": "contxtful-test-imp-banner", + "impid": "test-imp-banner", + "price": 2.50, + "adm": "
Contxtful Demo Ad
", + "w": 300, + "h": 250, + "crid": "demo-creative-123", + "nurl": "https://monitoring.receptivity.io/v1/pbs/DEMO123/pbs-impression?b=contxtful-test-imp-banner&a=DEMO123&bidder=contxtful&impId=test-imp-banner&price=2.50&traceId=&random=0.000000&domain=example.com&adRequestId=test-request-banner&w=300&h=250&f=b", + "burl": "https://monitoring.receptivity.io/v1/pbs/DEMO123/pbs-billing?b=contxtful-test-imp-banner&a=DEMO123&bidder=contxtful&impId=test-imp-banner&price=2.50&traceId=&random=0.000000&domain=example.com&adRequestId=test-request-banner&w=300&h=250&f=b", + "exp": 300, + "ext": { + "contxtful": { + "reseller": "demo-reseller", + "version": "v1.0" + }, + "origbidcpm": 2.50, + "origbidcur": "USD", + "prebid": { + "type": "banner", + "meta": { + "adaptercode": "contxtful" + } + } + } + } + ], + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "contxtful": "{{ contxtful.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-contxtful-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-contxtful-bid-request.json new file mode 100644 index 00000000000..c75b7002e29 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-contxtful-bid-request.json @@ -0,0 +1,91 @@ +{ + "bidRequests": [ + { + "bidId": "test-imp-banner", + "bidder": "contxtful", + "params": { + "placementId": "banner-placement-1" + } + } + ], + "bidderRequest": { + "bidderCode": "contxtful" + }, + "config": { + "contxtful": { + "customer": "DEMO123", + "version": "v1" + } + }, + "ortb2": { + "id": "test-request-banner", + "imp": [ + { + "id": "test-imp-banner", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "secure": 1, + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "customerId": "DEMO123", + "placementId": "banner-placement-1" + } + } + } + ], + "site": { + "id": "demo-site", + "domain": "example.com", + "page": "https://example.com/demo", + "publisher": { + "id": "demo-publisher", + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "dnt": 0, + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "ip": "192.0.2.1", + "language": "en" + }, + "user": { + "id": "demo-user" + }, + "at": 1, + "tmax": "${json-unit.any-number}", + "cur": [ + "USD" + ], + "source": { + "tid": "${json-unit.any-string}" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "ext": { + "prebid": { + "channel": { + "name": "web" + }, + "server": { + "externalurl": "http://localhost:8080", + "gvlid": 1, + "datacenter": "local", + "endpoint": "/openrtb2/auction" + } + } + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-contxtful-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-contxtful-bid-response.json new file mode 100644 index 00000000000..697faf74649 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/contxtful/test-contxtful-bid-response.json @@ -0,0 +1,22 @@ +[ + { + "cpm": 2.50, + "width": 300, + "height": 250, + "currency": "USD", + "requestId": "test-imp-banner", + "adm": "
Contxtful Demo Ad
", + "ttl": 300, + "creativeId": "demo-creative-123", + "netRevenue": true, + "mediaType": "banner", + "nurl": "https://monitoring.receptivity.io/v1/pbs/DEMO123/pbs-impression?b=contxtful-test-imp-banner&a=DEMO123&bidder=contxtful&impId=test-imp-banner&price=2.50&traceId=&random=0.000000&domain=example.com&adRequestId=test-request-banner&w=300&h=250&f=b", + "burl": "https://monitoring.receptivity.io/v1/pbs/DEMO123/pbs-billing?b=contxtful-test-imp-banner&a=DEMO123&bidder=contxtful&impId=test-imp-banner&price=2.50&traceId=&random=0.000000&domain=example.com&adRequestId=test-request-banner&w=300&h=250&f=b", + "ext": { + "contxtful": { + "reseller": "demo-reseller", + "version": "v1.0" + } + } + } +] diff --git a/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-auction-eplanning-response.json b/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-auction-eplanning-response.json index aee270657c9..9cb422d3532 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-auction-eplanning-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-auction-eplanning-response.json @@ -11,6 +11,9 @@ "adm": "
test
", "adid": "imp_id", "crid": "crid", + "adomain": [ + "test.com" + ], "w": 600, "h": 300, "ext": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-eplanning-bid-response-1.json b/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-eplanning-bid-response.json similarity index 89% rename from src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-eplanning-bid-response-1.json rename to src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-eplanning-bid-response.json index cbc840b6081..25396475d2a 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-eplanning-bid-response-1.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/eplanning/test-eplanning-bid-response.json @@ -9,10 +9,11 @@ "adm": "
test
", "crid": "crid", "id": "imp_id", + "adom": "test.com", "w": 600, "h": 300 } ] } ] -} \ No newline at end of file +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-request.json new file mode 100644 index 00000000000..2f50c3466e7 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-request.json @@ -0,0 +1,24 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 320, + "h": 250 + }, + "ext": { + "showheroes": { + "unitId": "12345678" + } + }, + "tagid": "tag_id" + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-response.json new file mode 100644 index 00000000000..74377edd182 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-response.json @@ -0,0 +1,41 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "exp": 300, + "price": 0.01, + "adid": "adid", + "cid": "cid", + "crid": "crid", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner", + "meta": { + "adaptercode": "showheroes" + } + }, + "origbidcpm": 0.01, + "origbidcur": "USD" + } + } + ], + "seat": "showheroes", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "showheroes": "{{ showheroes.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json new file mode 100644 index 00000000000..7b0681643f0 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json @@ -0,0 +1,62 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 320, + "h": 250 + }, + "tagid": "tag_id", + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "unitId": "12345678" + }, + "params": { + "unitId": "12345678" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": "${json-unit.any-number}", + "cur": ["USD"], + "source": { + "tid": "${json-unit.any-string}", + "ext": { + "pbs": { + "pbsv": "{{ pbs.java.version }}", + "pbsp": "java" + } + } + }, + "regs": { + "gdpr": 0 + }, + "ext": { + "prebid": { + "server": { + "externalurl": "http://localhost:8080", + "gvlid": 1, + "datacenter": "local", + "endpoint": "/openrtb2/auction" + } + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-response.json new file mode 100644 index 00000000000..2dde90a94ce --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-response.json @@ -0,0 +1,25 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "crid": "crid", + "adid": "adid", + "price": 0.01, + "id": "bid_id", + "impid": "imp_id", + "cid": "cid", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "type": "banner" + } + ], + "cur": "USD" +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-request.json new file mode 100644 index 00000000000..33078a75ab3 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-request.json @@ -0,0 +1,24 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 320, + "h": 250 + }, + "ext": { + "showheroesBs": { + "unitId": "12345678" + } + }, + "tagid": "tag_id" + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-response.json new file mode 100644 index 00000000000..2655490d994 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-response.json @@ -0,0 +1,41 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "exp": 300, + "price": 0.01, + "adid": "adid", + "cid": "cid", + "crid": "crid", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner", + "meta": { + "adaptercode": "showheroesBs" + } + }, + "origbidcpm": 0.01, + "origbidcur": "USD" + } + } + ], + "seat": "showheroesBs", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "showheroesBs": "{{ showheroesBs.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-request.json new file mode 100644 index 00000000000..7b0681643f0 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-request.json @@ -0,0 +1,62 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 320, + "h": 250 + }, + "tagid": "tag_id", + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "unitId": "12345678" + }, + "params": { + "unitId": "12345678" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": "${json-unit.any-number}", + "cur": ["USD"], + "source": { + "tid": "${json-unit.any-string}", + "ext": { + "pbs": { + "pbsv": "{{ pbs.java.version }}", + "pbsp": "java" + } + } + }, + "regs": { + "gdpr": 0 + }, + "ext": { + "prebid": { + "server": { + "externalurl": "http://localhost:8080", + "gvlid": 1, + "datacenter": "local", + "endpoint": "/openrtb2/auction" + } + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-response.json new file mode 100644 index 00000000000..2dde90a94ce --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-response.json @@ -0,0 +1,25 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "crid": "crid", + "adid": "adid", + "price": 0.01, + "id": "bid_id", + "impid": "imp_id", + "cid": "cid", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "type": "banner" + } + ], + "cur": "USD" +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-request.json new file mode 100644 index 00000000000..e44185fb537 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-request.json @@ -0,0 +1,24 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 320, + "h": 250 + }, + "ext": { + "showheroes-bs": { + "unitId": "12345678" + } + }, + "tagid": "tag_id" + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-response.json new file mode 100644 index 00000000000..53ce08b83ab --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-response.json @@ -0,0 +1,41 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "exp": 300, + "price": 0.01, + "adid": "adid", + "cid": "cid", + "crid": "crid", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner", + "meta": { + "adaptercode": "showheroes-bs" + } + }, + "origbidcpm": 0.01, + "origbidcur": "USD" + } + } + ], + "seat": "showheroes-bs", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "showheroes-bs": "{{ showheroes-bs.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-request.json new file mode 100644 index 00000000000..7b0681643f0 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-request.json @@ -0,0 +1,62 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 320, + "h": 250 + }, + "tagid": "tag_id", + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "unitId": "12345678" + }, + "params": { + "unitId": "12345678" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": "${json-unit.any-number}", + "cur": ["USD"], + "source": { + "tid": "${json-unit.any-string}", + "ext": { + "pbs": { + "pbsv": "{{ pbs.java.version }}", + "pbsp": "java" + } + } + }, + "regs": { + "gdpr": 0 + }, + "ext": { + "prebid": { + "server": { + "externalurl": "http://localhost:8080", + "gvlid": 1, + "datacenter": "local", + "endpoint": "/openrtb2/auction" + } + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-response.json new file mode 100644 index 00000000000..2dde90a94ce --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-response.json @@ -0,0 +1,25 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "crid": "crid", + "adid": "adid", + "price": 0.01, + "id": "bid_id", + "impid": "imp_id", + "cid": "cid", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "type": "banner" + } + ], + "cur": "USD" +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/sovrn/test-auction-sovrn-request.json b/src/test/resources/org/prebid/server/it/openrtb2/sovrn/test-auction-sovrn-request.json index 5351844a7e3..9021db66ebc 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/sovrn/test-auction-sovrn-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/sovrn/test-auction-sovrn-request.json @@ -17,9 +17,10 @@ }, "ext": { "sovrn": { - "adunitcode": "sovrn_auc", + "adunitcode": "sovrn_auc_bidder", "tagid": "tag_id" - } + }, + "gpid": "test_gpid" } } ], diff --git a/src/test/resources/org/prebid/server/it/openrtb2/sovrn/test-sovrn-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/sovrn/test-sovrn-bid-request.json index 7de057a5d19..7b2bfcd5ec2 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/sovrn/test-sovrn-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/sovrn/test-sovrn-bid-request.json @@ -21,9 +21,9 @@ "tid": "${json-unit.any-string}", "bidder": { "tagid": "tag_id", - "adunitcode": "sovrn_auc" + "adunitcode": "sovrn_auc_bidder" }, - "adunitcode": "sovrn_auc" + "gpid": "test_gpid" } } ], diff --git a/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-auction-xapads-request.json b/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-auction-xapads-request.json new file mode 100644 index 00000000000..d04556d666b --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-auction-xapads-request.json @@ -0,0 +1,23 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 320, + "h": 250 + }, + "ext": { + "xapads": { + "zoneId": 101 + } + } + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-auction-xapads-response.json b/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-auction-xapads-response.json new file mode 100644 index 00000000000..3e19b815ebd --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-auction-xapads-response.json @@ -0,0 +1,44 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "exp": 300, + "price": 2.25, + "adm": "", + "adid": "2002", + "adomain": [ + "tag-example.com" + ], + "cid": "1001", + "crid": "2002", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner", + "meta": { + "adaptercode": "xapads" + } + }, + "origbidcpm": 2.25 + } + } + ], + "seat": "xapads", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "xapads": "{{ xapads.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-xapads-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-xapads-bid-request.json new file mode 100644 index 00000000000..2c1d5b8f6bd --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-xapads-bid-request.json @@ -0,0 +1,47 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 320, + "h": 250 + } + } + ], + "source": { + "tid": "${json-unit.any-string}" + }, + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": "${json-unit.any-number}", + "cur": [ + "USD" + ], + "regs": { + "ext": { + "gdpr": 0 + } + }, + "ext": { + "prebid": { + "server": { + "externalurl": "http://localhost:8080", + "gvlid": 1, + "datacenter": "local", + "endpoint": "/openrtb2/auction" + } + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-xapads-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-xapads-bid-response.json new file mode 100644 index 00000000000..03821c0471a --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/xapads/test-xapads-bid-response.json @@ -0,0 +1,23 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 2.25, + "cid": "1001", + "crid": "2002", + "adid": "2002", + "adm": "", + "mtype": 1, + "adomain": [ + "tag-example.com" + ] + } + ] + } + ], + "bidid": "bid_id" +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/yandex/test-yandex-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/yandex/test-yandex-bid-request.json index c833c0633f5..4db9a09b23b 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/yandex/test-yandex-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/yandex/test-yandex-bid-request.json @@ -8,6 +8,8 @@ "w": 300, "h": 600 }, + "displaymanager" : "prebid.java", + "displaymanagerver" : "1.1", "ext": { "tid": "${json-unit.any-string}", "bidder": { 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 3d57277ff9d..e847998f90f 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -34,7 +34,13 @@ adapters.adhese.endpoint=http://localhost:8090/adhese-exchange adapters.adkerneladn.enabled=true adapters.adkerneladn.endpoint=http://localhost:8090/adkerneladn-exchange?account={{PublisherID}} adapters.adkernel.enabled=true -adapters.adkernel.endpoint=http://localhost:8090/adkernel-exchange?zone=%s +adapters.adkernel.endpoint=http://localhost:8090/adkernel-exchange +adapters.adkernel.aliases.xapads.enabled=true +adapters.adkernel.aliases.xapads.endpoint=http://localhost:8090/xapads-exchange +adapters.adkernel.aliases.rxnetwork.enabled=true +adapters.adkernel.aliases.rxnetwork.endpoint=http://localhost:8090/rxnetwork-exchange +adapters.adkernel.aliases.152media.enabled=true +adapters.adkernel.aliases.152media.endpoint=http://localhost:8090/152media-exchange adapters.adman.enabled=true adapters.adman.endpoint=http://localhost:8090/adman-exchange adapters.admatic.enabled=true @@ -199,6 +205,8 @@ adapters.coinzilla.enabled=true adapters.coinzilla.endpoint=http://localhost:8090/coinzilla-exchange adapters.consumable.enabled=true adapters.consumable.endpoint=http://localhost:8090/consumable-exchange +adapters.contxtful.enabled=true +adapters.contxtful.endpoint=http://localhost:8090/contxtful-exchange adapters.copper6ssp.enabled=true adapters.copper6ssp.endpoint=http://localhost:8090/copper6ssp-exchange adapters.criteo.enabled=true @@ -522,7 +530,7 @@ adapters.smarthub.aliases.artechnology.endpoint=http://localhost:8090/artechnolo adapters.smartyads.enabled=true adapters.smartyads.endpoint=http://localhost:8090/smartyads-exchange adapters.smilewanted.enabled=true -adapters.smilewanted.endpoint=http://localhost:8090/smilewanted-exchange +adapters.smilewanted.endpoint=http://localhost:8090/smilewanted-exchange/java/{{ZoneId}} adapters.smoot.enabled=true adapters.smoot.endpoint=http://localhost:8090/smoot-exchange adapters.smrtconnect.enabled=true @@ -539,6 +547,10 @@ adapters.sspbc.enabled=true adapters.sspbc.endpoint=http://localhost:8090/sspbc-exchange adapters.sharethrough.enabled=true adapters.sharethrough.endpoint=http://localhost:8090/sharethrough-exchange +adapters.showheroes.enabled=true +adapters.showheroes.endpoint=http://localhost:8090/showheroes-exchange +adapters.showheroes.aliases.showheroesBs.enabled=true +adapters.showheroes.aliases.showheroes-bs.enabled=true adapters.silvermob.enabled=true adapters.silvermob.endpoint=http://localhost:8090/silvermob-exchange adapters.silverpush.enabled=true