From a78e7835024e94828f392f7b504dcbd1cb991785 Mon Sep 17 00:00:00 2001 From: Viktor Kryshtal Date: Wed, 10 Dec 2025 16:36:29 +0200 Subject: [PATCH] Update cookie sync logic to allow duplicate cookie family name if glvid and usersync config are identical --- .../prebid/server/bidder/BidderCatalog.java | 2 +- .../server/cookie/CookieSyncService.java | 13 +- .../cookie/PrioritizedCoopSyncProvider.java | 14 +- .../prebid/server/handler/SetuidHandler.java | 109 +++++---- .../server/handler/SetuidHandlerTest.java | 225 ++++++++++++++++-- 5 files changed, 278 insertions(+), 85 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/BidderCatalog.java b/src/main/java/org/prebid/server/bidder/BidderCatalog.java index d8eb543780b..0251043ae97 100644 --- a/src/main/java/org/prebid/server/bidder/BidderCatalog.java +++ b/src/main/java/org/prebid/server/bidder/BidderCatalog.java @@ -187,7 +187,7 @@ public Optional usersyncerByName(String name) { .map(BidderInstanceDeps::getUsersyncer); } - private Optional aliasOf(String bidder) { + public Optional aliasOf(String bidder) { return Optional.ofNullable(bidder) .map(bidderDepsMap::get) .map(BidderInstanceDeps::getBidderInfo) diff --git a/src/main/java/org/prebid/server/cookie/CookieSyncService.java b/src/main/java/org/prebid/server/cookie/CookieSyncService.java index 4ebd38c45ed..89ea8118fa1 100644 --- a/src/main/java/org/prebid/server/cookie/CookieSyncService.java +++ b/src/main/java/org/prebid/server/cookie/CookieSyncService.java @@ -49,7 +49,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -382,14 +381,16 @@ private Set biddersToSync(CookieSyncContext cookieSyncContext) { final Set cookieFamiliesToSync = new HashSet<>(); // multiple bidders may have same cookie families final Set biddersToSync = new LinkedHashSet<>(); - final Iterator biddersIterator = allowedBiddersByPriority.iterator(); - while (cookieFamiliesToSync.size() < cookieSyncContext.getLimit() && biddersIterator.hasNext()) { - final String bidder = biddersIterator.next(); + for (String bidder : allowedBiddersByPriority) { final String cookieFamilyName = bidderCatalog.cookieFamilyName(bidder).orElseThrow(); - cookieFamiliesToSync.add(cookieFamilyName); - biddersToSync.add(bidder); + if (cookieFamiliesToSync.size() < cookieSyncContext.getLimit()) { + cookieFamiliesToSync.add(cookieFamilyName); + biddersToSync.add(bidder); + } else if (cookieFamiliesToSync.contains(cookieFamilyName)) { + biddersToSync.add(bidder); + } } return biddersToSync; diff --git a/src/main/java/org/prebid/server/cookie/PrioritizedCoopSyncProvider.java b/src/main/java/org/prebid/server/cookie/PrioritizedCoopSyncProvider.java index 7ee18499527..2f184e977ca 100644 --- a/src/main/java/org/prebid/server/cookie/PrioritizedCoopSyncProvider.java +++ b/src/main/java/org/prebid/server/cookie/PrioritizedCoopSyncProvider.java @@ -11,11 +11,9 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; public class PrioritizedCoopSyncProvider { @@ -23,14 +21,13 @@ public class PrioritizedCoopSyncProvider { private static final Logger logger = LoggerFactory.getLogger(PrioritizedCoopSyncProvider.class); private final Set prioritizedBidders; - private final Map prioritizedCookieFamilyNameToBidderName; + private final Set prioritizedCookieFamilyNames; public PrioritizedCoopSyncProvider(Set bidders, BidderCatalog bidderCatalog) { this.prioritizedBidders = validCoopSyncBidders(Objects.requireNonNull(bidders), bidderCatalog); - this.prioritizedCookieFamilyNameToBidderName = prioritizedBidders.stream() - .collect(Collectors.toMap( - bidder -> bidderCatalog.cookieFamilyName(bidder).orElseThrow(), - Function.identity())); + this.prioritizedCookieFamilyNames = prioritizedBidders.stream() + .map(bidder -> bidderCatalog.cookieFamilyName(bidder).orElseThrow()) + .collect(Collectors.toSet()); } private static Set validCoopSyncBidders(Set bidders, BidderCatalog bidderCatalog) { @@ -71,7 +68,6 @@ public Set prioritizedBidders(Account account) { } public boolean isPrioritizedFamily(String cookieFamilyName) { - final String bidder = prioritizedCookieFamilyNameToBidderName.get(cookieFamilyName); - return prioritizedBidders.contains(bidder); + return prioritizedCookieFamilyNames.contains(cookieFamilyName); } } diff --git a/src/main/java/org/prebid/server/handler/SetuidHandler.java b/src/main/java/org/prebid/server/handler/SetuidHandler.java index bce568db2d7..5807e1bae70 100644 --- a/src/main/java/org/prebid/server/handler/SetuidHandler.java +++ b/src/main/java/org/prebid/server/handler/SetuidHandler.java @@ -10,10 +10,10 @@ import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerResponse; import io.vertx.ext.web.RoutingContext; +import lombok.Value; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; import org.prebid.server.activity.Activity; import org.prebid.server.activity.ComponentType; import org.prebid.server.activity.infrastructure.ActivityInfrastructure; @@ -54,19 +54,16 @@ import org.prebid.server.settings.model.AccountGdprConfig; import org.prebid.server.settings.model.AccountPrivacyConfig; import org.prebid.server.util.HttpUtil; -import org.prebid.server.util.StreamUtil; import org.prebid.server.vertx.verticles.server.HttpEndpoint; import org.prebid.server.vertx.verticles.server.application.ApplicationResource; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; import java.util.stream.Collectors; public class SetuidHandler implements ApplicationResource { @@ -89,7 +86,7 @@ public class SetuidHandler implements ApplicationResource { private final AnalyticsReporterDelegator analyticsDelegator; private final Metrics metrics; private final TimeoutFactory timeoutFactory; - private final Map> cookieNameToBidderAndSyncType; + private final Map cookieFamilyNameToUsersyncConfig; public SetuidHandler(long defaultTimeout, UidsCookieService uidsCookieService, @@ -113,49 +110,58 @@ public SetuidHandler(long defaultTimeout, this.analyticsDelegator = Objects.requireNonNull(analyticsDelegator); this.metrics = Objects.requireNonNull(metrics); this.timeoutFactory = Objects.requireNonNull(timeoutFactory); - this.cookieNameToBidderAndSyncType = collectUsersyncers(bidderCatalog); + this.cookieFamilyNameToUsersyncConfig = collectUserSyncConfigs(bidderCatalog); } - private static Map> collectUsersyncers(BidderCatalog bidderCatalog) { - validateUsersyncersDuplicates(bidderCatalog); - + private static Map collectUserSyncConfigs(BidderCatalog bidderCatalog) { return bidderCatalog.usersyncReadyBidders().stream() - .sorted(Comparator.comparing(bidderName -> BooleanUtils.toInteger(bidderCatalog.isAlias(bidderName)))) - .filter(StreamUtil.distinctBy(bidderCatalog::cookieFamilyName)) - .map(bidderName -> bidderCatalog.usersyncerByName(bidderName) - .map(usersyncer -> Pair.of(bidderName, usersyncer))) - .flatMap(Optional::stream) - .collect(Collectors.toMap( - pair -> pair.getRight().getCookieFamilyName(), - pair -> Pair.of(pair.getLeft(), preferredUserSyncType(pair.getRight())))); + .collect(Collectors.groupingBy( + bidderName -> bidderCatalog.cookieFamilyName(bidderName).orElseThrow(), + Collectors.collectingAndThen(Collectors.toSet(), bidderNames -> + usersyncConfigForSingleCookieFamilyName(bidderNames, bidderCatalog)))); } - private static void validateUsersyncersDuplicates(BidderCatalog bidderCatalog) { - final List duplicatedCookieFamilyNames = bidderCatalog.usersyncReadyBidders().stream() - .filter(bidderName -> !isAliasWithRootCookieFamilyName(bidderCatalog, bidderName)) + private static UsersyncConfig usersyncConfigForSingleCookieFamilyName(Set bidderNames, + BidderCatalog bidderCatalog) { + + final Set biddersWithoutAliases = bidderNames.stream() + .filter(bidderName -> bidderCatalog.aliasOf(bidderName).filter(bidderNames::contains).isEmpty()) + .collect(Collectors.toSet()); + + validateBiddersHaveTheSameVendorId(biddersWithoutAliases, bidderCatalog); + validateBiddersHaveTheSameUsersyncConfig(biddersWithoutAliases, bidderCatalog); + + final Usersyncer usersyncer = biddersWithoutAliases.stream() .map(bidderCatalog::usersyncerByName) - .flatMap(Optional::stream) - .map(Usersyncer::getCookieFamilyName) - .filter(Predicate.not(StreamUtil.distinctBy(Function.identity()))) - .distinct() - .sorted() - .toList(); - - if (!duplicatedCookieFamilyNames.isEmpty()) { + .map(Optional::orElseThrow) + .findAny().orElseThrow(); + + return UsersyncConfig.of(biddersWithoutAliases, preferredUserSyncType(usersyncer)); + } + + private static void validateBiddersHaveTheSameVendorId(Set bidders, BidderCatalog bidderCatalog) { + final Set vendorIds = bidders.stream() + .map(bidderCatalog::vendorIdByName) + .collect(Collectors.toSet()); + + if (vendorIds.size() > 1) { throw new IllegalArgumentException( - "Duplicated \"cookie-family-name\" found, values: " - + String.join(", ", duplicatedCookieFamilyNames)); + "Found bidders with the same cookie family name but different vendor ids. " + + "Bidders: %s. Vendor ids: %s".formatted(bidders, vendorIds)); } } - private static boolean isAliasWithRootCookieFamilyName(BidderCatalog bidderCatalog, String bidder) { - final String bidderCookieFamilyName = bidderCatalog.cookieFamilyName(bidder).orElse(StringUtils.EMPTY); - final String parentCookieFamilyName = - bidderCatalog.cookieFamilyName(bidderCatalog.resolveBaseBidder(bidder)).orElse(null); + private static void validateBiddersHaveTheSameUsersyncConfig(Set bidders, BidderCatalog bidderCatalog) { + final Set usersyncers = bidders.stream() + .map(bidderCatalog::usersyncerByName) + .map(Optional::orElseThrow) + .collect(Collectors.toSet()); - return bidderCatalog.isAlias(bidder) - && parentCookieFamilyName != null - && parentCookieFamilyName.equals(bidderCookieFamilyName); + if (usersyncers.size() > 1) { + throw new IllegalArgumentException( + "Found bidders with the same cookie family name but different usersync configs. " + + "Bidders: %s. Usersync configs: %s".formatted(bidders, usersyncers)); + } } private static UsersyncMethodType preferredUserSyncType(Usersyncer usersyncer) { @@ -181,8 +187,8 @@ private Future toSetuidContext(RoutingContext routingContext) { final Timeout timeout = timeoutFactory.create(defaultTimeout); final UsersyncMethodType syncType = Optional.ofNullable(cookieName) - .map(cookieNameToBidderAndSyncType::get) - .map(Pair::getRight) + .map(cookieFamilyNameToUsersyncConfig::get) + .map(UsersyncConfig::getUsersyncMethodType) .orElse(null); return accountById(requestAccount, timeout) @@ -236,15 +242,14 @@ private void handleSetuidContextResult(AsyncResult setuidContextR final AccountPrivacyConfig privacyConfig = setuidContext.getAccount().getPrivacy(); final AccountGdprConfig accountGdprConfig = privacyConfig != null ? privacyConfig.getGdpr() : null; - final String bidderName = cookieNameToBidderAndSyncType.get(bidderCookieFamily).getLeft(); + final Set bidderNames = cookieFamilyNameToUsersyncConfig.get(bidderCookieFamily).getBidders(); Future.all( tcfDefinerService.isAllowedForHostVendorId(tcfContext), - tcfDefinerService.resultForBidderNames( - Collections.singleton(bidderName), tcfContext, accountGdprConfig)) + tcfDefinerService.resultForBidderNames(bidderNames, tcfContext, accountGdprConfig)) .onComplete(hostTcfResponseResult -> respondByTcfResponse( hostTcfResponseResult, - bidderName, + bidderNames, setuidContext)); } else { final Throwable error = setuidContextResult.cause(); @@ -255,7 +260,7 @@ private void handleSetuidContextResult(AsyncResult setuidContextR private void validateSetuidContext(SetuidContext setuidContext, String bidderCookieFamily) { final String cookieName = setuidContext.getCookieName(); final boolean isCookieNameBlank = StringUtils.isBlank(cookieName); - if (isCookieNameBlank || !cookieNameToBidderAndSyncType.containsKey(cookieName)) { + if (isCookieNameBlank || !cookieFamilyNameToUsersyncConfig.containsKey(cookieName)) { final String cookieNameError = isCookieNameBlank ? "required" : "invalid"; throw new InvalidRequestException("\"bidder\" query param is " + cookieNameError); } @@ -282,7 +287,7 @@ private void validateSetuidContext(SetuidContext setuidContext, String bidderCoo } private void respondByTcfResponse(AsyncResult hostTcfResponseResult, - String bidderName, + Set bidderNames, SetuidContext setuidContext) { final TcfContext tcfContext = setuidContext.getPrivacyContext().getTcfContext(); @@ -293,9 +298,11 @@ private void respondByTcfResponse(AsyncResult hostTcfResponseRe final HostVendorTcfResponse hostVendorTcfResponse = compositeFuture.resultAt(0); final TcfResponse bidderTcfResponse = compositeFuture.resultAt(1); + // bidders corresponding to the same cookie family name should have the same vendor id + // so we can use any one of them here final Map vendorIdToAction = bidderTcfResponse.getActions(); final PrivacyEnforcementAction action = vendorIdToAction != null - ? vendorIdToAction.get(bidderName) + ? vendorIdToAction.get(bidderNames.iterator().next()) : null; final boolean notInGdprScope = BooleanUtils.isFalse(bidderTcfResponse.getUserInGdprScope()); @@ -416,4 +423,12 @@ private void handleErrors(Throwable error, RoutingContext routingContext, TcfCon private void addCookie(RoutingContext routingContext, Cookie cookie) { routingContext.response().headers().add(HttpUtil.SET_COOKIE_HEADER, cookie.encode()); } + + @Value(staticConstructor = "of") + private static class UsersyncConfig { + + Set bidders; + + UsersyncMethodType usersyncMethodType; + } } diff --git a/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java b/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java index ed88f7f6fdd..3ea84d8414a 100644 --- a/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java @@ -55,6 +55,8 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static java.util.Collections.emptyMap; @@ -833,24 +835,61 @@ public void shouldPassSuccessfulEventToAnalyticsReporter() { } @Test - public void shouldThrowExceptionInCaseOfBaseBidderCookieFamilyNameDuplicates() { + public void shouldAcceptCookieFamilyNameDuplicatesIfTheyHaveTheSameVendorIdAndUsersyncer() { // given final Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); - final String firstDuplicateName = "firstBidderWithDuplicate"; - final String secondDuplicateName = "secondBidderWithDuplicate"; - final String thirdDuplicateName = "thirdDuplicateName"; + final String firstDuplicateBidderName = "firstBidderWithDuplicate"; + final String secondDuplicateBidderName = "secondBidderWithDuplicate"; + final String cookieFamilyName = "cookieFamilyName"; given(bidderCatalog.usersyncReadyBidders()) - .willReturn(Set.of(RUBICON, FACEBOOK, firstDuplicateName, secondDuplicateName, thirdDuplicateName)); - given(bidderCatalog.isAlias(thirdDuplicateName)).willReturn(true); - given(bidderCatalog.usersyncerByName(eq(firstDuplicateName))).willReturn( - Optional.of(Usersyncer.of(RUBICON, iframeMethod(), redirectMethod(), false, null))); - given(bidderCatalog.usersyncerByName(eq(secondDuplicateName))).willReturn( - Optional.of(Usersyncer.of(FACEBOOK, iframeMethod(), redirectMethod(), false, null))); - given(bidderCatalog.usersyncerByName(eq(thirdDuplicateName))).willReturn( - Optional.of(Usersyncer.of(FACEBOOK, iframeMethod(), redirectMethod(), false, null))); - - final Executable exceptionSource = () -> new SetuidHandler( + .willReturn(Set.of(firstDuplicateBidderName, secondDuplicateBidderName)); + given(bidderCatalog.usersyncerByName(eq(firstDuplicateBidderName))).willReturn( + Optional.of(Usersyncer.of(cookieFamilyName, iframeMethod(), redirectMethod(), false, null))); + given(bidderCatalog.usersyncerByName(eq(secondDuplicateBidderName))).willReturn( + Optional.of(Usersyncer.of(cookieFamilyName, iframeMethod(), redirectMethod(), false, null))); + given(bidderCatalog.cookieFamilyName(eq(firstDuplicateBidderName))).willReturn(Optional.of(cookieFamilyName)); + given(bidderCatalog.cookieFamilyName(eq(secondDuplicateBidderName))).willReturn(Optional.of(cookieFamilyName)); + + // when + final Executable buildSetuidHandler = () -> new SetuidHandler( + 2000, + uidsCookieService, + applicationSettings, + bidderCatalog, + setuidPrivacyContextFactory, + gppService, + activityInfrastructureCreator, + tcfDefinerService, + analyticsReporterDelegator, + metrics, + new TimeoutFactory(clock)); + + // then + Assertions.assertDoesNotThrow(buildSetuidHandler); + } + + @Test + public void shouldRejectCookieFamilyNameDuplicatesIfTheyHaveDifferentVendorIds() { + // given + final Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + final String firstDuplicateBidderName = "firstBidderWithDuplicate"; + final String secondDuplicateBidderName = "secondBidderWithDuplicate"; + final String cookieFamilyName = "cookieFamilyName"; + + given(bidderCatalog.usersyncReadyBidders()) + .willReturn(Set.of(firstDuplicateBidderName, secondDuplicateBidderName)); + given(bidderCatalog.usersyncerByName(eq(firstDuplicateBidderName))).willReturn( + Optional.of(Usersyncer.of(cookieFamilyName, iframeMethod(), redirectMethod(), false, null))); + given(bidderCatalog.usersyncerByName(eq(secondDuplicateBidderName))).willReturn( + Optional.of(Usersyncer.of(cookieFamilyName, iframeMethod(), redirectMethod(), false, null))); + given(bidderCatalog.cookieFamilyName(eq(firstDuplicateBidderName))).willReturn(Optional.of(cookieFamilyName)); + given(bidderCatalog.cookieFamilyName(eq(secondDuplicateBidderName))).willReturn(Optional.of(cookieFamilyName)); + given(bidderCatalog.vendorIdByName(eq(firstDuplicateBidderName))).willReturn(1); + given(bidderCatalog.vendorIdByName(eq(secondDuplicateBidderName))).willReturn(2); + + // when + final Executable setuidHandlerBuilder = () -> new SetuidHandler( 2000, uidsCookieService, applicationSettings, @@ -863,18 +902,160 @@ public void shouldThrowExceptionInCaseOfBaseBidderCookieFamilyNameDuplicates() { metrics, new TimeoutFactory(clock)); - //when + // then final IllegalArgumentException exception = - Assertions.assertThrows(IllegalArgumentException.class, exceptionSource); + Assertions.assertThrows(IllegalArgumentException.class, setuidHandlerBuilder); - //then - final String expectedPrefix = "Duplicated \"cookie-family-name\" found, values: "; - final String actualMessage = exception.getMessage(); + final Matcher matcher = Pattern.compile( + "Found bidders with the same cookie family name but different vendor ids. " + + "Bidders: \\[(.*)]. Vendor ids: \\[(.*)]").matcher(exception.getMessage()); - assertThat(actualMessage).startsWith(expectedPrefix); + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(1).split(", ")) + .containsExactlyInAnyOrder(firstDuplicateBidderName, secondDuplicateBidderName); + assertThat(matcher.group(2).split(", ")) + .containsExactlyInAnyOrder("1", "2"); + } + + @Test + public void shouldRejectCookieFamilyNameDuplicatesIfTheyHaveDifferentUsersyncers() { + // given + final Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + final String firstDuplicateBidderName = "firstBidderWithDuplicate"; + final String secondDuplicateBidderName = "secondBidderWithDuplicate"; + final String cookieFamilyName = "cookieFamilyName"; + + given(bidderCatalog.usersyncReadyBidders()) + .willReturn(Set.of(firstDuplicateBidderName, secondDuplicateBidderName)); + given(bidderCatalog.usersyncerByName(eq(firstDuplicateBidderName))).willReturn( + Optional.of(Usersyncer.of(cookieFamilyName, iframeMethod(), redirectMethod(), true, null))); + given(bidderCatalog.usersyncerByName(eq(secondDuplicateBidderName))).willReturn( + Optional.of(Usersyncer.of(cookieFamilyName, iframeMethod(), redirectMethod(), false, null))); + given(bidderCatalog.cookieFamilyName(eq(firstDuplicateBidderName))).willReturn(Optional.of(cookieFamilyName)); + given(bidderCatalog.cookieFamilyName(eq(secondDuplicateBidderName))).willReturn(Optional.of(cookieFamilyName)); + + // when + final Executable setuidHandlerBuilder = () -> new SetuidHandler( + 2000, + uidsCookieService, + applicationSettings, + bidderCatalog, + setuidPrivacyContextFactory, + gppService, + activityInfrastructureCreator, + tcfDefinerService, + analyticsReporterDelegator, + metrics, + new TimeoutFactory(clock)); + + // then + final IllegalArgumentException exception = + Assertions.assertThrows(IllegalArgumentException.class, setuidHandlerBuilder); + + final Matcher matcher = Pattern.compile( + "Found bidders with the same cookie family name but different usersync configs. " + + "Bidders: \\[(.*)]. Usersync configs: \\[.*]").matcher(exception.getMessage()); + + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(1).split(", ")) + .containsExactlyInAnyOrder(firstDuplicateBidderName, secondDuplicateBidderName); + } + + @Test + public void shouldIgnoreBidderAliasIfItHasTheSameCookieFamilyName() { + // given + final Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + final String baseBidderName = "baseBidder"; + final String aliasBidderName = "aliasBidder"; + final String cookieFamilyName = "cookieFamilyName"; + + given(bidderCatalog.usersyncReadyBidders()).willReturn(Set.of(baseBidderName, aliasBidderName)); + given(bidderCatalog.usersyncerByName(eq(baseBidderName))).willReturn( + Optional.of(Usersyncer.of(cookieFamilyName, iframeMethod(), redirectMethod(), false, null))); + given(bidderCatalog.usersyncerByName(eq(aliasBidderName))).willReturn( + Optional.of(Usersyncer.of(cookieFamilyName, iframeMethod(), redirectMethod(), false, null))); + given(bidderCatalog.cookieFamilyName(eq(baseBidderName))).willReturn(Optional.of(cookieFamilyName)); + given(bidderCatalog.cookieFamilyName(eq(aliasBidderName))).willReturn(Optional.of(cookieFamilyName)); + given(bidderCatalog.aliasOf(eq(aliasBidderName))).willReturn(Optional.of(baseBidderName)); + + given(uidsCookieService.parseFromRequest(any(RoutingContext.class))) + .willReturn(new UidsCookie(Uids.builder().uids(emptyMap()).build(), jacksonMapper)); + + given(uidsCookieService.updateUidsCookie(any(), any(), any())) + .willReturn(updated(emptyUidsCookie())); + + given(httpRequest.getParam("bidder")).willReturn(cookieFamilyName); + + final SetuidHandler setuidHandler = new SetuidHandler( + 2000, + uidsCookieService, + applicationSettings, + bidderCatalog, + setuidPrivacyContextFactory, + gppService, + activityInfrastructureCreator, + tcfDefinerService, + analyticsReporterDelegator, + metrics, + new TimeoutFactory(clock)); + + // when + setuidHandler.handle(routingContext); + + // then + final ArgumentCaptor> bidderNamesCaptor = ArgumentCaptor.forClass(Set.class); + verify(tcfDefinerService).resultForBidderNames(bidderNamesCaptor.capture(), any(), any()); + + assertThat(bidderNamesCaptor.getValue()).containsExactly(baseBidderName); + } + + @Test + public void shouldNotIgnoreBidderAliasIfItHasADifferentCookieFamilyName() { + // given + final Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + final String baseBidderName = "baseBidder"; + final String aliasBidderName = "aliasBidder"; + final String baseCookieFamilyName = "baseCookieFamilyName"; + final String aliasCookieFamilyName = "aliasCookieFamilyName"; + + given(bidderCatalog.usersyncReadyBidders()).willReturn(Set.of(baseBidderName, aliasBidderName)); + given(bidderCatalog.usersyncerByName(eq(baseBidderName))).willReturn( + Optional.of(Usersyncer.of(baseCookieFamilyName, iframeMethod(), redirectMethod(), false, null))); + given(bidderCatalog.usersyncerByName(eq(aliasBidderName))).willReturn( + Optional.of(Usersyncer.of(aliasCookieFamilyName, iframeMethod(), redirectMethod(), false, null))); + given(bidderCatalog.cookieFamilyName(eq(baseBidderName))).willReturn(Optional.of(baseCookieFamilyName)); + given(bidderCatalog.cookieFamilyName(eq(aliasBidderName))).willReturn(Optional.of(aliasCookieFamilyName)); + given(bidderCatalog.aliasOf(eq(aliasBidderName))).willReturn(Optional.of(baseBidderName)); + + given(uidsCookieService.parseFromRequest(any(RoutingContext.class))) + .willReturn(new UidsCookie(Uids.builder().uids(emptyMap()).build(), jacksonMapper)); + + given(uidsCookieService.updateUidsCookie(any(), any(), any())) + .willReturn(updated(emptyUidsCookie())); + + given(httpRequest.getParam("bidder")).willReturn(aliasCookieFamilyName); + + final SetuidHandler setuidHandler = new SetuidHandler( + 2000, + uidsCookieService, + applicationSettings, + bidderCatalog, + setuidPrivacyContextFactory, + gppService, + activityInfrastructureCreator, + tcfDefinerService, + analyticsReporterDelegator, + metrics, + new TimeoutFactory(clock)); + + // when + setuidHandler.handle(routingContext); + + // then + final ArgumentCaptor> bidderNamesCaptor = ArgumentCaptor.forClass(Set.class); + verify(tcfDefinerService).resultForBidderNames(bidderNamesCaptor.capture(), any(), any()); - final String[] values = actualMessage.substring(expectedPrefix.length()).split(", "); - assertThat(values).containsExactlyInAnyOrder("audienceNetwork", "rubicon"); + assertThat(bidderNamesCaptor.getValue()).containsExactly(aliasBidderName); } private String getUidsCookie() {