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