diff --git a/packages/analytics-js-integrations/__tests__/integrations/GA4/utils.test.js b/packages/analytics-js-integrations/__tests__/integrations/GA4/utils.test.js index 6813d2e716..88638c9de0 100644 --- a/packages/analytics-js-integrations/__tests__/integrations/GA4/utils.test.js +++ b/packages/analytics-js-integrations/__tests__/integrations/GA4/utils.test.js @@ -10,6 +10,7 @@ import { isReservedEventName, getCustomParameters, formatAndValidateEventName, + prepareStandardEventParams, } from '../../../src/integrations/GA4/utils'; import { @@ -338,22 +339,26 @@ describe('Google Analytics 4 utilities tests', () => { describe('filterUserTraits function tests', () => { it('Should update mentioned PII keys value to null', () => { - const piiPropertiesToIgnore = [{ piiProperty: 'email' }, { piiProperty: 'phone' }, { piiProperty: 'card_number' }]; + const piiPropertiesToIgnore = [ + { piiProperty: 'email' }, + { piiProperty: 'phone' }, + { piiProperty: 'card_number' }, + ]; const userTraits = { name: 'SDK Test', email: 'sdk@test.com', card_number: '123456', phone: '123456789', - country: 'usa' - } + country: 'usa', + }; const filteredUserTraits = { name: 'SDK Test', country: 'usa', email: null, card_number: null, - phone: null + phone: null, }; const result = filterUserTraits(piiPropertiesToIgnore, userTraits); @@ -361,20 +366,194 @@ describe('Google Analytics 4 utilities tests', () => { }); it('Should override values of pii fields to null', () => { - const piiPropertiesToIgnore = [{ piiProperty: 'email' }, { piiProperty: 'name' }, { piiProperty: 'isPaid' }, { piiProperty: undefined }, { piiProperty: {} }]; + const piiPropertiesToIgnore = [ + { piiProperty: 'email' }, + { piiProperty: 'name' }, + { piiProperty: 'isPaid' }, + { piiProperty: undefined }, + { piiProperty: {} }, + ]; const userTraits = { name: 'SDK Test', email: 'sdk@test.com', - isPaid: false - } + isPaid: false, + }; const result = filterUserTraits(piiPropertiesToIgnore, userTraits); expect(result).toEqual({ name: null, email: null, - isPaid: null + isPaid: null, }); }); }); }); + +describe('prepareStandardEventParams function tests', () => { + it('Should not fail when values are 0', () => { + const eventConfig = { + event: 'UserSignup', + mapping: [ + { sourceKeys: ['properties.total'], destKey: 'order_total', required: true }, + { sourceKeys: ['properties.value'], destKey: 'order_value', required: true }, + { sourceKeys: ['properties.revenue'], destKey: 'order_revenue', required: true }, + { sourceKeys: ['properties.price'], destKey: 'order_price', required: true }, + ], + }; + const message = { + properties: { + total: 0, // Total is 0 but should pass without failure + value: 0, // Value is 0 but should pass without failure + revenue: 0, // Revenue is 0 but should pass without failure + price: 0, // Price is 0 but should pass without failure + }, + event: 'UserSignup', + }; + const result = prepareStandardEventParams(message, eventConfig); + expect(result).toEqual({ + order_total: 0, + order_value: 0, + order_revenue: 0, + order_price: 0, + }); + }); + + it('Should handle mixed types (string, number, array, boolean) in the same payload', () => { + const eventConfig = { + event: 'UserSignup', + mapping: [ + { sourceKeys: ['properties.total'], destKey: 'order_total', required: true }, + { sourceKeys: ['properties.value'], destKey: 'order_value', required: true }, + { sourceKeys: ['properties.revenue'], destKey: 'order_revenue', required: true }, + { sourceKeys: ['properties.price'], destKey: 'order_price', required: true }, + ], + }; + const message = { + properties: { + total: '100', // String + value: 0, // Number + revenue: [50, 60], // Array + price: true, // Boolean + }, + event: 'UserSignup', + }; + const result = prepareStandardEventParams(message, eventConfig); + expect(result).toEqual({ + order_total: '100', + order_value: 0, + order_revenue: [50, 60], + order_price: true, + }); + }); + + it('should handle undefined and null values appropriately', () => { + const eventConfig = { + event: 'UserSignup', + mapping: [ + { sourceKeys: ['properties.total'], destKey: 'order_total', required: true }, + { sourceKeys: ['properties.optional'], destKey: 'optional_field', required: false }, + ], + }; + + const message = { + properties: { + total: 0, + optional: undefined, + }, + event: 'UserSignup', + }; + + const result = prepareStandardEventParams(message, eventConfig); + expect(result).toEqual({ + order_total: 0, + }); + }); + + it('should handle negative values correctly', () => { + const eventConfig = { + event: 'Refund', + mapping: [{ sourceKeys: ['properties.amount'], destKey: 'refund_amount', required: true }], + }; + + const message = { + properties: { + amount: -50, + }, + event: 'Refund', + }; + + const result = prepareStandardEventParams(message, eventConfig); + expect(result).toEqual({ + refund_amount: -50, + }); + }); + + it('Should correctly handle a mix of data types (string, number, array, boolean) and metadata', () => { + const eventConfig = { + event: 'UserSignup', + mapping: [ + { sourceKeys: ['properties.total'], destKey: 'order_total', required: true }, + { sourceKeys: ['properties.value'], destKey: 'order_value', required: true }, + { sourceKeys: ['properties.revenue'], destKey: 'order_revenue', required: true }, + { sourceKeys: ['properties.price'], destKey: 'order_price', required: true }, + { sourceKeys: ['properties.isActive'], destKey: 'user_isActive', required: true }, + { sourceKeys: ['properties.items'], destKey: 'order_items', required: false }, // Optional field + ], + }; + + const message = { + properties: { + total: 0, // Number + value: '200', // String + revenue: [300, 400], // Array + price: 12, // Number + isActive: false, // Boolean + items: ['item1', 'item2'], // Array (optional) + }, + event: 'UserSignup', + }; + + const metadata = { + order_total: 0, + order_value: 0, + order_revenue: 0, + order_price: 12, + user_isActive: true, + order_items: ['item3', 'item4'], + }; + + const result = prepareStandardEventParams(message, eventConfig, metadata); + + // Using toEqual to check if the result matches the expected object + expect(result).toEqual({ + order_total: 0, + order_value: '200', + order_revenue: [300, 400], + order_price: 12, + user_isActive: false, + order_items: ['item1', 'item2'], + }); + }); + + it('should return null when required parameters are missing', () => { + const eventConfig = { + event: 'Purchase', + mapping: [ + { sourceKeys: ['properties.total'], destKey: 'order_total', required: true }, + { sourceKeys: ['properties.currency'], destKey: 'currency', required: true } + ], + }; + + const message = { + properties: { + // total is missing + currency: 'USD' + }, + event: 'Purchase', + }; + + const result = prepareStandardEventParams(message, eventConfig); + expect(result).toBeNull(); + }); +}); diff --git a/packages/analytics-js-integrations/src/integrations/GA4/utils.js b/packages/analytics-js-integrations/src/integrations/GA4/utils.js index 2bb93c4688..6097aaf818 100644 --- a/packages/analytics-js-integrations/src/integrations/GA4/utils.js +++ b/packages/analytics-js-integrations/src/integrations/GA4/utils.js @@ -8,7 +8,7 @@ import { customParametersExclusion, rootLevelProductsSupportedEventsList, } from './config'; -import { isBlank, flattenJson } from '../../utils/commonUtils'; +import { isBlank, flattenJson, isDefinedAndNotNull } from '../../utils/commonUtils'; import { constructPayload, extractCustomFields } from '../../utils/utils'; const logger = new Logger(DISPLAY_NAME); @@ -297,7 +297,7 @@ const prepareStandardEventParams = (message, eventConfig) => { // Validation for required params if (Array.isArray(mapping) && mapping.length > 0) { const hasMissingRequiredValue = mapping.some(mappingItem => { - if (!payload[mappingItem.destKey] && mappingItem.required) { + if (!isDefinedAndNotNull(payload[mappingItem.destKey]) && mappingItem.required) { logger.error(`Missing required value from ${JSON.stringify(mappingItem.sourceKeys)}`); return true; } @@ -370,5 +370,6 @@ export { getCustomParameters, removeInvalidParams, prepareParamsAndEventName, + prepareStandardEventParams, formatAndValidateEventName, };