From f02d8e5ac1ca73aa0b6adda6ead7f0d002e29e59 Mon Sep 17 00:00:00 2001 From: MananRPatel Date: Thu, 15 Oct 2026 18:15:37 +0530 Subject: [PATCH 01/10] Yahoo Ads Adapter: resolve bid type from mtype before the impression On a banner+video impression the bidder labeled every bid as banner because it only looked at the impression. Read bid.mtype first, fall back to a VAST root tag check on adm when mtype is absent, and use the impression only when it offers a single format. --- .../bidder/yahooads/YahooAdsBidder.java | 30 +- .../bidder/yahooads/YahooAdsBidderTest.java | 298 ++++++++++++++++++ 2 files changed, 322 insertions(+), 6 deletions(-) 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 800f21f2c9c..fcc26d51df8 100644 --- a/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java +++ b/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java @@ -38,6 +38,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.regex.Pattern; public class YahooAdsBidder implements Bidder { @@ -49,6 +50,9 @@ public class YahooAdsBidder implements Bidder { private static final String GPP_PROPERTY = "gpp"; private static final String GPP_SID_PROPERTY = "gpp_sid"; + private static final Pattern VAST_MARKUP = + Pattern.compile("<\\s*VAST[\\s/>]", Pattern.CASE_INSENSITIVE); + private final String endpointUrl; private final JacksonMapper mapper; @@ -284,14 +288,28 @@ private static BidderBid makeBidderBid(Bid bid, List imps, String currency) private static BidType getBidType(Bid bid, List imps) { for (Imp imp : imps) { if (imp.getId().equals(bid.getImpid())) { - if (imp.getBanner() != null) { - return BidType.banner; - } else if (imp.getVideo() != null) { - return BidType.video; - } - return null; + return resolveBidType(bid, imp); } } throw new PreBidException("Unknown ad unit code '%s'".formatted(bid.getImpid())); } + + private static BidType resolveBidType(Bid bid, Imp imp) { + final Integer mtype = bid.getMtype(); + if (mtype != null && mtype != 0) { + return switch (mtype) { + case 1 -> BidType.banner; + case 2 -> BidType.video; + default -> null; + }; + } + if (imp.getVideo() != null && (imp.getBanner() == null || isVast(bid.getAdm()))) { + return BidType.video; + } + return imp.getBanner() != null ? BidType.banner : null; + } + + private static boolean isVast(String adm) { + return StringUtils.isNotEmpty(adm) && VAST_MARKUP.matcher(adm).find(); + } } 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 5abbb2dc509..26072318c32 100644 --- a/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java @@ -390,6 +390,304 @@ public void makeBidsShouldSkipNotSupportedImpAndReturnVideoBidWhenVideoPresent() .containsOnly(BidderBid.of(Bid.builder().impid("321").build(), video, "USD")); } + @Test + public void makeBidsShouldReturnVideoBidWhenMtypeIsVideoOnMultiFormatImp() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").mtype(2).adm("
ad
").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); + } + + @Test + public void makeBidsShouldReturnBannerBidWhenMtypeIsBannerEvenIfAdmIsVast() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").mtype(1).adm("").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); + } + + @Test + public void makeBidsShouldReturnVideoBidWhenNoMtypeAndAdmIsVastOnMultiFormatImp() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm("").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); + } + + @Test + public void makeBidsShouldReturnVideoBidWhenNoMtypeAndAdmIsLowercaseVast() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm("").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); + } + + @Test + public void makeBidsShouldReturnBannerBidWhenNoMtypeAndHtmlAdmOnMultiFormatImp() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm("
ad
").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); + } + + @Test + public void makeBidsShouldReturnBannerBidWhenNoMtypeAndNoAdmOnMultiFormatImp() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); + } + + @Test + public void makeBidsShouldReturnBannerBidWhenImpIsBannerOnlyEvenIfAdmIsVast() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm("").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); + } + + @Test + public void makeBidsShouldReturnVideoBidWhenImpIsVideoOnlyEvenIfAdmIsHtml() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm("
ad
").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); + } + + @Test + public void makeBidsShouldReturnBannerBidWhenNoMtypeAndAdmHasNoVastRootElement() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm("u").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); + } + + @Test + public void makeBidsShouldSkipBidWhenMtypeIsAudio() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").mtype(3).adm("").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldSkipBidWhenMtypeIsNative() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").mtype(4).adm("
ad
").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldTreatZeroMtypeAsAbsent() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").mtype(0).adm("").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); + } + @Test public void makeHttpRequestsShouldPreserveTopLevel26RegsAndExtTypedFields() { // given From 988dafce5051bc1d43db4e64df17f3559cd45d1f Mon Sep 17 00:00:00 2001 From: MananRPatel Date: Sun, 18 Oct 2026 18:02:33 +0530 Subject: [PATCH 02/10] Yahoo Ads Adapter: cover remaining bid type and request branches --- .../bidder/yahooads/YahooAdsBidderTest.java | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) 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 26072318c32..572f53391bb 100644 --- a/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java @@ -257,6 +257,71 @@ public void makeHttpRequestsSetFirstImpressionBannerWidthAndHeightWhenFromFirstF .containsOnly(tuple(250, 300)); } + @Test + public void makeHttpRequestsShouldNotAddBannerWhenImpHasOnlyVideo() { + // given + final BidRequest bidRequest = givenBidRequest( + impBuilder -> impBuilder.banner(null).video(Video.builder().w(640).h(480).build()), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBanner, Imp::getVideo) + .containsOnly(tuple(null, Video.builder().w(640).h(480).build())); + } + + @Test + public void makeHttpRequestsShouldTakeSizeFromFirstFormatWhenOnlyBannerWidthIsSet() { + // given + final BidRequest bidRequest = givenBidRequest( + impBuilder -> impBuilder.banner(Banner.builder() + .w(300) + .format(singletonList(Format.builder().w(728).h(90).build())) + .build()), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBanner) + .extracting(Banner::getW, Banner::getH) + .containsOnly(tuple(728, 90)); + } + + @Test + public void makeHttpRequestsShouldTakeSizeFromFirstFormatWhenOnlyBannerHeightIsSet() { + // given + final BidRequest bidRequest = givenBidRequest( + impBuilder -> impBuilder.banner(Banner.builder() + .h(250) + .format(singletonList(Format.builder().w(728).h(90).build())) + .build()), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBanner) + .extracting(Banner::getW, Banner::getH) + .containsOnly(tuple(728, 90)); + } + @Test public void makeHttpRequestsShouldSetExpectedHeaders() { // given @@ -688,6 +753,180 @@ public void makeBidsShouldTreatZeroMtypeAsAbsent() throws JsonProcessingExceptio assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); } + @Test + public void makeBidsShouldSkipBidWhenMtypeIsUnknown() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").mtype(99).adm("").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnVideoBidWhenNoMtypeAndNoAdmOnVideoOnlyImp() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); + } + + @Test + public void makeBidsShouldMatchVastWithLeadingWhitespaceAndMixedCase() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm(" ").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); + } + + @Test + public void makeBidsShouldReturnVideoBidWhenAdmIsSelfClosingVast() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm("").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); + } + + @Test + public void makeBidsShouldReturnBannerBidWhenAdmMentionsVastOnlyAsText() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm("
VAST
").build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); + } + + @Test + public void makeBidsShouldResolveBidTypePerImpression() throws JsonProcessingException { + // given + final Bid bannerOnlyImpBid = Bid.builder().impid("123").adm("").build(); + final Bid multiFormatImpBid = Bid.builder() + .impid("321") + .adm("") + .build(); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(asList( + Imp.builder().id("123").banner(Banner.builder().build()).build(), + Imp.builder() + .id("321") + .banner(Banner.builder().build()) + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder() + .bid(asList(bannerOnlyImpBid, multiFormatImpBid)) + .build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsExactly( + BidderBid.of(bannerOnlyImpBid, banner, "USD"), + BidderBid.of(multiFormatImpBid, video, "USD")); + } + + @Test + public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsEmpty() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidRequest.builder().imp(singletonList(Imp.builder().id("123").build())).build(), + mapper.writeValueAsString(BidResponse.builder().cur("USD").seatbid(emptyList()).build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + @Test public void makeHttpRequestsShouldPreserveTopLevel26RegsAndExtTypedFields() { // given @@ -833,6 +1072,85 @@ public void makeHttpRequestsShouldKeepGpcAndUnrelatedExtPropertyAfterPromotion() }); } + @Test + public void makeHttpRequestsShouldKeepExtGdprAfterPromotion() { + // given + final ExtRegs extRegs = ExtRegs.of(1, null, null, null); + extRegs.addProperty("gpp", TextNode.valueOf("gppconsent")); + + 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("gppconsent"); + assertThat(regs.getExt()).isEqualTo(ExtRegs.of(1, null, null, null)); + }); + } + + @Test + public void makeHttpRequestsShouldKeepExtUsPrivacyAfterPromotion() { + // given + final ExtRegs extRegs = ExtRegs.of(null, "1YNN", null, null); + extRegs.addProperty("gpp", TextNode.valueOf("gppconsent")); + + 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("gppconsent"); + assertThat(regs.getExt()).isEqualTo(ExtRegs.of(null, "1YNN", null, null)); + }); + } + + @Test + public void makeHttpRequestsShouldKeepExtDsaAfterPromotion() { + // given + final ExtRegsDsa dsa = ExtRegsDsa.of(2, 2, 3, emptyList()); + final ExtRegs extRegs = ExtRegs.of(null, null, null, dsa); + extRegs.addProperty("gpp", TextNode.valueOf("gppconsent")); + + 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("gppconsent"); + assertThat(regs.getExt()).isEqualTo(ExtRegs.of(null, null, null, dsa)); + }); + } + @Test public void makeHttpRequestsShouldNotPromoteWhenExtPropertyHasWrongType() { // given From 5216d7e2b86c30887595bd03e8df230fcb839209 Mon Sep 17 00:00:00 2001 From: MananRPatel Date: Sun, 18 Oct 2026 19:21:47 +0530 Subject: [PATCH 03/10] Yahoo Ads Adapter: report unsupported mtype as a bid error A bid with an mtype other than banner or video is now skipped with a badServerResponse error instead of being dropped silently; other bids in the response are kept. Tests are consolidated with shared helpers. --- .../bidder/yahooads/YahooAdsBidder.java | 39 +- .../bidder/yahooads/YahooAdsBidderTest.java | 404 ++---------------- 2 files changed, 73 insertions(+), 370 deletions(-) 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 fcc26d51df8..d28b06639b0 100644 --- a/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java +++ b/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java @@ -252,43 +252,55 @@ private static MultiMap makeHeaders(Device device) { public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - return Result.of(extractBids(bidResponse, httpCall.getRequest().getPayload()), Collections.emptyList()); + final List errors = new ArrayList<>(); + final List bids = extractBids(bidResponse, httpCall.getRequest().getPayload(), errors); + return Result.of(bids, errors); } catch (DecodeException | PreBidException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private static List extractBids(BidResponse bidResponse, BidRequest bidRequest) { + private static List extractBids(BidResponse bidResponse, + BidRequest bidRequest, + List errors) { final List seatBids = bidResponse != null ? bidResponse.getSeatbid() : null; if (seatBids == null) { return Collections.emptyList(); } - return bidsFromResponse(bidResponse, bidRequest.getImp()); + return bidsFromResponse(bidResponse, bidRequest.getImp(), errors); } - private static List bidsFromResponse(BidResponse bidResponse, List imps) { + private static List bidsFromResponse(BidResponse bidResponse, + List imps, + List errors) { return bidResponse.getSeatbid().stream() .filter(Objects::nonNull) .map(SeatBid::getBid) .filter(Objects::nonNull) .flatMap(Collection::stream) .filter(Objects::nonNull) - .map(bid -> makeBidderBid(bid, imps, bidResponse.getCur())) + .map(bid -> makeBidderBid(bid, imps, bidResponse.getCur(), errors)) .filter(Objects::nonNull) .toList(); } - private static BidderBid makeBidderBid(Bid bid, List imps, String currency) { - final BidType bidType = getBidType(bid, imps); - return bidType != null - ? BidderBid.of(bid, bidType, currency) - : null; + private static BidderBid makeBidderBid(Bid bid, List imps, String currency, List errors) { + final Imp imp = findImp(bid, imps); + try { + final BidType bidType = resolveBidType(bid, imp); + return bidType != null + ? BidderBid.of(bid, bidType, currency) + : null; + } catch (PreBidException e) { + errors.add(BidderError.badServerResponse(e.getMessage())); + return null; + } } - private static BidType getBidType(Bid bid, List imps) { + private static Imp findImp(Bid bid, List imps) { for (Imp imp : imps) { if (imp.getId().equals(bid.getImpid())) { - return resolveBidType(bid, imp); + return imp; } } throw new PreBidException("Unknown ad unit code '%s'".formatted(bid.getImpid())); @@ -300,7 +312,8 @@ private static BidType resolveBidType(Bid bid, Imp imp) { return switch (mtype) { case 1 -> BidType.banner; case 2 -> BidType.video; - default -> null; + default -> throw new PreBidException( + "Unsupported mtype %d for bid '%s'".formatted(mtype, bid.getId())); }; } if (imp.getVideo() != null && (imp.getBanner() == null || isVast(bid.getAdm()))) { 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 572f53391bb..e1c927d7a19 100644 --- a/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java @@ -459,18 +459,7 @@ public void makeBidsShouldSkipNotSupportedImpAndReturnVideoBidWhenVideoPresent() public void makeBidsShouldReturnVideoBidWhenMtypeIsVideoOnMultiFormatImp() throws JsonProcessingException { // given final Bid bid = Bid.builder().impid("321").mtype(2).adm("
ad
").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when final Result> result = target.makeBids(httpCall, null); @@ -484,18 +473,7 @@ public void makeBidsShouldReturnVideoBidWhenMtypeIsVideoOnMultiFormatImp() throw public void makeBidsShouldReturnBannerBidWhenMtypeIsBannerEvenIfAdmIsVast() throws JsonProcessingException { // given final Bid bid = Bid.builder().impid("321").mtype(1).adm("").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when final Result> result = target.makeBids(httpCall, null); @@ -505,245 +483,11 @@ public void makeBidsShouldReturnBannerBidWhenMtypeIsBannerEvenIfAdmIsVast() thro assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); } - @Test - public void makeBidsShouldReturnVideoBidWhenNoMtypeAndAdmIsVastOnMultiFormatImp() throws JsonProcessingException { - // given - final Bid bid = Bid.builder().impid("321").adm("").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); - } - - @Test - public void makeBidsShouldReturnVideoBidWhenNoMtypeAndAdmIsLowercaseVast() throws JsonProcessingException { - // given - final Bid bid = Bid.builder().impid("321").adm("").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); - } - - @Test - public void makeBidsShouldReturnBannerBidWhenNoMtypeAndHtmlAdmOnMultiFormatImp() throws JsonProcessingException { - // given - final Bid bid = Bid.builder().impid("321").adm("
ad
").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); - } - - @Test - public void makeBidsShouldReturnBannerBidWhenNoMtypeAndNoAdmOnMultiFormatImp() throws JsonProcessingException { - // given - final Bid bid = Bid.builder().impid("321").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); - } - - @Test - public void makeBidsShouldReturnBannerBidWhenImpIsBannerOnlyEvenIfAdmIsVast() throws JsonProcessingException { - // given - final Bid bid = Bid.builder().impid("321").adm("").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); - } - - @Test - public void makeBidsShouldReturnVideoBidWhenImpIsVideoOnlyEvenIfAdmIsHtml() throws JsonProcessingException { - // given - final Bid bid = Bid.builder().impid("321").adm("
ad
").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); - } - - @Test - public void makeBidsShouldReturnBannerBidWhenNoMtypeAndAdmHasNoVastRootElement() throws JsonProcessingException { - // given - final Bid bid = Bid.builder().impid("321").adm("u").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); - } - - @Test - public void makeBidsShouldSkipBidWhenMtypeIsAudio() throws JsonProcessingException { - // given - final Bid bid = Bid.builder().impid("321").mtype(3).adm("").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); - } - - @Test - public void makeBidsShouldSkipBidWhenMtypeIsNative() throws JsonProcessingException { - // given - final Bid bid = Bid.builder().impid("321").mtype(4).adm("
ad
").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); - } - @Test public void makeBidsShouldTreatZeroMtypeAsAbsent() throws JsonProcessingException { // given final Bid bid = Bid.builder().impid("321").mtype(0).adm("").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when final Result> result = target.makeBids(httpCall, null); @@ -754,45 +498,28 @@ public void makeBidsShouldTreatZeroMtypeAsAbsent() throws JsonProcessingExceptio } @Test - public void makeBidsShouldSkipBidWhenMtypeIsUnknown() throws JsonProcessingException { + public void makeBidsShouldReturnErrorForUnsupportedMtypeAndKeepOtherBids() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("321").mtype(99).adm("").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); + final Bid audioBid = Bid.builder().id("audioBidId").impid("321").mtype(3).build(); + final Bid nativeBid = Bid.builder().id("nativeBidId").impid("321").mtype(4).build(); + final Bid videoBid = Bid.builder().id("videoBidId").impid("321").mtype(2).build(); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), audioBid, nativeBid, videoBid); // when final Result> result = target.makeBids(httpCall, null); // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly( + BidderError.badServerResponse("Unsupported mtype 3 for bid 'audioBidId'"), + BidderError.badServerResponse("Unsupported mtype 4 for bid 'nativeBidId'")); + assertThat(result.getValue()).containsExactly(BidderBid.of(videoBid, video, "USD")); } @Test - public void makeBidsShouldReturnVideoBidWhenNoMtypeAndNoAdmOnVideoOnlyImp() throws JsonProcessingException { + public void makeBidsShouldReturnVideoBidWhenNoMtypeAndAdmIsVastOnMultiFormatImp() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("321").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); + final Bid bid = Bid.builder().impid("321").adm("").build(); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when final Result> result = target.makeBids(httpCall, null); @@ -806,18 +533,7 @@ public void makeBidsShouldReturnVideoBidWhenNoMtypeAndNoAdmOnVideoOnlyImp() thro public void makeBidsShouldMatchVastWithLeadingWhitespaceAndMixedCase() throws JsonProcessingException { // given final Bid bid = Bid.builder().impid("321").adm(" ").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when final Result> result = target.makeBids(httpCall, null); @@ -828,46 +544,24 @@ public void makeBidsShouldMatchVastWithLeadingWhitespaceAndMixedCase() throws Js } @Test - public void makeBidsShouldReturnVideoBidWhenAdmIsSelfClosingVast() throws JsonProcessingException { + public void makeBidsShouldReturnBannerBidWhenAdmHasNoVastRootElement() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("321").adm("").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); + final Bid bid = Bid.builder().impid("321").adm("u").build(); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); } @Test public void makeBidsShouldReturnBannerBidWhenAdmMentionsVastOnlyAsText() throws JsonProcessingException { // given final Bid bid = Bid.builder().impid("321").adm("
VAST
").build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build())) - .build())); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when final Result> result = target.makeBids(httpCall, null); @@ -878,53 +572,32 @@ public void makeBidsShouldReturnBannerBidWhenAdmMentionsVastOnlyAsText() throws } @Test - public void makeBidsShouldResolveBidTypePerImpression() throws JsonProcessingException { + public void makeBidsShouldReturnBannerBidWhenNoMtypeAndNoAdmOnMultiFormatImp() throws JsonProcessingException { // given - final Bid bannerOnlyImpBid = Bid.builder().impid("123").adm("").build(); - final Bid multiFormatImpBid = Bid.builder() - .impid("321") - .adm("") - .build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(asList( - Imp.builder().id("123").banner(Banner.builder().build()).build(), - Imp.builder() - .id("321") - .banner(Banner.builder().build()) - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder() - .bid(asList(bannerOnlyImpBid, multiFormatImpBid)) - .build())) - .build())); + final Bid bid = Bid.builder().impid("321").build(); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsExactly( - BidderBid.of(bannerOnlyImpBid, banner, "USD"), - BidderBid.of(multiFormatImpBid, video, "USD")); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); } @Test - public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsEmpty() throws JsonProcessingException { + public void makeBidsShouldReturnBannerBidWhenImpIsBannerOnlyEvenIfAdmIsVast() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall( - BidRequest.builder().imp(singletonList(Imp.builder().id("123").build())).build(), - mapper.writeValueAsString(BidResponse.builder().cur("USD").seatbid(emptyList()).build())); + final Bid bid = Bid.builder().impid("321").adm("").build(); + final Imp bannerOnlyImp = Imp.builder().id("321").banner(Banner.builder().build()).build(); + final BidderCall httpCall = givenHttpCall(bannerOnlyImp, bid); // when final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); } @Test @@ -1571,10 +1244,27 @@ private static BidResponse givenBidResponse(Function givenHttpCall(BidRequest bidRequest, String body) { return BidderCall.succeededHttp( HttpRequest.builder().payload(bidRequest).build(), HttpResponse.of(200, null, body), null); } + + private static BidderCall givenHttpCall(Imp imp, Bid... bids) throws JsonProcessingException { + return givenHttpCall( + BidRequest.builder().imp(singletonList(imp)).build(), + mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(asList(bids)).build())) + .build())); + } } From 1a498b4f723cc42bdc8d386fece3a4005544c592 Mon Sep 17 00:00:00 2001 From: MananRPatel Date: Wed, 21 Oct 2026 19:06:02 +0530 Subject: [PATCH 04/10] Yahoo Ads Adapter: rename VAST non-match test The VAST check finds a VAST element anywhere in adm, not only at the root, so drop "Root" from the test name. --- .../org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e1c927d7a19..7873d56486c 100644 --- a/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java @@ -544,7 +544,7 @@ public void makeBidsShouldMatchVastWithLeadingWhitespaceAndMixedCase() throws Js } @Test - public void makeBidsShouldReturnBannerBidWhenAdmHasNoVastRootElement() throws JsonProcessingException { + public void makeBidsShouldReturnBannerBidWhenAdmHasNoVastElement() throws JsonProcessingException { // given final Bid bid = Bid.builder().impid("321").adm("u").build(); final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); From f90395856904d941863170275c48e190f19cc04d Mon Sep 17 00:00:00 2001 From: MananRPatel Date: Wed, 21 Oct 2026 23:46:58 +0530 Subject: [PATCH 05/10] Yahoo Ads Adapter: match the VAST element case-sensitively XML names are case-sensitive and the VAST root element is always VAST, so drop the case-insensitive flag and the optional whitespace after "<". Align the markup tests: VAST after an XML declaration and with attributes on the next line is video; lowercase vast and a bare VASTAdTagURI are banner. --- .../bidder/yahooads/YahooAdsBidder.java | 3 +- .../bidder/yahooads/YahooAdsBidderTest.java | 37 +++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) 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 d28b06639b0..4b1f95a7be3 100644 --- a/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java +++ b/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java @@ -50,8 +50,7 @@ public class YahooAdsBidder implements Bidder { private static final String GPP_PROPERTY = "gpp"; private static final String GPP_SID_PROPERTY = "gpp_sid"; - private static final Pattern VAST_MARKUP = - Pattern.compile("<\\s*VAST[\\s/>]", Pattern.CASE_INSENSITIVE); + private static final Pattern VAST_MARKUP = Pattern.compile("]"); private final String endpointUrl; private final JacksonMapper mapper; 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 7873d56486c..0147d445921 100644 --- a/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java @@ -530,9 +530,26 @@ public void makeBidsShouldReturnVideoBidWhenNoMtypeAndAdmIsVastOnMultiFormatImp( } @Test - public void makeBidsShouldMatchVastWithLeadingWhitespaceAndMixedCase() throws JsonProcessingException { + public void makeBidsShouldReturnVideoBidWhenVastFollowsXmlDeclaration() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("321").adm(" ").build(); + final Bid bid = Bid.builder() + .impid("321") + .adm("") + .build(); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); + } + + @Test + public void makeBidsShouldReturnVideoBidWhenVastAttributesStartOnNextLine() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm("
").build(); final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when @@ -543,10 +560,24 @@ public void makeBidsShouldMatchVastWithLeadingWhitespaceAndMixedCase() throws Js assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); } + @Test + public void makeBidsShouldReturnBannerBidWhenVastTagIsLowercase() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm("").build(); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); + } + @Test public void makeBidsShouldReturnBannerBidWhenAdmHasNoVastElement() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("321").adm("u").build(); + final Bid bid = Bid.builder().impid("321").adm("u").build(); final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when From 69dc8e8371aa8447a46445a74505fec974f29359 Mon Sep 17 00:00:00 2001 From: MananRPatel Date: Thu, 22 Oct 2026 23:32:32 +0530 Subject: [PATCH 06/10] Yahoo Ads Adapter: add banner-only mtype test Cover mtype on a single-format impression: on a banner-only impression an mtype 1 bid is kept as banner and an mtype 4 bid is skipped with the unsupported mtype error. Use HTML markup in the banner-only test without mtype, since VAST markup on a banner-only impression is not a real case. --- .../bidder/yahooads/YahooAdsBidderTest.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) 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 0147d445921..7281190775a 100644 --- a/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java @@ -515,6 +515,23 @@ public void makeBidsShouldReturnErrorForUnsupportedMtypeAndKeepOtherBids() throw assertThat(result.getValue()).containsExactly(BidderBid.of(videoBid, video, "USD")); } + @Test + public void makeBidsShouldReturnErrorForUnsupportedMtypeOnBannerOnlyImp() throws JsonProcessingException { + // given + final Bid bannerBid = Bid.builder().id("bannerBidId").impid("321").mtype(1).adm("
ad
").build(); + final Bid nativeBid = Bid.builder().id("nativeBidId").impid("321").mtype(4).build(); + final Imp bannerOnlyImp = Imp.builder().id("321").banner(Banner.builder().build()).build(); + final BidderCall httpCall = givenHttpCall(bannerOnlyImp, bannerBid, nativeBid); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()) + .containsExactly(BidderError.badServerResponse("Unsupported mtype 4 for bid 'nativeBidId'")); + assertThat(result.getValue()).containsExactly(BidderBid.of(bannerBid, banner, "USD")); + } + @Test public void makeBidsShouldReturnVideoBidWhenNoMtypeAndAdmIsVastOnMultiFormatImp() throws JsonProcessingException { // given @@ -617,9 +634,9 @@ public void makeBidsShouldReturnBannerBidWhenNoMtypeAndNoAdmOnMultiFormatImp() t } @Test - public void makeBidsShouldReturnBannerBidWhenImpIsBannerOnlyEvenIfAdmIsVast() throws JsonProcessingException { + public void makeBidsShouldReturnBannerBidWhenNoMtypeOnBannerOnlyImp() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("321").adm("").build(); + final Bid bid = Bid.builder().impid("321").adm("
ad
").build(); final Imp bannerOnlyImp = Imp.builder().id("321").banner(Banner.builder().build()).build(); final BidderCall httpCall = givenHttpCall(bannerOnlyImp, bid); From 5ccd56f0cf11aa00faac71f973f28fc9369af07b Mon Sep 17 00:00:00 2001 From: MananRPatel Date: Thu, 22 Oct 2026 23:36:00 +0530 Subject: [PATCH 07/10] Yahoo Ads Adapter: note the VAST check is a heuristic for bids without mtype --- .../java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java | 1 + 1 file changed, 1 insertion(+) 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 4b1f95a7be3..94144b3ab5f 100644 --- a/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java +++ b/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java @@ -50,6 +50,7 @@ public class YahooAdsBidder implements Bidder { private static final String GPP_PROPERTY = "gpp"; private static final String GPP_SID_PROPERTY = "gpp_sid"; + // VAST_MARKUP is a heuristic to find video bids without mtype on banner+video imps. private static final Pattern VAST_MARKUP = Pattern.compile("]"); private final String endpointUrl; From cedfcf8871691c576f71a462d65298c680f6b6a1 Mon Sep 17 00:00:00 2001 From: MananRPatel Date: Thu, 22 Oct 2026 23:47:05 +0530 Subject: [PATCH 08/10] Yahoo Ads Adapter: handle mtype in one switch --- .../bidder/yahooads/YahooAdsBidder.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) 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 94144b3ab5f..e6ebc55a1d1 100644 --- a/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java +++ b/src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java @@ -17,6 +17,7 @@ import io.vertx.core.MultiMap; import io.vertx.core.http.HttpMethod; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.model.BidderBid; @@ -307,15 +308,16 @@ private static Imp findImp(Bid bid, List imps) { } private static BidType resolveBidType(Bid bid, Imp imp) { - final Integer mtype = bid.getMtype(); - if (mtype != null && mtype != 0) { - return switch (mtype) { - case 1 -> BidType.banner; - case 2 -> BidType.video; - default -> throw new PreBidException( - "Unsupported mtype %d for bid '%s'".formatted(mtype, bid.getId())); - }; - } + return switch (ObjectUtils.getIfNull(bid.getMtype(), 0)) { + case 0 -> resolveBidTypeFromImp(bid, imp); + case 1 -> BidType.banner; + case 2 -> BidType.video; + default -> throw new PreBidException( + "Unsupported mtype %d for bid '%s'".formatted(bid.getMtype(), bid.getId())); + }; + } + + private static BidType resolveBidTypeFromImp(Bid bid, Imp imp) { if (imp.getVideo() != null && (imp.getBanner() == null || isVast(bid.getAdm()))) { return BidType.video; } From d43033056b19ad4cfca57195d2b906e243cc689f Mon Sep 17 00:00:00 2001 From: MananRPatel Date: Thu, 22 Oct 2026 23:55:31 +0530 Subject: [PATCH 09/10] Yahoo Ads Adapter: drop request-side tests unrelated to the bid type fix --- .../bidder/yahooads/YahooAdsBidderTest.java | 144 ------------------ 1 file changed, 144 deletions(-) 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 7281190775a..e6119edb6af 100644 --- a/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java @@ -257,71 +257,6 @@ public void makeHttpRequestsSetFirstImpressionBannerWidthAndHeightWhenFromFirstF .containsOnly(tuple(250, 300)); } - @Test - public void makeHttpRequestsShouldNotAddBannerWhenImpHasOnlyVideo() { - // given - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder.banner(null).video(Video.builder().w(640).h(480).build()), - identity()); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getBanner, Imp::getVideo) - .containsOnly(tuple(null, Video.builder().w(640).h(480).build())); - } - - @Test - public void makeHttpRequestsShouldTakeSizeFromFirstFormatWhenOnlyBannerWidthIsSet() { - // given - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder.banner(Banner.builder() - .w(300) - .format(singletonList(Format.builder().w(728).h(90).build())) - .build()), - identity()); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getBanner) - .extracting(Banner::getW, Banner::getH) - .containsOnly(tuple(728, 90)); - } - - @Test - public void makeHttpRequestsShouldTakeSizeFromFirstFormatWhenOnlyBannerHeightIsSet() { - // given - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder.banner(Banner.builder() - .h(250) - .format(singletonList(Format.builder().w(728).h(90).build())) - .build()), - identity()); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getBanner) - .extracting(Banner::getW, Banner::getH) - .containsOnly(tuple(728, 90)); - } - @Test public void makeHttpRequestsShouldSetExpectedHeaders() { // given @@ -793,85 +728,6 @@ public void makeHttpRequestsShouldKeepGpcAndUnrelatedExtPropertyAfterPromotion() }); } - @Test - public void makeHttpRequestsShouldKeepExtGdprAfterPromotion() { - // given - final ExtRegs extRegs = ExtRegs.of(1, null, null, null); - extRegs.addProperty("gpp", TextNode.valueOf("gppconsent")); - - 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("gppconsent"); - assertThat(regs.getExt()).isEqualTo(ExtRegs.of(1, null, null, null)); - }); - } - - @Test - public void makeHttpRequestsShouldKeepExtUsPrivacyAfterPromotion() { - // given - final ExtRegs extRegs = ExtRegs.of(null, "1YNN", null, null); - extRegs.addProperty("gpp", TextNode.valueOf("gppconsent")); - - 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("gppconsent"); - assertThat(regs.getExt()).isEqualTo(ExtRegs.of(null, "1YNN", null, null)); - }); - } - - @Test - public void makeHttpRequestsShouldKeepExtDsaAfterPromotion() { - // given - final ExtRegsDsa dsa = ExtRegsDsa.of(2, 2, 3, emptyList()); - final ExtRegs extRegs = ExtRegs.of(null, null, null, dsa); - extRegs.addProperty("gpp", TextNode.valueOf("gppconsent")); - - 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("gppconsent"); - assertThat(regs.getExt()).isEqualTo(ExtRegs.of(null, null, null, dsa)); - }); - } - @Test public void makeHttpRequestsShouldNotPromoteWhenExtPropertyHasWrongType() { // given From 91c2d3522f6cb7bf1851acfb8ccad2e22a5955fd Mon Sep 17 00:00:00 2001 From: MananRPatel Date: Fri, 23 Oct 2026 00:12:55 +0530 Subject: [PATCH 10/10] Yahoo Ads Adapter: use markup that matches mtype in bid type tests --- .../bidder/yahooads/YahooAdsBidderTest.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) 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 e6119edb6af..0da85da532d 100644 --- a/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/yahooads/YahooAdsBidderTest.java @@ -393,7 +393,7 @@ public void makeBidsShouldSkipNotSupportedImpAndReturnVideoBidWhenVideoPresent() @Test public void makeBidsShouldReturnVideoBidWhenMtypeIsVideoOnMultiFormatImp() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("321").mtype(2).adm("
ad
").build(); + final Bid bid = Bid.builder().impid("321").mtype(2).adm("").build(); final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when @@ -405,9 +405,9 @@ public void makeBidsShouldReturnVideoBidWhenMtypeIsVideoOnMultiFormatImp() throw } @Test - public void makeBidsShouldReturnBannerBidWhenMtypeIsBannerEvenIfAdmIsVast() throws JsonProcessingException { + public void makeBidsShouldReturnBannerBidWhenMtypeIsBannerOnMultiFormatImp() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("321").mtype(1).adm("").build(); + final Bid bid = Bid.builder().impid("321").mtype(1).adm("
ad
").build(); final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); // when @@ -481,6 +481,20 @@ public void makeBidsShouldReturnVideoBidWhenNoMtypeAndAdmIsVastOnMultiFormatImp( assertThat(result.getValue()).containsOnly(BidderBid.of(bid, video, "USD")); } + @Test + public void makeBidsShouldReturnBannerBidWhenNoMtypeAndAdmIsHtmlOnMultiFormatImp() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("321").adm("
ad
").build(); + final BidderCall httpCall = givenHttpCall(givenMultiFormatImp(), bid); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); + } + @Test public void makeBidsShouldReturnVideoBidWhenVastFollowsXmlDeclaration() throws JsonProcessingException { // given