diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy index 657e0fef1e5..89a3273011a 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy @@ -31,7 +31,6 @@ import org.prebid.server.functional.util.privacy.gpp.data.UsUtahSensitiveData import java.time.Instant -import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.config.DataActivity.CONSENT import static org.prebid.server.functional.model.config.DataActivity.NOTICE_NOT_PROVIDED @@ -483,8 +482,11 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { ] } - def "PBS cookie sync call when privacy module contain invalid GPP string should exclude bidders URLs"() { - given: "Cookie sync request with link to account" + def "PBS cookie sync call when privacy module contain invalid GPP segment should respond with required bidder URL and emit error log"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Cookie sync request with link to account" def accountId = PBSUtils.randomString def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { it.gppSid = US_NAT_V1.value @@ -506,14 +508,68 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) + and: "Flush metrics" + flushMetrics(activityPbsService) + when: "PBS processes cookie sync request" def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) - then: "Response should not contain any URLs for bidders" - assert !response.bidderStatus.userSync.url + then: "Response should contain bidders userSync.urls" + assert response.getBidderUserSync(GENERIC).userSync.url + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(cookieSyncRequest, SYNC_USER)] == 1 + assert metrics[ALERT_GENERAL.value] == 1 - and: "Response should not contain any warning" + and: "Response shouldn't contain warnings" assert !response.warnings + + and: "Logs should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "UsNat privacy module creation failed: Unable to decode UsNatCoreSegment " + + "'${INVALID_GPP_SEGMENT}'. Activity: SYNC_USER. Section: ${US_NAT_V1.value}. Gpp: $INVALID_GPP_STRING").size() == 1 + } + + def "PBS cookie sync call when privacy module contain invalid GPP string should respond with required bidder URL and emit warning in response"() { + given: "Cookie sync request with link to account" + def accountId = PBSUtils.randomString + def invalidGpp = PBSUtils.randomString + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = US_NAT_V1.value + it.account = accountId + it.gpp = invalidGpp + } + + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with cookie sync and privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + when: "PBS processes cookie sync request" + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should contain bidders userSync.urls" + assert response.getBidderUserSync(GENERIC).userSync.url + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(cookieSyncRequest, SYNC_USER)] == 1 + + and: "Should add a warning when in debug mode" + assert response.warnings == ["GPP string invalid: Unable to decode '$invalidGpp'"] } def "PBS cookie sync call when request have different gpp consent but match and rejecting should exclude bidders URLs"() { @@ -613,14 +669,27 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) + and: "Flush metrics" + flushMetrics(activityPbsService) + when: "PBS processes cookie sync request" def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) then: "Response should contain bidders userSync.urls" assert response.getBidderUserSync(GENERIC).userSync.url + and: "Response shouldn't contain warnings" + assert !response.warnings + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(cookieSyncRequest, SYNC_USER)] == 1 + + and: "General alert metric shouldn't be updated" + !metrics[ALERT_GENERAL.getValue()] + where: - regsGpp << ["", new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] + regsGpp << [null, "", new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] } def "PBS cookie sync call when privacy regulation have duplicate should include proper responded with bidders URLs"() { @@ -776,7 +845,10 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { } def "PBS cookie sync call when custom privacy regulation empty and normalize is disabled should respond with an error and update metric"() { - given: "Generic BidRequest with gpp and account setup" + given: "Test start time" + def startTime = Instant.now() + + and: "Generic BidRequest with gpp and account setup" def gppConsent = new UsNatV1Consent.Builder().setGpc(true).build() def accountId = PBSUtils.randomNumber as String def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { @@ -807,17 +879,18 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { accountDao.save(account) when: "PBS processes auction requests" - activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) - then: "Response should contain error" - def error = thrown(PrebidServerException) - assert error.statusCode == BAD_REQUEST.code() - assert error.responseBody == "Invalid account configuration: JsonLogic exception: " + - "objects must have exactly 1 key defined, found 0" + then: "Response should contain bidders userSync.urls" + assert response.getBidderUserSync(GENERIC).userSync.url and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() assert metrics[ALERT_GENERAL.getValue()] == 1 + + and: "Logs should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, 'USCustomLogic creation failed: objects must have exactly 1 key defined, found 0').size() == 1 } def "PBS cookie sync when custom privacy regulation with normalizing should exclude bidders URLs"() { @@ -1359,8 +1432,11 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { ] } - def "PBS setuid request when privacy module contain invalid GPP string should reject bidders with status code invalidStatusCode"() { - given: "Cookie sync SetuidRequest with accountId" + def "PBS setuid request when privacy module contain invalid GPP segment should respond with valid bidders UIDs cookies"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Cookie sync SetuidRequest with accountId" def accountId = PBSUtils.randomString def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { it.account = accountId @@ -1385,13 +1461,66 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) + and: "Flush metrics" + flushMetrics(activityPbsService) + when: "PBS processes cookie sync request" - activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) + def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) - then: "Request should fail with error" - def exception = thrown(PrebidServerException) - assert exception.statusCode == INVALID_STATUS_CODE - assert exception.responseBody == INVALID_STATUS_MESSAGE + then: "Response should contain uids cookie" + assert response.uidsCookie + assert response.responseBody + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(setuidRequest, SYNC_USER)] == 1 + assert metrics[ALERT_GENERAL.value] == 1 + + and: "Logs should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "UsNat privacy module creation failed: Unable to decode UsNatCoreSegment " + + "'${INVALID_GPP_SEGMENT}'. Activity: SYNC_USER. Section: ${US_NAT_V1.value}. Gpp: $INVALID_GPP_STRING").size() == 1 + } + + def "PBS setuid request when privacy module contain invalid GPP string should respond with valid bidders UIDs cookies"() { + given: "Cookie sync SetuidRequest with accountId" + def accountId = PBSUtils.randomString + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = accountId + it.gppSid = US_NAT_V1.value + it.gpp = PBSUtils.randomString + } + + and: "UIDS Cookie" + def uidsCookie = UidsCookie.defaultUidsCookie + + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with cookie sync and allow activities setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + when: "PBS processes cookie sync request" + def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Response should contain uids cookie" + assert response.uidsCookie + assert response.responseBody + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(setuidRequest, SYNC_USER)] == 1 } def "PBS setuid request when request have different gpp consent but match and rejecting should reject bidders with status code invalidStatusCode"() { @@ -1502,6 +1631,9 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) + and: "Flush metrics" + flushMetrics(activityPbsService) + when: "PBS processes cookie sync request" def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) @@ -1509,8 +1641,15 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { assert response.uidsCookie assert response.responseBody + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(setuidRequest, SYNC_USER)] == 1 + + and: "General alert metric shouldn't be updated" + !metrics[ALERT_GENERAL.getValue()] + where: - regsGpp << ["", new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] + regsGpp << [null, "", new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] } def "PBS setuid request when privacy regulation have duplicate should respond with valid bidders UIDs cookies"() { @@ -1680,8 +1819,11 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { new EqualityValueRule(PERSONAL_DATA_CONSENTS, NOTICE_NOT_PROVIDED)] } - def "PBS setuid call when custom privacy regulation empty and normalize is disabled should respond with an error and update metric"() { - given: "Cookie sync SetuidRequest with accountId" + def "PBS setuid call when custom privacy regulation empty and normalize is disabled should respond with required UIDs cookies"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Cookie sync SetuidRequest with accountId" def accountId = PBSUtils.randomString def gppConsent = new UsNatV1Consent.Builder().setGpc(true).build() def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { @@ -1715,17 +1857,18 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { accountDao.save(account) when: "PBS processes setuid request" - activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) + def response = activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) - then: "Response should contain error" - def error = thrown(PrebidServerException) - assert error.statusCode == BAD_REQUEST.code() - assert error.responseBody == "Invalid account configuration: JsonLogic exception: " + - "objects must have exactly 1 key defined, found 0" + then: "Response should contain uids cookie" + assert response.responseBody and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() assert metrics[ALERT_GENERAL.getValue()] == 1 + + and: "Logs should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, 'USCustomLogic creation failed: objects must have exactly 1 key defined, found 0').size() == 1 } def "PBS setuid call when custom privacy regulation with normalizing should reject bidders with status code invalidStatusCode"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy index edd720700ca..76cdff173dc 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy @@ -29,7 +29,6 @@ import org.prebid.server.functional.util.privacy.gpp.data.UsUtahSensitiveData import java.time.Instant -import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED import static org.prebid.server.functional.model.config.DataActivity.CONSENT import static org.prebid.server.functional.model.config.DataActivity.NOTICE_NOT_PROVIDED @@ -194,7 +193,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { when: "PBS processes auction requests" activityPbsService.sendAuctionRequest(bidRequest) - then: "Response should contain error" + then: "Logs should contain error" def logs = activityPbsService.getLogsByTime(startTime) assert getLogsByText(logs, "Activity configuration for account ${accountId} " + "contains conditional rule with empty array").size() == 1 @@ -843,8 +842,11 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { ] } - def "PBS auction call when privacy module contain invalid GPP string should remove EIDS fields in request"() { - given: "Default Generic BidRequests with EIDS fields and account id" + def "PBS auction call when privacy module contain invalid GPP segment shouldn't remove EIDS fields in request and emit error log"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Default Generic BidRequests with EIDS fields and account id" def accountId = PBSUtils.randomNumber as String def bidRequest = getBidRequestWithPersonalData(accountId).tap { regs.gppSid = [US_NAT_V1.intValue] @@ -865,21 +867,78 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) + and: "Flush metrics" + flushMetrics(activityPbsService) + when: "PBS processes auction requests" def response = activityPbsService.sendAuctionRequest(bidRequest) - then: "Generic bidder request should have empty EIDS fields" + then: "Generic bidder request should have data in EIDS fields" def genericBidderRequest = bidder.getBidderRequest(bidRequest.id) - verifyAll { - !genericBidderRequest.user.eids - !genericBidderRequest.user?.ext?.eids - } + assert genericBidderRequest.user.eids[0].source == bidRequest.user.eids[0].source and: "Response should not contain any warnings" assert !response.ext.warnings and: "Response should not contain any errors" assert !response.ext.errors + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(bidRequest, TRANSMIT_EIDS)] == 1 + assert metrics[ACCOUNT_PROCESSED_RULES_COUNT.getValue(bidRequest, TRANSMIT_EIDS)] == 1 + assert metrics[ALERT_GENERAL.value] == 1 + + and: "Logs should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "UsNat privacy module creation failed: Unable to decode UsNatCoreSegment " + + "'${INVALID_GPP_SEGMENT}'. Activity: TRANSMIT_EIDS. Section: ${US_NAT_V1.value}. Gpp: $INVALID_GPP_STRING").size() == 1 + } + + def "PBS auction call when privacy module contain invalid GPP string shouldn't remove EIDS fields in request and emit warning in response"() { + given: "Default Generic BidRequests with EIDS fields and account id" + def accountId = PBSUtils.randomNumber as String + def invalidGpp = PBSUtils.randomString + def bidRequest = getBidRequestWithPersonalData(accountId).tap { + regs.gppSid = [US_NAT_V1.intValue] + regs.gpp = invalidGpp + } + + and: "Activities set for transmitEIDS with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_EIDS, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + when: "PBS processes auction requests" + def response = activityPbsService.sendAuctionRequest(bidRequest) + + then: "Generic bidder request should have data in EIDS fields" + def genericBidderRequest = bidder.getBidderRequest(bidRequest.id) + assert genericBidderRequest.user.eids[0].source == bidRequest.user.eids[0].source + + and: "Should add a warning when in debug mode" + assert response.ext.warnings[PREBID]?.code == [999] + assert response.ext.warnings[PREBID]?.message == ["GPP string invalid: Unable to decode '$invalidGpp'"] + + and: "Response should not contain any errors" + assert !response.ext.errors + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(bidRequest, TRANSMIT_EIDS)] == 1 + assert metrics[ACCOUNT_PROCESSED_RULES_COUNT.getValue(bidRequest, TRANSMIT_EIDS)] == 1 } def "PBS auction call when request have different gpp consent but match and rejecting should remove EIDS fields in request"() { @@ -979,15 +1038,32 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) + and: "Flush metrics" + flushMetrics(activityPbsService) + when: "PBS processes auction requests" - activityPbsService.sendAuctionRequest(bidRequest) + def response = activityPbsService.sendAuctionRequest(bidRequest) then: "Generic bidder request should have data in EIDS fields" def genericBidderRequest = bidder.getBidderRequest(bidRequest.id) assert genericBidderRequest.user.eids[0].source == bidRequest.user.eids[0].source + and: "Response shouldn't contain errors" + assert !response.ext.errors + + and: "Response shouldn't contain warnings" + assert !response.ext.warnings + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(bidRequest, TRANSMIT_EIDS)] == 1 + assert metrics[ACCOUNT_PROCESSED_RULES_COUNT.getValue(bidRequest, TRANSMIT_EIDS)] == 1 + + and: "General alert metric shouldn't be updated" + !metrics[ALERT_GENERAL.getValue()] + where: - regsGpp << ["", new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] + regsGpp << [null, "", new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] } def "PBS auction call when privacy regulation have duplicate should leave EIDS fields in request and update alerts metrics"() { @@ -1144,8 +1220,11 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { new EqualityValueRule(PERSONAL_DATA_CONSENTS, NOTICE_NOT_PROVIDED)] } - def "PBS auction call when custom privacy regulation empty and normalize is disabled should respond with an error and update metric"() { - given: "Generic BidRequest with gpp and account setup" + def "PBS auction call when custom privacy regulation empty and normalize is disabled should leave EIDS fields in request and emit error log"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Generic BidRequest with gpp and account setup" def gppConsent = new UsNatV1Consent.Builder().setGpc(true).build() def accountId = PBSUtils.randomNumber as String def bidRequest = getBidRequestWithPersonalData(accountId).tap { @@ -1176,16 +1255,25 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { accountDao.save(account) when: "PBS processes auction requests" - activityPbsService.sendAuctionRequest(bidRequest) + def response = activityPbsService.sendAuctionRequest(bidRequest) - then: "Response should contain error" - def error = thrown(PrebidServerException) - assert error.statusCode == BAD_REQUEST.code() - assert error.responseBody == "JsonLogic exception: objects must have exactly 1 key defined, found 0" + then: "Response should not contain any warnings" + assert !response.ext.warnings + + and: "Response should not contain any errors" + assert !response.ext.errors and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() assert metrics[ALERT_GENERAL.getValue()] == 1 + + and: "Generic bidder request should have data in EIDS fields" + def genericBidderRequest = bidder.getBidderRequest(bidRequest.id) + assert genericBidderRequest.user.eids[0].source == bidRequest.user.eids[0].source + + and: "Logs should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "USCustomLogic creation failed: objects must have exactly 1 key defined, found 0").size() == 1 } def "PBS auction call when custom privacy regulation with normalizing that match custom config should have empty EIDS fields"() { @@ -1304,7 +1392,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1340,7 +1428,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1381,7 +1469,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1418,7 +1506,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1438,7 +1526,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { when: "PBS processes amp request" activityPbsService.sendAmpRequest(ampRequest) - then: "Response should contain error" + then: "Logs should contain error" def logs = activityPbsService.getLogsByTime(startTime) assert getLogsByText(logs, "Activity configuration for account ${accountId} " + "contains conditional rule with empty array").size() == 1 @@ -1456,7 +1544,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1490,7 +1578,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1529,7 +1617,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { it.regs.ext = new RegsExt(gpc: null) } - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1575,7 +1663,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1617,7 +1705,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -1660,7 +1748,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -1825,7 +1913,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -1870,12 +1958,15 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { ] } - def "PBS amp call when privacy module contain invalid GPP string should remove EIDS fields in request"() { - given: "Default Generic BidRequest with EIDS fields field and account id" + def "PBS amp call when privacy module contain invalid GPP segment shouldn't remove EIDS fields in request and emit error log"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Default Generic BidRequest with EIDS fields field and account id" def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -1901,22 +1992,85 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) storedRequestDao.save(storedRequest) + and: "Flush metrics" + flushMetrics(activityPbsService) + when: "PBS processes amp request" def response = activityPbsService.sendAmpRequest(ampRequest) - then: "Generic bidder request should have empty EIDS fields" + then: "Generic bidder request should have data in EIDS fields" def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) - verifyAll { - !genericBidderRequest.user.eids - !genericBidderRequest.user?.ext?.eids - } + assert genericBidderRequest.user.eids[0].source == ampStoredRequest.user.eids[0].source + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(ampStoredRequest, TRANSMIT_EIDS)] == 1 + assert metrics[ALERT_GENERAL.value] == 1 and: "Response should not contain any warnings" assert !response.ext.warnings - and: "Response should contain amp error" - assert response.ext?.errors[PREBID]*.code == [999] - assert response.ext?.errors[PREBID]*.message == ["Amp request parameter consent_string has invalid format: $INVALID_GPP_STRING"] + and: "Response should contain consent_string errors" + assert response.ext.errors[PREBID].message == ["Amp request parameter consent_string has invalid format: $INVALID_GPP_STRING"] + + "Response should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "UsNat privacy module creation failed: Unable to decode UsNatCoreSegment " + + "'${INVALID_GPP_SEGMENT}'. Activity: TRANSMIT_EIDS. Section: ${US_NAT_V1.value}. Gpp: $INVALID_GPP_STRING").size() == 1 + } + + def "PBS amp call when privacy module contain invalid GPP string shouldn't remove EIDS fields in request and emit warning in response"() { + given: "Default Generic BidRequest with EIDS fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = getBidRequestWithPersonalData(accountId) + + and: "Default amp request with link to account" + def invalidGpp = PBSUtils.randomString + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = US_NAT_V1.value + it.consentString = invalidGpp + it.consentType = GPP + } + + and: "Activities set for transmitEIDS with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_EIDS, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + when: "PBS processes amp request" + def response = activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have data in EIDS fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + assert genericBidderRequest.user.eids == ampStoredRequest.user.eids + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(ampStoredRequest, TRANSMIT_EIDS)] == 1 + + and: "Should add a warning when in debug mode" + assert response.ext.warnings[PREBID]?.code == [999] + assert response.ext.warnings[PREBID]?.message == ["GPP string invalid: Unable to decode '$invalidGpp'"] + + and: "Response should contain consent_string errors" + assert response.ext.errors[PREBID].message == ["Amp request parameter consent_string has invalid format: $invalidGpp"] } def "PBS amp call when request have different gpp consent but match and rejecting should remove EIDS fields in request"() { @@ -1924,7 +2078,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = gppSid.value @@ -1975,7 +2129,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2012,12 +2166,70 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { ] } + def "PBS amp call when regs.gpp empty in request should leave EIDS fields in request"() { + given: "Default Generic BidRequest with EIDS fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = getBidRequestWithPersonalData(accountId) + + and: "Default amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = US_NAT_V1.value + it.consentString = regsGpp + it.consentType = GPP + } + + and: "Activities set for transmitEIDS with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_EIDS, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + when: "PBS processes amp request" + def response = activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have data in EIDS fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + assert genericBidderRequest.user.eids == ampStoredRequest.user.eids + + and: "Response shouldn't contain errors" + assert !response.ext.errors + + and: "Response shouldn't contain warnings" + assert !response.ext.warnings + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(ampStoredRequest, TRANSMIT_EIDS)] == 1 + + and: "General alert metric shouldn't be updated" + !metrics[ALERT_GENERAL.getValue()] + + where: + regsGpp << [null, ""] + } + def "PBS amp call when regs.gpp in request is allowing should leave EIDS fields in request"() { given: "Default Generic BidRequest with EIDS fields field and account id" def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2044,14 +2256,20 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { storedRequestDao.save(storedRequest) when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) + def response = activityPbsService.sendAmpRequest(ampRequest) then: "Generic bidder request should have data in EIDS fields" def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) - assert genericBidderRequest.user.eids[0].source == ampStoredRequest.user.eids[0].source + assert genericBidderRequest.user.eids == ampStoredRequest.user.eids + + and: "Response shouldn't contain warnings" + assert !response.ext.warnings + + and: "Response should contain consent_string errors" + assert response.ext.errors[PREBID].message == ["Amp request parameter consent_string has invalid format: $regsGpp"] where: - regsGpp << ["", new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] + regsGpp << [new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] } def "PBS amp call when privacy regulation have duplicate should leave EIDS fields in request and update alerts metrics"() { @@ -2059,7 +2277,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2105,7 +2323,7 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2245,8 +2463,11 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { new EqualityValueRule(PERSONAL_DATA_CONSENTS, NOTICE_NOT_PROVIDED)] } - def "PBS amp call when custom privacy regulation empty and normalize is disabled should respond with an error and update metric"() { - given: "Store bid request with link for account" + def "PBS amp call when custom privacy regulation empty and normalize is disabled should leave EIDS fields in request and emit error log"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Store bid request with link for account" def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) @@ -2285,17 +2506,25 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { storedRequestDao.save(storedRequest) when: "PBS processes amp requests" - activityPbsService.sendAmpRequest(ampRequest) + def response = activityPbsService.sendAmpRequest(ampRequest) - then: "Response should contain error" - def error = thrown(PrebidServerException) - assert error.statusCode == BAD_REQUEST.code() - assert error.responseBody == "Invalid account configuration: JsonLogic exception: " + - "objects must have exactly 1 key defined, found 0" + then: "Response should not contain any warnings" + assert !response.ext.warnings + + and: "Response should contain consent_string error" + assert response.ext.errors[PREBID].message == ["Amp request parameter consent_string has invalid format: $gppConsent"] and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() assert metrics[ALERT_GENERAL.getValue()] == 1 + + and: "Generic bidder request should have data in EIDS fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + assert genericBidderRequest.user.eids[0].source == ampStoredRequest.user.eids[0].source + + and: "Logs should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "USCustomLogic creation failed: objects must have exactly 1 key defined, found 0").size() == 1 } def "PBS amp call when custom privacy regulation with normalizing should change request consent and call to bidder"() { diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy index 0eaafecc6f0..9608e479a7c 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy @@ -33,7 +33,6 @@ import org.prebid.server.functional.util.privacy.gpp.data.UsUtahSensitiveData import java.time.Instant -import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED import static org.prebid.server.functional.model.config.DataActivity.CONSENT import static org.prebid.server.functional.model.config.DataActivity.NOTICE_NOT_PROVIDED @@ -247,7 +246,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { when: "PBS processes auction requests" activityPbsService.sendAuctionRequest(bidRequest) - then: "Response should contain error" + then: "Logs should contain error" def logs = activityPbsService.getLogsByTime(startTime) assert getLogsByText(logs, "Activity configuration for account ${accountId} " + "contains conditional rule with empty array").size() == 1 @@ -1109,8 +1108,11 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { ] } - def "PBS auction call when privacy module contain invalid GPP string should remove UFPD fields in request"() { - given: "Default Generic BidRequests with UFPD fields and account id" + def "PBS auction call when privacy module contain invalid GPP segment shouldn't remove UFPD fields in request and emit error log"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Default Generic BidRequests with UFPD fields and account id" def accountId = PBSUtils.randomNumber as String def bidRequest = getBidRequestWithPersonalData(accountId).tap { regs.gppSid = [US_NAT_V1.intValue] @@ -1131,25 +1133,30 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) + and: "Flush metrics" + flushMetrics(activityPbsService) + when: "PBS processes auction requests" - def response= activityPbsService.sendAuctionRequest(bidRequest) + def response = activityPbsService.sendAuctionRequest(bidRequest) - then: "Generic bidder request should have empty UFPD fields" + then: "Generic bidder request should have data in UFPD fields" def bidderRequest = bidder.getBidderRequest(bidRequest.id) + verifyAll { - !bidderRequest.device.didsha1 - !bidderRequest.device.didmd5 - !bidderRequest.device.dpidsha1 - !bidderRequest.device.ifa - !bidderRequest.device.macsha1 - !bidderRequest.device.macmd5 - !bidderRequest.device.dpidmd5 - !bidderRequest.user.id - !bidderRequest.user.buyeruid - !bidderRequest.user.yob - !bidderRequest.user.gender - !bidderRequest.user.data - !bidderRequest.user.ext + bidderRequest.device.didsha1 == bidRequest.device.didsha1 + bidderRequest.device.didmd5 == bidRequest.device.didmd5 + bidderRequest.device.dpidsha1 == bidRequest.device.dpidsha1 + bidderRequest.device.ifa == bidRequest.device.ifa + bidderRequest.device.macsha1 == bidRequest.device.macsha1 + bidderRequest.device.macmd5 == bidRequest.device.macmd5 + bidderRequest.device.dpidmd5 == bidRequest.device.dpidmd5 + bidderRequest.user.id == bidRequest.user.id + bidderRequest.user.buyeruid == bidRequest.user.buyeruid + bidderRequest.user.yob == bidRequest.user.yob + bidderRequest.user.gender == bidRequest.user.gender + bidderRequest.user.data == bidRequest.user.data + bidderRequest.user.geo.zip == bidRequest.user.geo.zip + bidderRequest.user.ext.data.buyeruid == bidRequest.user.ext.data.buyeruid } and: "Generic bidder request should have data in EIDS fields" @@ -1160,6 +1167,82 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { and: "Response should not contain any errors" assert !response.ext.errors + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(bidRequest, TRANSMIT_UFPD)] == 1 + assert metrics[ACCOUNT_PROCESSED_RULES_COUNT.getValue(bidRequest, TRANSMIT_UFPD)] == 1 + assert metrics[ALERT_GENERAL.value] == 1 + + and: "Logs should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "UsNat privacy module creation failed: Unable to decode UsNatCoreSegment " + + "'${INVALID_GPP_SEGMENT}'. Activity: TRANSMIT_UFPD. Section: ${US_NAT_V1.value}. Gpp: $INVALID_GPP_STRING").size() == 1 + } + + def "PBS auction call when privacy module contain invalid GPP string shouldn't remove UFPD fields in request and emit warning in response"() { + given: "Default Generic BidRequests with UFPD fields and account id" + def accountId = PBSUtils.randomNumber as String + def invalidGpp = PBSUtils.randomString + def bidRequest = getBidRequestWithPersonalData(accountId).tap { + regs.gppSid = [US_NAT_V1.intValue] + regs.gpp = invalidGpp + } + + and: "Activities set for transmitUfpd with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + when: "PBS processes auction requests" + def response = activityPbsService.sendAuctionRequest(bidRequest) + + then: "Generic bidder request should have data in UFPD fields" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + + verifyAll { + bidderRequest.device.didsha1 == bidRequest.device.didsha1 + bidderRequest.device.didmd5 == bidRequest.device.didmd5 + bidderRequest.device.dpidsha1 == bidRequest.device.dpidsha1 + bidderRequest.device.ifa == bidRequest.device.ifa + bidderRequest.device.macsha1 == bidRequest.device.macsha1 + bidderRequest.device.macmd5 == bidRequest.device.macmd5 + bidderRequest.device.dpidmd5 == bidRequest.device.dpidmd5 + bidderRequest.user.id == bidRequest.user.id + bidderRequest.user.buyeruid == bidRequest.user.buyeruid + bidderRequest.user.yob == bidRequest.user.yob + bidderRequest.user.gender == bidRequest.user.gender + bidderRequest.user.data == bidRequest.user.data + bidderRequest.user.geo.zip == bidRequest.user.geo.zip + bidderRequest.user.ext.data.buyeruid == bidRequest.user.ext.data.buyeruid + } + + and: "Generic bidder request should have data in EIDS fields" + assert bidderRequest.user.eids == bidRequest.user.eids + + and: "Should add a warning when in debug mode" + assert response.ext.warnings[PREBID]?.code == [999] + assert response.ext.warnings[PREBID]?.message == ["GPP string invalid: Unable to decode '$invalidGpp'"] + + and: "Response should not contain any errors" + assert !response.ext.errors + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(bidRequest, TRANSMIT_UFPD)] == 1 + assert metrics[ACCOUNT_PROCESSED_RULES_COUNT.getValue(bidRequest, TRANSMIT_UFPD)] == 1 } def "PBS auction call when request have different gpp consent but match and rejecting should remove UFPD fields in request"() { @@ -1294,8 +1377,11 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) accountDao.save(account) + and: "Flush metrics" + flushMetrics(activityPbsService) + when: "PBS processes auction requests" - activityPbsService.sendAuctionRequest(bidRequest) + def response = activityPbsService.sendAuctionRequest(bidRequest) then: "Generic bidder request should have data in UFPD fields" def bidderRequest = bidder.getBidderRequest(bidRequest.id) @@ -1321,8 +1407,22 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { and: "Generic bidder request should have data in EIDS fields" assert bidderRequest.user.eids == bidRequest.user.eids + and: "Response shouldn't contain errors" + assert !response.ext.errors + + and: "Response shouldn't contain warnings" + assert !response.ext.warnings + + and: "Metrics for disallowed activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(bidRequest, TRANSMIT_UFPD)] == 1 + assert metrics[ACCOUNT_PROCESSED_RULES_COUNT.getValue(bidRequest, TRANSMIT_UFPD)] == 1 + + and: "General alert metric shouldn't be updated" + !metrics[ALERT_GENERAL.getValue()] + where: - regsGpp << ["", new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] + regsGpp << [null, "", new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] } def "PBS auction call when privacy regulation have duplicate should leave UFPD fields in request and update alerts metrics"() { @@ -1532,8 +1632,11 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { new EqualityValueRule(PERSONAL_DATA_CONSENTS, NOTICE_NOT_PROVIDED)] } - def "PBS auction call when custom privacy regulation empty and normalize is disabled should respond with an error and update metric"() { - given: "Generic BidRequest with gpp and account setup" + def "PBS auction call when custom privacy regulation empty and normalize is disabled should leave UFPD fields in request and emit error log"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Generic BidRequest with gpp and account setup" def gppConsent = new UsNatV1Consent.Builder().setGpc(true).build() def accountId = PBSUtils.randomNumber as String def bidRequest = getBidRequestWithPersonalData(accountId).tap { @@ -1564,16 +1667,45 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { accountDao.save(account) when: "PBS processes auction requests" - activityPbsService.sendAuctionRequest(bidRequest) + def response = activityPbsService.sendAuctionRequest(bidRequest) - then: "Response should contain error" - def error = thrown(PrebidServerException) - assert error.statusCode == BAD_REQUEST.code() - assert error.responseBody == "JsonLogic exception: objects must have exactly 1 key defined, found 0" + then: "Response should not contain any warnings" + assert !response.ext.warnings + + and: "Response should not contain any errors" + assert !response.ext.errors and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() assert metrics[ALERT_GENERAL.getValue()] == 1 + + and: "Generic bidder request should have data in UFPD fields" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + + and: "Generic bidder should be called due to positive allow in activities" + verifyAll { + bidderRequest.device.didsha1 == bidRequest.device.didsha1 + bidderRequest.device.didmd5 == bidRequest.device.didmd5 + bidderRequest.device.dpidsha1 == bidRequest.device.dpidsha1 + bidderRequest.device.ifa == bidRequest.device.ifa + bidderRequest.device.macsha1 == bidRequest.device.macsha1 + bidderRequest.device.macmd5 == bidRequest.device.macmd5 + bidderRequest.device.dpidmd5 == bidRequest.device.dpidmd5 + bidderRequest.user.id == bidRequest.user.id + bidderRequest.user.buyeruid == bidRequest.user.buyeruid + bidderRequest.user.yob == bidRequest.user.yob + bidderRequest.user.gender == bidRequest.user.gender + bidderRequest.user.data == bidRequest.user.data + bidderRequest.user.geo == bidRequest.user.geo + bidderRequest.user.ext.data.buyeruid == bidRequest.user.ext.data.buyeruid + } + + and: "Generic bidder request should have data in EIDS fields" + assert bidderRequest.user.eids == bidRequest.user.eids + + and: "Logs should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "USCustomLogic creation failed: objects must have exactly 1 key defined, found 0").size() == 1 } def "PBS auction call when custom privacy regulation with normalizing that match custom config should have empty UFPD fields"() { @@ -1707,7 +1839,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1762,7 +1894,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1818,7 +1950,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1870,7 +2002,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1890,7 +2022,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { when: "PBS processes amp request" activityPbsService.sendAmpRequest(ampRequest) - then: "Response should contain error" + then: "Logs should contain error" def logs = activityPbsService.getLogsByTime(startTime) assert getLogsByText(logs, "Activity configuration for account ${accountId} " + "contains conditional rule with empty array").size() == 1 @@ -1908,7 +2040,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -1961,7 +2093,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -2015,7 +2147,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { it.regs.ext = new RegsExt(gpc: null) } - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -2076,7 +2208,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId } @@ -2137,7 +2269,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2195,7 +2327,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2375,7 +2507,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2435,12 +2567,15 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { ] } - def "PBS amp call when privacy module contain invalid GPP string should remove UFPD fields in request"() { - given: "Default Generic BidRequest with UFPD fields field and account id" + def "PBS amp call when privacy module contain invalid GPP segment shouldn't remove UFPD fields in request and emit error log"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Default Generic BidRequest with UFPD fields field and account id" def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2466,36 +2601,123 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) storedRequestDao.save(storedRequest) + and: "Flush metrics" + flushMetrics(activityPbsService) + when: "PBS processes amp request" def response = activityPbsService.sendAmpRequest(ampRequest) - then: "Generic bidder request should have empty UFPD fields" + then: "Generic bidder request should have data in UFPD fields" def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + verifyAll { - !bidderRequest.device.didsha1 - !bidderRequest.device.didmd5 - !bidderRequest.device.dpidsha1 - !bidderRequest.device.ifa - !bidderRequest.device.macsha1 - !bidderRequest.device.macmd5 - !bidderRequest.device.dpidmd5 - !bidderRequest.user.id - !bidderRequest.user.buyeruid - !bidderRequest.user.yob - !bidderRequest.user.gender - !bidderRequest.user.data - !bidderRequest.user.ext + bidderRequest.device.didsha1 == ampStoredRequest.device.didsha1 + bidderRequest.device.didmd5 == ampStoredRequest.device.didmd5 + bidderRequest.device.dpidsha1 == ampStoredRequest.device.dpidsha1 + bidderRequest.device.ifa == ampStoredRequest.device.ifa + bidderRequest.device.macsha1 == ampStoredRequest.device.macsha1 + bidderRequest.device.macmd5 == ampStoredRequest.device.macmd5 + bidderRequest.device.dpidmd5 == ampStoredRequest.device.dpidmd5 + bidderRequest.user.id == ampStoredRequest.user.id + bidderRequest.user.buyeruid == ampStoredRequest.user.buyeruid + bidderRequest.user.yob == ampStoredRequest.user.yob + bidderRequest.user.gender == ampStoredRequest.user.gender + bidderRequest.user.data == ampStoredRequest.user.data + bidderRequest.user.geo == ampStoredRequest.user.geo + bidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid } and: "Generic bidder request should have data in EIDS fields" assert bidderRequest.user.eids == ampStoredRequest.user.eids + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(ampStoredRequest, TRANSMIT_UFPD)] == 1 + assert metrics[ALERT_GENERAL.value] == 1 + and: "Response should not contain any warnings" assert !response.ext.warnings - and: "Response should contain amp error" - assert response.ext?.errors[PREBID]*.code == [999] - assert response.ext?.errors[PREBID]*.message == ["Amp request parameter consent_string has invalid format: $INVALID_GPP_STRING"] + and: "Response should contain consent_string errors" + assert response.ext.errors[PREBID].message == ["Amp request parameter consent_string has invalid format: $INVALID_GPP_STRING"] + + "Response should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "UsNat privacy module creation failed: Unable to decode UsNatCoreSegment " + + "'${INVALID_GPP_SEGMENT}'. Activity: TRANSMIT_UFPD. Section: ${US_NAT_V1.value}. Gpp: $INVALID_GPP_STRING").size() == 1 + } + + def "PBS amp call when privacy module contain invalid GPP string shouldn't remove UFPD fields in request and emit warning in response"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = getBidRequestWithPersonalData(accountId) + + and: "Default amp request with link to account" + def invalidGpp = PBSUtils.randomString + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = US_NAT_V1.value + it.consentString = invalidGpp + it.consentType = GPP + } + + and: "Activities set for transmitUfpd with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + when: "PBS processes amp request" + def response = activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have data in UFPD fields" + def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequest.device.didsha1 == ampStoredRequest.device.didsha1 + bidderRequest.device.didmd5 == ampStoredRequest.device.didmd5 + bidderRequest.device.dpidsha1 == ampStoredRequest.device.dpidsha1 + bidderRequest.device.ifa == ampStoredRequest.device.ifa + bidderRequest.device.macsha1 == ampStoredRequest.device.macsha1 + bidderRequest.device.macmd5 == ampStoredRequest.device.macmd5 + bidderRequest.device.dpidmd5 == ampStoredRequest.device.dpidmd5 + bidderRequest.user.id == ampStoredRequest.user.id + bidderRequest.user.buyeruid == ampStoredRequest.user.buyeruid + bidderRequest.user.yob == ampStoredRequest.user.yob + bidderRequest.user.gender == ampStoredRequest.user.gender + bidderRequest.user.data == ampStoredRequest.user.data + bidderRequest.user.geo == ampStoredRequest.user.geo + bidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid + } + + and: "Generic bidder request should have data in EIDS fields" + assert bidderRequest.user.eids == ampStoredRequest.user.eids + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(ampStoredRequest, TRANSMIT_UFPD)] == 1 + + and: "Should add a warning when in debug mode" + assert response.ext.warnings[PREBID]?.code == [999] + assert response.ext.warnings[PREBID]?.message == ["GPP string invalid: Unable to decode '$invalidGpp'"] + + and: "Response should contain consent_string errors" + assert response.ext.errors[PREBID].message == ["Amp request parameter consent_string has invalid format: $invalidGpp"] } def "PBS amp call when request have different gpp consent but match and rejecting should remove UFPD fields in request"() { @@ -2503,7 +2725,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = gppSid.value @@ -2569,7 +2791,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2625,12 +2847,89 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { ] } + def "PBS amp call when regs.gpp empty in request should leave UFPD fields in request"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = getBidRequestWithPersonalData(accountId) + + and: "Default amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = US_NAT_V1.value + it.consentString = regsGpp + it.consentType = GPP + } + + and: "Activities set for transmitUfpd with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Flush metrics" + flushMetrics(activityPbsService) + + when: "PBS processes amp request" + def response = activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have data in UFPD fields" + def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + + verifyAll { + bidderRequest.device.didsha1 == ampStoredRequest.device.didsha1 + bidderRequest.device.didmd5 == ampStoredRequest.device.didmd5 + bidderRequest.device.dpidsha1 == ampStoredRequest.device.dpidsha1 + bidderRequest.device.ifa == ampStoredRequest.device.ifa + bidderRequest.device.macsha1 == ampStoredRequest.device.macsha1 + bidderRequest.device.macmd5 == ampStoredRequest.device.macmd5 + bidderRequest.device.dpidmd5 == ampStoredRequest.device.dpidmd5 + bidderRequest.user.id == ampStoredRequest.user.id + bidderRequest.user.buyeruid == ampStoredRequest.user.buyeruid + bidderRequest.user.yob == ampStoredRequest.user.yob + bidderRequest.user.gender == ampStoredRequest.user.gender + bidderRequest.user.data == ampStoredRequest.user.data + bidderRequest.user.geo == ampStoredRequest.user.geo + bidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid + } + + and: "Generic bidder request should have data in EIDS fields" + assert bidderRequest.user.eids == ampStoredRequest.user.eids + + and: "Response shouldn't contain errors" + assert !response.ext.errors + + and: "Response shouldn't contain warnings" + assert !response.ext.warnings + + and: "Metrics processed across activities should be updated" + def metrics = activityPbsService.sendCollectedMetricsRequest() + assert metrics[PROCESSED_ACTIVITY_RULES_COUNT.getValue(ampStoredRequest, TRANSMIT_UFPD)] == 1 + + and: "General alert metric shouldn't be updated" + !metrics[ALERT_GENERAL.getValue()] + + where: + regsGpp << [null, ""] + } + def "PBS amp call when regs.gpp in request is allowing should leave UFPD fields in request"() { given: "Default Generic BidRequest with UFPD fields field and account id" def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2657,7 +2956,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { storedRequestDao.save(storedRequest) when: "PBS processes amp request" - activityPbsService.sendAmpRequest(ampRequest) + def response = activityPbsService.sendAmpRequest(ampRequest) then: "Generic bidder request should have data in UFPD fields" def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) @@ -2682,8 +2981,14 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { and: "Generic bidder request should have data in EIDS fields" assert bidderRequest.user.eids == ampStoredRequest.user.eids + and: "Response shouldn't contain warnings" + assert !response.ext.warnings + + and: "Response should contain consent_string errors" + assert response.ext.errors[PREBID].message == ["Amp request parameter consent_string has invalid format: $regsGpp"] + where: - regsGpp << ["", new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] + regsGpp << [new UsNatV1Consent.Builder().build(), new UsNatV1Consent.Builder().setGpc(false).build()] } def "PBS amp call when privacy regulation have duplicate should leave UFPD fields in request and update alerts metrics"() { @@ -2691,7 +2996,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2756,7 +3061,7 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) - and: "amp request with link to account" + and: "Default amp request with link to account" def ampRequest = AmpRequest.defaultAmpRequest.tap { it.account = accountId it.gppSid = US_NAT_V1.value @@ -2929,8 +3234,11 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { new EqualityValueRule(PERSONAL_DATA_CONSENTS, NOTICE_NOT_PROVIDED)] } - def "PBS amp call when custom privacy regulation empty and normalize is disabled should respond with an error and update metric"() { - given: "Store bid request with link for account" + def "PBS amp call when custom privacy regulation empty and normalize is disabled should leave UFPD fields in request and emit error log"() { + given: "Test start time" + def startTime = Instant.now() + + and: "Store bid request with link for account" def accountId = PBSUtils.randomNumber as String def ampStoredRequest = getBidRequestWithPersonalData(accountId) @@ -2969,17 +3277,43 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { storedRequestDao.save(storedRequest) when: "PBS processes amp requests" - activityPbsService.sendAmpRequest(ampRequest) + def response = activityPbsService.sendAmpRequest(ampRequest) - then: "Response should contain error" - def error = thrown(PrebidServerException) - assert error.statusCode == BAD_REQUEST.code() - assert error.responseBody == "Invalid account configuration: JsonLogic exception: " + - "objects must have exactly 1 key defined, found 0" + then: "Response should not contain any warnings" + assert !response.ext.warnings + + and: "Response should contain consent_string error" + assert response.ext.errors[PREBID].message == ["Amp request parameter consent_string has invalid format: $gppConsent"] and: "Metrics for disallowed activities should be updated" def metrics = activityPbsService.sendCollectedMetricsRequest() assert metrics[ALERT_GENERAL.getValue()] == 1 + + and: "Generic bidder request should have data in UFPD fields" + def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + verifyAll { + bidderRequest.device.didsha1 == ampStoredRequest.device.didsha1 + bidderRequest.device.didmd5 == ampStoredRequest.device.didmd5 + bidderRequest.device.dpidsha1 == ampStoredRequest.device.dpidsha1 + bidderRequest.device.ifa == ampStoredRequest.device.ifa + bidderRequest.device.macsha1 == ampStoredRequest.device.macsha1 + bidderRequest.device.macmd5 == ampStoredRequest.device.macmd5 + bidderRequest.device.dpidmd5 == ampStoredRequest.device.dpidmd5 + bidderRequest.user.id == ampStoredRequest.user.id + bidderRequest.user.buyeruid == ampStoredRequest.user.buyeruid + bidderRequest.user.yob == ampStoredRequest.user.yob + bidderRequest.user.gender == ampStoredRequest.user.gender + bidderRequest.user.data == ampStoredRequest.user.data + bidderRequest.user.geo == ampStoredRequest.user.geo + bidderRequest.user.ext.data.buyeruid == ampStoredRequest.user.ext.data.buyeruid + } + + and: "Generic bidder request should have data in EIDS fields" + assert bidderRequest.user.eids == ampStoredRequest.user.eids + + and: "Logs should contain error" + def logs = activityPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, "USCustomLogic creation failed: objects must have exactly 1 key defined, found 0").size() == 1 } def "PBS amp call when custom privacy regulation with normalizing should change request consent and call to bidder"() { 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 aff17c4d49b..e16dc69dd62 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 @@ -95,7 +95,8 @@ abstract class PrivacyBaseSpec extends BaseSpec { private static final Map GDPR_EEA_COUNTRY = ["gdpr.eea-countries": "$BULGARIA.ISOAlpha2, SK, VK" as String] protected static final String VENDOR_LIST_PATH = "/app/prebid-server/data/vendorlist-v{VendorVersion}/{VendorVersion}.json" - protected static final String INVALID_GPP_STRING = "DBABLA~BVQqAAAAAg.YA" // TODO replace BVQqAAAAAg with ${PBSUtils.getRandomString(7)} when proper fix is ready + protected static final String INVALID_GPP_SEGMENT = PBSUtils.getRandomString(7) + protected static final String INVALID_GPP_STRING = "DBABLA~${INVALID_GPP_SEGMENT}.YA" protected static final String VALID_VALUE_FOR_GPC_HEADER = "1" protected static final GppConsent SIMPLE_GPC_DISALLOW_LOGIC = new UsNatV1Consent.Builder().setGpc(true).build() protected static final VendorList vendorListResponse = new VendorList(networkServiceContainer)