From dfc620bcae207335ef3f351a95f0243d24a8303b Mon Sep 17 00:00:00 2001 From: rpanchyk Date: Fri, 14 Jan 2022 13:26:44 +0200 Subject: [PATCH] Extend PG Targeting to match single String or Integer value (#1653) --- .../deals/targeting/RequestContext.java | 94 +++-- .../server/deals/TargetingServiceTest.java | 53 +++ .../deals/targeting/RequestContextTest.java | 325 +++++++++++++----- 3 files changed, 348 insertions(+), 124 deletions(-) diff --git a/src/main/java/org/prebid/server/deals/targeting/RequestContext.java b/src/main/java/org/prebid/server/deals/targeting/RequestContext.java index 585e807aa2e..74b9cd20faa 100644 --- a/src/main/java/org/prebid/server/deals/targeting/RequestContext.java +++ b/src/main/java/org/prebid/server/deals/targeting/RequestContext.java @@ -5,6 +5,7 @@ import com.iab.openrtb.request.App; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Data; import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Geo; import com.iab.openrtb.request.Imp; @@ -13,6 +14,7 @@ import com.iab.openrtb.request.Site; import com.iab.openrtb.request.User; import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.prebid.server.deals.model.TxnLog; @@ -37,7 +39,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -65,13 +66,15 @@ public RequestContext(BidRequest bidRequest, Imp imp, TxnLog txnLog, JacksonMapp this.imp = Objects.requireNonNull(imp); this.deviceExt = getExtNode(bidRequest.getDevice(), Device::getExt, mapper); this.geoExt = getExtNode(bidRequest.getDevice(), - dev -> getIfNotNull(getIfNotNull(dev, Device::getGeo), Geo::getExt), mapper); + device -> getIfNotNull(getIfNotNull(device, Device::getGeo), Geo::getExt), mapper); this.userExt = getExtNode(bidRequest.getUser(), User::getExt, mapper); this.txnLog = Objects.requireNonNull(txnLog); } - private static ObjectNode getExtNode(T target, Function extExtractor, + private static ObjectNode getExtNode(T target, + Function extExtractor, JacksonMapper mapper) { + final FlexibleExtension ext = target != null ? extExtractor.apply(target) : null; return ext != null ? (ObjectNode) mapper.mapper().valueToTree(ext) : null; } @@ -80,7 +83,7 @@ public String lookupString(TargetingCategory category) { final TargetingCategory.Type type = category.type(); switch (type) { case domain: - return org.apache.commons.lang3.ObjectUtils.defaultIfNull( + return ObjectUtils.defaultIfNull( getIfNotNull(bidRequest.getSite(), Site::getDomain), getIfNotNull(getIfNotNull(bidRequest.getSite(), Site::getPublisher), Publisher::getDomain)); case publisherDomain: @@ -102,8 +105,8 @@ public String lookupString(TargetingCategory category) { case bidderParam: return impBidderAttributeReader.readFromExt(imp, category, RequestContext::nodeToString); case userFirstPartyData: - return userAttributeReader.read( - bidRequest.getUser(), category, RequestContext::nodeToString, String.class); + return userAttributeReader.read(bidRequest.getUser(), category, + RequestContext::nodeToString, String.class); case siteFirstPartyData: return getSiteFirstPartyData(category, RequestContext::nodeToString); default: @@ -124,8 +127,8 @@ public Integer lookupInteger(TargetingCategory category) { case bidderParam: return impBidderAttributeReader.readFromExt(imp, category, RequestContext::nodeToInteger); case userFirstPartyData: - return userAttributeReader.read( - bidRequest.getUser(), category, RequestContext::nodeToInteger, Integer.class); + return userAttributeReader.read(bidRequest.getUser(), category, + RequestContext::nodeToInteger, Integer.class); case siteFirstPartyData: return getSiteFirstPartyData(category, RequestContext::nodeToInteger); default: @@ -140,15 +143,14 @@ public List lookupStrings(TargetingCategory category) { case mediaType: return getMediaTypes(); case bidderParam: - return impBidderAttributeReader - .readFromExt(imp, category, node -> nodeToList(node, RequestContext::nodeToString)); + return impBidderAttributeReader.readFromExt(imp, category, RequestContext::nodeToListOfStrings); case userSegment: return getSegments(category); case userFirstPartyData: - return userAttributeReader.readFromExt( - bidRequest.getUser(), category, node -> nodeToList(node, RequestContext::nodeToString)); + return userAttributeReader.readFromExt(bidRequest.getUser(), category, + RequestContext::nodeToListOfStrings); case siteFirstPartyData: - return getSiteFirstPartyData(category, node -> nodeToList(node, RequestContext::nodeToString)); + return getSiteFirstPartyData(category, RequestContext::nodeToListOfStrings); default: throw new TargetingSyntaxException( String.format("Unexpected category for fetching string values for: %s", type)); @@ -159,13 +161,12 @@ public List lookupIntegers(TargetingCategory category) { final TargetingCategory.Type type = category.type(); switch (type) { case bidderParam: - return impBidderAttributeReader - .readFromExt(imp, category, node -> nodeToList(node, RequestContext::nodeToInteger)); + return impBidderAttributeReader.readFromExt(imp, category, RequestContext::nodeToListOfIntegers); case userFirstPartyData: - return userAttributeReader.readFromExt( - bidRequest.getUser(), category, node -> nodeToList(node, RequestContext::nodeToInteger)); + return userAttributeReader.readFromExt(bidRequest.getUser(), category, + RequestContext::nodeToListOfIntegers); case siteFirstPartyData: - return getSiteFirstPartyData(category, node -> nodeToList(node, RequestContext::nodeToInteger)); + return getSiteFirstPartyData(category, RequestContext::nodeToListOfIntegers); default: throw new TargetingSyntaxException( String.format("Unexpected category for fetching integer values for: %s", type)); @@ -204,7 +205,11 @@ public TxnLog txnLog() { } private String getFirstNonNullStringFromImpExt(String... path) { - return Arrays.stream(path).map(this::getStringFromImpExt).filter(Objects::nonNull).findFirst().orElse(null); + return Arrays.stream(path) + .map(this::getStringFromImpExt) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); } private String getStringFromImpExt(String path) { @@ -235,8 +240,10 @@ private List getMediaTypes() { return mediaTypes; } - private static T getValueFrom(ObjectNode objectNode, TargetingCategory category, + private static T getValueFrom(ObjectNode objectNode, + TargetingCategory category, Function valueExtractor) { + final JsonNode jsonNode = getIfNotNull(objectNode, node -> node.at(toJsonPointer(category.path()))); return getIfNotNull(jsonNode, valueExtractor); } @@ -249,7 +256,10 @@ private T getSiteFirstPartyData(TargetingCategory category, Function getSegments(TargetingCategory category) { - return ListUtils.emptyIfNull(getIfNotNull(getIfNotNull(bidRequest, BidRequest::getUser), User::getData)) + final User user = getIfNotNull(bidRequest, BidRequest::getUser); + final List userData = getIfNotNull(user, User::getData); + + return ListUtils.emptyIfNull(userData) .stream() .filter(Objects::nonNull) .filter(data -> Objects.equals(data.getId(), category.path())) @@ -260,7 +270,8 @@ public List getSegments(TargetingCategory category) { } private static String toJsonPointer(String path) { - return Arrays.stream(path.split("\\.")).collect(Collectors.joining("/", "/", StringUtils.EMPTY)); + return Arrays.stream(path.split("\\.")) + .collect(Collectors.joining("/", "/", StringUtils.EMPTY)); } private static String nodeToString(JsonNode node) { @@ -271,6 +282,20 @@ private static Integer nodeToInteger(JsonNode node) { return node.isInt() ? node.asInt() : null; } + private static List nodeToListOfStrings(JsonNode node) { + final Function valueExtractor = RequestContext::nodeToString; + return node.isTextual() + ? Collections.singletonList(valueExtractor.apply(node)) + : nodeToList(node, valueExtractor); + } + + private static List nodeToListOfIntegers(JsonNode node) { + final Function valueExtractor = RequestContext::nodeToInteger; + return node.isInt() + ? Collections.singletonList(valueExtractor.apply(node)) + : nodeToList(node, valueExtractor); + } + private static List nodeToList(JsonNode node, Function valueExtractor) { if (node.isArray()) { return StreamUtil.asStream(node.spliterator()) @@ -284,8 +309,7 @@ private static List nodeToList(JsonNode node, Function value private static class AttributeReader { - private static final Set> SUPPORTED_PROPERTY_TYPES = new HashSet<>(Arrays.asList( - String.class, Integer.class, int.class)); + private static final Set> SUPPORTED_PROPERTY_TYPES = Set.of(String.class, Integer.class, int.class); private static final String EXT_PREBID = "prebid"; private static final String EXT_BIDDER = "bidder"; @@ -336,8 +360,10 @@ public static AttributeReader forImpBidder() { node -> node.get(EXT_BIDDER))); } - public A read( - T target, TargetingCategory category, Function valueExtractor, Class attributeType) { + public A read(T target, + TargetingCategory category, + Function valueExtractor, + Class attributeType) { return ObjectUtil.firstNonNull( // look in the object itself @@ -347,17 +373,15 @@ public A read( } public A readFromObject(T target, TargetingCategory category, Class attributeType) { - if (isTopLevelAttribute(category.path())) { - return getIfNotNull(target, user -> readProperty(user, category.path(), attributeType)); - } - return null; + return isTopLevelAttribute(category.path()) + ? getIfNotNull(target, user -> readProperty(user, category.path(), attributeType)) + : null; } public A readFromExt(T target, TargetingCategory category, Function valueExtractor) { - return getIfNotNull( - getIfNotNull( - getIfNotNull(target, extPathExtractor), node -> node.at(toJsonPointer(category.path()))), - valueExtractor); + final JsonNode path = getIfNotNull(target, extPathExtractor); + final JsonNode value = getIfNotNull(path, node -> node.at(toJsonPointer(category.path()))); + return getIfNotNull(value, valueExtractor); } private boolean isTopLevelAttribute(String path) { @@ -368,7 +392,7 @@ private static Map supportedBeanProperties(Class try { final BeanInfo beanInfo = Introspector.getBeanInfo(beanClass, Object.class); return Arrays.stream(beanInfo.getPropertyDescriptors()) - .filter(pd -> SUPPORTED_PROPERTY_TYPES.contains(pd.getPropertyType())) + .filter(descriptor -> SUPPORTED_PROPERTY_TYPES.contains(descriptor.getPropertyType())) .collect(Collectors.toMap(FeatureDescriptor::getName, Function.identity())); } catch (IntrospectionException e) { return ExceptionUtils.rethrow(e); diff --git a/src/test/java/org/prebid/server/deals/TargetingServiceTest.java b/src/test/java/org/prebid/server/deals/TargetingServiceTest.java index cd2484d17f7..04e3fac7b67 100644 --- a/src/test/java/org/prebid/server/deals/TargetingServiceTest.java +++ b/src/test/java/org/prebid/server/deals/TargetingServiceTest.java @@ -1,6 +1,7 @@ package org.prebid.server.deals; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.IntNode; import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; @@ -475,6 +476,58 @@ public void matchesTargetingShouldReturnTrue() { assertThat(txnLog.lineItemsMatchedDomainTargeting()).containsOnly("lineItemId"); } + @Test + public void matchesTargetingShouldReturnTrueForIntersectsStringsOnSingleString() { + // given + final TargetingDefinition targetingDefinition = TargetingDefinition.of( + new IntersectsStrings(category(Type.userFirstPartyData, "segment"), asList("test", "111"))); + + final BidRequest bidRequest = BidRequest.builder() + .user(User.builder() + .ext(ExtUser.builder() + .data(mapper.createObjectNode().set("segment", TextNode.valueOf("test"))) + .build()) + .build()) + .build(); + + final TxnLog txnLog = TxnLog.create(); + final AuctionContext auctionContext = AuctionContext.builder() + .bidRequest(bidRequest) + .txnLog(txnLog) + .build(); + + final Imp imp = Imp.builder().build(); + + // when and then + assertThat(targetingService.matchesTargeting(auctionContext, imp, targetingDefinition)).isTrue(); + } + + @Test + public void matchesTargetingShouldReturnTrueForIntersectsIntegersOnSingleInteger() { + // given + final TargetingDefinition targetingDefinition = TargetingDefinition.of( + new IntersectsIntegers(category(Type.userFirstPartyData, "segment"), asList(123, 456))); + + final BidRequest bidRequest = BidRequest.builder() + .user(User.builder() + .ext(ExtUser.builder() + .data(mapper.createObjectNode().set("segment", IntNode.valueOf(123))) + .build()) + .build()) + .build(); + + final TxnLog txnLog = TxnLog.create(); + final AuctionContext auctionContext = AuctionContext.builder() + .bidRequest(bidRequest) + .txnLog(txnLog) + .build(); + + final Imp imp = Imp.builder().build(); + + // when and then + assertThat(targetingService.matchesTargeting(auctionContext, imp, targetingDefinition)).isTrue(); + } + @Test public void matchesTargetingShouldReturnTrueForNotInIntegers() throws IOException { // given diff --git a/src/test/java/org/prebid/server/deals/targeting/RequestContextTest.java b/src/test/java/org/prebid/server/deals/targeting/RequestContextTest.java index f3075008f94..bd3be93193a 100644 --- a/src/test/java/org/prebid/server/deals/targeting/RequestContextTest.java +++ b/src/test/java/org/prebid/server/deals/targeting/RequestContextTest.java @@ -51,14 +51,13 @@ public void setUp() { public void lookupStringShouldReturnDomainFromSite() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.domain); - final RequestContext context = - new RequestContext( - request(r -> r.site(site(s -> s - .domain("domain.com") - .publisher(Publisher.builder().domain("anotherdomain.com").build())))), - imp(identity()), - txnLog, - jacksonMapper); + final RequestContext context = new RequestContext( + request(r -> r.site(site(s -> s + .domain("domain.com") + .publisher(Publisher.builder().domain("anotherdomain.com").build())))), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("domain.com"); @@ -68,13 +67,12 @@ public void lookupStringShouldReturnDomainFromSite() { public void lookupStringShouldReturnDomainFromSitePublisher() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.domain); - final RequestContext context = - new RequestContext( - request(r -> r.site(site(s -> s + final RequestContext context = new RequestContext( + request(r -> r.site(site(s -> s .publisher(Publisher.builder().domain("domain.com").build())))), - imp(identity()), - txnLog, - jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("domain.com"); @@ -84,8 +82,11 @@ public void lookupStringShouldReturnDomainFromSitePublisher() { public void lookupStringShouldReturnNullWhenDomainIsMissing() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.domain); - final RequestContext context = - new RequestContext(request(r -> r.site(site(identity()))), imp(identity()), txnLog, jacksonMapper); + final RequestContext context = new RequestContext( + request(r -> r.site(site(identity()))), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isNull(); @@ -95,8 +96,11 @@ public void lookupStringShouldReturnNullWhenDomainIsMissing() { public void lookupStringShouldReturnNullForDomainWhenSiteIsMissing() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.domain); - final RequestContext context = - new RequestContext(request(identity()), imp(identity()), txnLog, jacksonMapper); + final RequestContext context = new RequestContext( + request(identity()), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isNull(); @@ -133,21 +137,25 @@ public void lookupStringShouldReturnNullForPublisherDomainWhenSiteIsMissing() { public void lookupStringShouldReturnReferrer() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.referrer); - final RequestContext context = - new RequestContext(request(r -> r.site(site(s -> s.page("http://domain.com/index")))), - imp(identity()), txnLog, jacksonMapper); + final RequestContext context = new RequestContext( + request(r -> r.site(site(s -> s.page("https://domain.com/index")))), + imp(identity()), + txnLog, + jacksonMapper); // when and then - assertThat(context.lookupString(category)).isEqualTo("http://domain.com/index"); + assertThat(context.lookupString(category)).isEqualTo("https://domain.com/index"); } @Test public void lookupStringShouldReturnAppBundle() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.appBundle); - final RequestContext context = - new RequestContext(request(r -> r.app(app(a -> a.bundle("com.google.calendar")))), imp(identity()), - txnLog, jacksonMapper); + final RequestContext context = new RequestContext( + request(r -> r.app(app(a -> a.bundle("com.google.calendar")))), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("com.google.calendar"); @@ -157,8 +165,11 @@ public void lookupStringShouldReturnAppBundle() { public void lookupStringShouldReturnNullWhenBundleIsMissing() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.appBundle); - final RequestContext context = - new RequestContext(request(r -> r.app(app(identity()))), imp(identity()), txnLog, jacksonMapper); + final RequestContext context = new RequestContext( + request(r -> r.app(app(identity()))), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isNull(); @@ -168,8 +179,11 @@ public void lookupStringShouldReturnNullWhenBundleIsMissing() { public void lookupStringShouldReturnNullWhenAppIsMissing() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.appBundle); - final RequestContext context = - new RequestContext(request(identity()), imp(identity()), txnLog, jacksonMapper); + final RequestContext context = new RequestContext( + request(identity()), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isNull(); @@ -253,10 +267,11 @@ public void lookupStringShouldReturnAdslotFromDataAdserverAdslot() { public void lookupStringShouldReturnAdslotFromAlternativeAdServerAdSlotPath() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.adslot); - final RequestContext context = - new RequestContext(request(identity()), - imp(i -> i.ext(obj("context", obj("data", obj("adserver", obj("adslot", "/123/456")))))), - txnLog, jacksonMapper); + final RequestContext context = new RequestContext( + request(identity()), + imp(i -> i.ext(obj("context", obj("data", obj("adserver", obj("adslot", "/123/456")))))), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("/123/456"); @@ -285,7 +300,9 @@ public void lookupStringShouldReturnCountryFromDeviceGeoExtValue() { extGeo.addProperty("vendor", obj("attribute", "value")); final RequestContext context = new RequestContext( request(r -> r.device(device(d -> d.geo(geo(g -> g.ext(extGeo)))))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("value"); @@ -300,7 +317,9 @@ public void lookupStringShouldReturnRegionFromDeviceGeoExtValue() { extGeo.addProperty("vendor", obj("nested", obj("attribute", "value"))); final RequestContext context = new RequestContext( request(r -> r.device(device(d -> d.geo(geo(g -> g.ext(extGeo)))))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("value"); @@ -316,7 +335,9 @@ public void lookupStringShouldReturnMetroFromDeviceExtValue() { final RequestContext context = new RequestContext( request(r -> r.device(device(d -> d.ext(extDevice)))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("value"); @@ -331,7 +352,9 @@ public void lookupStringShouldReturnMetroFromDeviceExtNestedValue() { extDevice.addProperty("vendor", obj("nested", obj("attribute", "value"))); final RequestContext context = new RequestContext( request(r -> r.device(device(d -> d.ext(extDevice)))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("value"); @@ -372,7 +395,9 @@ public void lookupStringShouldReturnNullWhenBidderParamIsNotString() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.bidderParam, "rubicon.siteId"); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.ext(obj("rubicon", obj("siteId", mapper.valueToTree(123))))), txnLog, jacksonMapper); + imp(i -> i.ext(obj("rubicon", obj("siteId", mapper.valueToTree(123))))), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isNull(); @@ -384,7 +409,9 @@ public void lookupStringShouldReturnNullWhenBidderParamIsMissing() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.bidderParam, "rubicon.siteId"); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.ext(obj("rubicon", "phony"))), txnLog, jacksonMapper); + imp(i -> i.ext(obj("rubicon", "phony"))), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isNull(); @@ -396,7 +423,9 @@ public void lookupStringShouldReturnNullWhenImpExtIsMissingForBidderParam() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.bidderParam, "rubicon.siteId"); final RequestContext context = new RequestContext( request(identity()), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isNull(); @@ -412,7 +441,9 @@ public void lookupStringShouldReturnSimpleUserFirstPartyDataFromObject() { request(r -> r.user(user(u -> u .buyeruid("123") .ext(extUser)))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("123"); @@ -426,7 +457,9 @@ public void lookupStringShouldReturnSimpleUserFirstPartyDataFromExt() { final ExtUser extUser = ExtUser.builder().data(obj("sport", "hockey")).build(); final RequestContext context = new RequestContext( request(r -> r.user(user(u -> u.ext(extUser)))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("hockey"); @@ -439,10 +472,10 @@ public void lookupStringShouldReturnUserFirstPartyDataFromExtWhenObjectAttribute TargetingCategory.Type.userFirstPartyData, "yob"); final ExtUser extUser = ExtUser.builder().data(obj("yob", "1900")).build(); final RequestContext context = new RequestContext( - request(r -> r.user(user(u -> u - .yob(1800) - .ext(extUser)))), - imp(identity()), txnLog, jacksonMapper); + request(r -> r.user(user(u -> u.yob(1800).ext(extUser)))), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("1900"); @@ -456,7 +489,9 @@ public void lookupStringShouldReturnNestedUserFirstPartyData() { final ExtUser extUser = ExtUser.builder().data(obj("section", obj("sport", "hockey"))).build(); final RequestContext context = new RequestContext( request(r -> r.user(user(u -> u.ext(extUser)))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("hockey"); @@ -470,7 +505,9 @@ public void lookupStringShouldReturnNullWhenUserFirstPartyDataIsNotString() { final ExtUser extUser = ExtUser.builder().data(obj("sport", mapper.valueToTree(123))).build(); final RequestContext context = new RequestContext( request(r -> r.user(user(u -> u.ext(extUser)))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isNull(); @@ -483,7 +520,9 @@ public void lookupStringShouldReturnNullWhenUserExtIsMissingForUserFirstPartyDat TargetingCategory.Type.userFirstPartyData, "sport"); final RequestContext context = new RequestContext( request(r -> r.user(user(identity()))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isNull(); @@ -496,7 +535,9 @@ public void lookupStringShouldReturnNullWhenUserIsMissingForUserFirstPartyData() TargetingCategory.Type.userFirstPartyData, "sport"); final RequestContext context = new RequestContext( request(identity()), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isNull(); @@ -514,7 +555,8 @@ public void lookupStringShouldReturnSiteFirstPartyDataFromImpExt() { .site(site(s -> s.ext(extSite))) .app(app(a -> a.ext(extApp)))), imp(i -> i.ext(obj("context", obj("data", obj("section", obj("sport", "hockey")))))), - txnLog, jacksonMapper); + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("hockey"); @@ -532,7 +574,8 @@ public void lookupStringShouldReturnSiteFirstPartyDataFromSiteExt() { .site(site(s -> s.ext(extSite))) .app(app(a -> a.ext(extApp)))), imp(identity()), - txnLog, jacksonMapper); + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("hockey"); @@ -547,7 +590,8 @@ public void lookupStringShouldReturnSiteFirstPartyDataFromAppExt() { final RequestContext context = new RequestContext( request(r -> r.app(app(a -> a.ext(extApp)))), imp(identity()), - txnLog, jacksonMapper); + txnLog, + jacksonMapper); // when and then assertThat(context.lookupString(category)).isEqualTo("hockey"); @@ -557,7 +601,11 @@ public void lookupStringShouldReturnSiteFirstPartyDataFromAppExt() { public void lookupStringShouldThrowExceptionWhenUnexpectedCategory() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.location); - final RequestContext context = new RequestContext(request(identity()), imp(identity()), txnLog, jacksonMapper); + final RequestContext context = new RequestContext( + request(identity()), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThatThrownBy(() -> context.lookupString(category)) @@ -573,7 +621,9 @@ public void lookupIntegerShouldReturnDowFromUserExt() { extUser.addProperty("time", obj("userdow", 5)); final RequestContext context = new RequestContext( request(r -> r.user(user(u -> u.ext(extUser)))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupInteger(category)).isEqualTo(5); @@ -587,7 +637,9 @@ public void lookupIntegerShouldReturnHourFromExt() { extUser.addProperty("time", obj("userhour", 15)); final RequestContext context = new RequestContext( request(r -> r.user(user(u -> u.ext(extUser)))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupInteger(category)).isEqualTo(15); @@ -613,7 +665,9 @@ public void lookupIntegerShouldReturnNullWhenBidderParamIsNotInteger() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.bidderParam, "rubicon.siteId"); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.ext(obj("rubicon", obj("siteId", mapper.valueToTree(123.456d))))), txnLog, jacksonMapper); + imp(i -> i.ext(obj("rubicon", obj("siteId", mapper.valueToTree(123.456d))))), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupInteger(category)).isNull(); @@ -625,7 +679,9 @@ public void lookupIntegerShouldReturnNullWhenBidderParamIsMissing() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.bidderParam, "rubicon.siteId"); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.ext(obj("rubicon", "phony"))), txnLog, jacksonMapper); + imp(i -> i.ext(obj("rubicon", "phony"))), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupInteger(category)).isNull(); @@ -639,7 +695,8 @@ public void lookupIntegerShouldReturnUserFirstPartyData() { final RequestContext context = new RequestContext( request(r -> r.user(user(u -> u.ext(extUser)))), imp(identity()), - txnLog, jacksonMapper); + txnLog, + jacksonMapper); // when and then assertThat(context.lookupInteger(category)).isEqualTo(123); @@ -653,7 +710,8 @@ public void lookupIntegerShouldReturnSiteFirstPartyData() { final RequestContext context = new RequestContext( request(r -> r.site(site(s -> s.ext(extSite)))), imp(identity()), - txnLog, jacksonMapper); + txnLog, + jacksonMapper); // when and then assertThat(context.lookupInteger(category)).isEqualTo(123); @@ -663,7 +721,9 @@ public void lookupIntegerShouldReturnSiteFirstPartyData() { public void lookupIntegerShouldThrowExceptionWhenUnexpectedCategory() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.domain); - final RequestContext context = new RequestContext(request(identity()), imp(identity()), txnLog, jacksonMapper); + final RequestContext context = new RequestContext(request(identity()), imp(identity()), + txnLog, + jacksonMapper); // when and then assertThatThrownBy(() -> context.lookupInteger(category)) @@ -677,7 +737,9 @@ public void lookupStringsShouldReturnMediaTypeBannerAndVideo() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.mediaType); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.banner(banner(identity())).video(Video.builder().build())), txnLog, jacksonMapper); + imp(i -> i.banner(banner(identity())).video(Video.builder().build())), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupStrings(category)).containsOnly("banner", "video"); @@ -689,7 +751,9 @@ public void lookupStringsShouldReturnMediaTypeVideoAndNative() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.mediaType); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.video(Video.builder().build()).xNative(Native.builder().build())), txnLog, jacksonMapper); + imp(i -> i.video(Video.builder().build()).xNative(Native.builder().build())), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupStrings(category)).containsOnly("video", "native"); @@ -716,7 +780,8 @@ public void lookupStringsShouldReturnEmptyListWhenBidderParamIsNotArray() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.bidderParam, "rubicon.siteId"); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.ext(obj("prebid", obj("bidder", obj("rubicon", obj("siteId", "phony")))))), + imp(i -> i.ext(obj("prebid", obj("bidder", + obj("rubicon", obj("siteId", mapper.createObjectNode())))))), txnLog, jacksonMapper); @@ -724,6 +789,21 @@ public void lookupStringsShouldReturnEmptyListWhenBidderParamIsNotArray() { assertThat(context.lookupStrings(category)).isEmpty(); } + @Test + public void lookupStringsShouldReturnListOfSingleStringWhenBidderParamIsString() { + // given + final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.bidderParam, "rubicon.siteId"); + final RequestContext context = new RequestContext( + request(identity()), + imp(i -> i.ext(obj("prebid", obj("bidder", + obj("rubicon", obj("siteId", "value")))))), + txnLog, + jacksonMapper); + + // when and then + assertThat(context.lookupStrings(category)).containsOnly("value"); + } + @Test public void lookupStringsShouldReturnOnlyStringsWhenNonStringBidderParamPresent() { // given @@ -745,7 +825,8 @@ public void lookupStringsShouldReturnEmptyListWhenBidderParamIsMissing() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.bidderParam, "rubicon.siteId"); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.ext(obj("prebid", obj("bidder", obj("prebid", obj("bidder", obj("rubicon", "phony"))))))), + imp(i -> i.ext(obj("prebid", obj("bidder", obj("prebid", obj("bidder", + obj("rubicon", "phony"))))))), txnLog, jacksonMapper); @@ -761,7 +842,8 @@ public void lookupStringsShouldReturnUserFirstPartyData() { final RequestContext context = new RequestContext( request(r -> r.user(user(u -> u.ext(extUser)))), imp(identity()), - txnLog, jacksonMapper); + txnLog, + jacksonMapper); // when and then assertThat(context.lookupStrings(category)).containsOnly("123", "456"); @@ -775,7 +857,8 @@ public void lookupStringsShouldReturnSiteFirstPartyData() { final RequestContext context = new RequestContext( request(r -> r.site(site(s -> s.ext(extSite)))), imp(identity()), - txnLog, jacksonMapper); + txnLog, + jacksonMapper); // when and then assertThat(context.lookupStrings(category)).containsOnly("123", "456"); @@ -790,7 +873,9 @@ public void lookupStringsShouldReturnSegmentsWithDesiredSource() { data(d -> d.id("rubicon").segment(asList(segment(s -> s.id("1")), segment(s -> s.id("2"))))), data(d -> d.id("bluekai").segment( asList(segment(s -> s.id("3")), segment(s -> s.id("4")))))))))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupStrings(category)).containsOnly("1", "2"); @@ -804,7 +889,9 @@ public void lookupStringsShouldReturnEmptyListWhenDesiredSourceIsMissing() { request(r -> r.user(user(u -> u.data(singletonList( data(d -> d.id("bluekai").segment( asList(segment(s -> s.id("3")), segment(s -> s.id("4")))))))))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupStrings(category)).isEmpty(); @@ -817,7 +904,9 @@ public void lookupStringsShouldSkipSegmentsWithoutIds() { final RequestContext context = new RequestContext( request(r -> r.user(user(u -> u.data(singletonList( data(d -> d.id("rubicon").segment(asList(segment(s -> s.id("1")), segment(identity()))))))))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupStrings(category)).containsOnly("1"); @@ -829,7 +918,9 @@ public void lookupStringsShouldReturnEmptyListWhenSegmentsAreMissing() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.userSegment, "rubicon"); final RequestContext context = new RequestContext( request(r -> r.user(user(u -> u.data(singletonList(data(d -> d.id("rubicon"))))))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupStrings(category)).isEmpty(); @@ -841,7 +932,9 @@ public void lookupStringsShouldTolerateMissingSource() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.userSegment, "rubicon"); final RequestContext context = new RequestContext( request(r -> r.user(user(u -> u.data(singletonList(data(identity())))))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupStrings(category)).isEmpty(); @@ -853,7 +946,9 @@ public void lookupStringsShouldReturnEmptyListWhenDataIsMissing() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.userSegment, "rubicon"); final RequestContext context = new RequestContext( request(r -> r.user(user(identity()))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupStrings(category)).isEmpty(); @@ -864,7 +959,10 @@ public void lookupStringsShouldReturnEmptyListWhenUserIsMissing() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.userSegment, "rubicon"); final RequestContext context = new RequestContext( - request(identity()), imp(identity()), txnLog, jacksonMapper); + request(identity()), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupStrings(category)).isEmpty(); @@ -874,7 +972,11 @@ public void lookupStringsShouldReturnEmptyListWhenUserIsMissing() { public void lookupStringsShouldThrowExceptionWhenUnexpectedCategory() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.domain); - final RequestContext context = new RequestContext(request(identity()), imp(identity()), txnLog, jacksonMapper); + final RequestContext context = new RequestContext( + request(identity()), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThatThrownBy(() -> context.lookupStrings(category)) @@ -903,7 +1005,8 @@ public void lookupIntegersShouldReturnEmptyListWhenBidderParamIsNotArray() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.bidderParam, "rubicon.siteId"); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.ext(obj("prebid", obj("bidder", obj("rubicon", obj("siteId", "phony")))))), + imp(i -> i.ext(obj("prebid", obj("bidder", + obj("rubicon", obj("siteId", mapper.createObjectNode())))))), txnLog, jacksonMapper); @@ -911,6 +1014,21 @@ public void lookupIntegersShouldReturnEmptyListWhenBidderParamIsNotArray() { assertThat(context.lookupIntegers(category)).isEmpty(); } + @Test + public void lookupIntegersShouldReturnListOfSingleIntegerWhenBidderParamIsInteger() { + // given + final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.bidderParam, "rubicon.siteId"); + final RequestContext context = new RequestContext( + request(identity()), + imp(i -> i.ext(obj("prebid", obj("bidder", + obj("rubicon", obj("siteId", 123)))))), + txnLog, + jacksonMapper); + + // when and then + assertThat(context.lookupIntegers(category)).containsOnly(123); + } + @Test public void lookupIntegersShouldReturnOnlyIntegersWhenNonIntegerBidderParamPresent() { // given @@ -932,7 +1050,8 @@ public void lookupIntegersShouldReturnEmptyListWhenBidderParamIsMissing() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.bidderParam, "rubicon.siteId"); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.ext(obj("prebid", obj("bidder", obj("rubicon", "phony"))))), + imp(i -> i.ext(obj("prebid", obj("bidder", + obj("rubicon", "phony"))))), txnLog, jacksonMapper); @@ -947,7 +1066,9 @@ public void lookupIntegersShouldReturnUserFirstPartyData() { final ExtUser extUser = ExtUser.builder().data(obj("sport", mapper.valueToTree(asList(123, 456)))).build(); final RequestContext context = new RequestContext( request(r -> r.user(user(u -> u.ext(extUser)))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupIntegers(category)).containsOnly(123, 456); @@ -960,7 +1081,9 @@ public void lookupIntegersShouldReturnSiteFirstPartyData() { final ExtSite extSite = ExtSite.of(null, obj("sport", mapper.valueToTree(asList(123, 456)))); final RequestContext context = new RequestContext( request(r -> r.site(site(s -> s.ext(extSite)))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupIntegers(category)).containsOnly(123, 456); @@ -984,7 +1107,8 @@ public void lookupSizesShouldReturnSizes() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.size); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.banner(banner(b -> b.format(asList(format(300, 250), format(400, 300)))))), txnLog, + imp(i -> i.banner(banner(b -> b.format(asList(format(300, 250), format(400, 300)))))), + txnLog, jacksonMapper); // when and then @@ -997,7 +1121,9 @@ public void lookupSizesShouldReturnEmptyListWhenFormatIsMissing() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.size); final RequestContext context = new RequestContext( request(identity()), - imp(i -> i.banner(banner(identity()))), txnLog, jacksonMapper); + imp(i -> i.banner(banner(identity()))), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupSizes(category)).isEmpty(); @@ -1008,7 +1134,10 @@ public void lookupSizesShouldReturnEmptyListWhenBannerIsMissing() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.size); final RequestContext context = new RequestContext( - request(identity()), imp(identity()), txnLog, jacksonMapper); + request(identity()), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupSizes(category)).isEmpty(); @@ -1018,7 +1147,11 @@ public void lookupSizesShouldReturnEmptyListWhenBannerIsMissing() { public void lookupSizesShouldThrowExceptionWhenUnexpectedCategory() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.domain); - final RequestContext context = new RequestContext(request(identity()), imp(identity()), txnLog, jacksonMapper); + final RequestContext context = new RequestContext( + request(identity()), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThatThrownBy(() -> context.lookupSizes(category)) @@ -1032,7 +1165,9 @@ public void lookupGeoLocationShouldReturnLocation() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.location); final RequestContext context = new RequestContext( request(r -> r.device(device(d -> d.geo(geo(g -> g.lat(50f).lon(60f)))))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupGeoLocation(category)).isEqualTo(GeoLocation.of(50f, 60f)); @@ -1044,7 +1179,9 @@ public void lookupGeoLocationShouldReturnNullWhenLonIsMissing() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.location); final RequestContext context = new RequestContext( request(r -> r.device(device(d -> d.geo(geo(g -> g.lat(50f)))))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupGeoLocation(category)).isNull(); @@ -1056,7 +1193,9 @@ public void lookupGeoLocationShouldReturnNullWhenLatIsMissing() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.location); final RequestContext context = new RequestContext( request(r -> r.device(device(d -> d.geo(geo(g -> g.lon(60f)))))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupGeoLocation(category)).isNull(); @@ -1068,7 +1207,9 @@ public void lookupGeoLocationShouldReturnNullWhenGeoIsMissing() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.location); final RequestContext context = new RequestContext( request(r -> r.device(device(identity()))), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupGeoLocation(category)).isNull(); @@ -1080,7 +1221,9 @@ public void lookupGeoLocationShouldReturnNullWhenDeviceIsMissing() { final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.location); final RequestContext context = new RequestContext( request(identity()), - imp(identity()), txnLog, jacksonMapper); + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThat(context.lookupGeoLocation(category)).isNull(); @@ -1090,7 +1233,11 @@ public void lookupGeoLocationShouldReturnNullWhenDeviceIsMissing() { public void lookupGeoLocationShouldThrowExceptionWhenUnexpectedCategory() { // given final TargetingCategory category = new TargetingCategory(TargetingCategory.Type.domain); - final RequestContext context = new RequestContext(request(identity()), imp(identity()), txnLog, jacksonMapper); + final RequestContext context = new RequestContext( + request(identity()), + imp(identity()), + txnLog, + jacksonMapper); // when and then assertThatThrownBy(() -> context.lookupGeoLocation(category))