From 8172dd609e347f5e2ffb89b2697e9d5334d3310a Mon Sep 17 00:00:00 2001 From: antonbabak Date: Mon, 16 Dec 2024 15:31:48 +0100 Subject: [PATCH 1/2] Enhanced /setuid TCF support --- .../prebid/server/handler/SetuidHandler.java | 63 ++++++++----------- .../privacy/HostVendorTcfDefinerService.java | 4 +- .../server/handler/SetuidHandlerTest.java | 18 +++--- 3 files changed, 38 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/prebid/server/handler/SetuidHandler.java b/src/main/java/org/prebid/server/handler/SetuidHandler.java index 4425e698458..1f6bed55e5f 100644 --- a/src/main/java/org/prebid/server/handler/SetuidHandler.java +++ b/src/main/java/org/prebid/server/handler/SetuidHandler.java @@ -2,6 +2,7 @@ import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.AsyncResult; +import io.vertx.core.CompositeFuture; import io.vertx.core.Future; import io.vertx.core.http.Cookie; import io.vertx.core.http.HttpHeaders; @@ -50,6 +51,8 @@ import org.prebid.server.privacy.gdpr.model.TcfResponse; import org.prebid.server.settings.ApplicationSettings; import org.prebid.server.settings.model.Account; +import org.prebid.server.settings.model.AccountGdprConfig; +import org.prebid.server.settings.model.AccountPrivacyConfig; import org.prebid.server.util.HttpUtil; import org.prebid.server.vertx.verticles.server.HttpEndpoint; import org.prebid.server.vertx.verticles.server.application.ApplicationResource; @@ -208,17 +211,23 @@ private void handleSetuidContextResult(AsyncResult setuidContextR if (setuidContextResult.succeeded()) { final SetuidContext setuidContext = setuidContextResult.result(); - final String bidder = setuidContext.getCookieName(); + final String bidderCookieName = setuidContext.getCookieName(); final TcfContext tcfContext = setuidContext.getPrivacyContext().getTcfContext(); try { - validateSetuidContext(setuidContext, bidder); + validateSetuidContext(setuidContext, bidderCookieName); } catch (InvalidRequestException | UnauthorizedUidsException | UnavailableForLegalReasonsException e) { handleErrors(e, routingContext, tcfContext); return; } - isAllowedForHostVendorId(tcfContext) + final AccountPrivacyConfig privacyConfig = setuidContext.getAccount().getPrivacy(); + final AccountGdprConfig accountGdprConfig = privacyConfig != null ? privacyConfig.getGdpr() : null; + + Future.all( + tcfDefinerService.isAllowedForHostVendorId(tcfContext), + tcfDefinerService.resultForBidderNames( + Collections.singleton(bidderCookieName), tcfContext, accountGdprConfig)) .onComplete(hostTcfResponseResult -> respondByTcfResponse(hostTcfResponseResult, setuidContext)); } else { final Throwable error = setuidContextResult.cause(); @@ -255,44 +264,25 @@ private void validateSetuidContext(SetuidContext setuidContext, String bidder) { } } - /** - * If host vendor id is null, host allowed to setuid. - */ - private Future isAllowedForHostVendorId(TcfContext tcfContext) { - final Integer gdprHostVendorId = tcfDefinerService.getGdprHostVendorId(); - return gdprHostVendorId == null - ? Future.succeededFuture(HostVendorTcfResponse.allowedVendor()) - : tcfDefinerService.resultForVendorIds(Collections.singleton(gdprHostVendorId), tcfContext) - .map(this::toHostVendorTcfResponse); - } - - private HostVendorTcfResponse toHostVendorTcfResponse(TcfResponse tcfResponse) { - return HostVendorTcfResponse.of(tcfResponse.getUserInGdprScope(), tcfResponse.getCountry(), - isSetuidAllowed(tcfResponse)); - } - - private boolean isSetuidAllowed(TcfResponse hostTcfResponseToSetuidContext) { - // allow cookie only if user is not in GDPR scope or vendor passed GDPR check - final boolean notInGdprScope = BooleanUtils.isFalse(hostTcfResponseToSetuidContext.getUserInGdprScope()); - - final Map vendorIdToAction = hostTcfResponseToSetuidContext.getActions(); - final PrivacyEnforcementAction hostPrivacyAction = vendorIdToAction != null - ? vendorIdToAction.get(tcfDefinerService.getGdprHostVendorId()) - : null; - final boolean blockPixelSync = hostPrivacyAction == null || hostPrivacyAction.isBlockPixelSync(); - - return notInGdprScope || !blockPixelSync; - } - - private void respondByTcfResponse(AsyncResult hostTcfResponseResult, - SetuidContext setuidContext) { + private void respondByTcfResponse(AsyncResult hostTcfResponseResult, SetuidContext setuidContext) { final String bidderCookieName = setuidContext.getCookieName(); final TcfContext tcfContext = setuidContext.getPrivacyContext().getTcfContext(); final RoutingContext routingContext = setuidContext.getRoutingContext(); if (hostTcfResponseResult.succeeded()) { - final HostVendorTcfResponse hostTcfResponse = hostTcfResponseResult.result(); - if (hostTcfResponse.isVendorAllowed()) { + final CompositeFuture compositeFuture = hostTcfResponseResult.result(); + final HostVendorTcfResponse hostVendorTcfResponse = compositeFuture.resultAt(0); + final TcfResponse bidderTcfResponse = compositeFuture.resultAt(1); + + final Map vendorIdToAction = bidderTcfResponse.getActions(); + final PrivacyEnforcementAction action = vendorIdToAction != null + ? vendorIdToAction.get(bidderCookieName) + : null; + + final boolean notInGdprScope = BooleanUtils.isFalse(bidderTcfResponse.getUserInGdprScope()); + final boolean isBidderVendorAllowed = notInGdprScope || action == null || !action.isBlockPixelSync(); + + if (hostVendorTcfResponse.isVendorAllowed() && isBidderVendorAllowed) { respondWithCookie(setuidContext); } else { metrics.updateUserSyncTcfBlockedMetric(bidderCookieName); @@ -308,7 +298,6 @@ private void respondByTcfResponse(AsyncResult hostTcfResp analyticsDelegator.processEvent(SetuidEvent.error(status.code()), tcfContext); } - } else { final Throwable error = hostTcfResponseResult.cause(); metrics.updateUserSyncTcfBlockedMetric(bidderCookieName); diff --git a/src/main/java/org/prebid/server/privacy/HostVendorTcfDefinerService.java b/src/main/java/org/prebid/server/privacy/HostVendorTcfDefinerService.java index 471eb5715e3..1588f1827af 100644 --- a/src/main/java/org/prebid/server/privacy/HostVendorTcfDefinerService.java +++ b/src/main/java/org/prebid/server/privacy/HostVendorTcfDefinerService.java @@ -51,10 +51,10 @@ private HostVendorTcfResponse toHostVendorTcfResponse(TcfResponse tcfRe return HostVendorTcfResponse.of( tcfResponse.getUserInGdprScope(), tcfResponse.getCountry(), - isCookieSyncAllowed(tcfResponse)); + isVendorAllowed(tcfResponse)); } - private boolean isCookieSyncAllowed(TcfResponse hostTcfResponse) { + private boolean isVendorAllowed(TcfResponse hostTcfResponse) { return Optional.ofNullable(hostTcfResponse.getActions()) .map(vendorIdToAction -> vendorIdToAction.get(gdprHostVendorId)) .map(hostActions -> !hostActions.isBlockPixelSync()) diff --git a/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java b/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java index 95e5ad24ced..8cb03cda9b5 100644 --- a/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java +++ b/src/test/java/org/prebid/server/handler/SetuidHandlerTest.java @@ -111,8 +111,10 @@ public class SetuidHandlerTest extends VertxTest { @BeforeEach public void setUp() { - final Map vendorIdToGdpr = singletonMap(1, - PrivacyEnforcementAction.allowAll()); + final Map bidderToGdpr = Map.of( + RUBICON, PrivacyEnforcementAction.allowAll(), + ADNXS, PrivacyEnforcementAction.allowAll(), + FACEBOOK, PrivacyEnforcementAction.allowAll()); tcfContext = TcfContext.builder().inGdprScope(false).build(); given(setuidPrivacyContextFactory.contextFrom(any(), any(), any())) @@ -122,8 +124,8 @@ public void setUp() { .willAnswer(invocation -> invocation.getArgument(0)); given(activityInfrastructureCreator.create(any(), any(), any())) .willReturn(activityInfrastructure); - given(tcfDefinerService.resultForVendorIds(anySet(), any())) - .willReturn(Future.succeededFuture(TcfResponse.of(true, vendorIdToGdpr, null))); + given(tcfDefinerService.resultForBidderNames(anySet(), any(), any())) + .willReturn(Future.succeededFuture(TcfResponse.of(true, bidderToGdpr, null))); given(tcfDefinerService.isAllowedForHostVendorId(any())) .willReturn(Future.succeededFuture(HostVendorTcfResponse.allowedVendor())); given(tcfDefinerService.getGdprHostVendorId()).willReturn(1); @@ -312,9 +314,9 @@ public void shouldPassUnsuccessfulEventToAnalyticsReporterIfUidMissingInRequest( public void shouldRespondWithoutCookieIfGdprProcessingPreventsCookieSetting() { // given final PrivacyEnforcementAction privacyEnforcementAction = PrivacyEnforcementAction.restrictAll(); - given(tcfDefinerService.resultForVendorIds(anySet(), any())) + given(tcfDefinerService.resultForBidderNames(anySet(), any(), any())) .willReturn(Future.succeededFuture( - TcfResponse.of(true, singletonMap(null, privacyEnforcementAction), null))); + TcfResponse.of(true, singletonMap(RUBICON, privacyEnforcementAction), null))); given(uidsCookieService.parseFromRequest(any(RoutingContext.class))) .willReturn(new UidsCookie(Uids.builder().uids(emptyMap()).build(), jacksonMapper)); @@ -338,7 +340,7 @@ public void shouldRespondWithoutCookieIfGdprProcessingPreventsCookieSetting() { @Test public void shouldRespondWithBadRequestStatusIfGdprProcessingFailsWithInvalidRequestException() { // given - given(tcfDefinerService.resultForVendorIds(anySet(), any())) + given(tcfDefinerService.resultForBidderNames(anySet(), any(), any())) .willReturn(Future.failedFuture(new InvalidRequestException("gdpr exception"))); given(uidsCookieService.parseFromRequest(any(RoutingContext.class))) @@ -361,7 +363,7 @@ public void shouldRespondWithBadRequestStatusIfGdprProcessingFailsWithInvalidReq @Test public void shouldRespondWithInternalServerErrorStatusIfGdprProcessingFailsWithUnexpectedException() { // given - given(tcfDefinerService.resultForVendorIds(anySet(), any())) + given(tcfDefinerService.resultForBidderNames(anySet(), any(), any())) .willReturn(Future.failedFuture("unexpected error TCF")); given(uidsCookieService.parseFromRequest(any(RoutingContext.class))) From b43c76ff78841d500312b5f1f4e9fdae396cc2dd Mon Sep 17 00:00:00 2001 From: Markiyan Mykush <95693607+marki1an@users.noreply.github.com> Date: Wed, 8 Jan 2025 12:20:13 +0200 Subject: [PATCH 2/2] Test: setuid tcf support (#3658) * Add functional tests for setuid tcf support --- .../server/functional/tests/SetUidSpec.groovy | 6 +- .../tests/privacy/GdprSetUidSpec.groovy | 239 ++++++++++++++++++ 2 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 src/test/groovy/org/prebid/server/functional/tests/privacy/GdprSetUidSpec.groovy 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 b9e310c2b26..53f633b710c 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -37,6 +37,8 @@ class SetUidSpec extends BaseSpec { "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 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) @@ -199,8 +201,8 @@ class SetUidSpec extends BaseSpec { then: "Request should fail with error" def exception = thrown(PrebidServerException) - assert exception.statusCode == 451 - assert exception.responseBody == "The gdpr_consent param prevents cookies from being saved" + assert exception.statusCode == UNAVAILABLE_FOR_LEGAL_REASONS_CODE + assert exception.responseBody == TCF_ERROR_MESSAGE and: "usersync.FAMILY.tcf.blocked metric should be updated" def metric = prebidServerService.sendCollectedMetricsRequest() diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprSetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprSetUidSpec.groovy new file mode 100644 index 00000000000..0fede80a71a --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprSetUidSpec.groovy @@ -0,0 +1,239 @@ +package org.prebid.server.functional.tests.privacy + +import org.prebid.server.functional.model.UidsCookie +import org.prebid.server.functional.model.config.AccountAuctionConfig +import org.prebid.server.functional.model.config.AccountConfig +import org.prebid.server.functional.model.config.AccountGdprConfig +import org.prebid.server.functional.model.config.AccountPrivacyConfig +import org.prebid.server.functional.model.config.PurposeConfig +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.request.setuid.SetuidRequest +import org.prebid.server.functional.model.response.cookiesync.UserSyncInfo +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 static org.prebid.server.functional.model.AccountStatus.ACTIVE +import static org.prebid.server.functional.model.bidder.BidderName.GENERIC +import static org.prebid.server.functional.model.config.Purpose.P1 +import static org.prebid.server.functional.model.config.PurposeEnforcement.FULL +import static org.prebid.server.functional.model.config.PurposeEnforcement.NO +import static org.prebid.server.functional.model.request.setuid.UidWithExpiry.getDefaultUidWithExpiry +import static org.prebid.server.functional.model.response.cookiesync.UserSyncInfo.Type.REDIRECT +import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer +import static org.prebid.server.functional.util.privacy.TcfConsent.GENERIC_VENDOR_ID +import static org.prebid.server.functional.util.privacy.TcfConsent.PurposeId.DEVICE_ACCESS + +class GdprSetUidSpec extends PrivacyBaseSpec { + + private static final boolean CORS_SUPPORT = false + private static final String USER_SYNC_URL = "$networkServiceContainer.rootUri/generic-usersync" + private static final UserSyncInfo.Type USER_SYNC_TYPE = REDIRECT + private static final Map VENDOR_GENERIC_PBS_CONFIG = GENERIC_VENDOR_CONFIG + + ["gdpr.purposes.p1.enforce-purpose" : NO.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 String TCF_ERROR_MESSAGE = "The gdpr_consent param prevents cookies from being saved" + private static final int UNAVAILABLE_FOR_LEGAL_REASONS_CODE = 451 + + private static final PrebidServerService prebidServerService = pbsServiceFactory.getService(VENDOR_GENERIC_PBS_CONFIG) + + def "PBS setuid shouldn't failed with tcf when purpose access device not enforced"() { + given: "Default setuid request with account" + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = PBSUtils.randomNumber.toString() + it.uid = UUID.randomUUID().toString() + it.bidder = GENERIC + it.gdpr = "1" + it.gdprConsent = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + } + + and: "Default uids cookie with rubicon bidder" + def uidsCookie = UidsCookie.defaultUidsCookie.tap { + it.tempUIDs = [(GENERIC): defaultUidWithExpiry] + } + + and: "Save account config with purpose into DB" + def accountConfig = new AccountConfig( + auction: new AccountAuctionConfig(debugAllow: true), + privacy: new AccountPrivacyConfig(gdpr: new AccountGdprConfig(purposes: [(P1): new PurposeConfig(enforcePurpose: NO)], enabled: true))) + def account = new Account(status: ACTIVE, uuid: setuidRequest.account, config: accountConfig) + accountDao.save(account) + + when: "PBS processes setuid request" + def response = prebidServerService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Response should contain tempUids cookie and headers" + assert response.headers.size() == 7 + assert response.uidsCookie.tempUIDs[GENERIC].uid == setuidRequest.uid + assert response.responseBody == + ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png") + } + + def "PBS setuid should failed with tcf when purpose access device enforced for account"() { + given: "Default setuid request with account" + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = PBSUtils.randomNumber.toString() + it.uid = UUID.randomUUID().toString() + it.bidder = GENERIC + it.gdpr = "1" + it.gdprConsent = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + } + + and: "Default uids cookie with rubicon bidder" + def uidsCookie = UidsCookie.defaultUidsCookie.tap { + it.tempUIDs = [(GENERIC): defaultUidWithExpiry] + } + + and: "Save account config with purpose into DB" + def accountConfig = new AccountConfig( + auction: new AccountAuctionConfig(debugAllow: true), + privacy: new AccountPrivacyConfig(gdpr: new AccountGdprConfig(purposes: [(P1): new PurposeConfig(enforcePurpose: FULL)], enabled: true))) + def account = new Account(status: ACTIVE, uuid: setuidRequest.account, config: accountConfig) + accountDao.save(account) + + when: "PBS processes setuid request" + prebidServerService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == UNAVAILABLE_FOR_LEGAL_REASONS_CODE + assert exception.responseBody == TCF_ERROR_MESSAGE + + and: "Metric should be increased usersync.FAMILY.tcf.blocked" + def metric = prebidServerService.sendCollectedMetricsRequest() + assert metric["usersync.${GENERIC.value}.tcf.blocked"] == 1 + } + + def "PBS setuid should failed with tcf when purpose access device enforced for host"() { + given: "PBS config" + def pbsConfig = VENDOR_GENERIC_PBS_CONFIG + ["gdpr.purposes.p1.enforce-purpose": FULL.value] + def prebidServerService = pbsServiceFactory.getService(pbsConfig) + + and: "Default setuid request with account" + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = PBSUtils.randomNumber.toString() + it.uid = UUID.randomUUID().toString() + it.bidder = GENERIC + it.gdpr = "1" + it.gdprConsent = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + } + + and: "Default uids cookie with rubicon bidder" + def uidsCookie = UidsCookie.defaultUidsCookie.tap { + it.tempUIDs = [(GENERIC): defaultUidWithExpiry] + } + + and: "Save account config with purpose into DB" + def accountConfig = new AccountConfig( + auction: new AccountAuctionConfig(debugAllow: true), + privacy: new AccountPrivacyConfig(gdpr: new AccountGdprConfig(purposes: [(P1): new PurposeConfig(enforcePurpose: NO)], enabled: true))) + def account = new Account(status: ACTIVE, uuid: setuidRequest.account, config: accountConfig) + accountDao.save(account) + + when: "PBS processes setuid request" + prebidServerService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == UNAVAILABLE_FOR_LEGAL_REASONS_CODE + assert exception.responseBody == TCF_ERROR_MESSAGE + + and: "Metric should be increased usersync.FAMILY.tcf.blocked" + def metric = prebidServerService.sendCollectedMetricsRequest() + assert metric["usersync.${GENERIC.value}.tcf.blocked"] == 1 + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS setuid shouldn't failed with tcf when purpose access device not enforced for host and host-vendor-id empty"() { + given: "PBS config" + def pbsConfig = VENDOR_GENERIC_PBS_CONFIG + ["gdpr.purposes.p1.enforce-purpose": NO.value, + "gdpr.host-vendor-id" : ""] + def prebidServerService = pbsServiceFactory.getService(pbsConfig) + + and: "Default setuid request with account" + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = PBSUtils.randomNumber.toString() + it.uid = UUID.randomUUID().toString() + it.bidder = GENERIC + it.gdpr = "1" + it.gdprConsent = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + } + + and: "Default uids cookie with rubicon bidder" + def uidsCookie = UidsCookie.defaultUidsCookie.tap { + it.tempUIDs = [(GENERIC): defaultUidWithExpiry] + } + + and: "Save account config with purpose into DB" + def accountConfig = new AccountConfig( + auction: new AccountAuctionConfig(debugAllow: true), + privacy: new AccountPrivacyConfig(gdpr: new AccountGdprConfig(purposes: [(P1): new PurposeConfig(enforcePurpose: NO)], enabled: true))) + def account = new Account(status: ACTIVE, uuid: setuidRequest.account, config: accountConfig) + accountDao.save(account) + + when: "PBS processes setuid request" + def response = prebidServerService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Response should contain tempUids cookie and headers" + assert response.headers.size() == 7 + assert response.uidsCookie.tempUIDs[GENERIC].uid == setuidRequest.uid + assert response.responseBody == + ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png") + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS setuid shouldn't failed with purpose access device enforced for account when bidder included in vendorExceptions"() { + given: "Default setuid request with account" + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = PBSUtils.randomNumber.toString() + it.uid = UUID.randomUUID().toString() + it.bidder = GENERIC + it.gdpr = "1" + it.gdprConsent = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + } + + and: "Default uids cookie with rubicon bidder" + def uidsCookie = UidsCookie.defaultUidsCookie.tap { + it.tempUIDs = [(GENERIC): defaultUidWithExpiry] + } + + and: "Save account config with purpose into DB" + def purposeConfig = new PurposeConfig(enforcePurpose: FULL, vendorExceptions: [GENERIC.value]) + def accountConfig = new AccountConfig( + auction: new AccountAuctionConfig(debugAllow: true), + privacy: new AccountPrivacyConfig(gdpr: new AccountGdprConfig(purposes: [(P1): purposeConfig], enabled: true))) + def account = new Account(status: ACTIVE, uuid: setuidRequest.account, config: accountConfig) + accountDao.save(account) + + when: "PBS processes setuid request" + def response = prebidServerService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Response should contain tempUids cookie and headers" + assert response.headers.size() == 7 + assert response.uidsCookie.tempUIDs[GENERIC].uid == setuidRequest.uid + assert response.responseBody == + ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png") + } +}