From 459e6582e005b8a6f3af41d0d9eee53d012da7d9 Mon Sep 17 00:00:00 2001 From: mtuchkova Date: Tue, 16 Nov 2021 13:21:36 +0200 Subject: [PATCH] Rename integration to channel in account config tests --- .../model/{config => }/ChannelType.groovy | 2 +- .../model/config/AccountCcpaConfig.groovy | 3 + .../model/config/AccountGdprConfig.groovy | 3 + .../model/request/amp/AmpRequest.groovy | 2 +- .../model/request/auction/App.groovy | 5 +- .../model/request/auction/BidRequest.groovy | 13 +- .../model/request/auction/Channel.groovy | 4 +- .../model/request/auction/UserExt.groovy | 2 +- .../container/PrebidServerContainer.groovy | 10 +- .../tests/privacy/CcpaAmpSpec.groovy | 152 +++++++++++++ ...CcpaSpec.groovy => CcpaAuctionSpec.groovy} | 140 +++++++----- .../{GdprSpec.groovy => GdprAmpSpec.groovy} | 146 ++++++------ .../tests/privacy/GdprAuctionSpec.groovy | 207 ++++++++++++++++++ .../tests/privacy/PrivacyBaseSpec.groovy | 47 +++- .../functional/util/privacy/TcfConsent.groovy | 7 + 15 files changed, 603 insertions(+), 140 deletions(-) rename src/test/groovy/org/prebid/server/functional/model/{config => }/ChannelType.groovy (77%) create mode 100644 src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaAmpSpec.groovy rename src/test/groovy/org/prebid/server/functional/tests/privacy/{CcpaSpec.groovy => CcpaAuctionSpec.groovy} (52%) rename src/test/groovy/org/prebid/server/functional/tests/privacy/{GdprSpec.groovy => GdprAmpSpec.groovy} (52%) create mode 100644 src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy diff --git a/src/test/groovy/org/prebid/server/functional/model/config/ChannelType.groovy b/src/test/groovy/org/prebid/server/functional/model/ChannelType.groovy similarity index 77% rename from src/test/groovy/org/prebid/server/functional/model/config/ChannelType.groovy rename to src/test/groovy/org/prebid/server/functional/model/ChannelType.groovy index f01abb7800a..e6df4510b97 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/ChannelType.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/ChannelType.groovy @@ -1,4 +1,4 @@ -package org.prebid.server.functional.model.config +package org.prebid.server.functional.model import com.fasterxml.jackson.annotation.JsonValue diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountCcpaConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountCcpaConfig.groovy index 155889280f6..34a6f202176 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountCcpaConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountCcpaConfig.groovy @@ -1,13 +1,16 @@ package org.prebid.server.functional.model.config +import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString +import org.prebid.server.functional.model.ChannelType @ToString(includeNames = true, ignoreNulls = true) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class AccountCcpaConfig { Boolean enabled + @JsonProperty("channel-enabled") Map enabledForRequestType } diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountGdprConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountGdprConfig.groovy index 850c95aa2c2..2a0625f92de 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountGdprConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountGdprConfig.groovy @@ -1,14 +1,17 @@ package org.prebid.server.functional.model.config +import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import groovy.transform.ToString +import org.prebid.server.functional.model.ChannelType @ToString(includeNames = true, ignoreNulls = true) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy) class AccountGdprConfig { Boolean enabled + @JsonProperty("channel-enabled") Map enabledForRequestType Map purposes Map specialFeatures diff --git a/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy index 1b4129e5365..60312a627d2 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/amp/AmpRequest.groovy @@ -21,7 +21,7 @@ class AmpRequest { String slot String curl Integer account - ConsentString gdprConsent + String gdprConsent String targeting Integer consentType Boolean gdprApplies diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/App.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/App.groovy index bff2f7e0cd9..05903d6fe8f 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/App.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/App.groovy @@ -22,6 +22,9 @@ class App { String keywords static App getDefaultApp() { - new App(id: PBSUtils.randomString) + new App().tap { + id = PBSUtils.randomString + publisher = Publisher.defaultPublisher + } } } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy index 04d0dcc0d70..56b5e5c7ecd 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/BidRequest.groovy @@ -3,6 +3,10 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.annotation.JsonIgnore import groovy.transform.EqualsAndHashCode import groovy.transform.ToString +import org.prebid.server.functional.model.ChannelType + +import static org.prebid.server.functional.model.ChannelType.APP +import static org.prebid.server.functional.model.ChannelType.WEB @EqualsAndHashCode @ToString(includeNames = true, ignoreNulls = true) @@ -29,14 +33,19 @@ class BidRequest { Regs regs BidRequestExt ext - static BidRequest getDefaultBidRequest() { + static BidRequest getDefaultBidRequest(ChannelType channelType = WEB) { new BidRequest().tap { it.addImp(Imp.defaultImpression) regs = Regs.defaultRegs id = UUID.randomUUID() tmax = 2500 - site = Site.defaultSite ext = new BidRequestExt(prebid: new Prebid(debug: 1)) + if (channelType == WEB) { + site = Site.defaultSite + } + if (channelType == APP) { + app = App.defaultApp + } } } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Channel.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Channel.groovy index 551d94a40c2..51c504c6822 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Channel.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Channel.groovy @@ -1,9 +1,11 @@ package org.prebid.server.functional.model.request.auction import groovy.transform.ToString +import org.prebid.server.functional.model.ChannelType @ToString(includeNames = true, ignoreNulls = true) class Channel { - String name + ChannelType name + String version } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy index 4ccee84ff0e..912b8cd8f0a 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/UserExt.groovy @@ -6,5 +6,5 @@ import org.prebid.server.functional.util.privacy.ConsentString @ToString(includeNames = true, ignoreNulls = true) class UserExt { - ConsentString consent + String consent } diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy index ae34e2fe71e..73b732998d6 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/container/PrebidServerContainer.groovy @@ -1,10 +1,13 @@ package org.prebid.server.functional.testcontainers.container import org.prebid.server.functional.testcontainers.Dependencies +import org.prebid.server.functional.util.privacy.TcfConsent import org.testcontainers.containers.GenericContainer import org.testcontainers.containers.MySQLContainer import org.testcontainers.containers.wait.strategy.Wait +import static org.prebid.server.functional.util.privacy.TcfConsent.RUBICON_VENDOR_ID + class PrebidServerContainer extends GenericContainer { public static final int PORT = 8080 @@ -114,9 +117,10 @@ LIMIT 1 void withBidder(String host) { withConfig(["adapters.generic.enabled" : "true", - "adapters.generic.endpoint" : "$host/auction".toString(), - "adapters.generic.usersync.url" : "$host/generic-usersync".toString(), - "adapters.generic.usersync.type": "redirect" + "adapters.generic.endpoint" : "$host/auction" as String, + "adapters.generic.usersync.url" : "$host/generic-usersync" as String, + "adapters.generic.usersync.type": "redirect", + "adapters.generic.meta-info.vendor-id": RUBICON_VENDOR_ID as String ]) } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaAmpSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaAmpSpec.groovy new file mode 100644 index 00000000000..4cf8da84abe --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaAmpSpec.groovy @@ -0,0 +1,152 @@ +package org.prebid.server.functional.tests.privacy + +import org.prebid.server.functional.model.bidder.BidderName +import org.prebid.server.functional.model.config.AccountCcpaConfig +import org.prebid.server.functional.model.config.AccountConfig +import org.prebid.server.functional.model.config.AccountPrivacyConfig +import org.prebid.server.functional.model.db.Account +import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.testcontainers.PBSTest +import org.prebid.server.functional.util.privacy.BogusConsent +import org.prebid.server.functional.util.privacy.CcpaConsent +import spock.lang.PendingFeature +import spock.lang.Unroll + +import static org.prebid.server.functional.model.ChannelType.AMP +import static org.prebid.server.functional.util.privacy.CcpaConsent.Signal.ENFORCED + +@PBSTest +class CcpaAmpSpec extends PrivacyBaseSpec { + + @PendingFeature + def "PBS should add debug log for amp request when valid ccpa was passed"() { + given: "Default AmpRequest" + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + def ampRequest = getCcpaAmpRequest(validCcpa) + + and: "Save storedRequest into DB" + def ampStoredRequest = storedRequestWithGeo + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = defaultPbsService.sendAmpRequest(ampRequest) + + then: "Response should contain debug log" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.ccpa?.usPrivacy == validCcpa as String + privacy.resolvedPrivacy?.ccpa?.usPrivacy == validCcpa as String + + !privacy.originPrivacy?.coppa?.coppa + !privacy.resolvedPrivacy?.coppa?.coppa + + !privacy.originPrivacy?.tcf?.gdpr + !privacy.originPrivacy?.tcf?.tcfConsentString + !privacy.originPrivacy?.tcf?.tcfConsentVersion + !privacy.originPrivacy?.tcf?.inEea + !privacy.resolvedPrivacy?.tcf?.gdpr + !privacy.resolvedPrivacy?.tcf?.tcfConsentString + !privacy.resolvedPrivacy?.tcf?.tcfConsentVersion + !privacy.resolvedPrivacy?.tcf?.inEea + + privacy.privacyActionsPerBidder[BidderName.GENERIC] == + ["Geolocation was masked in request to bidder according to CCPA policy."] + + privacy.errors?.isEmpty() + } + } + + @PendingFeature + def "PBS should add debug log for amp request when invalid ccpa was passed"() { + given: "Default AmpRequest" + def invalidCcpa = new BogusConsent() + def ampRequest = getCcpaAmpRequest(invalidCcpa) + + and: "Save storedRequest into DB" + def ampStoredRequest = storedRequestWithGeo + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = defaultPbsService.sendAmpRequest(ampRequest) + + then: "Response should not contain error" + assert !response.ext?.errors + + and: "Response should contain debug log with error" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.ccpa?.usPrivacy == invalidCcpa as String + privacy.resolvedPrivacy?.ccpa?.usPrivacy == invalidCcpa as String + + privacy.privacyActionsPerBidder[BidderName.GENERIC].isEmpty() + + privacy.errors == + ["Amp request parameter consent_string or gdpr_consent have invalid format: $invalidCcpa" as String] + } + } + + @Unroll + def "PBS should apply ccpa when privacy.ccpa.channel-enabled.amp or privacy.ccpa.enabled = true in account config"() { + given: "Default AmpRequest" + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + def ampRequest = getCcpaAmpRequest(validCcpa) + + and: "Save storedRequest into DB" + def ampStoredRequest = storedRequestWithGeo + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(ccpa: ccpaConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: ampRequest.account, config: accountConfig) + accountDao.save(account) + + when: "PBS processes amp request" + defaultPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain masked values" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + assert bidderRequests.device?.geo == maskGeo(ampStoredRequest) + + where: + ccpaConfig << [new AccountCcpaConfig(enabled: false, enabledForRequestType: [(AMP): true]), + new AccountCcpaConfig(enabled: true)] + } + + @Unroll + def "PBS should not apply ccpa when privacy.ccpa.channel-enabled.amp or privacy.ccpa.enabled = false in account config"() { + given: "Default AmpRequest" + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + def ampRequest = getCcpaAmpRequest(validCcpa) + + and: "Save storedRequest into DB" + def ampStoredRequest = storedRequestWithGeo + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(ccpa: ccpaConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: ampRequest.account, config: accountConfig) + accountDao.save(account) + + when: "PBS processes amp request" + defaultPbsService.sendAmpRequest(ampRequest) + + then: "Bidder request should contain not masked values" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + assert bidderRequests.device?.geo?.lat == ampStoredRequest.device.geo.lat + assert bidderRequests.device?.geo?.lon == ampStoredRequest.device.geo.lon + + where: + ccpaConfig << [new AccountCcpaConfig(enabled: true, enabledForRequestType: [(AMP): false]), + new AccountCcpaConfig(enabled: false)] + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaAuctionSpec.groovy similarity index 52% rename from src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaAuctionSpec.groovy index 9439a0ca0f0..67c56a89e7c 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/CcpaAuctionSpec.groovy @@ -6,15 +6,22 @@ import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountPrivacyConfig import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.Device +import org.prebid.server.functional.model.request.auction.Geo import org.prebid.server.functional.testcontainers.PBSTest import org.prebid.server.functional.util.privacy.BogusConsent import org.prebid.server.functional.util.privacy.CcpaConsent import spock.lang.PendingFeature +import spock.lang.Unroll +import static org.prebid.server.functional.model.ChannelType.AMP +import static org.prebid.server.functional.model.ChannelType.APP +import static org.prebid.server.functional.model.ChannelType.WEB import static org.prebid.server.functional.util.privacy.CcpaConsent.Signal.ENFORCED @PBSTest -class CcpaSpec extends PrivacyBaseSpec { +class CcpaAuctionSpec extends PrivacyBaseSpec { // TODO: extend ccpa test with actual fields that we should mask def "PBS should mask publisher info when privacy.ccpa.enabled = true in account config"() { @@ -122,76 +129,101 @@ class CcpaSpec extends PrivacyBaseSpec { } } - @PendingFeature - def "PBS should add debug log for amp request when valid ccpa was passed"() { - given: "Default AmpRequest" + @Unroll + def "PBS should apply ccpa when privacy.ccpa.channel-enabled.app or privacy.ccpa.enabled = true in account config"() { + given: "Default basic generic BidRequest" def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) - def ampRequest = getCcpaAmpRequest(validCcpa) + def bidRequest = getCcpaBidRequest(APP, validCcpa) - and: "Save storedRequest into DB" - def ampStoredRequest = bidRequestWithGeo - def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(ccpa: ccpaConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: bidRequest.app.publisher.id, config: accountConfig) + accountDao.save(account) - when: "PBS processes amp request" - def response = defaultPbsService.sendAmpRequest(ampRequest) + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) - then: "Response should contain debug log" - assert response.ext?.debug?.privacy - def privacy = response.ext?.debug?.privacy + then: "Bidder request should contain masked values" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + assert bidderRequests.device?.geo == maskGeo(bidRequest) - verifyAll { - privacy.originPrivacy?.ccpa?.usPrivacy == validCcpa as String - privacy.resolvedPrivacy?.ccpa?.usPrivacy == validCcpa as String + where: + ccpaConfig << [new AccountCcpaConfig(enabled: false, enabledForRequestType: [(APP): true]), + new AccountCcpaConfig(enabled: true)] + } - !privacy.originPrivacy?.coppa?.coppa - !privacy.resolvedPrivacy?.coppa?.coppa + @Unroll + def "PBS should apply ccpa when privacy.ccpa.channel-enabled.web or privacy.ccpa.enabled = true in account config"() { + given: "Default basic generic BidRequest" + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + def bidRequest = getCcpaBidRequest(WEB, validCcpa) - !privacy.originPrivacy?.tcf?.gdpr - !privacy.originPrivacy?.tcf?.tcfConsentString - !privacy.originPrivacy?.tcf?.tcfConsentVersion - !privacy.originPrivacy?.tcf?.inEea - !privacy.resolvedPrivacy?.tcf?.gdpr - !privacy.resolvedPrivacy?.tcf?.tcfConsentString - !privacy.resolvedPrivacy?.tcf?.tcfConsentVersion - !privacy.resolvedPrivacy?.tcf?.inEea + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(ccpa: ccpaConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig) + accountDao.save(account) - privacy.privacyActionsPerBidder[BidderName.GENERIC] == - ["Geolocation was masked in request to bidder according to CCPA policy."] + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) - privacy.errors?.isEmpty() - } + then: "Bidder request should contain masked values" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + assert bidderRequests.device?.geo == maskGeo(bidRequest) + + where: + ccpaConfig << [new AccountCcpaConfig(enabled: false, enabledForRequestType: [(WEB): true]), + new AccountCcpaConfig(enabled: true)] } - @PendingFeature - def "PBS should add debug log for amp request when invalid ccpa was passed"() { - given: "Default AmpRequest" - def invalidCcpa = new BogusConsent() - def ampRequest = getCcpaAmpRequest(invalidCcpa) + @Unroll + def "PBS should not apply ccpa when privacy.ccpa.channel-enabled.app or privacy.ccpa.enabled = false in account config"() { + given: "Default basic generic BidRequest" + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + def bidRequest = getCcpaBidRequest(APP, validCcpa) - and: "Save storedRequest into DB" - def ampStoredRequest = bidRequestWithGeo - def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) - storedRequestDao.save(storedRequest) + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(ccpa: ccpaConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: bidRequest.app.publisher.id, config: accountConfig) + accountDao.save(account) - when: "PBS processes amp request" - def response = defaultPbsService.sendAmpRequest(ampRequest) + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) - then: "Response should not contain error" - assert !response.ext?.errors + then: "Bidder request should contain not masked values" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + assert bidderRequests.device?.geo?.lat == bidRequest.device.geo.lat + assert bidderRequests.device?.geo?.lon == bidRequest.device.geo.lon - and: "Response should contain debug log with error" - assert response.ext?.debug?.privacy - def privacy = response.ext?.debug?.privacy + where: + ccpaConfig << [new AccountCcpaConfig(enabled: true, enabledForRequestType: [(APP): false]), + new AccountCcpaConfig(enabled: false)] + } - verifyAll { - privacy.originPrivacy?.ccpa?.usPrivacy == invalidCcpa as String - privacy.resolvedPrivacy?.ccpa?.usPrivacy == invalidCcpa as String + @Unroll + def "PBS should not apply ccpa when privacy.ccpa.channel-enabled.web or privacy.ccpa.enabled = false in account config"() { + given: "Default basic generic BidRequest" + def validCcpa = new CcpaConsent(explicitNotice: ENFORCED, optOutSale: ENFORCED) + def bidRequest = getCcpaBidRequest(validCcpa) - privacy.privacyActionsPerBidder[BidderName.GENERIC].isEmpty() + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(ccpa: ccpaConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig) + accountDao.save(account) - privacy.errors == - ["Amp request parameter consent_string or gdpr_consent have invalid format: $invalidCcpa" as String] - } + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain not masked values" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + assert bidderRequests.device?.geo?.lat == bidRequest.device.geo.lat + assert bidderRequests.device?.geo?.lon == bidRequest.device.geo.lon + + where: + ccpaConfig << [new AccountCcpaConfig(enabled: true, enabledForRequestType: [(WEB): false]), + new AccountCcpaConfig(enabled: false)] } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAmpSpec.groovy similarity index 52% rename from src/test/groovy/org/prebid/server/functional/tests/privacy/GdprSpec.groovy rename to src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAmpSpec.groovy index ff07ceaf0fb..a6c487109ef 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAmpSpec.groovy @@ -1,28 +1,45 @@ package org.prebid.server.functional.tests.privacy import org.prebid.server.functional.model.bidder.BidderName +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.db.Account import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.testcontainers.PBSTest import org.prebid.server.functional.util.privacy.BogusConsent import org.prebid.server.functional.util.privacy.TcfConsent import spock.lang.PendingFeature +import spock.lang.Unroll +import static org.prebid.server.functional.model.ChannelType.AMP +import static org.prebid.server.functional.util.privacy.TcfConsent.GENERIC_VENDOR_ID import static org.prebid.server.functional.util.privacy.TcfConsent.PurposeId.BASIC_ADS @PBSTest -class GdprSpec extends PrivacyBaseSpec { +class GdprAmpSpec extends PrivacyBaseSpec { - @PendingFeature - def "PBS should add debug log for auction request when valid gdpr was passed"() { - given: "Default gdpr BidRequest" + def setupSpec(){ + cacheVendorList() + } + + @PendingFeature + def "PBS should add debug log for amp request when valid gdpr was passed"() { + given: "AmpRequest with consent string" def validConsentString = new TcfConsent.Builder() .setPurposesLITransparency(BASIC_ADS) + .addVendorLegitimateInterest([GENERIC_VENDOR_ID]) .build() - def bidRequest = getGdprBidRequest(validConsentString) + def ampRequest = getGdprAmpRequest(validConsentString) - when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + and: "Save storedRequest into DB" + def ampStoredRequest = storedRequestWithGeo + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = defaultPbsService.sendAmpRequest(ampRequest) then: "Response should contain debug log" assert response.ext?.debug?.privacy @@ -52,13 +69,18 @@ class GdprSpec extends PrivacyBaseSpec { } @PendingFeature - def "PBS should add debug log for auction request when invalid gdpr was passed"() { - given: "Default gdpr BidRequest" + def "PBS should add debug log for amp request when invalid gdpr was passed"() { + given: "Default AmpRequest" def invalidConsentString = new BogusConsent() - def bidRequest = getGdprBidRequest(invalidConsentString) + def ampRequest = getGdprAmpRequest(invalidConsentString) - when: "PBS processes auction request" - def response = defaultPbsService.sendAuctionRequest(bidRequest) + and: "Save storedRequest into DB" + def ampStoredRequest = storedRequestWithGeo + def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = defaultPbsService.sendAmpRequest(ampRequest) then: "Response should not contain ext.errors" assert !response.ext?.errors @@ -79,89 +101,73 @@ class GdprSpec extends PrivacyBaseSpec { privacy.privacyActionsPerBidder[BidderName.GENERIC].isEmpty() - privacy.errors == ["Placeholder: invalid consent string"] + privacy.errors == ["Amp request parameter consent_string or gdpr_consent have invalid format:" + + " $invalidConsentString" as String] } } - @PendingFeature - def "PBS should add debug log for amp request when valid gdpr was passed"() { - given: "AmpRequest with consent string" + @Unroll + def "PBS should apply gdpr when privacy.gdpr.channel-enabled.amp or privacy.gdpr.enabled = true in account config"() { + given: "Default AmpRequest" def validConsentString = new TcfConsent.Builder() .setPurposesLITransparency(BASIC_ADS) + .addVendorLegitimateInterest([GENERIC_VENDOR_ID]) .build() - def ampRequest = getGdprAmpRequest(validConsentString) and: "Save storedRequest into DB" - def ampStoredRequest = bidRequestWithGeo + def ampStoredRequest = storedRequestWithGeo def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) storedRequestDao.save(storedRequest) - when: "PBS processes amp request" - def response = defaultPbsService.sendAmpRequest(ampRequest) + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(gdpr: gdprConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: ampRequest.account, config: accountConfig) + accountDao.save(account) - then: "Response should contain debug log" - assert response.ext?.debug?.privacy - def privacy = response.ext?.debug?.privacy - - verifyAll { - privacy.originPrivacy?.tcf?.gdpr == "1" - privacy.originPrivacy?.tcf?.tcfConsentString == validConsentString as String - !privacy.originPrivacy?.tcf?.tcfConsentVersion - !privacy.originPrivacy?.tcf?.inEea - privacy.resolvedPrivacy?.tcf?.gdpr == "1" - privacy.resolvedPrivacy?.tcf?.tcfConsentString == validConsentString as String - privacy.resolvedPrivacy?.tcf?.tcfConsentVersion == 2 - !privacy.resolvedPrivacy?.tcf?.inEea - - !privacy.originPrivacy?.ccpa?.usPrivacy - !privacy.resolvedPrivacy?.ccpa?.usPrivacy - - !privacy.originPrivacy?.coppa?.coppa - privacy.resolvedPrivacy?.coppa?.coppa == 0 + when: "PBS processes amp request" + defaultPbsService.sendAmpRequest(ampRequest) - privacy.privacyActionsPerBidder[BidderName.GENERIC] == - ["Geolocation was masked in request to bidder according to TCF policy."] + then: "Bidder request should contain masked values" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + assert bidderRequests.device?.geo == maskGeo(ampStoredRequest) - privacy.errors?.isEmpty() - } + where: + gdprConfig << [new AccountGdprConfig(enabled: false, enabledForRequestType: [(AMP): true]), + new AccountGdprConfig(enabled: true)] } - @PendingFeature - def "PBS should add debug log for amp request when invalid gdpr was passed"() { + @Unroll + def "PBS should not apply gdpr when privacy.gdpr.channel-enabled.amp or privacy.gdpr.enabled = false in account config"() { given: "Default AmpRequest" - def invalidConsentString = new BogusConsent() - def ampRequest = getGdprAmpRequest(invalidConsentString) + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .addVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + def ampRequest = getGdprAmpRequest(validConsentString) and: "Save storedRequest into DB" - def ampStoredRequest = bidRequestWithGeo + def ampStoredRequest = storedRequestWithGeo def storedRequest = StoredRequest.getDbStoredRequest(ampRequest, ampStoredRequest) storedRequestDao.save(storedRequest) - when: "PBS processes amp request" - def response = defaultPbsService.sendAmpRequest(ampRequest) - - then: "Response should not contain ext.errors" - assert !response.ext?.errors + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(gdpr: gdprConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: ampRequest.account, config: accountConfig) + accountDao.save(account) - and: "Response should contain debug log with error" - assert response.ext?.debug?.privacy - def privacy = response.ext?.debug?.privacy - - verifyAll { - privacy.originPrivacy?.tcf?.gdpr == "1" - privacy.originPrivacy?.tcf?.tcfConsentString == invalidConsentString as String - !privacy.originPrivacy?.tcf?.tcfConsentVersion - !privacy.originPrivacy?.tcf?.inEea - privacy.resolvedPrivacy?.tcf?.gdpr == "1" - privacy.resolvedPrivacy?.tcf?.tcfConsentString == invalidConsentString as String - privacy.resolvedPrivacy?.tcf?.tcfConsentVersion == 2 - !privacy.resolvedPrivacy?.tcf?.inEea + when: "PBS processes amp request" + defaultPbsService.sendAmpRequest(ampRequest) - privacy.privacyActionsPerBidder[BidderName.GENERIC].isEmpty() + then: "Bidder request should contain not masked values" + def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) + assert bidderRequests.device?.geo?.lat == ampStoredRequest.device.geo.lat + assert bidderRequests.device?.geo?.lon == ampStoredRequest.device.geo.lon - privacy.errors == ["Amp request parameter consent_string or gdpr_consent have invalid format:" + - " $invalidConsentString" as String] - } + where: + gdprConfig << [new AccountGdprConfig(enabled: true, enabledForRequestType: [(AMP): false]), + new AccountGdprConfig(enabled: false)] } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy new file mode 100644 index 00000000000..36fd0ad4c81 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy @@ -0,0 +1,207 @@ +package org.prebid.server.functional.tests.privacy + +import org.prebid.server.functional.model.bidder.BidderName +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.db.Account +import org.prebid.server.functional.testcontainers.PBSTest +import org.prebid.server.functional.util.privacy.BogusConsent +import org.prebid.server.functional.util.privacy.TcfConsent +import spock.lang.PendingFeature +import spock.lang.Unroll + +import static org.prebid.server.functional.model.ChannelType.APP +import static org.prebid.server.functional.model.ChannelType.WEB +import static org.prebid.server.functional.util.privacy.TcfConsent.GENERIC_VENDOR_ID +import static org.prebid.server.functional.util.privacy.TcfConsent.PurposeId.BASIC_ADS + +@PBSTest +class GdprAuctionSpec extends PrivacyBaseSpec { + + def setupSpec(){ + cacheVendorList() + } + + @PendingFeature + def "PBS should add debug log for auction request when valid gdpr was passed"() { + given: "Default gdpr BidRequest" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .addVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + + def bidRequest = getGdprBidRequest(validConsentString) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain debug log" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.tcf?.gdpr == "1" + privacy.originPrivacy?.tcf?.tcfConsentString == validConsentString as String + !privacy.originPrivacy?.tcf?.tcfConsentVersion + !privacy.originPrivacy?.tcf?.inEea + privacy.resolvedPrivacy?.tcf?.gdpr == "1" + privacy.resolvedPrivacy?.tcf?.tcfConsentString == validConsentString as String + privacy.resolvedPrivacy?.tcf?.tcfConsentVersion == 2 + !privacy.resolvedPrivacy?.tcf?.inEea + + !privacy.originPrivacy?.ccpa?.usPrivacy + !privacy.resolvedPrivacy?.ccpa?.usPrivacy + + !privacy.originPrivacy?.coppa?.coppa + privacy.resolvedPrivacy?.coppa?.coppa == 0 + + privacy.privacyActionsPerBidder[BidderName.GENERIC] == + ["Geolocation was masked in request to bidder according to TCF policy."] + + privacy.errors?.isEmpty() + } + } + + @PendingFeature + def "PBS should add debug log for auction request when invalid gdpr was passed"() { + given: "Default gdpr BidRequest" + def invalidConsentString = new BogusConsent() + def bidRequest = getGdprBidRequest(invalidConsentString) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should not contain ext.errors" + assert !response.ext?.errors + + and: "Response should contain debug log with error" + assert response.ext?.debug?.privacy + def privacy = response.ext?.debug?.privacy + + verifyAll { + privacy.originPrivacy?.tcf?.gdpr == "1" + privacy.originPrivacy?.tcf?.tcfConsentString == invalidConsentString as String + !privacy.originPrivacy?.tcf?.tcfConsentVersion + !privacy.originPrivacy?.tcf?.inEea + privacy.resolvedPrivacy?.tcf?.gdpr == "1" + privacy.resolvedPrivacy?.tcf?.tcfConsentString == invalidConsentString as String + privacy.resolvedPrivacy?.tcf?.tcfConsentVersion == 2 + !privacy.resolvedPrivacy?.tcf?.inEea + + privacy.privacyActionsPerBidder[BidderName.GENERIC].isEmpty() + + privacy.errors == ["Placeholder: invalid consent string"] + } + } + + @Unroll + def "PBS should apply gdpr when privacy.gdpr.channel-enabled.app or privacy.gdpr.enabled = true in account config"() { + given: "Default basic generic BidRequest" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .addVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + def bidRequest = getGdprBidRequest(APP, validConsentString) + + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(gdpr: gdprConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: bidRequest.app.publisher.id, config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain masked values" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + assert bidderRequests.device?.geo == maskGeo(bidRequest) + + where: + gdprConfig << [new AccountGdprConfig(enabled: false, enabledForRequestType: [(APP): true]), + new AccountGdprConfig(enabled: true)] + } + + @Unroll + def "PBS should apply gdpr when privacy.gdpr.channel-enabled.web or privacy.gdpr.enabled = true in account config"() { + given: "Default basic generic BidRequest" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .addVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + def bidRequest = getGdprBidRequest(validConsentString) + + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(gdpr: gdprConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain masked values" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + assert bidderRequests.device?.geo == maskGeo(bidRequest) + + where: + gdprConfig << [new AccountGdprConfig(enabled: true), + new AccountGdprConfig(enabled: false, enabledForRequestType: [(WEB): true])] + } + + @Unroll + def "PBS should not apply gdpr when privacy.gdpr.channel-enabled.app or privacy.gdpr.enabled = false in account config"() { + given: "Default basic generic BidRequest" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .addVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + def bidRequest = getGdprBidRequest(APP, validConsentString) + + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(gdpr: gdprConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: bidRequest.app.publisher.id, config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain not masked values" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + assert bidderRequests.device?.geo?.lat == bidRequest.device.geo.lat + assert bidderRequests.device?.geo?.lon == bidRequest.device.geo.lon + + where: + gdprConfig << [new AccountGdprConfig(enabled: true, enabledForRequestType: [(APP): false]), + new AccountGdprConfig(enabled: false)] + } + + @Unroll + def "PBS should not apply gdpr when privacy.gdpr.channel-enabled.web or privacy.gdpr.enabled = false in account config"() { + given: "Default basic generic BidRequest" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .addVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + def bidRequest = getGdprBidRequest(validConsentString) + + and: "Save account config into DB" + def privacy = new AccountPrivacyConfig(gdpr: gdprConfig) + def accountConfig = new AccountConfig(privacy: privacy) + def account = new Account(uuid: bidRequest.site.publisher.id, config: accountConfig) + accountDao.save(account) + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain not masked values" + def bidderRequests = bidder.getBidderRequest(bidRequest.id) + assert bidderRequests.device?.geo?.lat == bidRequest.device.geo.lat + assert bidderRequests.device?.geo?.lon == bidRequest.device.geo.lon + + where: + gdprConfig << [new AccountGdprConfig(enabled: true, enabledForRequestType: [(WEB): false]), + new AccountGdprConfig(enabled: false)] + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy index d02bc0cd040..117a8308455 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.tests.privacy +import org.prebid.server.functional.model.ChannelType import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Device @@ -11,20 +12,31 @@ import org.prebid.server.functional.testcontainers.PBSTest import org.prebid.server.functional.tests.BaseSpec import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.ConsentString +import org.prebid.server.functional.util.privacy.TcfConsent + +import static org.prebid.server.functional.model.ChannelType.WEB +import static org.prebid.server.functional.util.privacy.TcfConsent.GENERIC_VENDOR_ID +import static org.prebid.server.functional.util.privacy.TcfConsent.PurposeId.BASIC_ADS @PBSTest abstract class PrivacyBaseSpec extends BaseSpec { private static final int GEO_PRECISION = 2 - protected static BidRequest getBidRequestWithGeo() { - BidRequest.defaultBidRequest.tap { + protected static BidRequest getBidRequestWithGeo(ChannelType channelType = WEB) { + BidRequest.getDefaultBidRequest(channelType).tap { + device = new Device(geo: new Geo(lat: PBSUtils.getFractionalRandomNumber(0, 90), lon: PBSUtils.getFractionalRandomNumber(0, 90))) + } + } + + protected static BidRequest getStoredRequestWithGeo() { + BidRequest.defaultStoredRequest.tap { device = new Device(geo: new Geo(lat: PBSUtils.getFractionalRandomNumber(0, 90), lon: PBSUtils.getFractionalRandomNumber(0, 90))) } } - protected static BidRequest getCcpaBidRequest(ConsentString consentString) { - bidRequestWithGeo.tap { + protected static BidRequest getCcpaBidRequest(ChannelType channelType = WEB, ConsentString consentString) { + getBidRequestWithGeo(channelType).tap { regs.ext = new RegsExt(usPrivacy: consentString) } } @@ -36,8 +48,8 @@ abstract class PrivacyBaseSpec extends BaseSpec { } } - protected static BidRequest getGdprBidRequest(ConsentString consentString) { - bidRequestWithGeo.tap { + protected static BidRequest getGdprBidRequest(ChannelType channelType = WEB, ConsentString consentString) { + getBidRequestWithGeo(channelType).tap { regs.ext = new RegsExt(gdpr: 1) user = new User(ext: new UserExt(consent: consentString)) } @@ -57,4 +69,27 @@ abstract class PrivacyBaseSpec extends BaseSpec { geo.lon = PBSUtils.getRoundedFractionalNumber(bidRequest.device.geo.lon, precision) geo } + + protected static void cacheVendorList() { + def waitTime = 1000 + def count = 0 + while (count < 10) { + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .addVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + def bidRequest = getGdprBidRequest(validConsentString) + + defaultPbsService.sendAuctionRequest(bidRequest) + + if (defaultPbsService.sendCollectedMetricsRequest()["privacy.tcf.v2.vendorlist.missing"] == 0) { + break + } + Thread.sleep(waitTime) + count++ + } + if (count == 10) { + throw new IllegalStateException("Vendor list isn't loaded in more than ${count*waitTime} seconds") + } + } } diff --git a/src/test/groovy/org/prebid/server/functional/util/privacy/TcfConsent.groovy b/src/test/groovy/org/prebid/server/functional/util/privacy/TcfConsent.groovy index 5eb0bc4bc51..1ee57da710c 100644 --- a/src/test/groovy/org/prebid/server/functional/util/privacy/TcfConsent.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/privacy/TcfConsent.groovy @@ -9,6 +9,8 @@ class TcfConsent implements ConsentString { private static final String VENDOR_LIST_URL = "https://vendor-list.consensu.org/v2/vendor-list.json" private static final Integer VENDOR_LIST_VERSION = vendorListVersion + public static final Integer RUBICON_VENDOR_ID = 52 + public static final Integer GENERIC_VENDOR_ID = RUBICON_VENDOR_ID private final TCStringEncoder.Builder tcStringEncoder @@ -72,6 +74,11 @@ class TcfConsent implements ConsentString { this } + Builder addVendorLegitimateInterest(List vendorLegitimateInterest) { + tcStringEncoder.addVendorLegitimateInterest(BitSetIntIterable.from(vendorLegitimateInterest)) + this + } + Builder setPurposesLITransparency(PurposeId purposesLITransparency) { tcStringEncoder.addPurposesLITransparency(purposesLITransparency.value) this