From 43d6b0b8cf56090fd4f527544712d626e76e87a1 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Wed, 22 Jan 2025 15:23:08 +0200 Subject: [PATCH 1/4] Tests: Multiple Uids Cookies Support --- .../server/functional/model/UidsCookie.groovy | 4 +- .../functional/model/bidder/BidderName.groovy | 3 +- .../model/request/setuid/SetuidRequest.groovy | 8 +- .../model/request/setuid/UidWithExpiry.groovy | 4 +- .../response/setuid/SetuidResponse.groovy | 2 +- .../service/PrebidServerService.groovy | 51 +++- .../server/functional/tests/SetUidSpec.groovy | 284 +++++++++++++++--- .../util/ObjectMapperWrapper.groovy | 4 + 8 files changed, 299 insertions(+), 61 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/UidsCookie.groovy b/src/test/groovy/org/prebid/server/functional/model/UidsCookie.groovy index 8bbda5dd297..fc1df21e48f 100644 --- a/src/test/groovy/org/prebid/server/functional/model/UidsCookie.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/UidsCookie.groovy @@ -16,11 +16,11 @@ class UidsCookie { Map tempUIDs Boolean optout - static UidsCookie getDefaultUidsCookie(BidderName bidder = GENERIC) { + static UidsCookie getDefaultUidsCookie(BidderName bidder = GENERIC, Integer expireDays = 2) { new UidsCookie().tap { uids = [(bidder): UUID.randomUUID().toString()] tempUIDs = [(bidder): new UidWithExpiry(uid: UUID.randomUUID().toString(), - expires: ZonedDateTime.now(Clock.systemUTC()).plusDays(2))] + expires: ZonedDateTime.now(Clock.systemUTC()).plusDays(expireDays))] } } } diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy index 352080844ab..77060b2da49 100644 --- a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy @@ -24,7 +24,8 @@ enum BidderName { ADKERNEL("adkernel"), IX("ix"), GRID("grid"), - MEDIANET("medianet") + MEDIANET("medianet"), + AUDIENCE_NETWORK("audienceNetwork") @JsonValue final String value diff --git a/src/test/groovy/org/prebid/server/functional/model/request/setuid/SetuidRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/setuid/SetuidRequest.groovy index a40623d1f27..49553ded1ff 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/setuid/SetuidRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/setuid/SetuidRequest.groovy @@ -23,9 +23,9 @@ class SetuidRequest { String account static SetuidRequest getDefaultSetuidRequest() { - def request = new SetuidRequest() - request.bidder = GENERIC - request.gdpr = "0" - request + new SetuidRequest().tap { + bidder = GENERIC + gdpr = "0" + } } } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy b/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy index 2d9d7ee7d36..d4fd01b8680 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy @@ -11,10 +11,10 @@ class UidWithExpiry { String uid ZonedDateTime expires - static UidWithExpiry getDefaultUidWithExpiry() { + static UidWithExpiry getDefaultUidWithExpiry(Integer plusDays = 2) { new UidWithExpiry().tap { uid = UUID.randomUUID().toString() - expires = ZonedDateTime.now(Clock.systemUTC()).plusDays(2) + expires = ZonedDateTime.now(Clock.systemUTC()).plusDays(plusDays) } } } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy index bc35cd07d82..42e21523081 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy @@ -6,7 +6,7 @@ import org.prebid.server.functional.model.UidsCookie @ToString(includeNames = true, ignoreNulls = true) class SetuidResponse { - Map headers + LinkedHashMap> headers UidsCookie uidsCookie Byte[] responseBody } diff --git a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy index bac2badd46a..31df1efc8d5 100644 --- a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy +++ b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy @@ -167,12 +167,21 @@ class PrebidServerService implements ObjectMapperWrapper { } SetuidResponse sendSetUidRequest(SetuidRequest request, UidsCookie uidsCookie, Map header = [:]) { - def uidsCookieAsJson = encode(uidsCookie) - def uidsCookieAsEncodedJson = Base64.urlEncoder.encodeToString(uidsCookieAsJson.bytes) - def response = given(requestSpecification).cookie(UIDS_COOKIE_NAME, uidsCookieAsEncodedJson) - .queryParams(toMap(request)) - .headers(header) - .get(SET_UID_ENDPOINT) + sendSetUidRequest(request, [uidsCookie], header) + } + + SetuidResponse sendSetUidRequest(SetuidRequest request, List uidsCookies, Map header = [:]) { + def cookies = uidsCookies.withIndex().collectEntries { group, index -> + def uidsCookieAsJson = encode(group) + def uidsCookieAsEncodedJson = Base64.urlEncoder.encodeToString(uidsCookieAsJson.bytes) + ["${UIDS_COOKIE_NAME}${index > 0 ? index + 1 : ''}": uidsCookieAsEncodedJson] + } + + def response = given(requestSpecification) + .cookies(cookies) + .queryParams(toMap(request)) + .headers(header) + .get(SET_UID_ENDPOINT) checkResponseStatusCode(response) @@ -344,16 +353,32 @@ class PrebidServerService implements ObjectMapperWrapper { } } - private static Map getHeaders(Response response) { - response.headers().collectEntries { [it.name, it.value] } + private static Map> getHeaders(Response response) { + response.headers().groupBy { it.name }.collectEntries { [(it.key): it.value*.value] } } private static UidsCookie getDecodedUidsCookie(Response response) { - def uids = response.detailedCookie(UIDS_COOKIE_NAME)?.value - if (uids) { - return decode(new String(Base64.urlDecoder.decode(uids)), UidsCookie) - } else { - throw new IllegalStateException("uids cookie is missing in response") + def sortedCookies = response.detailedCookies() + .findAll { cookie -> !(cookie =~ /\buids\d*=\s*;/) } + .sort { a, b -> + def aMatch = (a.name =~ /uids(\d*)/)[0] + def bMatch = (b.name =~ /uids(\d*)/)[0] + + def aNumber = (aMatch?.getAt(1) ? aMatch[1].toInteger() : 0) + def bNumber = (bMatch?.getAt(1) ? bMatch[1].toInteger() : 0) + + aNumber <=> bNumber + } + + def decodedCookiesList = sortedCookies.collect { cookie -> + def uid = (cookie =~ /uids\d*=(\S+?);/)[0][1] + decodeWithBase64(uid as String, UidsCookie) + } + + decodedCookiesList.inject(new UidsCookie()) { uidsCookie, decodedCookie -> + uidsCookie.uids = (uidsCookie.uids ?: new LinkedHashMap()) + (decodedCookie.uids ?: new LinkedHashMap()) + uidsCookie.tempUIDs = (uidsCookie.tempUIDs ?: new LinkedHashMap()) + (decodedCookie.tempUIDs ?: new LinkedHashMap()) + uidsCookie } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index 1e33542e564..c5fbb602f68 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -3,19 +3,22 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.UidsCookie import org.prebid.server.functional.model.request.setuid.SetuidRequest import org.prebid.server.functional.model.response.cookiesync.UserSyncInfo +import org.prebid.server.functional.model.response.setuid.SetuidResponse import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.service.PrebidServerService +import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.TcfConsent import org.prebid.server.util.ResourceUtil import spock.lang.Shared import java.time.Clock import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_CAMEL_CASE import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS -import static org.prebid.server.functional.model.bidder.BidderName.EMPTY +import static org.prebid.server.functional.model.bidder.BidderName.AUDIENCE_NETWORK import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.bidder.BidderName.GENERIC_CAMEL_CASE import static org.prebid.server.functional.model.bidder.BidderName.OPENX @@ -30,8 +33,11 @@ import static org.prebid.server.functional.util.privacy.TcfConsent.RUBICON_VENDO class SetUidSpec extends BaseSpec { private static final Integer MAX_COOKIE_SIZE = 500 + private static final Integer MAX_NUMBER_OF_UID_COOKIES = 30 + private static final Integer UPDATED_EXPIRE_DAYS = 14 private static final UserSyncInfo.Type USER_SYNC_TYPE = REDIRECT private static final boolean CORS_SUPPORT = false + private static final Integer RANDOM_EXPIRE_DAY = PBSUtils.getRandomNumber(1, 10) private static final String USER_SYNC_URL = "$networkServiceContainer.rootUri/generic-usersync" private static final Map PBS_CONFIG = ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String, @@ -39,17 +45,24 @@ class SetUidSpec extends BaseSpec { "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value, "adapters.${OPENX.value}.enabled" : "true", "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value, + "adapters.${AUDIENCE_NETWORK.value}.enabled" : "true", + "adapters.${AUDIENCE_NETWORK.value}.usersync.cookie-family-name" : AUDIENCE_NETWORK.value, + "adapters.${AUDIENCE_NETWORK.value}.platform-id" : "0", + "adapters.${AUDIENCE_NETWORK.value}.app-secret" : PBSUtils.randomString, "adapters.${APPNEXUS.value}.enabled" : "true", "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value, "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()] + private static final Map UID_COOKIES_CONFIG = ['setuid.number-of-uid-cookies': MAX_NUMBER_OF_UID_COOKIES.toString()] private static final Map GENERIC_ALIAS_CONFIG = ["adapters.generic.aliases.alias.enabled" : "true", - "adapters.generic.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + "adapters.generic.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] private static final String TCF_ERROR_MESSAGE = "The gdpr_consent param prevents cookies from being saved" private static final int UNAVAILABLE_FOR_LEGAL_REASONS_CODE = 451 @Shared - PrebidServerService prebidServerService = pbsServiceFactory.getService(PBS_CONFIG + GENERIC_ALIAS_CONFIG) + PrebidServerService singleCookiesPbsService = pbsServiceFactory.getService(PBS_CONFIG + GENERIC_ALIAS_CONFIG) + @Shared + PrebidServerService multipleCookiesPbsService = pbsServiceFactory.getService(PBS_CONFIG + UID_COOKIES_CONFIG + GENERIC_ALIAS_CONFIG) def "PBS should set uids cookie"() { given: "Default SetuidRequest" @@ -57,30 +70,49 @@ class SetUidSpec extends BaseSpec { def uidsCookie = UidsCookie.defaultUidsCookie when: "PBS processes setuid request" - def response = prebidServerService.sendSetUidRequest(request, uidsCookie) + def response = singleCookiesPbsService.sendSetUidRequest(request, uidsCookie) + + then: "Response should contain uid cookie" + assert response.uidsCookie.tempUIDs[GENERIC].uid + assert response.responseBody == + ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png") + } + + def "PBS should updated uids cookie when request parameters contain uid"() { + given: "Default SetuidRequest" + def requestUid = UUID.randomUUID().toString() + def request = SetuidRequest.defaultSetuidRequest.tap { + uid = requestUid + } + def uidsCookie = UidsCookie.defaultUidsCookie + + and: "Flush metrics" + flushMetrics(singleCookiesPbsService) + + when: "PBS processes setuid request" + def response = singleCookiesPbsService.sendSetUidRequest(request, uidsCookie) then: "Response should contain uids cookie" - assert !response.uidsCookie.tempUIDs - assert !response.uidsCookie.uids + assert daysDifference(response.uidsCookie.tempUIDs[GENERIC].expires) == UPDATED_EXPIRE_DAYS + assert response.uidsCookie.tempUIDs[GENERIC].uid == requestUid assert response.responseBody == ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png") + + and: "usersync.FAMILY.sets metric should be updated" + def metrics = singleCookiesPbsService.sendCollectedMetricsRequest() + assert metrics["usersync.${GENERIC.value}.sets"] == 1 } def "PBS setuid should remove expired uids cookie"() { given: "Default SetuidRequest" def request = SetuidRequest.defaultSetuidRequest - def uidsCookie = UidsCookie.defaultUidsCookie.tap { - def uidWithExpiry = defaultUidWithExpiry.tap { - expires = ZonedDateTime.now(Clock.systemUTC()).minusDays(2) - } - tempUIDs = [(RUBICON): uidWithExpiry] - } + def uidsCookie = UidsCookie.getDefaultUidsCookie(RUBICON, -RANDOM_EXPIRE_DAY) when: "PBS processes setuid request" - def response = prebidServerService.sendSetUidRequest(request, uidsCookie) + def response = singleCookiesPbsService.sendSetUidRequest(request, uidsCookie) then: "Response shouldn't contain uids cookie" - assert !response.uidsCookie.tempUIDs[RUBICON] + assert !response.uidsCookie.tempUIDs } def "PBS setuid should return requested uids cookie when priority bidder not present in config"() { @@ -120,8 +152,8 @@ class SetUidSpec extends BaseSpec { } def rubiconBidder = RUBICON def uidsCookie = UidsCookie.defaultUidsCookie.tap { - tempUIDs = [(APPNEXUS) : defaultUidWithExpiry, - (rubiconBidder): defaultUidWithExpiry] + tempUIDs = [(APPNEXUS) : getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY + 1), + (rubiconBidder): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY)] } when: "PBS processes setuid request" @@ -135,7 +167,7 @@ class SetUidSpec extends BaseSpec { pbsServiceFactory.removeContainer(pbsConfig) } - def "PBS setuid should remove earliest expiration bidder when size is full"() { + def "PBS setuid should remove most distant expiration bidder when size is full"() { given: "PBS config" def pbsConfig = PBS_CONFIG + ["cookie-sync.pri": GENERIC.value] def prebidServerService = pbsServiceFactory.getService(pbsConfig) @@ -159,7 +191,7 @@ class SetUidSpec extends BaseSpec { def response = prebidServerService.sendSetUidRequest(request, uidsCookie) then: "Response should contain uids cookies" - assert response.uidsCookie.tempUIDs[APPNEXUS] + assert response.uidsCookie.tempUIDs[RUBICON] assert response.uidsCookie.tempUIDs[GENERIC] cleanup: "Stop and remove pbs container" @@ -200,13 +232,12 @@ class SetUidSpec extends BaseSpec { def "PBS setuid should reject bidder when cookie's filled and requested bidder in pri and rejected by tcf"() { given: "Setuid request" - def bidderName = RUBICON def pbsConfig = PBS_CONFIG + ["gdpr.host-vendor-id": RUBICON_VENDOR_ID.toString(), - "cookie-sync.pri" : bidderName.value] + "cookie-sync.pri" : RUBICON.value] def prebidServerService = pbsServiceFactory.getService(pbsConfig) def request = SetuidRequest.defaultSetuidRequest.tap { - it.bidder = bidderName + it.bidder = RUBICON gdpr = "1" gdprConsent = new TcfConsent.Builder().build() } @@ -226,13 +257,13 @@ class SetUidSpec extends BaseSpec { and: "usersync.FAMILY.tcf.blocked metric should be updated" def metric = prebidServerService.sendCollectedMetricsRequest() - assert metric["usersync.${bidderName.value}.tcf.blocked"] == 1 + assert metric["usersync.${RUBICON.value}.tcf.blocked"] == 1 cleanup: "Stop and remove pbs container" pbsServiceFactory.removeContainer(pbsConfig) } - def "PBS setuid should remove oldest uid and log metric when cookie's filled and oldest uid's not on the pri"() { + def "PBS setuid should remove most distant expiration uid and log metric when cookie's filled and this uid's not on the pri"() { given: "PBS config" def pbsConfig = PBS_CONFIG + ["cookie-sync.pri": GENERIC.value] def prebidServerService = pbsServiceFactory.getService(pbsConfig) @@ -245,21 +276,17 @@ class SetUidSpec extends BaseSpec { uid = UUID.randomUUID().toString() } - def bidderName = RUBICON def uidsCookie = UidsCookie.defaultUidsCookie.tap { - def uidWithExpiry = defaultUidWithExpiry.tap { - expires.plusDays(10) - } - tempUIDs = [(APPNEXUS) : defaultUidWithExpiry, - (bidderName): uidWithExpiry] + tempUIDs = [(APPNEXUS) : getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY), + (RUBICON): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY + 1)] } when: "PBS processes setuid request" def response = prebidServerService.sendSetUidRequest(request, uidsCookie) - and: "usersync.FAMILY.sizedout metric should be updated" + and: "usersync.FAMILY.sizeblocked metric should be updated" def metrics = prebidServerService.sendCollectedMetricsRequest() - assert metrics["usersync.${bidderName.value}.sizedout"] == 1 + assert metrics["usersync.${RUBICON.value}.sizeblocked"] == 1 then: "Response should contain uids cookies" assert response.uidsCookie.tempUIDs[APPNEXUS] @@ -269,7 +296,7 @@ class SetUidSpec extends BaseSpec { pbsServiceFactory.removeContainer(pbsConfig) } - def "PBS SetUid should remove oldest bidder from uids cookie in favor of prioritized bidder"() { + def "PBS SetUid should remove most distant expiration bidder from uids cookie in favor of prioritized bidder"() { given: "PBS config" def pbsConfig = PBS_CONFIG + ["cookie-sync.pri": "$OPENX.value, $GENERIC.value" as String] def prebidServerService = pbsServiceFactory.getService(pbsConfig) @@ -282,8 +309,8 @@ class SetUidSpec extends BaseSpec { and: "Set up set uid cookie" def uidsCookie = UidsCookie.defaultUidsCookie.tap { - it.tempUIDs = [(APPNEXUS): defaultUidWithExpiry, - (RUBICON) : defaultUidWithExpiry] + tempUIDs = [(APPNEXUS) : getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY + 1), + (RUBICON): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY)] } and: "Flush metrics" @@ -296,14 +323,14 @@ class SetUidSpec extends BaseSpec { assert response.uidsCookie.tempUIDs[OPENX] and: "Response set cookie header size should be lowest or the same as max cookie config size" - assert response.headers.get("Set-Cookie").split("Secure;")[0].length() <= MAX_COOKIE_SIZE + assert getSetUidsHeaders(response).first.split("Secure;")[0].length() <= MAX_COOKIE_SIZE and: "Request bidder should contain uid from Set uid request" assert response.uidsCookie.tempUIDs[OPENX].uid == request.uid - and: "usersync.FAMILY.sizedout metric should be updated" + and: "usersync.FAMILY.sizeblocked metric should be updated" def metricsRequest = prebidServerService.sendCollectedMetricsRequest() - assert metricsRequest["usersync.${APPNEXUS.value}.sizedout"] == 1 + assert metricsRequest["usersync.${APPNEXUS.value}.sizeblocked"] == 1 and: "usersync.FAMILY.sets metric should be updated" assert metricsRequest["usersync.${OPENX.value}.sets"] == 1 @@ -312,6 +339,42 @@ class SetUidSpec extends BaseSpec { pbsServiceFactory.removeContainer(pbsConfig) } + def "PBS SetUid should remove most distant expiration bidder from uids cookie in prioritized bidder"() { + given: "PBS config" + def pbsConfig = PBS_CONFIG + ["cookie-sync.pri": "$OPENX.value, $APPNEXUS.value, $RUBICON.value" as String] + def prebidServerService = pbsServiceFactory.getService(pbsConfig) + + and: "Set uid request" + def request = SetuidRequest.defaultSetuidRequest + + and: "Set up set uid cookie" + def uidsCookie = UidsCookie.defaultUidsCookie.tap { + tempUIDs = [(APPNEXUS) : getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY + 1), + (OPENX): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY), + (RUBICON): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY)] + } + + and: "Flush metrics" + flushMetrics(prebidServerService) + + when: "PBS processes set uid request" + def response = prebidServerService.sendSetUidRequest(request, uidsCookie) + + then: "Response should contain pri bidder in uids cookies" + assert response.uidsCookie.tempUIDs[OPENX] + assert response.uidsCookie.tempUIDs[RUBICON] + + and: "Response set cookie header size should be lowest or the same as max cookie config size" + assert getSetUidsHeaders(response).first.split("Secure;")[0].length() <= MAX_COOKIE_SIZE + + and: "usersync.FAMILY.sizedout metric should be updated" + def metricsRequest = prebidServerService.sendCollectedMetricsRequest() + assert metricsRequest["usersync.${APPNEXUS.value}.sizedout"] == 1 + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + def "PBS setuid should reject request when requested bidder mismatching with cookie-family-name"() { given: "Default SetuidRequest" def request = SetuidRequest.getDefaultSetuidRequest().tap { @@ -319,7 +382,7 @@ class SetUidSpec extends BaseSpec { } when: "PBS processes setuid request" - prebidServerService.sendSetUidRequest(request, UidsCookie.defaultUidsCookie) + singleCookiesPbsService.sendSetUidRequest(request, UidsCookie.defaultUidsCookie) then: "Request should fail with error" def exception = thrown(PrebidServerException) @@ -329,4 +392,149 @@ class SetUidSpec extends BaseSpec { where: bidderName << [UNKNOWN, WILDCARD, GENERIC_CAMEL_CASE, ALIAS, ALIAS_CAMEL_CASE] } + + def "PBS should remove all cookies when incoming request have specified optout flad"() { + given: "Setuid request" + def request = SetuidRequest.defaultSetuidRequest + def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC).tap { + optout = false + } + + when: "PBS processes setuid request" + singleCookiesPbsService.sendSetUidRequest(request, genericUidsCookie) + + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == 400 + assert exception.responseBody == 'Sync is not allowed for this uids' + } + + def "PBS should merge cookies when incoming request have multiple uids cookies"() { + given: "Setuid request" + def request = SetuidRequest.defaultSetuidRequest.tap { + uid = UUID.randomUUID().toString() + } + def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC) + def rubiconUidsCookie = UidsCookie.getDefaultUidsCookie(RUBICON) + + when: "PBS processes setuid request" + def response = multipleCookiesPbsService.sendSetUidRequest(request, [genericUidsCookie, rubiconUidsCookie]) + + then: "Response should contain requested uids" + assert response.uidsCookie.tempUIDs[GENERIC] + assert response.uidsCookie.tempUIDs[RUBICON] + + and: "Headers uids cookies should contain same cookie as response" + def setUidsHeaders = getSetUidsHeaders(response) + def uidsCookie = extractHeaderTempUIDs(setUidsHeaders.first) + assert setUidsHeaders.size() == 1 + assert uidsCookie.tempUIDs[GENERIC] + assert uidsCookie.tempUIDs[RUBICON] + } + + def "PBS should sout multiple uids cookies by priority and expiration timestamp"() { + given: "PBS config" + def pbsConfig = PBS_CONFIG + + UID_COOKIES_CONFIG + + ["cookie-sync.pri": "$OPENX.value, $GENERIC.value" as String] + + ["host-cookie.max-cookie-size-bytes": MAX_COOKIE_SIZE as String] + def prebidServerService = pbsServiceFactory.getService(pbsConfig) + + + and: "Setuid request" + def request = SetuidRequest.defaultSetuidRequest + + def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC, RANDOM_EXPIRE_DAY + 1) + def rubiconUidsCookie = UidsCookie.getDefaultUidsCookie(RUBICON, RANDOM_EXPIRE_DAY + 2) + def openxUidsCookie = UidsCookie.getDefaultUidsCookie(OPENX, RANDOM_EXPIRE_DAY + 3) + def appnexusUidsCookie = UidsCookie.getDefaultUidsCookie(APPNEXUS, RANDOM_EXPIRE_DAY) + + when: "PBS processes setuid request" + def response = prebidServerService.sendSetUidRequest(request, [appnexusUidsCookie, genericUidsCookie, rubiconUidsCookie, openxUidsCookie]) + + then: "Response should contain requested uids" + assert response.uidsCookie.tempUIDs.keySet() == new LinkedHashSet([GENERIC, OPENX, APPNEXUS, RUBICON]) + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS should remove duplicates when incoming cookie-family already exists in the working list"() { + given: "Setuid request" + def request = SetuidRequest.defaultSetuidRequest + + and: "Duplicated uids cookies" + def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC, RANDOM_EXPIRE_DAY) + def duplicateUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC, RANDOM_EXPIRE_DAY + 1) + + when: "PBS processes setuid request" + def response = multipleCookiesPbsService.sendSetUidRequest(request, [genericUidsCookie, duplicateUidsCookie]) + + then: "Response should contain single generic uid with most distant expiration timestamp" + assert response.uidsCookie.tempUIDs.size() == 1 + assert response.uidsCookie.tempUIDs[GENERIC].uid == duplicateUidsCookie.tempUIDs[GENERIC].uid + assert response.uidsCookie.tempUIDs[GENERIC].expires == duplicateUidsCookie.tempUIDs[GENERIC].expires + } + + def "PBS should shouldn't modify uids cookie for specific conditions"() { + given: "Setuid request" + def request = SetuidRequest.defaultSetuidRequest.tap { + it.uid = requestUid + it.bidder = requestBidder + } + + and: "Specific uids cookies" + def uidsCookie = UidsCookie.getDefaultUidsCookie(cookieBidder).tap { + tempUIDs[cookieBidder].uid = cookieUid + } + + when: "PBS processes setuid request" + def response = singleCookiesPbsService.sendSetUidRequest(request, [uidsCookie]) + + then: "Response should contain single generic uid" + assert response.uidsCookie.tempUIDs.size() == 1 + assert response.uidsCookie.tempUIDs[cookieBidder].uid == uidsCookie.tempUIDs[cookieBidder].uid + assert response.uidsCookie.tempUIDs[cookieBidder].expires == uidsCookie.tempUIDs[cookieBidder].expires + + where: + requestUid | requestBidder | cookieUid | cookieBidder + null | GENERIC | UUID.randomUUID().toString() | GENERIC + UUID.randomUUID().toString() | AUDIENCE_NETWORK | '0' | AUDIENCE_NETWORK + } + + def "PBS should include all cookies even empty when incoming request have multiple uids cookies"() { + given: "Setuid request" + def request = SetuidRequest.defaultSetuidRequest.tap { + uid = UUID.randomUUID().toString() + } + def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC) + def rubiconUidsCookie = UidsCookie.getDefaultUidsCookie(RUBICON) + + when: "PBS processes setuid request" + def response = multipleCookiesPbsService.sendSetUidRequest(request, [genericUidsCookie, rubiconUidsCookie]) + + then: "Response should contain requested uids" + assert response.uidsCookie.tempUIDs[GENERIC] + assert response.uidsCookie.tempUIDs[RUBICON] + + and: "Headers uids cookies should contain same cookie as response" + assert getSetUidsHeaders(response).size() == 1 + assert getSetUidsHeaders(response, true).size() == MAX_NUMBER_OF_UID_COOKIES + } + + List getSetUidsHeaders(SetuidResponse response, boolean includeEmpty = false) { + response.headers.get("Set-Cookie").findAll { cookie -> + includeEmpty || !(cookie =~ /\buids\d*=\s*;/) + } + } + + static UidsCookie extractHeaderTempUIDs(String header) { + def uid = (header =~ /uids\d*=(\S+?);/)[0][1] + decodeWithBase64(uid as String, UidsCookie) + } + + def daysDifference(ZonedDateTime inputDate) { + ZonedDateTime now = ZonedDateTime.now(Clock.systemUTC()).minusHours(1) + return ChronoUnit.DAYS.between(now, inputDate) + } } diff --git a/src/test/groovy/org/prebid/server/functional/util/ObjectMapperWrapper.groovy b/src/test/groovy/org/prebid/server/functional/util/ObjectMapperWrapper.groovy index e6b808cd2aa..3ab9e349ac9 100644 --- a/src/test/groovy/org/prebid/server/functional/util/ObjectMapperWrapper.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/ObjectMapperWrapper.groovy @@ -29,6 +29,10 @@ trait ObjectMapperWrapper { mapper.readValue(jsonString, typeReference) } + final static T decodeWithBase64(String base64String, Class clazz) { + mapper.readValue(new String(Base64.decoder.decode(base64String)), clazz) + } + final static Map toMap(Object object) { mapper.convertValue(object, Map) } From 04bebb0bc547ae3bd33138eba95f6d9358b6ef08 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Thu, 23 Jan 2025 11:29:33 +0200 Subject: [PATCH 2/4] update functional test --- .../functional/model/bidder/BidderName.groovy | 3 +- .../server/functional/tests/SetUidSpec.groovy | 61 ++++++++----------- 2 files changed, 26 insertions(+), 38 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy index 77060b2da49..352080844ab 100644 --- a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy @@ -24,8 +24,7 @@ enum BidderName { ADKERNEL("adkernel"), IX("ix"), GRID("grid"), - MEDIANET("medianet"), - AUDIENCE_NETWORK("audienceNetwork") + MEDIANET("medianet") @JsonValue final String value diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index c5fbb602f68..42cbea672d4 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -18,7 +18,6 @@ import java.time.temporal.ChronoUnit import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_CAMEL_CASE import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS -import static org.prebid.server.functional.model.bidder.BidderName.AUDIENCE_NETWORK import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.bidder.BidderName.GENERIC_CAMEL_CASE import static org.prebid.server.functional.model.bidder.BidderName.OPENX @@ -39,20 +38,17 @@ class SetUidSpec extends BaseSpec { private static final boolean CORS_SUPPORT = false private static final Integer RANDOM_EXPIRE_DAY = PBSUtils.getRandomNumber(1, 10) private static final String USER_SYNC_URL = "$networkServiceContainer.rootUri/generic-usersync" + private static final String USER_SYNC_URL_2 = "$networkServiceContainer.rootUri/audience-network-usersync" private static final Map PBS_CONFIG = - ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String, - "adapters.${RUBICON.value}.enabled" : "true", - "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value, - "adapters.${OPENX.value}.enabled" : "true", - "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value, - "adapters.${AUDIENCE_NETWORK.value}.enabled" : "true", - "adapters.${AUDIENCE_NETWORK.value}.usersync.cookie-family-name" : AUDIENCE_NETWORK.value, - "adapters.${AUDIENCE_NETWORK.value}.platform-id" : "0", - "adapters.${AUDIENCE_NETWORK.value}.app-secret" : PBSUtils.randomString, - "adapters.${APPNEXUS.value}.enabled" : "true", - "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value, - "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, - "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()] + ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String, + "adapters.${RUBICON.value}.enabled" : "true", + "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value, + "adapters.${OPENX.value}.enabled" : "true", + "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value, + "adapters.${APPNEXUS.value}.enabled" : "true", + "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value, + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString()] private static final Map UID_COOKIES_CONFIG = ['setuid.number-of-uid-cookies': MAX_NUMBER_OF_UID_COOKIES.toString()] private static final Map GENERIC_ALIAS_CONFIG = ["adapters.generic.aliases.alias.enabled" : "true", "adapters.generic.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] @@ -277,8 +273,8 @@ class SetUidSpec extends BaseSpec { } def uidsCookie = UidsCookie.defaultUidsCookie.tap { - tempUIDs = [(APPNEXUS) : getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY), - (RUBICON): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY + 1)] + tempUIDs = [(APPNEXUS): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY), + (RUBICON) : getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY + 1)] } when: "PBS processes setuid request" @@ -309,8 +305,8 @@ class SetUidSpec extends BaseSpec { and: "Set up set uid cookie" def uidsCookie = UidsCookie.defaultUidsCookie.tap { - tempUIDs = [(APPNEXUS) : getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY + 1), - (RUBICON): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY)] + tempUIDs = [(APPNEXUS): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY + 1), + (RUBICON) : getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY)] } and: "Flush metrics" @@ -349,9 +345,9 @@ class SetUidSpec extends BaseSpec { and: "Set up set uid cookie" def uidsCookie = UidsCookie.defaultUidsCookie.tap { - tempUIDs = [(APPNEXUS) : getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY + 1), - (OPENX): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY), - (RUBICON): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY)] + tempUIDs = [(APPNEXUS): getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY + 1), + (OPENX) : getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY), + (RUBICON) : getDefaultUidWithExpiry(RANDOM_EXPIRE_DAY)] } and: "Flush metrics" @@ -443,7 +439,7 @@ class SetUidSpec extends BaseSpec { and: "Setuid request" def request = SetuidRequest.defaultSetuidRequest - + def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC, RANDOM_EXPIRE_DAY + 1) def rubiconUidsCookie = UidsCookie.getDefaultUidsCookie(RUBICON, RANDOM_EXPIRE_DAY + 2) def openxUidsCookie = UidsCookie.getDefaultUidsCookie(OPENX, RANDOM_EXPIRE_DAY + 3) @@ -476,30 +472,23 @@ class SetUidSpec extends BaseSpec { assert response.uidsCookie.tempUIDs[GENERIC].expires == duplicateUidsCookie.tempUIDs[GENERIC].expires } - def "PBS should shouldn't modify uids cookie for specific conditions"() { + def "PBS should shouldn't modify uids cookie when uid is empty"() { given: "Setuid request" def request = SetuidRequest.defaultSetuidRequest.tap { - it.uid = requestUid - it.bidder = requestBidder + it.uid = null + it.bidder = GENERIC } and: "Specific uids cookies" - def uidsCookie = UidsCookie.getDefaultUidsCookie(cookieBidder).tap { - tempUIDs[cookieBidder].uid = cookieUid - } + def uidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC) when: "PBS processes setuid request" - def response = singleCookiesPbsService.sendSetUidRequest(request, [uidsCookie]) + def response = multipleCookiesPbsService.sendSetUidRequest(request, [uidsCookie]) then: "Response should contain single generic uid" assert response.uidsCookie.tempUIDs.size() == 1 - assert response.uidsCookie.tempUIDs[cookieBidder].uid == uidsCookie.tempUIDs[cookieBidder].uid - assert response.uidsCookie.tempUIDs[cookieBidder].expires == uidsCookie.tempUIDs[cookieBidder].expires - - where: - requestUid | requestBidder | cookieUid | cookieBidder - null | GENERIC | UUID.randomUUID().toString() | GENERIC - UUID.randomUUID().toString() | AUDIENCE_NETWORK | '0' | AUDIENCE_NETWORK + assert response.uidsCookie.tempUIDs[GENERIC].uid == uidsCookie.tempUIDs[GENERIC].uid + assert response.uidsCookie.tempUIDs[GENERIC].expires == uidsCookie.tempUIDs[GENERIC].expires } def "PBS should include all cookies even empty when incoming request have multiple uids cookies"() { From 127a596bd1b0409e7aa201257e404bc9b27e675c Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Sun, 26 Jan 2025 18:19:03 +0200 Subject: [PATCH 3/4] update functional tests --- .../server/functional/model/UidsCookie.groovy | 4 +- .../model/request/setuid/UidWithExpiry.groovy | 4 +- .../response/setuid/SetuidResponse.groovy | 2 +- .../server/functional/tests/SetUidSpec.groovy | 68 ++++++++++++------- 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/UidsCookie.groovy b/src/test/groovy/org/prebid/server/functional/model/UidsCookie.groovy index fc1df21e48f..d721255741d 100644 --- a/src/test/groovy/org/prebid/server/functional/model/UidsCookie.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/UidsCookie.groovy @@ -16,11 +16,11 @@ class UidsCookie { Map tempUIDs Boolean optout - static UidsCookie getDefaultUidsCookie(BidderName bidder = GENERIC, Integer expireDays = 2) { + static UidsCookie getDefaultUidsCookie(BidderName bidder = GENERIC, Integer daysUntilExpiry = 2) { new UidsCookie().tap { uids = [(bidder): UUID.randomUUID().toString()] tempUIDs = [(bidder): new UidWithExpiry(uid: UUID.randomUUID().toString(), - expires: ZonedDateTime.now(Clock.systemUTC()).plusDays(expireDays))] + expires: ZonedDateTime.now(Clock.systemUTC()).plusDays(daysUntilExpiry))] } } } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy b/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy index d4fd01b8680..146f2724325 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/setuid/UidWithExpiry.groovy @@ -11,10 +11,10 @@ class UidWithExpiry { String uid ZonedDateTime expires - static UidWithExpiry getDefaultUidWithExpiry(Integer plusDays = 2) { + static UidWithExpiry getDefaultUidWithExpiry(Integer daysUntilExpiry = 2) { new UidWithExpiry().tap { uid = UUID.randomUUID().toString() - expires = ZonedDateTime.now(Clock.systemUTC()).plusDays(plusDays) + expires = ZonedDateTime.now(Clock.systemUTC()).plusDays(daysUntilExpiry) } } } diff --git a/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy index 42e21523081..08a9adbb8fb 100644 --- a/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/response/setuid/SetuidResponse.groovy @@ -6,7 +6,7 @@ import org.prebid.server.functional.model.UidsCookie @ToString(includeNames = true, ignoreNulls = true) class SetuidResponse { - LinkedHashMap> headers + Map> headers UidsCookie uidsCookie Byte[] responseBody } diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index 42cbea672d4..3b936579109 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -38,17 +38,16 @@ class SetUidSpec extends BaseSpec { private static final boolean CORS_SUPPORT = false private static final Integer RANDOM_EXPIRE_DAY = PBSUtils.getRandomNumber(1, 10) private static final String USER_SYNC_URL = "$networkServiceContainer.rootUri/generic-usersync" - private static final String USER_SYNC_URL_2 = "$networkServiceContainer.rootUri/audience-network-usersync" private static final Map PBS_CONFIG = - ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String, - "adapters.${RUBICON.value}.enabled" : "true", - "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value, - "adapters.${OPENX.value}.enabled" : "true", - "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value, - "adapters.${APPNEXUS.value}.enabled" : "true", - "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value, - "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, - "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString()] + ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String, + "adapters.${RUBICON.value}.enabled" : "true", + "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value, + "adapters.${OPENX.value}.enabled" : "true", + "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value, + "adapters.${APPNEXUS.value}.enabled" : "true", + "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value, + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()] private static final Map UID_COOKIES_CONFIG = ['setuid.number-of-uid-cookies': MAX_NUMBER_OF_UID_COOKIES.toString()] private static final Map GENERIC_ALIAS_CONFIG = ["adapters.generic.aliases.alias.enabled" : "true", "adapters.generic.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] @@ -127,7 +126,7 @@ class SetUidSpec extends BaseSpec { when: "PBS processes setuid request" def response = prebidServerService.sendSetUidRequest(request, uidsCookie) - then: "Response should contain requested uids" + then: "Response should contain requested tempUIDs" assert response.uidsCookie.tempUIDs[GENERIC] assert response.uidsCookie.tempUIDs[RUBICON] @@ -292,7 +291,7 @@ class SetUidSpec extends BaseSpec { pbsServiceFactory.removeContainer(pbsConfig) } - def "PBS SetUid should remove most distant expiration bidder from uids cookie in favor of prioritized bidder"() { + def "PBS set uid should emit sizeblocked metric and remove most distant expiration bidder from uids cookie for non-prioritized bidder"() { given: "PBS config" def pbsConfig = PBS_CONFIG + ["cookie-sync.pri": "$OPENX.value, $GENERIC.value" as String] def prebidServerService = pbsServiceFactory.getService(pbsConfig) @@ -335,7 +334,7 @@ class SetUidSpec extends BaseSpec { pbsServiceFactory.removeContainer(pbsConfig) } - def "PBS SetUid should remove most distant expiration bidder from uids cookie in prioritized bidder"() { + def "PBS set uid should emit sizedout metric and remove most distant expiration bidder from uids cookie in prioritized bidder"() { given: "PBS config" def pbsConfig = PBS_CONFIG + ["cookie-sync.pri": "$OPENX.value, $APPNEXUS.value, $RUBICON.value" as String] def prebidServerService = pbsServiceFactory.getService(pbsConfig) @@ -389,20 +388,41 @@ class SetUidSpec extends BaseSpec { bidderName << [UNKNOWN, WILDCARD, GENERIC_CAMEL_CASE, ALIAS, ALIAS_CAMEL_CASE] } - def "PBS should remove all cookies when incoming request have specified optout flad"() { + def "PBS should throw an exception when incoming request have optout flag"() { given: "Setuid request" def request = SetuidRequest.defaultSetuidRequest - def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC).tap { - optout = false - } + def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC) + + and: "PBS service with optout cookies" + def pbsConfig = PBS_CONFIG + ["host-cookie.optout-cookie.name" : "uids", + "host-cookie.optout-cookie.value": Base64.urlEncoder.encodeToString(encode(genericUidsCookie).bytes)] + def prebidServerService = pbsServiceFactory.getService(pbsConfig) when: "PBS processes setuid request" - singleCookiesPbsService.sendSetUidRequest(request, genericUidsCookie) + prebidServerService.sendSetUidRequest(request, [genericUidsCookie]) then: "Request should fail with error" def exception = thrown(PrebidServerException) - assert exception.statusCode == 400 - assert exception.responseBody == 'Sync is not allowed for this uids' + assert exception.statusCode == 401 + assert exception.responseBody == 'Unauthorized: Sync is not allowed for this uids' + } + + def "PBS should remove all cookies when incoming request have optout flag"() { + given: "Setuid request" + def request = SetuidRequest.defaultSetuidRequest + def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC) + def rubiconUidsCookie = UidsCookie.getDefaultUidsCookie(RUBICON) + + and: "PBS service with optout cookies" + def pbsConfig = PBS_CONFIG + UID_COOKIES_CONFIG + ["host-cookie.optout-cookie.name" : "uids", + "host-cookie.optout-cookie.value": Base64.urlEncoder.encodeToString(encode(rubiconUidsCookie).bytes)] + def prebidServerService = pbsServiceFactory.getService(pbsConfig) + + when: "PBS processes setuid request" + def response = prebidServerService.sendSetUidRequest(request, [genericUidsCookie, rubiconUidsCookie]) + + then: "Response shouldn't contain any tempUIDs" + assert !response.uidsCookie.tempUIDs } def "PBS should merge cookies when incoming request have multiple uids cookies"() { @@ -416,7 +436,7 @@ class SetUidSpec extends BaseSpec { when: "PBS processes setuid request" def response = multipleCookiesPbsService.sendSetUidRequest(request, [genericUidsCookie, rubiconUidsCookie]) - then: "Response should contain requested uids" + then: "Response should contain requested tempUIDs" assert response.uidsCookie.tempUIDs[GENERIC] assert response.uidsCookie.tempUIDs[RUBICON] @@ -428,7 +448,7 @@ class SetUidSpec extends BaseSpec { assert uidsCookie.tempUIDs[RUBICON] } - def "PBS should sout multiple uids cookies by priority and expiration timestamp"() { + def "PBS should send multiple uids cookies by priority and expiration timestamp"() { given: "PBS config" def pbsConfig = PBS_CONFIG + UID_COOKIES_CONFIG + @@ -448,7 +468,7 @@ class SetUidSpec extends BaseSpec { when: "PBS processes setuid request" def response = prebidServerService.sendSetUidRequest(request, [appnexusUidsCookie, genericUidsCookie, rubiconUidsCookie, openxUidsCookie]) - then: "Response should contain requested uids" + then: "Response should contain requested tempUIDs" assert response.uidsCookie.tempUIDs.keySet() == new LinkedHashSet([GENERIC, OPENX, APPNEXUS, RUBICON]) cleanup: "Stop and remove pbs container" @@ -502,7 +522,7 @@ class SetUidSpec extends BaseSpec { when: "PBS processes setuid request" def response = multipleCookiesPbsService.sendSetUidRequest(request, [genericUidsCookie, rubiconUidsCookie]) - then: "Response should contain requested uids" + then: "Response should contain requested tempUIDs" assert response.uidsCookie.tempUIDs[GENERIC] assert response.uidsCookie.tempUIDs[RUBICON] From 702d597af39b74dfa071d606a42b8e4dfeab028d Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Mon, 27 Jan 2025 11:36:23 +0200 Subject: [PATCH 4/4] update functional tests --- .../server/functional/tests/SetUidSpec.groovy | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index 3b936579109..7e9eff9ebd3 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -407,24 +407,6 @@ class SetUidSpec extends BaseSpec { assert exception.responseBody == 'Unauthorized: Sync is not allowed for this uids' } - def "PBS should remove all cookies when incoming request have optout flag"() { - given: "Setuid request" - def request = SetuidRequest.defaultSetuidRequest - def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC) - def rubiconUidsCookie = UidsCookie.getDefaultUidsCookie(RUBICON) - - and: "PBS service with optout cookies" - def pbsConfig = PBS_CONFIG + UID_COOKIES_CONFIG + ["host-cookie.optout-cookie.name" : "uids", - "host-cookie.optout-cookie.value": Base64.urlEncoder.encodeToString(encode(rubiconUidsCookie).bytes)] - def prebidServerService = pbsServiceFactory.getService(pbsConfig) - - when: "PBS processes setuid request" - def response = prebidServerService.sendSetUidRequest(request, [genericUidsCookie, rubiconUidsCookie]) - - then: "Response shouldn't contain any tempUIDs" - assert !response.uidsCookie.tempUIDs - } - def "PBS should merge cookies when incoming request have multiple uids cookies"() { given: "Setuid request" def request = SetuidRequest.defaultSetuidRequest.tap {