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..630fe1d4d34 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 GPP_PROPERTY = "gpp"; + private static final String GPP_SID_PROPERTY = "gpp_sid"; + private static final String COPPA_PROPERTY = "coppa"; + 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,15 +64,13 @@ 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 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, + final BidRequest modifiedRequest = modifyRequest(bidRequest, imp, extImpYahooAds, regs); bidRequests.add(makeHttpRequest(modifiedRequest)); } catch (PreBidException e) { @@ -125,7 +117,7 @@ private BidRequest modifyRequest(BidRequest request, Imp imp, ExtImpYahooAds ext } if (regs != null) { - requestBuilder.regs(modifyRegs(regs)); + requestBuilder.regs(promoteRegsExtToTopLevel(regs)); } return requestBuilder @@ -170,50 +162,98 @@ private static Banner modifyBanner(Banner banner) { .build(); } - private Regs modifyRegs(Regs regs) { - final ExtRegs extRegs = resolveExtRegs(regs); + // Promote legacy 2.5 regs.ext gpp/gpp_sid/coppa to their 2.6 top-level slots. + // A field is promoted only when it is absent at top-level and present and well-formed + // in ext; each promoted key is then removed from ext. Anything not promoted (already + // top-level, missing, or malformed) is left untouched, in ext. + private static Regs promoteRegsExtToTopLevel(Regs regs) { + final ExtRegs ext = regs.getExt(); + if (ext == null || ext.getProperties().isEmpty()) { + return regs; + } - return Regs.builder().ext(extRegs).build(); - } + final String promotedGpp = gppToPromote(regs, ext); + final List promotedGppSid = gppSidToPromote(regs, ext); + final Integer promotedCoppa = coppaToPromote(regs, ext); - private ExtRegs resolveExtRegs(Regs regs) { - final Integer gdpr = resolveGdpr(regs); - final String usPrivacy = resolveUsPrivacy(regs); - final String gpp = regs.getGpp(); - final List gppSid = regs.getGppSid(); - - 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); + if (promotedGpp == null && promotedGppSid == null && promotedCoppa == null) { + return regs; } - if (regs.getCoppa() != null) { - extRegs.addProperty("coppa", IntNode.valueOf(regs.getCoppa())); + + final Regs.RegsBuilder builder = regs.toBuilder(); + if (promotedGpp != null) { + builder.gpp(promotedGpp); + } + if (promotedGppSid != null) { + builder.gppSid(promotedGppSid); } + if (promotedCoppa != null) { + builder.coppa(promotedCoppa); + } + return builder + .ext(removePromotedKeys(ext, promotedGpp != null, promotedGppSid != null, promotedCoppa != null)) + .build(); + } - Optional.ofNullable(regs.getExt()) - .map(FlexibleExtension::getProperties) - .ifPresent(extRegs::addProperties); + // Value to lift from ext, or null when top-level already has it or ext lacks a valid value. + private static String gppToPromote(Regs regs, ExtRegs ext) { + if (regs.getGpp() != null) { + return null; + } + final JsonNode node = ext.getProperties().get(GPP_PROPERTY); + return node != null && node.isTextual() ? node.asText() : null; + } - return extRegs; + private static List gppSidToPromote(Regs regs, ExtRegs ext) { + if (!CollectionUtils.isEmpty(regs.getGppSid())) { + return null; + } + final JsonNode node = ext.getProperties().get(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()) { + return null; + } + sids.add(elem.asInt()); + } + return sids; } - private static Integer resolveGdpr(Regs regs) { - return regs.getGdpr() != null ? regs.getGdpr() - : (regs.getExt() != null ? regs.getExt().getGdpr() : null); + private static Integer coppaToPromote(Regs regs, ExtRegs ext) { + if (regs.getCoppa() != null) { + return null; + } + final JsonNode node = ext.getProperties().get(COPPA_PROPERTY); + return node != null && node.isIntegralNumber() ? node.asInt() : null; + } + + // Rebuild regs.ext keeping the typed fields and every property except the promoted ones. + private static ExtRegs removePromotedKeys(ExtRegs ext, + boolean gppPromoted, + boolean gppSidPromoted, + boolean coppaPromoted) { + final ExtRegs result = ExtRegs.of( + ext.getGdpr(), ext.getUsPrivacy(), ext.getGpc(), ext.getDsa()); + ext.getProperties().forEach((key, value) -> { + final boolean isPromotedKey = (gppPromoted && GPP_PROPERTY.equals(key)) + || (gppSidPromoted && GPP_SID_PROPERTY.equals(key)) + || (coppaPromoted && COPPA_PROPERTY.equals(key)); + if (!isPromotedKey) { + result.addProperty(key, value); + } + }); + return isExtEmpty(result) ? null : 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 +268,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, "2.6"); 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/spring/config/bidder/YahooAdsConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/YahooAdsConfiguration.java index 9cd3ed1249b..fae8aa0a1b1 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; @@ -31,13 +30,12 @@ BidderConfigurationProperties configurationProperties() { @Bean BidderDeps yahooAdsBidderDeps(BidderConfigurationProperties yahooAdsConfigurationProperties, @NotBlank @Value("${external-url}") String externalUrl, - JacksonMapper mapper, - BidRequestOrtbVersionConversionManager conversionManager) { + JacksonMapper mapper) { return BidderDepsAssembler.forBidder(BIDDER_NAME) .withConfig(yahooAdsConfigurationProperties) .usersyncerCreator(UsersyncerCreator.create(externalUrl)) - .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..170569171d5 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,10 @@ package org.prebid.server.bidder.yahooads; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +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 +20,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 +44,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 +52,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 +273,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,8 +393,7 @@ public void makeBidsShouldSkipNotSupportedImpAndReturnVideoBidWhenVideoPresent() } @Test - public void makeBidsShouldRemoveTheOpenRTB26Regs() { - // given + public void makeHttpRequestsShouldPreserveTopLevel26RegsAndExtTypedFields() { final ExtRegsDsa dsa = ExtRegsDsa.of(2, 2, 3, emptyList()); final BidRequest bidRequest = givenBidRequest(identity(), requestBuilder -> requestBuilder.regs(Regs.builder() @@ -411,6 +401,7 @@ public void makeBidsShouldRemoveTheOpenRTB26Regs() { .usPrivacy("1YNN") .gpp("gppconsent") .gppSid(List.of(6)) + .coppa(1) .ext(ExtRegs.of(null, null, "1", dsa)) .build()).device(Device.builder().ua("UA").build())); @@ -420,26 +411,213 @@ public void makeBidsShouldRemoveTheOpenRTB26Regs() { // 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.getGdpr()).isEqualTo(1); + assertThat(regs.getUsPrivacy()).isEqualTo("1YNN"); + assertThat(regs.getGpp()).isEqualTo("gppconsent"); + assertThat(regs.getGppSid()).containsExactly(6); + assertThat(regs.getCoppa()).isEqualTo(1); 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"); } @Test - public void makeBidsShouldOverwriteRegsExtValues() { - // given + public void makeHttpRequestsShouldPromoteLegacyExtGppGppSidAndCoppaToTopLevel() { + final BidRequest bidRequest = givenBidRequest(identity(), + requestBuilder -> requestBuilder.regs(Regs.builder() + .ext(ExtRegs.of(null, null, null, null)) + .build()).device(Device.builder().ua("UA").build())); + bidRequest.getRegs().getExt().addProperty("gpp", TextNode.valueOf("legacy_gpp_value")); + final ArrayNode sidArray = mapper.createArrayNode(); + sidArray.add(6); + sidArray.add(8); + bidRequest.getRegs().getExt().addProperty("gpp_sid", sidArray); + bidRequest.getRegs().getExt().addProperty("coppa", IntNode.valueOf(1)); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final Regs regs = result.getValue().getFirst().getPayload().getRegs(); + assertThat(regs.getGpp()).isEqualTo("legacy_gpp_value"); + assertThat(regs.getGppSid()).containsExactly(6, 8); + assertThat(regs.getCoppa()).isEqualTo(1); + assertThat(regs.getExt()).isNull(); + } + + @Test + public void makeHttpRequestsShouldPromoteOnlyGppFromExtAndStripIt() { + final BidRequest bidRequest = givenBidRequest(identity(), + requestBuilder -> requestBuilder.regs(Regs.builder() + .ext(ExtRegs.of(null, null, null, null)) + .build()).device(Device.builder().ua("UA").build())); + bidRequest.getRegs().getExt().addProperty("gpp", TextNode.valueOf("only_gpp")); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final Regs regs = result.getValue().getFirst().getPayload().getRegs(); + assertThat(regs.getGpp()).isEqualTo("only_gpp"); + assertThat(regs.getGppSid()).isNull(); + assertThat(regs.getCoppa()).isNull(); + assertThat(regs.getExt()).isNull(); + } + + @Test + public void makeHttpRequestsShouldPreserveTopLevelGdprWhilePromotingGppFromExt() { final BidRequest bidRequest = givenBidRequest(identity(), requestBuilder -> requestBuilder.regs(Regs.builder() .gdpr(1) - .ext(ExtRegs.of(0, "1YNN", null, null)) + .ext(ExtRegs.of(null, null, null, null)) + .build()).device(Device.builder().ua("UA").build())); + bidRequest.getRegs().getExt().addProperty("gpp", TextNode.valueOf("mixed_gpp")); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final Regs regs = result.getValue().getFirst().getPayload().getRegs(); + assertThat(regs.getGdpr()).isEqualTo(1); + assertThat(regs.getGpp()).isEqualTo("mixed_gpp"); + assertThat(regs.getExt()).isNull(); + } + + @Test + public void makeHttpRequestsShouldKeepGpcAndUnrelatedExtPropertyAfterPromotion() { + final BidRequest bidRequest = givenBidRequest(identity(), + requestBuilder -> requestBuilder.regs(Regs.builder() + .ext(ExtRegs.of(null, null, "1", null)) + .build()).device(Device.builder().ua("UA").build())); + bidRequest.getRegs().getExt().addProperty("gpp", TextNode.valueOf("with_gpc")); + bidRequest.getRegs().getExt().addProperty("unrelated", TextNode.valueOf("keep_me")); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final Regs regs = result.getValue().getFirst().getPayload().getRegs(); + assertThat(regs.getGpp()).isEqualTo("with_gpc"); + assertThat(regs.getExt()).isNotNull(); + assertThat(regs.getExt().getGpc()).isEqualTo("1"); + assertThat(regs.getExt().getProperty("gpp")).isNull(); + assertThat(regs.getExt().getProperty("unrelated").asText()).isEqualTo("keep_me"); + } + + @Test + public void makeHttpRequestsShouldNotPromoteWhenExtPropertyHasWrongType() { + final BidRequest bidRequest = givenBidRequest(identity(), + requestBuilder -> requestBuilder.regs(Regs.builder() + .ext(ExtRegs.of(null, null, null, null)) + .build()).device(Device.builder().ua("UA").build())); + bidRequest.getRegs().getExt().addProperty("gpp", IntNode.valueOf(99)); + bidRequest.getRegs().getExt().addProperty("gpp_sid", TextNode.valueOf("not_array")); + bidRequest.getRegs().getExt().addProperty("coppa", TextNode.valueOf("not_int")); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final Regs regs = result.getValue().getFirst().getPayload().getRegs(); + assertThat(regs.getGpp()).isNull(); + assertThat(regs.getGppSid()).isNull(); + assertThat(regs.getCoppa()).isNull(); + assertThat(regs.getExt()).isNotNull(); + assertThat(regs.getExt().getProperty("gpp").asInt()).isEqualTo(99); + assertThat(regs.getExt().getProperty("gpp_sid").asText()).isEqualTo("not_array"); + assertThat(regs.getExt().getProperty("coppa").asText()).isEqualTo("not_int"); + } + + @Test + public void makeHttpRequestsShouldLeaveMalformedExtValueInExtWhenSiblingFieldIsPromoted() { + final BidRequest bidRequest = givenBidRequest(identity(), + requestBuilder -> requestBuilder.regs(Regs.builder() + .ext(ExtRegs.of(null, null, null, null)) + .build()).device(Device.builder().ua("UA").build())); + bidRequest.getRegs().getExt().addProperty("coppa", IntNode.valueOf(1)); + bidRequest.getRegs().getExt().addProperty("gpp", IntNode.valueOf(99)); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final Regs regs = result.getValue().getFirst().getPayload().getRegs(); + assertThat(regs.getCoppa()).isEqualTo(1); + assertThat(regs.getGpp()).isNull(); + assertThat(regs.getExt()).isNotNull(); + assertThat(regs.getExt().getProperty("coppa")).isNull(); + assertThat(regs.getExt().getProperty("gpp").asInt()).isEqualTo(99); + } + + @Test + public void makeHttpRequestsShouldNotPromoteGppSidWhenArrayHasNonIntegerElement() { + final BidRequest bidRequest = givenBidRequest(identity(), + requestBuilder -> requestBuilder.regs(Regs.builder() + .ext(ExtRegs.of(null, null, null, null)) + .build()).device(Device.builder().ua("UA").build())); + final ArrayNode mixed = mapper.createArrayNode(); + mixed.add(7); + mixed.add("foo"); + mixed.add(8); + bidRequest.getRegs().getExt().addProperty("gpp_sid", mixed); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final Regs regs = result.getValue().getFirst().getPayload().getRegs(); + // whole array treated as malformed: not promoted, left in ext untouched (nothing dropped) + assertThat(regs.getGppSid()).isNull(); + assertThat(regs.getExt()).isNotNull(); + final JsonNode keptSid = regs.getExt().getProperty("gpp_sid"); + assertThat(keptSid.isArray()).isTrue(); + assertThat(keptSid).hasSize(3); + assertThat(keptSid.get(0).asInt()).isEqualTo(7); + assertThat(keptSid.get(1).asText()).isEqualTo("foo"); + assertThat(keptSid.get(2).asInt()).isEqualTo(8); + } + + @Test + public void makeHttpRequestsShouldKeepExtGppWhenTopLevelGppAlreadySetEvenIfSiblingIsPromoted() { + // gpp is present at BOTH top-level and in ext; a sibling (coppa) is promoted from ext. + // gpp is not promoted (top-level wins), so its ext copy must be left untouched - + // the strip decision does not depend on the sibling rebuild. + final BidRequest bidRequest = givenBidRequest(identity(), + requestBuilder -> requestBuilder.regs(Regs.builder() + .gpp("top-level-gpp") + .ext(ExtRegs.of(null, null, null, null)) + .build()).device(Device.builder().ua("UA").build())); + bidRequest.getRegs().getExt().addProperty("gpp", TextNode.valueOf("ext-gpp")); + bidRequest.getRegs().getExt().addProperty("coppa", IntNode.valueOf(1)); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final Regs regs = result.getValue().getFirst().getPayload().getRegs(); + // coppa promoted and removed from ext + assertThat(regs.getCoppa()).isEqualTo(1); + // gpp top-level untouched; ext gpp left in place (not promoted, not stripped) + assertThat(regs.getGpp()).isEqualTo("top-level-gpp"); + assertThat(regs.getExt()).isNotNull(); + assertThat(regs.getExt().getProperty("coppa")).isNull(); + assertThat(regs.getExt().getProperty("gpp").asText()).isEqualTo("ext-gpp"); + } + + @Test + public void makeHttpRequestsShouldShortCircuitWhenRegsHasNoExt() { + final BidRequest bidRequest = givenBidRequest(identity(), + requestBuilder -> requestBuilder.regs(Regs.builder() + .gdpr(0) + .gpp("already_top") .build()).device(Device.builder().ua("UA").build())); // when @@ -448,11 +626,9 @@ public void makeBidsShouldOverwriteRegsExtValues() { // then 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(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-yahooads-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/yahooads/test-yahooads-bid-request.json index d5dbb07bade..dfe01cf07f1 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": {