diff --git a/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java b/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java index 3e15eb554a3..800f21f2c9c 100644 --- a/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java +++ b/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java @@ -1,10 +1,8 @@ package org.prebid.server.bidder.yahooads; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; @@ -20,8 +18,6 @@ import io.vertx.core.http.HttpMethod; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.prebid.server.auction.versionconverter.BidRequestOrtbVersionConversionManager; -import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; @@ -32,9 +28,7 @@ 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.FlexibleExtension; import org.prebid.server.proto.openrtb.ext.request.ExtRegs; -import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsa; import org.prebid.server.proto.openrtb.ext.request.yahooads.ExtImpYahooAds; import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.util.HttpUtil; @@ -44,7 +38,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.Optional; public class YahooAdsBidder implements Bidder { @@ -52,16 +45,17 @@ public class YahooAdsBidder implements Bidder { new TypeReference<>() { }; + private static final String OPENRTB_VERSION = "2.6"; + private static final String GPP_PROPERTY = "gpp"; + private static final String GPP_SID_PROPERTY = "gpp_sid"; + private final String endpointUrl; - private final BidRequestOrtbVersionConversionManager conversionManager; private final JacksonMapper mapper; public YahooAdsBidder(String endpointUrl, - BidRequestOrtbVersionConversionManager conversionManager, JacksonMapper mapper) { this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); this.mapper = Objects.requireNonNull(mapper); - this.conversionManager = Objects.requireNonNull(conversionManager); } @Override @@ -70,16 +64,14 @@ public Result>> makeHttpRequests(BidRequest bidRequ final List errors = new ArrayList<>(); final Regs regs = bidRequest.getRegs(); - final BidRequest bidRequestOpenRtb25 = this.conversionManager.convertFromAuctionSupportedVersion(bidRequest, - OrtbVersion.ORTB_2_5); + final Regs modifiedRegs = regs != null ? modifyRegs(regs) : null; - final List impList = bidRequestOpenRtb25.getImp(); + final List impList = bidRequest.getImp(); for (int i = 0; i < impList.size(); i++) { try { final Imp imp = impList.get(i); final ExtImpYahooAds extImpYahooAds = parseAndValidateImpExt(imp.getExt(), i); - final BidRequest modifiedRequest = modifyRequest(bidRequestOpenRtb25, imp, extImpYahooAds, - regs); + final BidRequest modifiedRequest = modifyRequest(bidRequest, imp, extImpYahooAds, modifiedRegs); bidRequests.add(makeHttpRequest(modifiedRequest)); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); @@ -111,8 +103,7 @@ private ExtImpYahooAds parseAndValidateImpExt(ObjectNode impExtNode, int index) return extImpYahooAds; } - private BidRequest modifyRequest(BidRequest request, Imp imp, ExtImpYahooAds extImpYahooAds, - Regs regs) { + private BidRequest modifyRequest(BidRequest request, Imp imp, ExtImpYahooAds extImpYahooAds, Regs regs) { final BidRequest.BidRequestBuilder requestBuilder = request.toBuilder(); final Site site = request.getSite(); @@ -125,7 +116,7 @@ private BidRequest modifyRequest(BidRequest request, Imp imp, ExtImpYahooAds ext } if (regs != null) { - requestBuilder.regs(modifyRegs(regs)); + requestBuilder.regs(regs); } return requestBuilder @@ -171,49 +162,66 @@ private static Banner modifyBanner(Banner banner) { } private Regs modifyRegs(Regs regs) { - final ExtRegs extRegs = resolveExtRegs(regs); - - return Regs.builder().ext(extRegs).build(); - } + final ExtRegs ext = regs.getExt(); + if (ext == null || !(ext.containsProperty(GPP_PROPERTY) || ext.containsProperty(GPP_SID_PROPERTY))) { + return regs; + } - private ExtRegs resolveExtRegs(Regs regs) { - final Integer gdpr = resolveGdpr(regs); - final String usPrivacy = resolveUsPrivacy(regs); final String gpp = regs.getGpp(); + final String resolvedGpp = gpp == null ? gppToPromote(ext) : gpp; + final List gppSid = regs.getGppSid(); + final List resolvedGppSid = CollectionUtils.isEmpty(gppSid) ? gppSidToPromote(ext) : gppSid; - final String gpc = Optional.ofNullable(regs.getExt()) - .map(ExtRegs::getGpc) - .orElse(null); - final ExtRegsDsa dsa = Optional.ofNullable(regs.getExt()) - .map(ExtRegs::getDsa) - .orElse(null); - final ExtRegs extRegs = ExtRegs.of(gdpr, usPrivacy, gpc, dsa); - extRegs.addProperty("gpp", TextNode.valueOf(gpp)); - if (!CollectionUtils.isEmpty(gppSid)) { - final ArrayNode gppArrayNode = mapper.mapper().createArrayNode(); - gppSid.forEach(gppArrayNode::add); - extRegs.addProperty("gpp_sid", gppArrayNode); + final ExtRegs modifiedExt = copy(ext); + if (resolvedGpp != null) { + modifiedExt.removeProperty(GPP_PROPERTY); } - if (regs.getCoppa() != null) { - extRegs.addProperty("coppa", IntNode.valueOf(regs.getCoppa())); + if (CollectionUtils.isNotEmpty(resolvedGppSid)) { + modifiedExt.removeProperty(GPP_SID_PROPERTY); } - Optional.ofNullable(regs.getExt()) - .map(FlexibleExtension::getProperties) - .ifPresent(extRegs::addProperties); + return regs.toBuilder() + .gpp(resolvedGpp) + .gppSid(resolvedGppSid) + .ext(isExtEmpty(modifiedExt) ? null : modifiedExt) + .build(); + } + + private static String gppToPromote(ExtRegs ext) { + final JsonNode node = ext.getProperty(GPP_PROPERTY); + return node != null && node.isTextual() ? node.asText() : null; + } + + private static List gppSidToPromote(ExtRegs ext) { + final JsonNode node = ext.getProperty(GPP_SID_PROPERTY); + if (node == null || !node.isArray() || node.isEmpty()) { + return null; + } + + final List sids = new ArrayList<>(node.size()); + for (final JsonNode elem : node) { + if (!elem.isIntegralNumber() || !elem.canConvertToInt()) { + return null; + } + sids.add(elem.intValue()); + } - return extRegs; + return sids; } - private static Integer resolveGdpr(Regs regs) { - return regs.getGdpr() != null ? regs.getGdpr() - : (regs.getExt() != null ? regs.getExt().getGdpr() : null); + private ExtRegs copy(ExtRegs ext) { + final ExtRegs result = ExtRegs.of(ext.getGdpr(), ext.getUsPrivacy(), ext.getGpc(), ext.getDsa()); + mapper.fillExtension(result, ext.getProperties()); + return result; } - private static String resolveUsPrivacy(Regs regs) { - return regs.getUsPrivacy() != null ? regs.getUsPrivacy() - : (regs.getExt() != null ? regs.getExt().getUsPrivacy() : null); + private static boolean isExtEmpty(ExtRegs ext) { + return ext.getGdpr() == null + && ext.getUsPrivacy() == null + && ext.getGpc() == null + && ext.getDsa() == null + && ext.getProperties().isEmpty(); } private HttpRequest makeHttpRequest(BidRequest outgoingRequest) { @@ -228,7 +236,7 @@ private HttpRequest makeHttpRequest(BidRequest outgoingRequest) { private static MultiMap makeHeaders(Device device) { final MultiMap headers = HttpUtil.headers() - .add(HttpUtil.X_OPENRTB_VERSION_HEADER, "2.5"); + .add(HttpUtil.X_OPENRTB_VERSION_HEADER, OPENRTB_VERSION); final String deviceUa = device != null ? device.getUa() : null; HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, deviceUa); diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/FlexibleExtension.java b/src/main/java/org/prebid/server/proto/openrtb/ext/FlexibleExtension.java index 4a33f49772f..d32c8eb0032 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/FlexibleExtension.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/FlexibleExtension.java @@ -42,4 +42,8 @@ public void addProperty(String key, JsonNode value) { public void addProperties(Map properties) { this.properties.putAll(properties); } + + public void removeProperty(String key) { + properties.remove(key); + } } diff --git a/src/main/java/org/prebid/server/spring/config/bidder/YahooAdsConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/YahooAdsConfiguration.java index 78a857edcd1..0be607ce024 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/YahooAdsConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/YahooAdsConfiguration.java @@ -1,6 +1,5 @@ package org.prebid.server.spring.config.bidder; -import org.prebid.server.auction.versionconverter.BidRequestOrtbVersionConversionManager; import org.prebid.server.bidder.BidderDeps; import org.prebid.server.bidder.yahooads.YahooAdsBidder; import org.prebid.server.json.JacksonMapper; @@ -26,12 +25,11 @@ BidderConfigurationProperties configurationProperties() { @Bean BidderDeps yahooAdsBidderDeps(BidderConfigurationProperties yahooAdsConfigurationProperties, - JacksonMapper mapper, - BidRequestOrtbVersionConversionManager conversionManager) { + JacksonMapper mapper) { return BidderDepsAssembler.forBidder(BIDDER_NAME) .withConfig(yahooAdsConfigurationProperties) - .bidderCreator(config -> new YahooAdsBidder(config.getEndpoint(), conversionManager, mapper)) + .bidderCreator(config -> new YahooAdsBidder(config.getEndpoint(), mapper)) .assemble(); } } diff --git a/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java b/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java index 7966a94e911..5abbb2dc509 100644 --- a/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java @@ -1,6 +1,8 @@ package org.prebid.server.bidder.yahooads; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; @@ -16,11 +18,8 @@ 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.auction.versionconverter.BidRequestOrtbVersionConversionManager; -import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; import org.prebid.server.bidder.model.BidderError; @@ -43,10 +42,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.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mock.Strictness.LENIENT; -import static org.mockito.Mockito.when; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.video; @@ -55,22 +50,16 @@ public class YahooAdsBidderTest extends VertxTest { private static final String ENDPOINT_URL = "https://test.endpoint.com"; - @Mock(strictness = LENIENT) - private BidRequestOrtbVersionConversionManager conversionManager; - private YahooAdsBidder target; @BeforeEach public void setUp() { - when(conversionManager.convertFromAuctionSupportedVersion(any(BidRequest.class), eq(OrtbVersion.ORTB_2_5))) - .thenAnswer(answer -> answer.getArgument(0)); - target = new YahooAdsBidder(ENDPOINT_URL, conversionManager, jacksonMapper); + target = new YahooAdsBidder(ENDPOINT_URL, jacksonMapper); } @Test public void creationShouldFailOnInvalidEndpointUrl() { - assertThatIllegalArgumentException().isThrownBy(() -> new YahooAdsBidder("invalid_url", - conversionManager, jacksonMapper)); + assertThatIllegalArgumentException().isThrownBy(() -> new YahooAdsBidder("invalid_url", jacksonMapper)); } @Test @@ -282,7 +271,7 @@ public void makeHttpRequestsShouldSetExpectedHeaders() { assertThat(result.getValue().getFirst().getHeaders()) .extracting(Map.Entry::getKey, Map.Entry::getValue) .containsOnly(tuple("User-Agent", "UA"), - tuple("x-openrtb-version", "2.5"), + tuple("x-openrtb-version", "2.6"), tuple("Content-Type", "application/json;charset=utf-8"), tuple("Accept", "application/json")); } @@ -402,57 +391,542 @@ public void makeBidsShouldSkipNotSupportedImpAndReturnVideoBidWhenVideoPresent() } @Test - public void makeBidsShouldRemoveTheOpenRTB26Regs() { + public void makeHttpRequestsShouldPreserveTopLevel26RegsAndExtTypedFields() { // given final ExtRegsDsa dsa = ExtRegsDsa.of(2, 2, 3, emptyList()); - final BidRequest bidRequest = givenBidRequest(identity(), - requestBuilder -> requestBuilder.regs(Regs.builder() - .gdpr(1) - .usPrivacy("1YNN") - .gpp("gppconsent") - .gppSid(List.of(6)) - .ext(ExtRegs.of(null, null, "1", dsa)) - .build()).device(Device.builder().ua("UA").build())); + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder() + .gdpr(1) + .usPrivacy("1YNN") + .gpp("gppconsent") + .gppSid(List.of(6)) + .ext(ExtRegs.of(null, null, "1", dsa)) + .build()) + .device(Device.builder().ua("UA").build())); // when final Result>> result = target.makeHttpRequests(bidRequest); // then assertThat(result.getErrors()).isEmpty(); - final Regs regs = result.getValue().getFirst().getPayload().getRegs(); - assertThat(regs.getGdpr()).isNull(); - assertThat(regs.getUsPrivacy()).isNull(); - assertThat(regs.getGpp()).isNull(); - assertThat(regs.getGppSid()).isNull(); - assertThat(regs.getExt()).isNotNull(); - assertThat(regs.getExt().getGdpr()).isEqualTo(1); - assertThat(regs.getExt().getUsPrivacy()).isEqualTo("1YNN"); - assertThat(regs.getExt().getGpc()).isEqualTo("1"); - assertThat(regs.getExt().getDsa()).isEqualTo(dsa); - assertThat(regs.getExt().getProperty("gpp").asText()).isEqualTo("gppconsent"); - assertThat(regs.getExt().getProperty("gpp_sid").get(0).asText()).isEqualTo("6"); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGdpr()).isEqualTo(1); + assertThat(regs.getUsPrivacy()).isEqualTo("1YNN"); + assertThat(regs.getGpp()).isEqualTo("gppconsent"); + assertThat(regs.getGppSid()).containsExactly(6); + assertThat(regs.getExt()).isEqualTo(ExtRegs.of(null, null, "1", dsa)); + }); } @Test - public void makeBidsShouldOverwriteRegsExtValues() { + public void makeHttpRequestsShouldPromoteLegacyExtGppAndGppSidToTopLevel() { // given - final BidRequest bidRequest = givenBidRequest(identity(), - requestBuilder -> requestBuilder.regs(Regs.builder() - .gdpr(1) - .ext(ExtRegs.of(0, "1YNN", null, null)) - .build()).device(Device.builder().ua("UA").build())); + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp", TextNode.valueOf("legacy_gpp_value")); + extRegs.addProperty("gpp_sid", mapper.valueToTree(List.of(6, 8))); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGpp()).isEqualTo("legacy_gpp_value"); + assertThat(regs.getGppSid()).containsExactly(6, 8); + assertThat(regs.getExt()).isNull(); + }); + } + + @Test + public void makeHttpRequestsShouldPromoteOnlyGppFromExtAndStripIt() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp", TextNode.valueOf("only_gpp")); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGpp()).isEqualTo("only_gpp"); + assertThat(regs.getGppSid()).isNull(); + assertThat(regs.getExt()).isNull(); + }); + } + + @Test + public void makeHttpRequestsShouldPreserveTopLevelGdprWhilePromotingGppFromExt() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp", TextNode.valueOf("mixed_gpp")); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().gdpr(1).ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGdpr()).isEqualTo(1); + assertThat(regs.getGpp()).isEqualTo("mixed_gpp"); + assertThat(regs.getExt()).isNull(); + }); + } + + @Test + public void makeHttpRequestsShouldKeepGpcAndUnrelatedExtPropertyAfterPromotion() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, "1", null); + extRegs.addProperty("gpp", TextNode.valueOf("with_gpc")); + extRegs.addProperty("unrelated", TextNode.valueOf("keep_me")); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final ExtRegs expectedExt = ExtRegs.of(null, null, "1", null); + expectedExt.addProperty("unrelated", TextNode.valueOf("keep_me")); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGpp()).isEqualTo("with_gpc"); + assertThat(regs.getExt()).isEqualTo(expectedExt); + }); + } + + @Test + public void makeHttpRequestsShouldNotPromoteWhenExtPropertyHasWrongType() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp", IntNode.valueOf(99)); + extRegs.addProperty("gpp_sid", TextNode.valueOf("not_array")); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); // when final Result>> result = target.makeHttpRequests(bidRequest); // then + final ExtRegs expectedExt = ExtRegs.of(null, null, null, null); + expectedExt.addProperty("gpp", IntNode.valueOf(99)); + expectedExt.addProperty("gpp_sid", TextNode.valueOf("not_array")); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGpp()).isNull(); + assertThat(regs.getGppSid()).isNull(); + assertThat(regs.getExt()).isEqualTo(expectedExt); + }); + } + + @Test + public void makeHttpRequestsShouldLeaveMalformedExtValueInExtWhenSiblingFieldIsPromoted() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp_sid", mapper.valueToTree(List.of(6))); + extRegs.addProperty("gpp", IntNode.valueOf(99)); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final ExtRegs expectedExt = ExtRegs.of(null, null, null, null); + expectedExt.addProperty("gpp", IntNode.valueOf(99)); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGppSid()).containsExactly(6); + assertThat(regs.getGpp()).isNull(); + assertThat(regs.getExt()).isEqualTo(expectedExt); + }); + } + + @Test + public void makeHttpRequestsShouldNotPromoteGppSidWhenArrayHasNonIntegerElement() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp_sid", mapper.valueToTree(List.of(7, "foo", 8))); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final ExtRegs expectedExt = ExtRegs.of(null, null, null, null); + expectedExt.addProperty("gpp_sid", mapper.valueToTree(List.of(7, "foo", 8))); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGppSid()).isNull(); + assertThat(regs.getExt()).isEqualTo(expectedExt); + }); + } + + @Test + public void makeHttpRequestsShouldNotPromoteExtValuesThatDoNotFitInInt() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp_sid", mapper.valueToTree(List.of(7, 3_000_000_000L))); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final ExtRegs expectedExt = ExtRegs.of(null, null, null, null); + expectedExt.addProperty("gpp_sid", mapper.valueToTree(List.of(7, 3_000_000_000L))); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGppSid()).isNull(); + assertThat(regs.getExt()).isEqualTo(expectedExt); + }); + } + + @Test + public void makeHttpRequestsShouldNotPromoteEmptyExtGppSidArray() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp_sid", mapper.createArrayNode()); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final ExtRegs expectedExt = ExtRegs.of(null, null, null, null); + expectedExt.addProperty("gpp_sid", mapper.createArrayNode()); + assertThat(result.getErrors()).isEmpty(); - final Regs regs = result.getValue().getFirst().getPayload().getRegs(); - assertThat(regs.getGdpr()).isNull(); - assertThat(regs.getUsPrivacy()).isNull(); - assertThat(regs.getExt().getGdpr()).isEqualTo(1); - assertThat(regs.getExt().getUsPrivacy()).isEqualTo("1YNN"); - assertThat(regs.getExt().getDsa()).isNull(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGppSid()).isNull(); + assertThat(regs.getExt()).isEqualTo(expectedExt); + }); + } + + @Test + public void makeHttpRequestsShouldNotPromoteGppSidWhenArrayHasFractionalElement() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp_sid", mapper.valueToTree(List.of(6.5))); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final ExtRegs expectedExt = ExtRegs.of(null, null, null, null); + expectedExt.addProperty("gpp_sid", mapper.valueToTree(List.of(6.5))); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGppSid()).isNull(); + assertThat(regs.getExt()).isEqualTo(expectedExt); + }); + } + + @Test + public void makeHttpRequestsShouldPromoteExtGppSidOverEmptyTopLevelArray() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp_sid", mapper.valueToTree(List.of(6))); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().gppSid(emptyList()).ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGppSid()).containsExactly(6); + assertThat(regs.getExt()).isNull(); + }); + } + + @Test + public void makeHttpRequestsShouldPromoteGppSidWhileTopLevelGppSupersedesItsExtCopy() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp", TextNode.valueOf("stale-ext-gpp")); + extRegs.addProperty("gpp_sid", mapper.valueToTree(List.of(6))); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().gpp("top-level-gpp").ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGpp()).isEqualTo("top-level-gpp"); + assertThat(regs.getGppSid()).containsExactly(6); + assertThat(regs.getExt()).isNull(); + }); + } + + @Test + public void makeHttpRequestsShouldReturnRegsUnchangedWhenExtHasOnlyUnrelatedProperties() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("customKey", TextNode.valueOf("keep_me")); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final ExtRegs expectedExt = ExtRegs.of(null, null, null, null); + expectedExt.addProperty("customKey", TextNode.valueOf("keep_me")); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGpp()).isNull(); + assertThat(regs.getGppSid()).isNull(); + assertThat(regs.getExt()).isEqualTo(expectedExt); + }); + } + + @Test + public void makeHttpRequestsShouldDropExtGppWhenTopLevelGppAlreadySet() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp", TextNode.valueOf("ext-gpp")); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().gpp("top-level-gpp").ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGpp()).isEqualTo("top-level-gpp"); + assertThat(regs.getExt()).isNull(); + }); + } + + @Test + public void makeHttpRequestsShouldTreatEmptyTopLevelGppAsSet() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp", TextNode.valueOf("ext-gpp")); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().gpp("").ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGpp()).isEmpty(); + assertThat(regs.getExt()).isNull(); + }); + } + + @Test + public void makeHttpRequestsShouldDropExtGppSidWhenTopLevelGppSidAlreadySet() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp_sid", mapper.valueToTree(List.of(6))); + + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().gppSid(List.of(99)).ext(extRegs).build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGppSid()).containsExactly(99); + assertThat(regs.getExt()).isNull(); + }); + } + + @Test + public void makeHttpRequestsShouldApplyTheSamePromotedRegsToEveryImp() { + // given + final ExtRegs extRegs = ExtRegs.of(null, null, null, null); + extRegs.addProperty("gpp", TextNode.valueOf("shared-gpp")); + + final BidRequest bidRequest = BidRequest.builder() + .site(Site.builder().id("123").build()) + .imp(asList( + givenImp(impBuilder -> impBuilder.id("imp1")), + givenImp(impBuilder -> impBuilder.id("imp2")))) + .regs(Regs.builder().ext(extRegs).build()) + .device(Device.builder().ua("UA").build()) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(2) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGpp()).isEqualTo("shared-gpp"); + assertThat(regs.getExt()).isNull(); + }); + } + + @Test + public void makeHttpRequestsShouldShortCircuitWhenRegsHasNoExt() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder + .regs(Regs.builder().gdpr(0).gpp("already_top").build()) + .device(Device.builder().ua("UA").build())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getRegs) + .allSatisfy(regs -> { + assertThat(regs.getGdpr()).isEqualTo(0); + assertThat(regs.getGpp()).isEqualTo("already_top"); + assertThat(regs.getExt()).isNull(); + }); } private static BidRequest givenBidRequest( diff --git a/src/test/resources/org/prebid/server/it/openrtb2/yahooads/test-auction-yahooads-request.json b/src/test/resources/org/prebid/server/it/openrtb2/yahooads/test-auction-yahooads-request.json index fc5d0ee2c1a..40ca0f80c8e 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/yahooads/test-auction-yahooads-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/yahooads/test-auction-yahooads-request.json @@ -17,10 +17,10 @@ ], "tmax": 5000, "regs": { - "gpp": "gppstring", - "gpp_sid": [6], "ext": { - "gdpr": 0 + "gdpr": 0, + "gpp": "gppstring", + "gpp_sid": [6] } } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/yahooads/test-yahooads-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/yahooads/test-yahooads-bid-request.json index 54cece45a31..6c8dde3bf33 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/yahooads/test-yahooads-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/yahooads/test-yahooads-bid-request.json @@ -42,11 +42,9 @@ "ip": "193.168.244.1" }, "regs": { - "ext": { - "gpp": "gppstring", - "gpp_sid": [6], - "gdpr": 0 - } + "gpp": "gppstring", + "gpp_sid": [6], + "gdpr": 0 }, "ext": { "prebid": {