From e6f9b31466e7c86a177bca7bbde11fe3f31c2402 Mon Sep 17 00:00:00 2001 From: Aleksander Sekowski Date: Mon, 26 Oct 2026 17:07:28 -0700 Subject: [PATCH] Core: handle missing bidder entries when merging rejection trackers --- .../server/auction/ExchangeService.java | 2 ++ .../server/auction/ExchangeServiceTest.java | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java index 9860ae553df..e47a36f94dd 100644 --- a/src/main/java/org/prebid/server/auction/ExchangeService.java +++ b/src/main/java/org/prebid/server/auction/ExchangeService.java @@ -1378,6 +1378,8 @@ private void mergeBidRejectionTrackers(AuctionContext auctionContext, Map> bidderToFutureResponse) { final Map mergedBidRejectionTrackers = newBidRejectionTrackers.keySet().stream() + .filter(bidder -> bidderToFutureResponse.containsKey(bidder) + || auctionContext.getBidRejectionTrackers().containsKey(bidder)) .collect(Collectors.toMap(Function.identity(), bidder -> mergeBidRejectionTrackersForSingleBidder( bidderToFutureResponse.get(bidder), auctionContext.getBidRejectionTrackers().get(bidder), diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index b37d908f286..43b902e2e2f 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -4544,6 +4544,34 @@ public void shouldFallBackToAccountLevelSecondaryBiddersWhenRequestLevelIsAbsent assertThat(result.succeeded()).isTrue(); } + @Test + public void shouldIgnoreCopiedBidRejectionTrackersWithoutOriginalTrackerOrBidderFuture() { + // given + given(bidderRequestPostProcessor.process(any(), any(), any())) + .willAnswer(invocation -> { + final AuctionContext bidderContext = invocation.getArgument(2); + bidderContext.getBidRejectionTrackers().put("copiedOnlyBidder", + new BidRejectionTracker("copiedOnlyBidder", Set.of("impId1"), 0.0)); + return Future.succeededFuture( + BidderRequestPostProcessingResult.withValue(invocation.getArgument(0))); + }); + doReturn(Future.succeededFuture(givenSeatBid(emptyList()))).when(httpBidderRequester) + .requestBids(any(), any(), any(), any(), any(), any(), anyBoolean()); + + final BidRequest bidRequest = givenBidRequest(givenSingleImp(Map.of("testBidder", 1))); + final AuctionContext auctionContext = givenRequestContext(bidRequest); + + // when + final Future result = target.holdAuction(auctionContext); + + // then + assertThat(result.succeeded()).isTrue(); + assertThat(result.result().getBidRejectionTrackers()).containsOnlyKeys("testBidder"); + assertThat(result.result().getBidRejectionTrackers().get("testBidder").getRejected()) + .extracting(Rejection::reason) + .containsExactly(BidRejectionReason.NO_BID); + } + @Test public void shouldRetainBidRejectionsForBiddersThatWereRejectedBeforeBidderFutureSplit() { // given