diff --git a/src/v0/destinations/ga4/utils.test.js b/src/v0/destinations/ga4/utils.test.js index 18b3ab5766..041cb0d524 100644 --- a/src/v0/destinations/ga4/utils.test.js +++ b/src/v0/destinations/ga4/utils.test.js @@ -1,4 +1,9 @@ -const { validateEventName, prepareUserProperties, removeInvalidParams } = require('./utils'); +const { + validateEventName, + removeInvalidParams, + prepareUserConsents, + prepareUserProperties, +} = require('./utils'); const userPropertyData = [ { @@ -447,4 +452,84 @@ describe('Google Analytics 4 utils test', () => { expect(result).toEqual(expected); }); }); + + describe('prepareUserConsents function tests', () => { + it('Should return an empty object when no consents are given', () => { + const message = {}; + const result = prepareUserConsents(message); + expect(result).toEqual({}); + }); + + it('Should return an empty object when no consents are given', () => { + const message = { + integrations: { + GA4: {}, + }, + }; + const result = prepareUserConsents(message); + expect(result).toEqual({}); + }); + + it('Should return an empty object when no consents are given', () => { + const message = { + integrations: { + GA4: { + consents: {}, + }, + }, + }; + const result = prepareUserConsents(message); + expect(result).toEqual({}); + }); + + it('Should return a consents object when consents are given', () => { + const message = { + integrations: { + GA4: { + consents: { + personalizationConsent: 'GRANTED', + userDataConsent: 'GRANTED', + }, + }, + }, + }; + const result = prepareUserConsents(message); + expect(result).toEqual({ + ad_personalization: 'GRANTED', + ad_user_data: 'GRANTED', + }); + }); + + it('Should return an empty object when invalid consents are given', () => { + const message = { + integrations: { + GA4: { + consents: { + personalizationConsent: 'NOT_SPECIFIED', + userDataConsent: 'NOT_SPECIFIED', + }, + }, + }, + }; + const result = prepareUserConsents(message); + expect(result).toEqual({}); + }); + + it('Should return a valid consents values from consents object', () => { + const message = { + integrations: { + GA4: { + consents: { + personalizationConsent: 'NOT_SPECIFIED', + userDataConsent: 'DENIED', + }, + }, + }, + }; + const result = prepareUserConsents(message); + expect(result).toEqual({ + ad_user_data: 'DENIED', + }); + }); + }); }); diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js index 76b186e0a4..912eb167e2 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -83,6 +83,12 @@ const finaliseConsent = (consentConfigMap, eventLevelConsent = {}, destConfig = return consentObj; }; +/** + * Populates the consent object based on the provided configuration and consent mapping. + * @param {*} consentConfigMap + * @param {*} eventLevelConsent + * @returns + */ const finaliseAnalyticsConsents = (consentConfigMap, eventLevelConsent = {}) => { const consentObj = {}; // Iterate through each key in consentConfigMap to set the consent @@ -92,12 +98,14 @@ const finaliseAnalyticsConsents = (consentConfigMap, eventLevelConsent = {}) => // Set consent only if valid if ( eventLevelConsent && - eventLevelConsent.hasOwnProperty(consentKey) && - GA4_ALLOWED_CONSENT_STATUS.includes(eventLevelConsent[consentKey]) + eventLevelConsent.hasOwnProperty(configKey) && + GA4_ALLOWED_CONSENT_STATUS.includes(eventLevelConsent[configKey]) ) { - consentObj[consentKey] = eventLevelConsent[consentKey]; + consentObj[consentKey] = eventLevelConsent[configKey]; } }); + + return consentObj; }; module.exports = { diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js index 28e0fa9ac8..8ab77afedd 100644 --- a/src/v0/util/googleUtils/index.test.js +++ b/src/v0/util/googleUtils/index.test.js @@ -1,4 +1,8 @@ -const { populateConsentFromConfig, finaliseConsent } = require('./index'); +const { + finaliseConsent, + populateConsentFromConfig, + finaliseAnalyticsConsents, +} = require('./index'); describe('unit test for populateConsentFromConfig', () => { const consentConfigMap = { @@ -243,3 +247,49 @@ describe('finaliseConsent', () => { }); }); }); + +describe('unit test for finaliseAnalyticsConsents', () => { + const consentConfigMap = { + personalizationConsent: 'ad_personalization', + userDataConsent: 'ad_user_data', + }; + it('Should return an empty object when no valid consents are provided', () => { + const result = finaliseAnalyticsConsents(consentConfigMap, {}); + expect(result).toEqual({}); + }); + + it('Should set ad_user_data property of consent object when userDataConsent property is provided and its value is one of the allowed consent statuses', () => { + const properties = { userDataConsent: 'GRANTED' }; + const result = finaliseAnalyticsConsents(consentConfigMap, properties); + expect(result).toEqual({ ad_user_data: 'GRANTED' }); + }); + + it('Should set ad_personalization property of consent object when personalizationConsent property is provided and its value is one of the allowed consent statuses', () => { + const properties = { personalizationConsent: 'DENIED' }; + const result = finaliseAnalyticsConsents(consentConfigMap, properties); + expect(result).toEqual({ ad_personalization: 'DENIED' }); + }); + + it('Should return an empty object when properties parameter is not provided', () => { + const result = finaliseAnalyticsConsents(consentConfigMap, undefined); + expect(result).toEqual({}); + }); + + it('Should return an empty object when properties parameter is null', () => { + const result = finaliseAnalyticsConsents(consentConfigMap, null); + expect(result).toEqual({}); + }); + + it('Should return an empty object when properties parameter is an UNSPECIFIED object', () => { + const result = finaliseAnalyticsConsents(consentConfigMap, {}); + expect(result).toEqual({}); + }); + + it('should return empty object when properties parameter contains ad_user_data and ad_personalization with non-allowed values', () => { + const result = finaliseAnalyticsConsents(consentConfigMap, { + userDataConsent: 'RANDOM', + personalizationConsent: 'RANDOM', + }); + expect(result).toEqual({}); + }); +}); diff --git a/test/integrations/destinations/ga4/processor/data.ts b/test/integrations/destinations/ga4/processor/data.ts index f96ca9e74a..ae0e5def29 100644 --- a/test/integrations/destinations/ga4/processor/data.ts +++ b/test/integrations/destinations/ga4/processor/data.ts @@ -14900,4 +14900,313 @@ export const data = [ }, mockFns: defaultMockFns, }, + { + name: 'ga4', + description: '(gtag) send consents setting to ga4 with login event', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be', + originalTimestamp: '2022-04-26T05:17:09Z', + anonymousId: 'ea5cfab2-3961-4d8a-8187-3d1858c99090', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + device: { + adTrackingEnabled: 'false', + advertisingId: 'T0T0T072-5e28-45a1-9eda-ce22a3e36d1a', + id: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', + manufacturer: 'Google', + model: 'AOSP on IA Emulator', + name: 'generic_x86_arm', + type: 'ios', + attTrackingStatus: 3, + }, + ip: '0.0.0.0', + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + locale: 'en-US', + os: { + name: 'iOS', + version: '14.4.1', + }, + screen: { + density: 2, + }, + externalId: [ + { + type: 'ga4AppInstanceId', + id: 'dummyGA4AppInstanceId', + }, + { + type: 'ga4ClientId', + id: 'client_id', + }, + ], + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36', + }, + type: 'track', + event: 'login', + properties: { + method: 'Google', + }, + integrations: { + All: true, + GA4: { + consents: { + personalizationConsent: 'GRANTED', + userDataConsent: 'GRANTED', + }, + }, + }, + sentAt: '2022-04-20T15:20:57Z', + }, + destination: { + Config: { + apiSecret: 'dummyApiSecret', + measurementId: 'G-123456', + firebaseAppId: '', + blockPageViewEvent: false, + typesOfClient: 'gtag', + extendPageViewParams: false, + sendUserId: false, + eventFilteringOption: 'disable', + blacklistedEvents: [ + { + eventName: '', + }, + ], + whitelistedEvents: [ + { + eventName: '', + }, + ], + enableServerSideIdentify: false, + sendLoginSignup: false, + generateLead: false, + }, + Enabled: true, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://www.google-analytics.com/mp/collect', + headers: { + HOST: 'www.google-analytics.com', + 'Content-Type': 'application/json', + }, + params: { + api_secret: 'dummyApiSecret', + measurement_id: 'G-123456', + }, + body: { + JSON: { + client_id: 'client_id', + consent: { + ad_personalization: 'GRANTED', + ad_user_data: 'GRANTED', + }, + timestamp_micros: 1650950229000000, + non_personalized_ads: true, + events: [ + { + name: 'login', + params: { + method: 'Google', + engagement_time_msec: 1, + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + mockFns: defaultMockFns, + }, + { + name: 'ga4', + description: '(gtag) send consents setting to ga4 with login event', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be', + originalTimestamp: '2022-04-26T05:17:09Z', + anonymousId: 'ea5cfab2-3961-4d8a-8187-3d1858c99090', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + device: { + adTrackingEnabled: 'false', + advertisingId: 'T0T0T072-5e28-45a1-9eda-ce22a3e36d1a', + id: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', + manufacturer: 'Google', + model: 'AOSP on IA Emulator', + name: 'generic_x86_arm', + type: 'ios', + attTrackingStatus: 3, + }, + ip: '0.0.0.0', + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + locale: 'en-US', + os: { + name: 'iOS', + version: '14.4.1', + }, + screen: { + density: 2, + }, + externalId: [ + { + type: 'ga4AppInstanceId', + id: 'dummyGA4AppInstanceId', + }, + { + type: 'ga4ClientId', + id: 'client_id', + }, + ], + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36', + }, + type: 'track', + event: 'login', + properties: { + method: 'Google', + }, + integrations: { + All: true, + GA4: { + consents: { + personalizationConsent: 'NOT_SPECIFIED', + userDataConsent: 'DENIED', + }, + }, + }, + sentAt: '2022-04-20T15:20:57Z', + }, + destination: { + Config: { + apiSecret: 'dummyApiSecret', + measurementId: 'G-123456', + firebaseAppId: '', + blockPageViewEvent: false, + typesOfClient: 'gtag', + extendPageViewParams: false, + sendUserId: false, + eventFilteringOption: 'disable', + blacklistedEvents: [ + { + eventName: '', + }, + ], + whitelistedEvents: [ + { + eventName: '', + }, + ], + enableServerSideIdentify: false, + sendLoginSignup: false, + generateLead: false, + }, + Enabled: true, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://www.google-analytics.com/mp/collect', + headers: { + HOST: 'www.google-analytics.com', + 'Content-Type': 'application/json', + }, + params: { + api_secret: 'dummyApiSecret', + measurement_id: 'G-123456', + }, + body: { + JSON: { + client_id: 'client_id', + consent: { + ad_user_data: 'DENIED', + }, + timestamp_micros: 1650950229000000, + non_personalized_ads: true, + events: [ + { + name: 'login', + params: { + method: 'Google', + engagement_time_msec: 1, + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + mockFns: defaultMockFns, + }, ];