From 09d89bea8090a502108a1566fc8f964b7e24a9ad Mon Sep 17 00:00:00 2001 From: antonbabak Date: Fri, 16 Feb 2024 14:33:35 +0100 Subject: [PATCH 1/2] Support Seat Non-bid for DSA Rejections --- .../prebid/server/auction/DsaEnforcer.java | 80 ++++++++ .../server/auction/ExchangeService.java | 23 +-- .../auction/model/BidRejectionReason.java | 1 + .../spring/config/ServiceConfiguration.java | 8 + .../validation/ResponseBidValidator.java | 17 +- .../auction/BidRejectionReason.groovy | 1 + .../functional/tests/privacy/DsaSpec.groovy | 48 ++++- .../server/auction/DsaEnforcerTest.java | 154 ++++++++++++++ .../server/auction/ExchangeServiceTest.java | 99 ++++----- .../validation/ResponseBidValidatorTest.java | 191 ++++-------------- 10 files changed, 391 insertions(+), 231 deletions(-) create mode 100644 src/main/java/org/prebid/server/auction/DsaEnforcer.java create mode 100644 src/test/java/org/prebid/server/auction/DsaEnforcerTest.java diff --git a/src/main/java/org/prebid/server/auction/DsaEnforcer.java b/src/main/java/org/prebid/server/auction/DsaEnforcer.java new file mode 100644 index 00000000000..898d1bfd085 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/DsaEnforcer.java @@ -0,0 +1,80 @@ +package org.prebid.server.auction; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Regs; +import com.iab.openrtb.response.Bid; +import org.apache.commons.collections4.CollectionUtils; +import org.prebid.server.auction.model.AuctionParticipation; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidRejectionTracker; +import org.prebid.server.auction.model.BidderResponse; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.BidderSeatBid; +import org.prebid.server.proto.openrtb.ext.request.ExtRegs; +import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsa; +import org.prebid.server.util.ObjectUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public class DsaEnforcer { + + private static final String DSA_EXT = "dsa"; + private static final Set DSA_REQUIRED = Set.of(2, 3); + + public AuctionParticipation enforce(BidRequest bidRequest, + AuctionParticipation auctionParticipation, + BidRejectionTracker rejectionTracker) { + + final BidderResponse bidderResponse = auctionParticipation.getBidderResponse(); + final BidderSeatBid seatBid = ObjectUtil.getIfNotNull(bidderResponse, BidderResponse::getSeatBid); + final List bidderBids = ObjectUtil.getIfNotNull(seatBid, BidderSeatBid::getBids); + + if (CollectionUtils.isEmpty(bidderBids) || !isDsaValidationRequired(bidRequest)) { + return auctionParticipation; + } + + final List updatedBidderBids = new ArrayList<>(bidderBids); + final List warnings = new ArrayList<>(seatBid.getWarnings()); + + for (BidderBid bidderBid : bidderBids) { + final Bid bid = bidderBid.getBid(); + + if (!isValid(bid)) { + warnings.add(BidderError.invalidBid("Bid \"%s\" missing DSA".formatted(bid.getId()))); + rejectionTracker.reject(bid.getImpid(), BidRejectionReason.GENERAL); + updatedBidderBids.remove(bidderBid); + } + } + + if (bidderBids.size() == updatedBidderBids.size() && seatBid.getWarnings().size() == warnings.size()) { + return auctionParticipation; + } + + rejectionTracker.restoreFromRejection(updatedBidderBids); + final BidderSeatBid bidderSeatBid = seatBid.toBuilder() + .bids(updatedBidderBids) + .warnings(warnings) + .build(); + return auctionParticipation.with(bidderResponse.with(bidderSeatBid)); + } + + private static boolean isDsaValidationRequired(BidRequest bidRequest) { + return Optional.ofNullable(bidRequest.getRegs()) + .map(Regs::getExt) + .map(ExtRegs::getDsa) + .map(ExtRegsDsa::getDsaRequired) + .map(DSA_REQUIRED::contains) + .orElse(false); + } + + private boolean isValid(Bid bid) { + final ObjectNode bidExt = bid.getExt(); + return bidExt != null && bidExt.hasNonNull(DSA_EXT) && !bidExt.get(DSA_EXT).isEmpty(); + } + +} diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java index 0d8449860b7..f2334feb5e3 100644 --- a/src/main/java/org/prebid/server/auction/ExchangeService.java +++ b/src/main/java/org/prebid/server/auction/ExchangeService.java @@ -12,7 +12,6 @@ import com.iab.openrtb.request.Dooh; import com.iab.openrtb.request.Eid; import com.iab.openrtb.request.Imp; -import com.iab.openrtb.request.Regs; import com.iab.openrtb.request.Site; import com.iab.openrtb.request.Source; import com.iab.openrtb.request.SupplyChain; @@ -96,8 +95,6 @@ import org.prebid.server.proto.openrtb.ext.request.ExtDooh; import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebidFloors; -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.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.ExtRequestBidAdjustmentFactors; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; @@ -170,7 +167,6 @@ public class ExchangeService { private static final Integer DEFAULT_MULTIBID_LIMIT_MAX = 9; private static final String EID_ALLOWED_FOR_ALL_BIDDERS = "*"; private static final BigDecimal THOUSAND = BigDecimal.valueOf(1000); - private static final Set DSA_REQUIRED = Set.of(2, 3); private final double logSamplingRate; private final int timeoutAdjustmentFactor; @@ -196,6 +192,7 @@ public class ExchangeService { private final HttpInteractionLogger httpInteractionLogger; private final PriceFloorAdjuster priceFloorAdjuster; private final PriceFloorEnforcer priceFloorEnforcer; + private final DsaEnforcer dsaEnforcer; private final BidAdjustmentFactorResolver bidAdjustmentFactorResolver; private final Metrics metrics; private final Clock clock; @@ -227,6 +224,7 @@ public ExchangeService(double logSamplingRate, HttpInteractionLogger httpInteractionLogger, PriceFloorAdjuster priceFloorAdjuster, PriceFloorEnforcer priceFloorEnforcer, + DsaEnforcer dsaEnforcer, BidAdjustmentFactorResolver bidAdjustmentFactorResolver, Metrics metrics, Clock clock, @@ -261,6 +259,7 @@ public ExchangeService(double logSamplingRate, this.httpInteractionLogger = Objects.requireNonNull(httpInteractionLogger); this.priceFloorAdjuster = Objects.requireNonNull(priceFloorAdjuster); this.priceFloorEnforcer = Objects.requireNonNull(priceFloorEnforcer); + this.dsaEnforcer = Objects.requireNonNull(dsaEnforcer); this.bidAdjustmentFactorResolver = Objects.requireNonNull(bidAdjustmentFactorResolver); this.metrics = Objects.requireNonNull(metrics); this.clock = Objects.requireNonNull(clock); @@ -1482,6 +1481,10 @@ private List validateAndAdjustBids(List dsaEnforcer.enforce( + auctionContext.getBidRequest(), + auctionParticipation, + auctionContext.getBidRejectionTrackers().get(auctionParticipation.getBidder()))) .toList(); } @@ -1525,8 +1528,7 @@ private AuctionParticipation validBidderResponse(AuctionParticipation auctionPar bid, bidderResponse.getBidder(), auctionContext, - aliases, - isDsaValidationRequired(bidRequest)); + aliases); if (validationResult.hasWarnings() || validationResult.hasErrors()) { errors.add(makeValidationBidderError(bid.getBid(), validationResult)); @@ -1553,15 +1555,6 @@ private AuctionParticipation validBidderResponse(AuctionParticipation auctionPar return auctionParticipation.with(resultBidderResponse); } - private static boolean isDsaValidationRequired(BidRequest bidRequest) { - return Optional.ofNullable(bidRequest.getRegs()) - .map(Regs::getExt) - .map(ExtRegs::getDsa) - .map(ExtRegsDsa::getDsaRequired) - .map(DSA_REQUIRED::contains) - .orElse(false); - } - private BidderError makeValidationBidderError(Bid bid, ValidationResult validationResult) { final String validationErrors = Stream.concat( validationResult.getErrors().stream().map(message -> "Error: " + message), diff --git a/src/main/java/org/prebid/server/auction/model/BidRejectionReason.java b/src/main/java/org/prebid/server/auction/model/BidRejectionReason.java index 5287abda4e7..4858c80b0c4 100644 --- a/src/main/java/org/prebid/server/auction/model/BidRejectionReason.java +++ b/src/main/java/org/prebid/server/auction/model/BidRejectionReason.java @@ -10,6 +10,7 @@ public enum BidRejectionReason { REJECTED_BY_HOOK(200), REJECTED_BY_PRIVACY(202), REJECTED_BY_MEDIA_TYPE(204), + GENERAL(300), REJECTED_DUE_TO_PRICE_FLOOR(301), FAILED_TO_REQUEST_BIDS(100), OTHER_ERROR(100); diff --git a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java index f02aec27192..954067122b5 100644 --- a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java @@ -15,6 +15,7 @@ import org.prebid.server.auction.BidResponseCreator; import org.prebid.server.auction.BidResponsePostProcessor; import org.prebid.server.auction.DebugResolver; +import org.prebid.server.auction.DsaEnforcer; import org.prebid.server.auction.ExchangeService; import org.prebid.server.auction.FpdResolver; import org.prebid.server.auction.ImplicitParametersExtractor; @@ -798,6 +799,7 @@ ExchangeService exchangeService( HttpInteractionLogger httpInteractionLogger, PriceFloorAdjuster priceFloorAdjuster, PriceFloorEnforcer priceFloorEnforcer, + DsaEnforcer dsaEnforcer, BidAdjustmentFactorResolver bidAdjustmentFactorResolver, Metrics metrics, Clock clock, @@ -830,6 +832,7 @@ ExchangeService exchangeService( httpInteractionLogger, priceFloorAdjuster, priceFloorEnforcer, + dsaEnforcer, bidAdjustmentFactorResolver, metrics, clock, @@ -1066,6 +1069,11 @@ LoggerControlKnob loggerControlKnob(Vertx vertx) { return new LoggerControlKnob(vertx); } + @Bean + DsaEnforcer dsaEnforcer() { + return new DsaEnforcer(); + } + private static List splitToList(String listAsString) { return splitToCollection(listAsString, ArrayList::new); } diff --git a/src/main/java/org/prebid/server/validation/ResponseBidValidator.java b/src/main/java/org/prebid/server/validation/ResponseBidValidator.java index 293beaf22eb..353f4389a14 100644 --- a/src/main/java/org/prebid/server/validation/ResponseBidValidator.java +++ b/src/main/java/org/prebid/server/validation/ResponseBidValidator.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Deal; @@ -61,8 +60,6 @@ public class ResponseBidValidator { private static final String PREBID_EXT = "prebid"; private static final String BIDDER_EXT = "bidder"; - private static final String DSA_EXT = "dsa"; - private static final String DEALS_ONLY = "dealsonly"; private final BidValidationEnforcement bannerMaxSizeEnforcement; @@ -92,8 +89,7 @@ public ResponseBidValidator(BidValidationEnforcement bannerMaxSizeEnforcement, public ValidationResult validate(BidderBid bidderBid, String bidder, AuctionContext auctionContext, - BidderAliases aliases, - boolean isDsaValidationEnabled) { + BidderAliases aliases) { final Bid bid = bidderBid.getBid(); final BidRequest bidRequest = auctionContext.getBidRequest(); @@ -105,10 +101,6 @@ public ValidationResult validate(BidderBid bidderBid, validateTypeSpecific(bidderBid, bidder); validateCurrency(bidderBid.getBidCurrency()); - if (isDsaValidationEnabled) { - validateDsaFor(bid); - } - final Imp correspondingImp = findCorrespondingImp(bid, bidRequest); if (bidderBid.getType() == BidType.banner) { warnings.addAll(validateBannerFields(bid, bidder, bidRequest, account, correspondingImp, aliases)); @@ -164,13 +156,6 @@ private static void validateCurrency(String currency) throws ValidationException } } - private void validateDsaFor(Bid bid) throws ValidationException { - final ObjectNode bidExt = bid.getExt(); - if (bidExt == null || !bidExt.hasNonNull(DSA_EXT) || bidExt.get(DSA_EXT).isEmpty()) { - throw new ValidationException("Bid \"%s\" missing DSA", bid.getId()); - } - } - private Imp findCorrespondingImp(Bid bid, BidRequest bidRequest) throws ValidationException { return bidRequest.getImp().stream() .filter(imp -> Objects.equals(imp.getId(), bid.getImpid())) diff --git a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidRejectionReason.groovy b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidRejectionReason.groovy index e4701b7b49d..ce517248dca 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/auction/BidRejectionReason.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/auction/BidRejectionReason.groovy @@ -9,6 +9,7 @@ enum BidRejectionReason { REJECTED_BY_HOOK(200), REJECTED_BY_PRIVACY(202), REJECTED_BY_MEDIA_TYPE(204), + GENERAL(300), REJECTED_DUE_TO_PRICE_FLOOR(301), OTHER_ERROR(100) diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/DsaSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/DsaSpec.groovy index 76e669f7262..e7576c23396 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/DsaSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/DsaSpec.groovy @@ -13,6 +13,7 @@ import org.prebid.server.functional.model.response.auction.Dsa as BidDsa import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.TcfConsent +import static org.prebid.server.functional.model.response.auction.BidRejectionReason.GENERAL import static org.prebid.server.functional.model.response.auction.ErrorType.GENERIC import static org.prebid.server.functional.util.privacy.TcfConsent.GENERIC_VENDOR_ID import static org.prebid.server.functional.util.privacy.TcfConsent.PurposeId.BASIC_ADS @@ -159,8 +160,8 @@ class DsaSpec extends PrivacyBaseSpec { and: "Response should contain an error" def bidId = bidResponse.seatbid[0].bid[0].id - assert response.ext?.errors[GENERIC]*.code == [5] - assert response.ext?.errors[GENERIC]*.message == ["BidId `$bidId` validation messages: Error: Bid \"$bidId\" missing DSA"] + assert response.ext?.warnings[GENERIC]*.code == [5] + assert response.ext?.warnings[GENERIC]*.message == ["Bid \"$bidId\" missing DSA"] where: dsaRequired << [DsaRequired.REQUIRED, @@ -276,8 +277,47 @@ class DsaSpec extends PrivacyBaseSpec { and: "Response should contain an error" def bidId = bidResponse.seatbid[0].bid[0].id - assert response.ext?.errors[GENERIC]*.code == [5] - assert response.ext?.errors[GENERIC]*.message == ["BidId `$bidId` validation messages: Error: Bid \"$bidId\" missing DSA"] + assert response.ext?.warnings[GENERIC]*.code == [5] + assert response.ext?.warnings[GENERIC]*.message == ["Bid \"$bidId\" missing DSA"] + + where: + dsaRequired << [DsaRequired.REQUIRED, + DsaRequired.REQUIRED_PUBLISHER_IS_ONLINE_PLATFORM] + } + + def "Auction request should reject bids without DSA and populate seatNonBid when dsarequired is #dsaRequired"() { + given: "Default bid request with DSA" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.returnAllBidStatus = true + regs.ext.dsa = RequestDsa.getDefaultDsa(dsaRequired) + } + + and: "Default bidder response without DSA" + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid[0].bid[0].ext = new BidExt(dsa: null) + } + + and: "Set bidder response" + bidder.setResponse(bidRequest.id, bidResponse) + + when: "PBS processes auction request" + def response = privacyPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should reject bid" + assert !response.seatbid + + and: "PBS response should contain seatNonBid for rejected bids" + assert response.ext.seatnonbid.size() == 1 + + def seatNonBid = response.ext.seatnonbid[0] + assert seatNonBid.seat == GENERIC.value + assert seatNonBid.nonBid[0].impId == bidRequest.imp[0].id + assert seatNonBid.nonBid[0].statusCode == GENERAL + + and: "Response should contain an error" + def bidId = bidResponse.seatbid[0].bid[0].id + assert response.ext?.warnings[GENERIC]*.code == [5] + assert response.ext?.warnings[GENERIC]*.message == ["Bid \"$bidId\" missing DSA"] where: dsaRequired << [DsaRequired.REQUIRED, diff --git a/src/test/java/org/prebid/server/auction/DsaEnforcerTest.java b/src/test/java/org/prebid/server/auction/DsaEnforcerTest.java new file mode 100644 index 00000000000..13fb76ef9dc --- /dev/null +++ b/src/test/java/org/prebid/server/auction/DsaEnforcerTest.java @@ -0,0 +1,154 @@ +package org.prebid.server.auction; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Regs; +import com.iab.openrtb.response.Bid; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.prebid.server.VertxTest; +import org.prebid.server.auction.model.AuctionParticipation; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidRejectionTracker; +import org.prebid.server.auction.model.BidderResponse; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.BidderSeatBid; +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.response.BidType; + +import java.util.List; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +public class DsaEnforcerTest extends VertxTest { + + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + private final DsaEnforcer target = new DsaEnforcer(); + @Mock + private BidRejectionTracker bidRejectionTracker; + + @Test + public void enforceShouldDoNothingWhenBidsAreEmpty() { + // given + final BidRequest givenRequest = BidRequest.builder().build(); + final AuctionParticipation givenParticipation = AuctionParticipation.builder() + .bidderResponse(BidderResponse.of("bidder", BidderSeatBid.of(emptyList()), 100)) + .build(); + + // when + final AuctionParticipation actual = target.enforce(givenRequest, givenParticipation, bidRejectionTracker); + + // then + assertThat(actual).isEqualTo(givenParticipation); + verifyNoInteractions(bidRejectionTracker); + } + + @Test + public void enforceShouldDoNothingWhenDsaIsNotRequired() { + // given + final ExtRegs extRegs = ExtRegs.of(1, "usPrivacy", "1", ExtRegsDsa.of(1, 2, 3, emptyList())); + final BidRequest givenRequest = BidRequest.builder().regs(Regs.builder().ext(extRegs).build()).build(); + final BidderBid bid = BidderBid.of(Bid.builder().build(), BidType.banner, "USD"); + final AuctionParticipation givenParticipation = AuctionParticipation.builder() + .bidderResponse(BidderResponse.of("bidder", BidderSeatBid.of(List.of(bid)), 100)) + .build(); + + // when + final AuctionParticipation actual = target.enforce(givenRequest, givenParticipation, bidRejectionTracker); + + // then + assertThat(actual).isEqualTo(givenParticipation); + verifyNoInteractions(bidRejectionTracker); + } + + @Test + public void enforceDoNothingWhenDsaIsRequiredAndBidHasDsa() { + final ExtRegs extRegs = ExtRegs.of(1, "usPrivacy", "1", ExtRegsDsa.of(2, 2, 3, emptyList())); + final BidRequest givenRequest = BidRequest.builder().regs(Regs.builder().ext(extRegs).build()).build(); + + final ObjectNode dsaNode = mapper.createObjectNode() + .put("behalf", "Advertiser") + .put("paid", "Advertiser") + .put("adrender", 1); + final ObjectNode ext = mapper.createObjectNode().set("dsa", dsaNode); + final BidderBid bid = BidderBid.of(Bid.builder().ext(ext).build(), BidType.banner, "USD"); + final AuctionParticipation givenParticipation = AuctionParticipation.builder() + .bidderResponse(BidderResponse.of("bidder", BidderSeatBid.of(List.of(bid)), 100)) + .build(); + + // when + final AuctionParticipation actual = target.enforce(givenRequest, givenParticipation, bidRejectionTracker); + + // then + assertThat(actual).isEqualTo(givenParticipation); + verifyNoInteractions(bidRejectionTracker); + } + + @Test + public void enforceShouldRejectBidAndAddWarningWhenBidExtHasEmptyDsaAndDsaIsRequired() { + final ExtRegs extRegs = ExtRegs.of(1, "usPrivacy", "1", ExtRegsDsa.of(2, 2, 3, emptyList())); + final BidRequest givenRequest = BidRequest.builder().regs(Regs.builder().ext(extRegs).build()).build(); + + final ObjectNode ext = mapper.createObjectNode().set("dsa", mapper.createObjectNode()); + final BidderBid bid = BidderBid.of( + Bid.builder().id("bid_id").impid("imp_id").ext(ext).build(), + BidType.banner, + "USD"); + final AuctionParticipation givenParticipation = AuctionParticipation.builder() + .bidderResponse(BidderResponse.of("bidder", BidderSeatBid.of(List.of(bid)), 100)) + .build(); + + // when + final AuctionParticipation actual = target.enforce(givenRequest, givenParticipation, bidRejectionTracker); + + // then + final BidderSeatBid expectedSeatBid = BidderSeatBid.builder() + .warnings(List.of(BidderError.invalidBid("Bid \"bid_id\" missing DSA"))) + .bids(emptyList()) + .build(); + final AuctionParticipation expectedParticipation = AuctionParticipation.builder() + .bidderResponse(BidderResponse.of("bidder", expectedSeatBid, 100)) + .build(); + assertThat(actual).isEqualTo(expectedParticipation); + verify(bidRejectionTracker).reject("imp_id", BidRejectionReason.GENERAL); + } + + @Test + public void enforceShouldRejectBidAndAddWarningWhenBidExtIsEmptyDsaAndDsaIsRequired() { + final ExtRegs extRegs = ExtRegs.of(1, "usPrivacy", "1", ExtRegsDsa.of(3, 2, 3, emptyList())); + final BidRequest givenRequest = BidRequest.builder().regs(Regs.builder().ext(extRegs).build()).build(); + + final ObjectNode ext = mapper.createObjectNode(); + final BidderBid bid = BidderBid.of( + Bid.builder().id("bid_id").impid("imp_id").ext(ext).build(), + BidType.banner, + "USD"); + final AuctionParticipation givenParticipation = AuctionParticipation.builder() + .bidderResponse(BidderResponse.of("bidder", BidderSeatBid.of(List.of(bid)), 100)) + .build(); + + // when + final AuctionParticipation actual = target.enforce(givenRequest, givenParticipation, bidRejectionTracker); + + // then + final BidderSeatBid expectedSeatBid = BidderSeatBid.builder() + .warnings(List.of(BidderError.invalidBid("Bid \"bid_id\" missing DSA"))) + .bids(emptyList()) + .build(); + final AuctionParticipation expectedParticipation = AuctionParticipation.builder() + .bidderResponse(BidderResponse.of("bidder", expectedSeatBid, 100)) + .build(); + assertThat(actual).isEqualTo(expectedParticipation); + verify(bidRejectionTracker).reject("imp_id", BidRejectionReason.GENERAL); + } + +} diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index 487824fd092..2c529b8474e 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -114,8 +114,6 @@ import org.prebid.server.proto.openrtb.ext.request.ExtGranularityRange; import org.prebid.server.proto.openrtb.ext.request.ExtImpAuctionEnvironment; import org.prebid.server.proto.openrtb.ext.request.ExtPriceGranularity; -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.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.ExtRequestBidAdjustmentFactors; import org.prebid.server.proto.openrtb.ext.request.ExtRequestCurrency; @@ -286,6 +284,9 @@ public class ExchangeServiceTest extends VertxTest { @Mock private PriceFloorEnforcer priceFloorEnforcer; + @Mock + private DsaEnforcer dsaEnforcer; + @Mock private BidAdjustmentFactorResolver bidAdjustmentFactorResolver; @@ -373,8 +374,7 @@ public void setUp() { given(mediaTypeProcessor.process(any(), anyString(), any(), any())) .willAnswer(invocation -> MediaTypeProcessingResult.succeeded(invocation.getArgument(0), emptyList())); - given(responseBidValidator.validate(any(), any(), any(), any(), eq(false))) - .willReturn(ValidationResult.success()); + given(responseBidValidator.validate(any(), any(), any(), any())).willReturn(ValidationResult.success()); given(currencyService.convertCurrency(any(), any(), any(), any())) .willAnswer(invocationOnMock -> invocationOnMock.getArgument(0)); @@ -397,8 +397,8 @@ public void setUp() { given(dealsService.matchAndPopulateDeals(any(), any(), any())) .willAnswer(inv -> inv.getArgument(0)); - given(priceFloorEnforcer.enforce(any(), any(), any(), any())) - .willAnswer(inv -> inv.getArgument(1)); + given(priceFloorEnforcer.enforce(any(), any(), any(), any())).willAnswer(inv -> inv.getArgument(1)); + given(dsaEnforcer.enforce(any(), any(), any())).willAnswer(inv -> inv.getArgument(1)); given(priceFloorAdjuster.adjustForImp(any(), any(), any(), any())) .willAnswer(inv -> ((Imp) inv.getArgument(0)).getBidfloor()); @@ -452,6 +452,7 @@ public void setUp() { httpInteractionLogger, priceFloorAdjuster, priceFloorEnforcer, + dsaEnforcer, bidAdjustmentFactorResolver, metrics, clock, @@ -488,6 +489,7 @@ public void creationShouldFailOnNegativeTimeoutAdjustmentFactor() { httpInteractionLogger, priceFloorAdjuster, priceFloorEnforcer, + dsaEnforcer, bidAdjustmentFactorResolver, metrics, clock, @@ -1793,7 +1795,7 @@ public void shouldTolerateResponseBidValidationErrors() { .auctiontimestamp(1000L) .build()))); - given(responseBidValidator.validate(any(), any(), any(), any(), eq(false))).willReturn(ValidationResult.error( + given(responseBidValidator.validate(any(), any(), any(), any())).willReturn(ValidationResult.error( singletonList("bid validation warning"), "bid validation error")); @@ -1831,7 +1833,7 @@ public void shouldTolerateResponseBidValidationWarnings() { .auctiontimestamp(1000L) .build()))); - given(responseBidValidator.validate(any(), any(), any(), any(), eq(false))).willReturn(ValidationResult.success( + given(responseBidValidator.validate(any(), any(), any(), any())).willReturn(ValidationResult.success( singletonList("bid validation warning"))); givenBidResponseCreator(singletonList(Bid.builder().build())); @@ -1869,7 +1871,7 @@ public void shouldRejectBidIfCurrencyIsNotValid() { .auctiontimestamp(1000L) .build()))); - given(responseBidValidator.validate(any(), any(), any(), any(), eq(false))) + given(responseBidValidator.validate(any(), any(), any(), any())) .willReturn(ValidationResult.error("BidResponse currency is not valid: USDD")); final List bidderErrors = singletonList(ExtBidderError.of(BidderError.Type.generic.getCode(), @@ -1889,41 +1891,6 @@ public void shouldRejectBidIfCurrencyIsNotValid() { .isEmpty(); } - @Test - public void shouldValidateBidResponsesDsaWhenRegsExtDsaRequiredFieldRequires() { - // given - givenBidder("bidder1", mock(Bidder.class), givenSeatBid(singletonList( - givenBidderBid(Bid.builder().id("bidId1").impid("impId1").price(BigDecimal.valueOf(1.23)).build(), - "USDD")))); - - final ExtRegs extRegs = ExtRegs.of(1, "usPrivacy", "1", ExtRegsDsa.of(2, 2, 3, emptyList())); - final BidRequest bidRequest = givenBidRequest(singletonList( - // imp ids are not really used for matching, included them here for clarity - givenImp(singletonMap("bidder1", 1), builder -> builder.id("impId1"))), - builder -> builder - .ext(ExtRequest.of(ExtRequestPrebid.builder().auctiontimestamp(1000L).build())) - .regs(Regs.builder().ext(extRegs).build())); - - given(responseBidValidator.validate(any(), any(), any(), any(), eq(true))) - .willReturn(ValidationResult.error("Bid \"bidId1\" missing DSA")); - - final List bidderErrors = singletonList(ExtBidderError.of(BidderError.Type.generic.getCode(), - "Bid \"bidId1\" missing DSA")); - givenBidResponseCreator(singletonMap("bidder1", bidderErrors)); - - // when - final AuctionContext result = target.holdAuction(givenRequestContext(bidRequest)).result(); - - // then - final BidResponse bidResponse = result.getBidResponse(); - final ExtBidResponse ext = bidResponse.getExt(); - assertThat(ext.getErrors()).hasSize(1) - .containsOnly(entry("bidder1", bidderErrors)); - assertThat(bidResponse.getSeatbid()) - .extracting(SeatBid::getBid) - .isEmpty(); - } - @Test public void shouldCreateRequestsFromImpsReturnedByStoredResponseProcessor() { // given @@ -3199,6 +3166,7 @@ public void holdAuctionShouldFailWhenFpdProvidesSiteButDoohIsAlreadyInBidRequest httpInteractionLogger, priceFloorAdjuster, priceFloorEnforcer, + dsaEnforcer, bidAdjustmentFactorResolver, metrics, clock, @@ -3249,6 +3217,7 @@ public void holdAuctionShouldFailWhenSiteAppAndDoohArePresentInBidRequestAndStri httpInteractionLogger, priceFloorAdjuster, priceFloorEnforcer, + dsaEnforcer, bidAdjustmentFactorResolver, metrics, clock, @@ -4071,6 +4040,41 @@ public void shouldReturnBidsAcceptedByPriceFloorEnforcer() { .containsExactly(bidToAccept); } + @Test + public void shouldReturnBidsAcceptedByDsaEnforcer() { + // given + final BidderBid bidToAccept = + givenBidderBid(Bid.builder().id("bidId1").impid("impId1").price(ONE).build(), "USD"); + final BidderBid bidToReject = + givenBidderBid(Bid.builder().id("bidId2").impid("impId2").price(TEN).build(), "USD"); + + givenBidder("bidder1", mock(Bidder.class), givenSeatBid(List.of(bidToAccept, bidToReject))); + + final BidRequest bidRequest = givenBidRequest(List.of( + // imp ids are not really used for matching, included them here for clarity + givenImp(singletonMap("bidder1", 1), builder -> builder.id("impId1")), + givenImp(singletonMap("bidder1", 1), builder -> builder.id("impId2"))), + identity()); + + given(dsaEnforcer.enforce(any(), any(), any())) + .willReturn(AuctionParticipation.builder() + .bidder("bidder1") + .bidderResponse(BidderResponse.of( + "bidder1", BidderSeatBid.of(singletonList(bidToAccept)), 0)) + .build()); + + // when + target.holdAuction(givenRequestContext(bidRequest)); + + // then + final List capturedParticipations = captureAuctionParticipations(); + assertThat(capturedParticipations) + .extracting(AuctionParticipation::getBidderResponse) + .extracting(BidderResponse::getSeatBid) + .flatExtracting(BidderSeatBid::getBids) + .containsExactly(bidToAccept); + } + @Test public void shouldReturnBidResponseModifiedByAuctionResponseHooks() { // given @@ -4618,9 +4622,9 @@ public void shouldRecordLineItemMetricsInTransactionLog() { givenBidderBid(Bid.builder().impid("impId").dealid("dealId2").price(BigDecimal.ONE).build())))); willReturn(ValidationResult.success()).given(responseBidValidator) - .validate(argThat(bid -> bid.getBid().getDealid().equals("dealId1")), any(), any(), any(), eq(false)); + .validate(argThat(bid -> bid.getBid().getDealid().equals("dealId1")), any(), any(), any()); willReturn(ValidationResult.error("validation error")).given(responseBidValidator) - .validate(argThat(bid -> bid.getBid().getDealid().equals("dealId2")), any(), any(), any(), eq(false)); + .validate(argThat(bid -> bid.getBid().getDealid().equals("dealId2")), any(), any(), any()); final List deals = asList( Deal.builder() @@ -4786,7 +4790,7 @@ public void shouldReduceBidsHavingDealIdWithSameImpIdByBidderWithToleratingNotOb givenBidder(givenSingleSeatBid(bidderBid)); - given(responseBidValidator.validate(any(), any(), any(), any(), eq(false))) + given(responseBidValidator.validate(any(), any(), any(), any())) .willReturn(ValidationResult.success()); // when @@ -4883,6 +4887,7 @@ public void shouldResponseWithEmptySeatBidIfBidderNotSupportProvidedMediaTypes() httpInteractionLogger, priceFloorAdjuster, priceFloorEnforcer, + dsaEnforcer, bidAdjustmentFactorResolver, metrics, clock, diff --git a/src/test/java/org/prebid/server/validation/ResponseBidValidatorTest.java b/src/test/java/org/prebid/server/validation/ResponseBidValidatorTest.java index dc2c20ba4b0..2b87c30ae63 100644 --- a/src/test/java/org/prebid/server/validation/ResponseBidValidatorTest.java +++ b/src/test/java/org/prebid/server/validation/ResponseBidValidatorTest.java @@ -55,11 +55,11 @@ public class ResponseBidValidatorTest extends VertxTest { @Mock private Metrics metrics; + private ResponseBidValidator target; + @Mock private BidderAliases bidderAliases; - private ResponseBidValidator target; - @Before public void setUp() { target = new ResponseBidValidator(enforce, enforce, metrics, jacksonMapper, true, 0.01); @@ -74,8 +74,7 @@ public void validateShouldFailedIfBidderBidCurrencyIsIncorrect() { givenBid(BidType.banner, "invalid", identity()), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then assertThat(result.getErrors()).containsOnly("BidResponse currency \"invalid\" is not valid"); @@ -85,7 +84,7 @@ public void validateShouldFailedIfBidderBidCurrencyIsIncorrect() { public void validateShouldFailIfMissingBid() { // when final ValidationResult result = target.validate( - BidderBid.of(null, null, "USD"), BIDDER_NAME, givenAuctionContext(), bidderAliases, false); + BidderBid.of(null, null, "USD"), BIDDER_NAME, givenAuctionContext(), bidderAliases); // then assertThat(result.getErrors()).containsOnly("Empty bid object submitted"); @@ -95,7 +94,7 @@ public void validateShouldFailIfMissingBid() { public void validateShouldFailIfBidHasNoId() { // when final ValidationResult result = target.validate( - givenBid(builder -> builder.id(null)), BIDDER_NAME, givenAuctionContext(), bidderAliases, false); + givenBid(builder -> builder.id(null)), BIDDER_NAME, givenAuctionContext(), bidderAliases); // then assertThat(result.getErrors()).containsOnly("Bid missing required field 'id'"); @@ -105,7 +104,7 @@ public void validateShouldFailIfBidHasNoId() { public void validateShouldFailIfBidHasNoImpId() { // when final ValidationResult result = target.validate( - givenBid(builder -> builder.impid(null)), BIDDER_NAME, givenAuctionContext(), bidderAliases, false); + givenBid(builder -> builder.impid(null)), BIDDER_NAME, givenAuctionContext(), bidderAliases); // then assertThat(result.getErrors()).containsOnly("Bid \"bidId1\" missing required field 'impid'"); @@ -118,8 +117,7 @@ public void validateShouldSuccessForDealZeroPriceBid() { givenVideoBid(builder -> builder.price(BigDecimal.valueOf(0)).dealid("dealId")), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -129,7 +127,7 @@ public void validateShouldSuccessForDealZeroPriceBid() { public void validateShouldFailIfBidHasNoCrid() { // when final ValidationResult result = target.validate( - givenBid(builder -> builder.crid(null)), BIDDER_NAME, givenAuctionContext(), bidderAliases, false); + givenBid(builder -> builder.crid(null)), BIDDER_NAME, givenAuctionContext(), bidderAliases); // then assertThat(result.getErrors()).containsOnly("Bid \"bidId1\" missing creative ID"); @@ -139,7 +137,7 @@ public void validateShouldFailIfBidHasNoCrid() { public void validateShouldFailIfBannerBidHasNoWidthAndHeight() { // when final ValidationResult result = target.validate( - givenBid(builder -> builder.w(null).h(null)), BIDDER_NAME, givenAuctionContext(), bidderAliases, false); + givenBid(builder -> builder.w(null).h(null)), BIDDER_NAME, givenAuctionContext(), bidderAliases); // then assertThat(result.getErrors()) @@ -153,7 +151,7 @@ public void validateShouldFailIfBannerBidHasNoWidthAndHeight() { public void validateShouldFailIfBannerBidWidthIsGreaterThanImposedByImp() { // when final ValidationResult result = target.validate( - givenBid(builder -> builder.w(150).h(150)), BIDDER_NAME, givenAuctionContext(), bidderAliases, false); + givenBid(builder -> builder.w(150).h(150)), BIDDER_NAME, givenAuctionContext(), bidderAliases); // then assertThat(result.getErrors()) @@ -170,8 +168,7 @@ public void validateShouldFailIfBannerBidHeightIsGreaterThanImposedByImp() { givenBid(builder -> builder.w(50).h(250)), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then assertThat(result.getErrors()) @@ -188,8 +185,7 @@ public void validateShouldReturnSuccessIfNonBannerBidHasAnySize() { givenBid(BidType.video, builder -> builder.w(3).h(3)), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -206,8 +202,7 @@ public void validateShouldTolerateMissingImpExtBidderNode() { givenBid(BidType.video, builder -> builder.w(3).h(3)), BIDDER_NAME, givenAuctionContext(bidRequest), - bidderAliases, - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -223,8 +218,7 @@ public void validateShouldReturnSuccessIfBannerBidHasInvalidSizeButAccountDoesNo givenAccount(builder -> builder.auction(AccountAuctionConfig.builder() .bidValidations(AccountBidValidationConfig.of(skip)) .build()))), - bidderAliases, - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -237,8 +231,7 @@ public void validateShouldFailIfBidHasNoCorrespondingImp() { givenBid(builder -> builder.impid("nonExistentsImpid")), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then assertThat(result.getErrors()) @@ -252,8 +245,7 @@ public void validateShouldFailIfBidHasInsecureMarkerInCreativeInSecureContext() givenBid(builder -> builder.adm("http://site.com/creative.jpg")), BIDDER_NAME, givenAuctionContext(givenBidRequest(builder -> builder.secure(1))), - bidderAliases, - false); + bidderAliases); // then assertThat(result.getErrors()) @@ -270,8 +262,7 @@ public void validateShouldFailIfBidHasInsecureEncodedMarkerInCreativeInSecureCon givenBid(builder -> builder.adm("http%3A//site.com/creative.jpg")), BIDDER_NAME, givenAuctionContext(givenBidRequest(builder -> builder.secure(1))), - bidderAliases, - false); + bidderAliases); // then assertThat(result.getErrors()) @@ -288,8 +279,7 @@ public void validateShouldFailIfBidHasNoSecureMarkersInCreativeInSecureContext() givenBid(builder -> builder.adm("//site.com/creative.jpg")), BIDDER_NAME, givenAuctionContext(givenBidRequest(builder -> builder.secure(1))), - bidderAliases, - false); + bidderAliases); // then assertThat(result.getErrors()) @@ -306,8 +296,7 @@ public void validateShouldReturnSuccessIfBidHasInsecureCreativeInInsecureContext givenBid(builder -> builder.adm("http://site.com/creative.jpg")), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -320,8 +309,7 @@ public void validateShouldFailedIfVideoBidHasNoNurlAndAdm() { givenBid(BidType.video, builder -> builder.adm(null).nurl(null)), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then assertThat(result.getErrors()) @@ -336,8 +324,7 @@ public void validateShouldReturnSuccessfulResultForValidVideoBidWithNurl() { givenBid(BidType.video, builder -> builder.adm(null)), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -350,9 +337,7 @@ public void validateShouldReturnSuccessfulResultForValidVideoBidWithAdm() { givenBid(BidType.video, builder -> builder.nurl(null)), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -365,8 +350,7 @@ public void validateShouldReturnSuccessfulResultForValidBid() { givenBid(identity()), BIDDER_NAME, givenAuctionContext(givenBidRequest(builder -> builder.secure(1))), - bidderAliases, - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -382,8 +366,7 @@ public void validateShouldReturnSuccessIfBannerSizeValidationNotEnabled() { givenBid(identity()), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -399,8 +382,7 @@ public void validateShouldReturnSuccessWithWarningIfBannerSizeEnforcementIsWarn( givenBid(builder -> builder.w(null).h(null)), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -421,8 +403,7 @@ public void validateShouldReturnSuccessIfSecureMarkupValidationNotEnabled() { givenBid(builder -> builder.adm("http://site.com/creative.jpg")), BIDDER_NAME, givenAuctionContext(givenBidRequest(builder -> builder.secure(1))), - bidderAliases, - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -438,8 +419,7 @@ public void validateShouldReturnSuccessWithWarningIfSecureMarkupEnforcementIsWar givenBid(builder -> builder.adm("http://site.com/creative.jpg")), BIDDER_NAME, givenAuctionContext(givenBidRequest(builder -> builder.secure(1))), - bidderAliases, - false); + bidderAliases); // then assertThat(result.hasErrors()).isFalse(); @@ -457,8 +437,7 @@ public void validateShouldIncrementSizeValidationErrMetrics() { givenBid(builder -> builder.w(150).h(200)), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then verify(metrics).updateSizeValidationMetrics(BIDDER_NAME, ACCOUNT_ID, MetricName.err); @@ -474,8 +453,7 @@ public void validateShouldIncrementSizeValidationWarnMetrics() { givenBid(builder -> builder.w(150).h(200)), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); // then verify(metrics).updateSizeValidationMetrics(BIDDER_NAME, ACCOUNT_ID, MetricName.warn); @@ -488,8 +466,7 @@ public void validateShouldIncrementSecureValidationErrMetrics() { givenBid(builder -> builder.adm("http://site.com/creative.jpg")), BIDDER_NAME, givenAuctionContext(givenBidRequest(builder -> builder.secure(1))), - bidderAliases, - false); + bidderAliases); // then verify(metrics).updateSecureValidationMetrics(BIDDER_NAME, ACCOUNT_ID, MetricName.err); @@ -505,8 +482,7 @@ public void validateShouldIncrementSecureValidationWarnMetrics() { givenBid(builder -> builder.adm("http://site.com/creative.jpg")), BIDDER_NAME, givenAuctionContext(givenBidRequest(builder -> builder.secure(1))), - bidderAliases, - false); + bidderAliases); // then verify(metrics).updateSecureValidationMetrics(BIDDER_NAME, ACCOUNT_ID, MetricName.warn); @@ -518,8 +494,7 @@ public void validateShouldReturnSuccessfulResultForValidNonDealBid() { givenVideoBid(identity()), BIDDER_NAME, givenAuctionContext(), - bidderAliases, - false); + bidderAliases); assertThat(result.hasErrors()).isFalse(); } @@ -530,8 +505,7 @@ public void validateShouldFailIfBidHasNoDealid() { givenVideoBid(identity()), BIDDER_NAME, givenAuctionContext(givenRequest(identity())), - bidderAliases, - false); + bidderAliases); assertThat(result.getErrors()).hasSize(1) .containsOnly("Bid \"bidId1\" missing required field 'dealid'"); @@ -543,8 +517,7 @@ public void validateShouldSuccessIfBidHasDealidAndImpHasNoDeals() { givenVideoBid(bid -> bid.dealid("dealId1")), BIDDER_NAME, givenAuctionContext(givenRequest(identity())), - bidderAliases, - false); + bidderAliases); assertThat(result.getErrors()).isEmpty(); assertThat(result.getWarnings()).isEmpty(); @@ -570,8 +543,7 @@ public void validateShouldWarnIfBidHasDealidMissingInImp() { .id("dealId4") .ext(mapper.valueToTree(ExtDeal.of( ExtDealLine.of(null, null, null, "anotherBidder")))))))))), - bidderAliases, - false); + bidderAliases); assertThat(result.getWarnings()).hasSize(1) .containsOnly("WARNING: Bid \"bidId1\" has 'dealid' not present in corresponding imp in request." @@ -587,8 +559,7 @@ public void validateShouldFailIfBidIsBannerAndImpHasNoBanner() { BIDDER_NAME, givenAuctionContext(givenRequest(imp -> imp .pmp(pmp(singletonList(deal(builder -> builder.id("dealId1"))))))), - bidderAliases, - false); + bidderAliases); assertThat(result.getErrors()).hasSize(1) .containsOnly("Bid \"bidId1\" has banner media type but corresponding imp in request is missing " @@ -605,8 +576,7 @@ public void validateShouldFailIfBidIsBannerAndSizeHasNoMatchInBannerFormats() { .banner(Banner.builder() .format(singletonList(Format.builder().w(400).h(500).build())) .build()))), - bidderAliases, - false); + bidderAliases); assertThat(result.getErrors()).hasSize(1) .containsOnly("Bid \"bidId1\" has 'w' and 'h' not supported by corresponding imp in request. Bid " @@ -626,8 +596,7 @@ public void validateShouldFailIfBidIsBannerAndSizeHasNoMatchInLineItem() { .banner(Banner.builder() .format(singletonList(Format.builder().w(300).h(400).build())) .build()))), - bidderAliases, - false); + bidderAliases); assertThat(result.getErrors()).hasSize(1) .containsOnly("Bid \"bidId1\" has 'w' and 'h' not matched to Line Item. Bid dimensions: '300x400', " @@ -647,8 +616,7 @@ public void validateShouldFailIfBidIsBannerAndMatchingLineItemDoesNotHaveSizes() .banner(Banner.builder() .format(singletonList(Format.builder().w(300).h(400).build())) .build()))), - bidderAliases, - false); + bidderAliases); assertThat(result.getErrors()).hasSize(1) .containsOnly("Line item sizes were not found for bidId bidId1 and dealId dealId1"); @@ -667,8 +635,7 @@ public void validateShouldSuccessIfBidIsBannerAndSizeHasNoMatchInLineItemForNonP .banner(Banner.builder() .format(singletonList(Format.builder().w(300).h(400).build())) .build()))), - bidderAliases, - false); + bidderAliases); assertThat(result.getErrors()).isEmpty(); assertThat(result.getWarnings()).isEmpty(); @@ -681,8 +648,7 @@ public void validateShouldReturnSuccessfulResultForValidDealNonBannerBid() { BIDDER_NAME, givenAuctionContext(givenRequest(imp -> imp.pmp(pmp(singletonList( deal(builder -> builder.id("dealId1"))))))), - bidderAliases, - false); + bidderAliases); assertThat(result.hasErrors()).isFalse(); } @@ -700,81 +666,8 @@ public void validateShouldReturnSuccessfulResultForValidDealBannerBid() { .banner(Banner.builder() .format(singletonList(Format.builder().w(300).h(400).build())) .build()))), - bidderAliases, - false); - - assertThat(result.hasErrors()).isFalse(); - } - - @Test - public void validateShouldReturnSuccessfulResultWhenDsaValidationIsRequiredAndBidIsValid() { - // given - final ObjectNode dsaNode = mapper.createObjectNode() - .put("behalf", "Advertiser") - .put("paid", "Advertiser") - .put("adrender", 1); - final ObjectNode ext = mapper.createObjectNode().set("dsa", dsaNode); - - // when - final ValidationResult result = target.validate( - givenBid(builder -> builder.ext(ext)), - BIDDER_NAME, - givenAuctionContext(), - bidderAliases, - true); - - // then - assertThat(result.hasErrors()).isFalse(); - } + bidderAliases); - @Test - public void validateShouldFailWhenDsaValidationIsRequiredAndBidExtHasEmptyDsa() { - // given - final ObjectNode ext = mapper.createObjectNode().set("dsa", mapper.createObjectNode()); - - // when - final ValidationResult result = target.validate( - givenBid(builder -> builder.ext(ext)), - BIDDER_NAME, - givenAuctionContext(), - bidderAliases, - true); - - // then - assertThat(result.getErrors()).containsOnly("Bid \"bidId1\" missing DSA"); - } - - @Test - public void validateShouldFailWhenDsaValidationIsRequiredAndBidExtNotHaveDsa() { - // given - final ObjectNode ext = mapper.createObjectNode(); - - // when - final ValidationResult result = target.validate( - givenBid(builder -> builder.ext(ext)), - BIDDER_NAME, - givenAuctionContext(), - bidderAliases, - true); - - // then - assertThat(result.getErrors()).containsOnly("Bid \"bidId1\" missing DSA"); - } - - @Test - public void validateShouldReturnSuccessfulResultWhenDsaValidationIsNotRequiredAndBidExtHasEmptyDsa() { - // given - final ObjectNode ext = mapper.createObjectNode(); - - // when - final ValidationResult result = target.validate( - givenBid(builder -> builder.ext(ext)), - BIDDER_NAME, - givenAuctionContext(), - bidderAliases, - false); - - // then assertThat(result.hasErrors()).isFalse(); } From 643c0da881a6488ac9ea976169b0739646486755 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Mon, 19 Feb 2024 15:35:07 +0100 Subject: [PATCH 2/2] Fix comments --- src/main/java/org/prebid/server/auction/DsaEnforcer.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/prebid/server/auction/DsaEnforcer.java b/src/main/java/org/prebid/server/auction/DsaEnforcer.java index 898d1bfd085..ee992fae6ed 100644 --- a/src/main/java/org/prebid/server/auction/DsaEnforcer.java +++ b/src/main/java/org/prebid/server/auction/DsaEnforcer.java @@ -51,11 +51,10 @@ public AuctionParticipation enforce(BidRequest bidRequest, } } - if (bidderBids.size() == updatedBidderBids.size() && seatBid.getWarnings().size() == warnings.size()) { + if (bidderBids.size() == updatedBidderBids.size()) { return auctionParticipation; } - rejectionTracker.restoreFromRejection(updatedBidderBids); final BidderSeatBid bidderSeatBid = seatBid.toBuilder() .bids(updatedBidderBids) .warnings(warnings)