diff --git a/src/v0/destinations/adobe_analytics/transform.js b/src/v0/destinations/adobe_analytics/transform.js index b428138724..5d3d6e7d00 100644 --- a/src/v0/destinations/adobe_analytics/transform.js +++ b/src/v0/destinations/adobe_analytics/transform.js @@ -18,6 +18,7 @@ const { getIntegrationsObj, removeUndefinedAndNullValues, simpleProcessRouterDest, + validateEventAndLowerCaseConversion, } = require('../../util'); const { @@ -307,7 +308,7 @@ const processTrackEvent = (message, adobeEventName, destinationConfig, extras = destinationConfig; const { event: rawMessageEvent, properties } = message; const { overrideEventString, overrideProductString } = properties; - const event = rawMessageEvent.toLowerCase(); + const event = validateEventAndLowerCaseConversion(rawMessageEvent, true, true); const adobeEventArr = adobeEventName ? adobeEventName.split(',') : []; // adobeEventArr is an array of events which is defined as // ["eventName", "mapped Adobe Event=mapped merchproperty's value", "mapped Adobe Event=mapped merchproperty's value", . . .] diff --git a/src/v0/destinations/facebook_conversions/utils.js b/src/v0/destinations/facebook_conversions/utils.js index 26204ec61a..c6e3993e33 100644 --- a/src/v0/destinations/facebook_conversions/utils.js +++ b/src/v0/destinations/facebook_conversions/utils.js @@ -93,28 +93,26 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego ); const contentCategory = eventTypeCustomData.content_category; - let contentType; + let defaultContentType; if (contentIds.length > 0) { - contentType = 'product'; + defaultContentType = 'product'; } else if (contentCategory) { contentIds.push(contentCategory); contents.push({ id: contentCategory, quantity: 1, }); - contentType = 'product_group'; + defaultContentType = 'product_group'; } + const contentType = + message.properties?.content_type || + getContentType(message, defaultContentType, categoryToContent, DESTINATION.toLowerCase()); eventTypeCustomData = { ...eventTypeCustomData, content_ids: contentIds, contents, - content_type: getContentType( - message, - contentType, - categoryToContent, - DESTINATION.toLowerCase(), - ), + content_type: contentType, content_category: getContentCategory(contentCategory), }; break; @@ -125,18 +123,20 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego case 'payment info entered': case 'product added to wishlist': { const contentCategory = eventTypeCustomData.content_category; - const contentType = eventTypeCustomData.content_type; + const contentType = + message.properties?.content_type || + getContentType( + message, + eventTypeCustomData.content_type, + categoryToContent, + DESTINATION.toLowerCase(), + ); const { contentIds, contents } = populateContentsAndContentIDs([message.properties]); eventTypeCustomData = { ...eventTypeCustomData, content_ids: contentIds, contents, - content_type: getContentType( - message, - contentType, - categoryToContent, - DESTINATION.toLowerCase(), - ), + content_type: contentType, content_category: getContentCategory(contentCategory), }; validateProductSearchedData(eventTypeCustomData); @@ -151,18 +151,20 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego ); const contentCategory = eventTypeCustomData.content_category; - const contentType = eventTypeCustomData.content_type; + const contentType = + message.properties?.content_type || + getContentType( + message, + eventTypeCustomData.content_type, + categoryToContent, + DESTINATION.toLowerCase(), + ); eventTypeCustomData = { ...eventTypeCustomData, content_ids: contentIds, contents, - content_type: getContentType( - message, - contentType, - categoryToContent, - DESTINATION.toLowerCase(), - ), + content_type: contentType, content_category: getContentCategory(contentCategory), num_items: contentIds.length, }; diff --git a/src/v0/destinations/facebook_pixel/utils.js b/src/v0/destinations/facebook_pixel/utils.js index 8a63a0b0fe..cfa625ee3d 100644 --- a/src/v0/destinations/facebook_pixel/utils.js +++ b/src/v0/destinations/facebook_pixel/utils.js @@ -53,13 +53,9 @@ const getActionSource = (payload, channel) => { * Handles order completed and checkout started types of specific events */ const handleOrder = (message, categoryToContent) => { - const { products, revenue } = message.properties; - const value = formatRevenue(revenue); - - const contentType = getContentType(message, 'product', categoryToContent); - const contentIds = []; - const contents = []; const { + products, + revenue, category, quantity, price, @@ -67,6 +63,12 @@ const handleOrder = (message, categoryToContent) => { contentName, delivery_category: deliveryCategory, } = message.properties; + const value = formatRevenue(revenue); + let { content_type: contentType } = message.properties; + contentType = contentType || getContentType(message, 'product', categoryToContent); + const contentIds = []; + const contents = []; + if (products) { if (products.length > 0 && Array.isArray(products)) { products.forEach((singleProduct) => { @@ -109,10 +111,17 @@ const handleOrder = (message, categoryToContent) => { * Handles product list viewed */ const handleProductListViewed = (message, categoryToContent) => { - let contentType; + let defaultContentType; const contentIds = []; const contents = []; - const { products, category, quantity, value, contentName } = message.properties; + const { + products, + category, + quantity, + value, + contentName, + content_type: contentType, + } = message.properties; if (products && products.length > 0 && Array.isArray(products)) { products.forEach((product, index) => { if (isObject(product)) { @@ -132,7 +141,7 @@ const handleProductListViewed = (message, categoryToContent) => { } if (contentIds.length > 0) { - contentType = 'product'; + defaultContentType = 'product'; // for viewContent event content_ids and content arrays are not mandatory } else if (category) { contentIds.push(category); @@ -140,12 +149,12 @@ const handleProductListViewed = (message, categoryToContent) => { id: category, quantity: 1, }); - contentType = 'product_group'; + defaultContentType = 'product_group'; } return { content_ids: contentIds, - content_type: getContentType(message, contentType, categoryToContent), + content_type: contentType || getContentType(message, defaultContentType, categoryToContent), contents, content_category: getContentCategory(category), content_name: contentName, @@ -165,7 +174,8 @@ const handleProduct = (message, categoryToContent, valueFieldIdentifier) => { const useValue = valueFieldIdentifier === 'properties.value'; const contentId = message.properties?.product_id || message.properties?.sku || message.properties?.id; - const contentType = getContentType(message, 'product', categoryToContent); + const contentType = + message.properties?.content_type || getContentType(message, 'product', categoryToContent); const contentName = message.properties.product_name || message.properties.name || ''; const contentCategory = message.properties.category || ''; const currency = message.properties.currency || 'USD'; diff --git a/src/v0/destinations/google_adwords_remarketing_lists/transform.js b/src/v0/destinations/google_adwords_remarketing_lists/transform.js index 9526973fb8..9ab415346a 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/transform.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/transform.js @@ -218,7 +218,7 @@ const processEvent = async (metadata, message, destination) => { } Object.values(createdPayload).forEach((data) => { - const consentObj = populateConsentForGoogleDestinations(message.properties); + const consentObj = populateConsentForGoogleDestinations(destination.Config); response.push(responseBuilder(metadata, data, destination, message, consentObj)); }); return response; diff --git a/src/v0/destinations/mp/transform.js b/src/v0/destinations/mp/transform.js index 493169cd4e..10271bebef 100644 --- a/src/v0/destinations/mp/transform.js +++ b/src/v0/destinations/mp/transform.js @@ -36,6 +36,7 @@ const { groupEventsByEndpoint, batchEvents, trimTraits, + generatePageOrScreenCustomEventName, } = require('./util'); const { CommonUtils } = require('../../../util/common'); @@ -297,17 +298,25 @@ const processIdentifyEvents = async (message, type, destination) => { }; const processPageOrScreenEvents = (message, type, destination) => { + const { + token, + identityMergeApi, + useUserDefinedPageEventName, + userDefinedPageEventTemplate, + useUserDefinedScreenEventName, + userDefinedScreenEventTemplate, + } = destination.Config; const mappedProperties = constructPayload(message, mPEventPropertiesConfigJson); let properties = { ...get(message, 'context.traits'), ...message.properties, ...mappedProperties, - token: destination.Config.token, + token, distinct_id: message.userId || message.anonymousId, time: toUnixTimestampInMS(message.timestamp || message.originalTimestamp), ...buildUtmParams(message.context?.campaign), }; - if (destination.Config?.identityMergeApi === 'simplified') { + if (identityMergeApi === 'simplified') { properties = { ...properties, distinct_id: message.userId || `$device:${message.anonymousId}`, @@ -326,7 +335,18 @@ const processPageOrScreenEvents = (message, type, destination) => { properties.$browser = browser.name; properties.$browser_version = browser.version; } - const eventName = type === 'page' ? 'Loaded a Page' : 'Loaded a Screen'; + + let eventName; + if (type === 'page') { + eventName = useUserDefinedPageEventName + ? generatePageOrScreenCustomEventName(message, userDefinedPageEventTemplate) + : 'Loaded a Page'; + } else { + eventName = useUserDefinedScreenEventName + ? generatePageOrScreenCustomEventName(message, userDefinedScreenEventTemplate) + : 'Loaded a Screen'; + } + const payload = { event: eventName, properties, diff --git a/src/v0/destinations/mp/util.js b/src/v0/destinations/mp/util.js index 8e943f41dd..f56242d88b 100644 --- a/src/v0/destinations/mp/util.js +++ b/src/v0/destinations/mp/util.js @@ -1,7 +1,7 @@ const lodash = require('lodash'); const set = require('set-value'); const get = require('get-value'); -const { InstrumentationError } = require('@rudderstack/integrations-lib'); +const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib'); const { isDefined, constructPayload, @@ -16,6 +16,7 @@ const { IsGzipSupported, isObject, isDefinedAndNotNullAndNotEmpty, + isDefinedAndNotNull, } = require('../../util'); const { ConfigCategory, @@ -301,6 +302,46 @@ function trimTraits(traits, contextTraits, setOnceProperties) { }; } +/** + * Generates a custom event name for a page or screen. + * + * @param {Object} message - The message object + * @param {string} userDefinedEventTemplate - The user-defined event template to be used for generating the event name. + * @throws {ConfigurationError} If the event template is missing. + * @returns {string} The generated custom event name. + * @example + * const userDefinedEventTemplate = "Viewed {{ category }} {{ name }} Page"; + * const message = {name: 'Home', properties: {category: 'Index'}}; + * output: "Viewed Index Home Page" + */ +const generatePageOrScreenCustomEventName = (message, userDefinedEventTemplate) => { + if (!userDefinedEventTemplate) { + throw new ConfigurationError( + 'Event name template is not configured. Please provide a valid value for the `Page/Screen Event Name Template` in the destination dashboard.', + ); + } + + let eventName = userDefinedEventTemplate; + + if (isDefinedAndNotNull(message.properties?.category)) { + // Replace {{ category }} with actual values + eventName = eventName.replace(/{{\s*category\s*}}/g, message.properties.category); + } else { + // find {{ category }} surrounded by whitespace characters and replace it with a single whitespace character + eventName = eventName.replace(/\s{{\s*category\s*}}\s/g, ' '); + } + + if (isDefinedAndNotNull(message.name)) { + // Replace {{ name }} with actual values + eventName = eventName.replace(/{{\s*name\s*}}/g, message.name); + } else { + // find {{ name }} surrounded by whitespace characters and replace it with a single whitespace character + eventName = eventName.replace(/\s{{\s*name\s*}}\s/g, ' '); + } + + return eventName; +}; + module.exports = { createIdentifyResponse, isImportAuthCredentialsAvailable, @@ -309,4 +350,5 @@ module.exports = { generateBatchedPayloadForArray, batchEvents, trimTraits, + generatePageOrScreenCustomEventName, }; diff --git a/src/v0/destinations/mp/util.test.js b/src/v0/destinations/mp/util.test.js index 866119a336..40cdb34649 100644 --- a/src/v0/destinations/mp/util.test.js +++ b/src/v0/destinations/mp/util.test.js @@ -4,45 +4,88 @@ const { generateBatchedPayloadForArray, buildUtmParams, trimTraits, + generatePageOrScreenCustomEventName, } = require('./util'); const { FEATURE_GZIP_SUPPORT } = require('../../util/constant'); - -const destinationMock = { - Config: { - token: 'test_api_token', - prefixProperties: true, - useNativeSDK: false, - useOldMapping: true, - }, - DestinationDefinition: { - DisplayName: 'Mixpanel', - ID: 'test_destination_definition_id', - Name: 'MP', - }, - Enabled: true, - ID: 'test_id', - Name: 'Mixpanel', - Transformations: [], -}; +const { ConfigurationError } = require('@rudderstack/integrations-lib'); const maxBatchSizeMock = 2; -describe('Mixpanel utils test', () => { - describe('Unit test cases for groupEventsByEndpoint', () => { - it('should return an object with empty arrays for all properties when the events array is empty', () => { - const events = []; - const result = groupEventsByEndpoint(events); - expect(result).toEqual({ - engageEvents: [], - groupsEvents: [], - trackEvents: [], - importEvents: [], - batchErrorRespList: [], - }); +describe('Unit test cases for groupEventsByEndpoint', () => { + it('should return an object with empty arrays for all properties when the events array is empty', () => { + const events = []; + const result = groupEventsByEndpoint(events); + expect(result).toEqual({ + engageEvents: [], + groupsEvents: [], + trackEvents: [], + importEvents: [], + batchErrorRespList: [], }); + }); - it('should return an object with all properties containing their respective events when the events array contains events of all types', () => { - const events = [ + it('should return an object with all properties containing their respective events when the events array contains events of all types', () => { + const events = [ + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{prop:1}]', + }, + }, + userId: 'user1', + }, + }, + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{prop:2}]', + }, + }, + userId: 'user2', + }, + }, + { + message: { + endpoint: '/groups', + body: { + JSON_ARRAY: { + batch: '[{prop:3}]', + }, + }, + userId: 'user1', + }, + }, + { + message: { + endpoint: '/track', + body: { + JSON_ARRAY: { + batch: '[{prop:4}]', + }, + }, + userId: 'user1', + }, + }, + { + message: { + endpoint: '/import', + body: { + JSON_ARRAY: { + batch: '[{prop:5}]', + }, + }, + userId: 'user2', + }, + }, + { error: 'Message type abc not supported' }, + ]; + const result = groupEventsByEndpoint(events); + expect(result).toEqual({ + engageEvents: [ { message: { endpoint: '/engage', @@ -65,6 +108,8 @@ describe('Mixpanel utils test', () => { userId: 'user2', }, }, + ], + groupsEvents: [ { message: { endpoint: '/groups', @@ -76,6 +121,8 @@ describe('Mixpanel utils test', () => { userId: 'user1', }, }, + ], + trackEvents: [ { message: { endpoint: '/track', @@ -87,6 +134,8 @@ describe('Mixpanel utils test', () => { userId: 'user1', }, }, + ], + importEvents: [ { message: { endpoint: '/import', @@ -98,371 +147,359 @@ describe('Mixpanel utils test', () => { userId: 'user2', }, }, - { error: 'Message type abc not supported' }, - ]; - const result = groupEventsByEndpoint(events); - expect(result).toEqual({ - engageEvents: [ - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{prop:1}]', - }, - }, - userId: 'user1', - }, - }, - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{prop:2}]', - }, - }, - userId: 'user2', - }, - }, - ], - groupsEvents: [ - { - message: { - endpoint: '/groups', - body: { - JSON_ARRAY: { - batch: '[{prop:3}]', - }, - }, - userId: 'user1', - }, - }, - ], - trackEvents: [ - { - message: { - endpoint: '/track', - body: { - JSON_ARRAY: { - batch: '[{prop:4}]', - }, - }, - userId: 'user1', - }, - }, - ], - importEvents: [ - { - message: { - endpoint: '/import', - body: { - JSON_ARRAY: { - batch: '[{prop:5}]', - }, - }, - userId: 'user2', - }, - }, - ], - batchErrorRespList: [{ error: 'Message type abc not supported' }], - }); + ], + batchErrorRespList: [{ error: 'Message type abc not supported' }], }); }); +}); - describe('Unit test cases for batchEvents', () => { - it('should return an array of batched events with correct payload and metadata', () => { - const successRespList = [ - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{"prop":1}]', - }, +describe('Unit test cases for batchEvents', () => { + it('should return an array of batched events with correct payload and metadata', () => { + const successRespList = [ + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{"prop":1}]', }, - headers: {}, - params: {}, - userId: 'user1', }, - metadata: { jobId: 3 }, + headers: {}, + params: {}, + userId: 'user1', }, - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{"prop":2}]', - }, + metadata: { jobId: 3 }, + }, + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{"prop":2}]', }, - headers: {}, - params: {}, - userId: 'user2', }, - metadata: { jobId: 4 }, + headers: {}, + params: {}, + userId: 'user2', }, - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{"prop":3}]', - }, + metadata: { jobId: 4 }, + }, + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{"prop":3}]', }, - headers: {}, - params: {}, - userId: 'user2', }, - metadata: { jobId: 6 }, + headers: {}, + params: {}, + userId: 'user2', }, - ]; - - const result = batchEvents(successRespList, maxBatchSizeMock); - - expect(result).toEqual([ - { - batched: true, - batchedRequest: { - body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":1},{"prop":2}]' }, XML: {} }, - endpoint: '/engage', - files: {}, - headers: {}, - method: 'POST', - params: {}, - type: 'REST', - version: '1', - }, - destination: undefined, - metadata: [{ jobId: 3 }, { jobId: 4 }], - statusCode: 200, + metadata: { jobId: 6 }, + }, + ]; + + const result = batchEvents(successRespList, maxBatchSizeMock); + + expect(result).toEqual([ + { + batched: true, + batchedRequest: { + body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":1},{"prop":2}]' }, XML: {} }, + endpoint: '/engage', + files: {}, + headers: {}, + method: 'POST', + params: {}, + type: 'REST', + version: '1', }, - { - batched: true, - batchedRequest: { - body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":3}]' }, XML: {} }, - endpoint: '/engage', - files: {}, - headers: {}, - method: 'POST', - params: {}, - type: 'REST', - version: '1', - }, - destination: undefined, - metadata: [{ jobId: 6 }], - statusCode: 200, + destination: undefined, + metadata: [{ jobId: 3 }, { jobId: 4 }], + statusCode: 200, + }, + { + batched: true, + batchedRequest: { + body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":3}]' }, XML: {} }, + endpoint: '/engage', + files: {}, + headers: {}, + method: 'POST', + params: {}, + type: 'REST', + version: '1', }, - ]); - }); + destination: undefined, + metadata: [{ jobId: 6 }], + statusCode: 200, + }, + ]); + }); - it('should return an empty array when successRespList is empty', () => { - const successRespList = []; - const result = batchEvents(successRespList, maxBatchSizeMock); - expect(result).toEqual([]); - }); + it('should return an empty array when successRespList is empty', () => { + const successRespList = []; + const result = batchEvents(successRespList, maxBatchSizeMock); + expect(result).toEqual([]); }); +}); - describe('Unit test cases for generateBatchedPayloadForArray', () => { - it('should generate a batched payload with GZIP payload for /import endpoint when given an array of events', () => { - const events = [ - { - body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, - endpoint: '/import', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - { - body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, - endpoint: '/import', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - ]; - const expectedBatchedRequest = { - body: { - FORM: {}, - JSON: {}, - JSON_ARRAY: {}, - XML: {}, - GZIP: { - payload: '[{"event":"event1"},{"event":"event2"}]', - }, - }, +describe('Unit test cases for generateBatchedPayloadForArray', () => { + it('should generate a batched payload with GZIP payload for /import endpoint when given an array of events', () => { + const events = [ + { + body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, endpoint: '/import', - files: {}, headers: { 'Content-Type': 'application/json' }, - method: 'POST', params: {}, - type: 'REST', - version: '1', - }; - - const result = generateBatchedPayloadForArray(events, { - features: { [FEATURE_GZIP_SUPPORT]: true }, - }); - - expect(result).toEqual(expectedBatchedRequest); + }, + { + body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, + endpoint: '/import', + headers: { 'Content-Type': 'application/json' }, + params: {}, + }, + ]; + const expectedBatchedRequest = { + body: { + FORM: {}, + JSON: {}, + JSON_ARRAY: {}, + XML: {}, + GZIP: { + payload: '[{"event":"event1"},{"event":"event2"}]', + }, + }, + endpoint: '/import', + files: {}, + headers: { 'Content-Type': 'application/json' }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }; + + const result = generateBatchedPayloadForArray(events, { + features: { [FEATURE_GZIP_SUPPORT]: true }, }); - it('should generate a batched payload with JSON_ARRAY body when given an array of events', () => { - const events = [ - { - body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, - endpoint: '/endpoint', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - { - body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, - endpoint: '/endpoint', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - ]; - const expectedBatchedRequest = { - body: { - FORM: {}, - JSON: {}, - JSON_ARRAY: { batch: '[{"event":"event1"},{"event":"event2"}]' }, - XML: {}, - }, + expect(result).toEqual(expectedBatchedRequest); + }); + + it('should generate a batched payload with JSON_ARRAY body when given an array of events', () => { + const events = [ + { + body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, endpoint: '/endpoint', - files: {}, headers: { 'Content-Type': 'application/json' }, - method: 'POST', params: {}, - type: 'REST', - version: '1', - }; - - const result = generateBatchedPayloadForArray(events, { - features: { [FEATURE_GZIP_SUPPORT]: true }, - }); - - expect(result).toEqual(expectedBatchedRequest); + }, + { + body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, + endpoint: '/endpoint', + headers: { 'Content-Type': 'application/json' }, + params: {}, + }, + ]; + const expectedBatchedRequest = { + body: { + FORM: {}, + JSON: {}, + JSON_ARRAY: { batch: '[{"event":"event1"},{"event":"event2"}]' }, + XML: {}, + }, + endpoint: '/endpoint', + files: {}, + headers: { 'Content-Type': 'application/json' }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }; + + const result = generateBatchedPayloadForArray(events, { + features: { [FEATURE_GZIP_SUPPORT]: true }, }); + + expect(result).toEqual(expectedBatchedRequest); }); +}); - describe('Unit test cases for buildUtmParams', () => { - it('should return an empty object when campaign is undefined', () => { - const campaign = undefined; - const result = buildUtmParams(campaign); - expect(result).toEqual({}); - }); +describe('Unit test cases for buildUtmParams', () => { + it('should return an empty object when campaign is undefined', () => { + const campaign = undefined; + const result = buildUtmParams(campaign); + expect(result).toEqual({}); + }); - it('should return an empty object when campaign is an empty object', () => { - const campaign = {}; - const result = buildUtmParams(campaign); - expect(result).toEqual({}); - }); + it('should return an empty object when campaign is an empty object', () => { + const campaign = {}; + const result = buildUtmParams(campaign); + expect(result).toEqual({}); + }); - it('should return an empty object when campaign is not an object', () => { - const campaign = [{ name: 'test' }]; - const result = buildUtmParams(campaign); - expect(result).toEqual({}); - }); + it('should return an empty object when campaign is not an object', () => { + const campaign = [{ name: 'test' }]; + const result = buildUtmParams(campaign); + expect(result).toEqual({}); + }); - it('should handle campaign object with null/undefined values', () => { - const campaign = { name: null, source: 'rudder', medium: 'rudder', test: undefined }; - const result = buildUtmParams(campaign); - expect(result).toEqual({ - utm_campaign: null, - utm_source: 'rudder', - utm_medium: 'rudder', - test: undefined, - }); + it('should handle campaign object with null/undefined values', () => { + const campaign = { name: null, source: 'rudder', medium: 'rudder', test: undefined }; + const result = buildUtmParams(campaign); + expect(result).toEqual({ + utm_campaign: null, + utm_source: 'rudder', + utm_medium: 'rudder', + test: undefined, }); }); - describe('Unit test cases for trimTraits', () => { - // Given a valid traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing traits, contextTraits, and setOnce properties. - it('should return an object containing traits, contextTraits, and setOnce properties when given valid inputs', () => { - const traits = { name: 'John', age: 30 }; - const contextTraits = { email: 'john@example.com' }; - const setOnceProperties = ['name', 'email']; - - const result = trimTraits(traits, contextTraits, setOnceProperties); - - expect(result).toEqual({ - traits: { - age: 30, - }, - contextTraits: {}, - setOnce: { $name: 'John', $email: 'john@example.com' }, - }); +}); +describe('Unit test cases for trimTraits', () => { + // Given a valid traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing traits, contextTraits, and setOnce properties. + it('should return an object containing traits, contextTraits, and setOnce properties when given valid inputs', () => { + const traits = { name: 'John', age: 30 }; + const contextTraits = { email: 'john@example.com' }; + const setOnceProperties = ['name', 'email']; + + const result = trimTraits(traits, contextTraits, setOnceProperties); + + expect(result).toEqual({ + traits: { + age: 30, + }, + contextTraits: {}, + setOnce: { $name: 'John', $email: 'john@example.com' }, }); + }); - // Given an empty traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing empty traits and contextTraits, and an empty setOnce property. - it('should return an object containing empty traits and contextTraits, and an empty setOnce property when given empty traits and contextTraits objects', () => { - const traits = {}; - const contextTraits = {}; - const setOnceProperties = ['name', 'email']; + // Given an empty traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing empty traits and contextTraits, and an empty setOnce property. + it('should return an object containing empty traits and contextTraits, and an empty setOnce property when given empty traits and contextTraits objects', () => { + const traits = {}; + const contextTraits = {}; + const setOnceProperties = ['name', 'email']; - const result = trimTraits(traits, contextTraits, setOnceProperties); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: {}, - contextTraits: {}, - setOnce: {}, - }); + expect(result).toEqual({ + traits: {}, + contextTraits: {}, + setOnce: {}, }); + }); - // Given an empty setOnceProperties array, the function should return an object containing the original traits and contextTraits objects, and an empty setOnce property. - it('should return an object containing the original traits and contextTraits objects, and an empty setOnce property when given an empty setOnceProperties array', () => { - const traits = { name: 'John', age: 30 }; - const contextTraits = { email: 'john@example.com' }; - const setOnceProperties = []; + // Given an empty setOnceProperties array, the function should return an object containing the original traits and contextTraits objects, and an empty setOnce property. + it('should return an object containing the original traits and contextTraits objects, and an empty setOnce property when given an empty setOnceProperties array', () => { + const traits = { name: 'John', age: 30 }; + const contextTraits = { email: 'john@example.com' }; + const setOnceProperties = []; - const result = trimTraits(traits, contextTraits, setOnceProperties); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: { name: 'John', age: 30 }, - contextTraits: { email: 'john@example.com' }, - setOnce: {}, - }); + expect(result).toEqual({ + traits: { name: 'John', age: 30 }, + contextTraits: { email: 'john@example.com' }, + setOnce: {}, }); + }); - // Given a setOnceProperties array containing properties that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property. - it('should not add properties to the setOnce property when given setOnceProperties array with non-existent properties', () => { - const traits = { name: 'John', age: 30 }; - const contextTraits = { email: 'john@example.com' }; - const setOnceProperties = ['name', 'email', 'address']; + // Given a setOnceProperties array containing properties that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property. + it('should not add properties to the setOnce property when given setOnceProperties array with non-existent properties', () => { + const traits = { name: 'John', age: 30 }; + const contextTraits = { email: 'john@example.com' }; + const setOnceProperties = ['name', 'email', 'address']; - const result = trimTraits(traits, contextTraits, setOnceProperties); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: { age: 30 }, - contextTraits: {}, - setOnce: { $name: 'John', $email: 'john@example.com' }, - }); + expect(result).toEqual({ + traits: { age: 30 }, + contextTraits: {}, + setOnce: { $name: 'John', $email: 'john@example.com' }, }); + }); - // Given a setOnceProperties array containing properties with nested paths that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property. - it('should not add properties to the setOnce property when given setOnceProperties array with non-existent nested properties', () => { - const traits = { name: 'John', age: 30, address: 'kolkata' }; - const contextTraits = { email: 'john@example.com' }; - const setOnceProperties = ['name', 'email', 'address.city']; + // Given a setOnceProperties array containing properties with nested paths that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property. + it('should not add properties to the setOnce property when given setOnceProperties array with non-existent nested properties', () => { + const traits = { name: 'John', age: 30, address: 'kolkata' }; + const contextTraits = { email: 'john@example.com' }; + const setOnceProperties = ['name', 'email', 'address.city']; - const result = trimTraits(traits, contextTraits, setOnceProperties); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: { age: 30, address: 'kolkata' }, - contextTraits: {}, - setOnce: { $name: 'John', $email: 'john@example.com' }, - }); + expect(result).toEqual({ + traits: { age: 30, address: 'kolkata' }, + contextTraits: {}, + setOnce: { $name: 'John', $email: 'john@example.com' }, }); + }); - it('should add properties to the setOnce property when given setOnceProperties array with existent nested properties', () => { - const traits = { name: 'John', age: 30, address: { city: 'kolkata' }, isAdult: false }; - const contextTraits = { email: 'john@example.com' }; - const setOnceProperties = ['name', 'email', 'address.city']; + it('should add properties to the setOnce property when given setOnceProperties array with existent nested properties', () => { + const traits = { name: 'John', age: 30, address: { city: 'kolkata' }, isAdult: false }; + const contextTraits = { email: 'john@example.com' }; + const setOnceProperties = ['name', 'email', 'address.city']; - const result = trimTraits(traits, contextTraits, setOnceProperties); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: { age: 30, address: {}, isAdult: false }, - contextTraits: {}, - setOnce: { $name: 'John', $email: 'john@example.com', $city: 'kolkata' }, - }); + expect(result).toEqual({ + traits: { age: 30, address: {}, isAdult: false }, + contextTraits: {}, + setOnce: { $name: 'John', $email: 'john@example.com', $city: 'kolkata' }, }); }); }); + +describe('generatePageOrScreenCustomEventName', () => { + it('should throw a ConfigurationError when userDefinedEventTemplate is not provided', () => { + const message = { name: 'Home' }; + const userDefinedEventTemplate = undefined; + expect(() => { + generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + }).toThrow(ConfigurationError); + }); + + it('should generate a custom event name when userDefinedEventTemplate contains event template and message object is provided', () => { + let message = { name: 'Doc', properties: { category: 'Integration' } }; + const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page'; + let expected = 'Viewed Integration Doc page'; + let result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + + message = { name: true, properties: { category: 0 } }; + expected = 'Viewed 0 true page'; + result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should generate a custom event name when userDefinedEventTemplate contains event template and category or name is missing in message object', () => { + const message = { name: 'Doc', properties: { category: undefined } }; + const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page someKeyword'; + const expected = 'Viewed Doc page someKeyword'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should generate a custom event name when userDefinedEventTemplate contains only category or name placeholder and message object is provided', () => { + const message = { name: 'Doc', properties: { category: 'Integration' } }; + const userDefinedEventTemplate = 'Viewed {{ name }} page'; + const expected = 'Viewed Doc page'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should return the userDefinedEventTemplate when it does not contain placeholder {{}}', () => { + const message = { name: 'Index' }; + const userDefinedEventTemplate = 'Viewed a Home page'; + const expected = 'Viewed a Home page'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should return a event name when message object is not provided/empty', () => { + const message = {}; + const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page someKeyword'; + const expected = 'Viewed page someKeyword'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); +}); diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js index c8d872e90e..de73b0fb05 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -8,21 +8,27 @@ const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DEN * ref : https://developers.google.com/google-ads/api/rest/reference/rest/v15/Consent */ -const populateConsentForGoogleDestinations = (properties) => { +const populateConsentForGoogleDestinations = (config) => { const consent = {}; - if ( - properties?.userDataConsent && - GOOGLE_ALLOWED_CONSENT_STATUS.includes(properties.userDataConsent) - ) { - consent.adUserData = properties.userDataConsent; + if (config?.userDataConsent) { + if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(config.userDataConsent)) { + consent.adUserData = config.userDataConsent; + } else { + consent.adUserData = 'UNKNOWN'; + } + } else { + consent.adUserData = 'UNSPECIFIED'; } - if ( - properties?.personalizationConsent && - GOOGLE_ALLOWED_CONSENT_STATUS.includes(properties.personalizationConsent) - ) { - consent.adPersonalization = properties.personalizationConsent; + if (config?.personalizationConsent) { + if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(config.personalizationConsent)) { + consent.adPersonalization = config.personalizationConsent; + } else { + consent.adPersonalization = 'UNKNOWN'; + } + } else { + consent.adPersonalization = 'UNSPECIFIED'; } return consent; }; diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js index 27eff2a793..9d1aa5e51a 100644 --- a/src/v0/util/googleUtils/index.test.js +++ b/src/v0/util/googleUtils/index.test.js @@ -1,50 +1,58 @@ const { populateConsentForGoogleDestinations } = require('./index'); describe('unit test for populateConsentForGoogleDestinations', () => { - // Returns an empty object when no properties are provided. - it('should return an empty object when no properties are provided', () => { + it('should return an UNSPECIFIED object when no properties are provided', () => { const result = populateConsentForGoogleDestinations({}); - expect(result).toEqual({}); + expect(result).toEqual({ + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }); }); - // Sets adUserData property of consent object when userDataConsent property is provided and its value is one of the allowed consent statuses. it('should set adUserData 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 = populateConsentForGoogleDestinations(properties); - expect(result).toEqual({ adUserData: 'GRANTED' }); + expect(result).toEqual({ adUserData: 'GRANTED', adPersonalization: 'UNSPECIFIED' }); }); - // Sets adPersonalization property of consent object when personalizationConsent property is provided and its value is one of the allowed consent statuses. it('should set adPersonalization 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 = populateConsentForGoogleDestinations(properties); - expect(result).toEqual({ adPersonalization: 'DENIED' }); + expect(result).toEqual({ adPersonalization: 'DENIED', adUserData: 'UNSPECIFIED' }); }); - // Returns an empty object when properties parameter is not provided. - it('should return an empty object when properties parameter is not provided', () => { + it('should return an UNSPECIFIED object when properties parameter is not provided', () => { const result = populateConsentForGoogleDestinations(); - expect(result).toEqual({}); + expect(result).toEqual({ + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }); }); - // Returns an empty object when properties parameter is null. - it('should return an empty object when properties parameter is null', () => { + it('should return an UNSPECIFIED object when properties parameter is null', () => { const result = populateConsentForGoogleDestinations(null); - expect(result).toEqual({}); + expect(result).toEqual({ + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }); }); - // Returns an empty object when properties parameter is an empty object. - it('should return an empty object when properties parameter is an empty object', () => { + it('should return an UNSPECIFIED object when properties parameter is an UNSPECIFIED object', () => { const result = populateConsentForGoogleDestinations({}); - expect(result).toEqual({}); + expect(result).toEqual({ + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }); }); - // Returns an empty object when properties parameter is an empty object. - it('should return an empty object when properties parameter contains adUserData and adPersonalization with non-allowed values', () => { + it('should return UNKNOWN when properties parameter contains adUserData and adPersonalization with non-allowed values', () => { const result = populateConsentForGoogleDestinations({ - adUserData: 'RANDOM', + userDataConsent: 'RANDOM', personalizationConsent: 'RANDOM', }); - expect(result).toEqual({}); + expect(result).toEqual({ + adPersonalization: 'UNKNOWN', + adUserData: 'UNKNOWN', + }); }); }); diff --git a/src/v0/util/index.js b/src/v0/util/index.js index 9792401241..c1debce088 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -2201,6 +2201,25 @@ const combineBatchRequestsWithSameJobIds = (inputBatches) => { return combineBatches(combineBatches(inputBatches)); }; +/** + * This function validates the event and return it as string. + * @param {*} isMandatory The event is a required field. + * @param {*} convertToLowerCase The event should be converted to lower-case. + * @returns {string} Event name converted to string. + */ +const validateEventAndLowerCaseConversion = (event, isMandatory, convertToLowerCase) => { + if (!isDefined(event) || typeof event === 'object' || typeof event === 'boolean') { + throw new InstrumentationError('Event should not be a object, NaN, boolean or undefined'); + } + + // handling 0 as it is a valid value + if (isMandatory && !event && event !== 0) { + throw new InstrumentationError('Event is a required field'); + } + + return convertToLowerCase ? event.toString().toLowerCase() : event.toString(); +}; + // ======================================================================== // EXPORTS // ======================================================================== @@ -2317,4 +2336,5 @@ module.exports = { findExistingBatch, removeDuplicateMetadata, combineBatchRequestsWithSameJobIds, + validateEventAndLowerCaseConversion, }; diff --git a/src/v0/util/index.test.js b/src/v0/util/index.test.js index 4dc6255691..810eb5a9d4 100644 --- a/src/v0/util/index.test.js +++ b/src/v0/util/index.test.js @@ -1,4 +1,4 @@ -const { TAG_NAMES } = require('@rudderstack/integrations-lib'); +const { TAG_NAMES, InstrumentationError } = require('@rudderstack/integrations-lib'); const utilities = require('.'); const { getFuncTestData } = require('../../../test/testHelper'); const { FilteredEventsError } = require('./errorTypes'); @@ -7,6 +7,7 @@ const { flattenJson, generateExclusionList, combineBatchRequestsWithSameJobIds, + validateEventAndLowerCaseConversion, } = require('./index'); // Names of the utility functions to test @@ -36,7 +37,6 @@ describe('Utility Functions Tests', () => { test.each(funcTestData)('$description', async ({ description, input, output }) => { try { let result; - // This is to allow sending multiple arguments to the function if (Array.isArray(input)) { result = utilities[funcName](...input); @@ -456,3 +456,53 @@ describe('Unit test cases for combineBatchRequestsWithSameJobIds', () => { expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput); }); }); + +describe('validateEventAndLowerCaseConversion Tests', () => { + it('should return string conversion of number types', () => { + const ev = 0; + expect(validateEventAndLowerCaseConversion(ev, false, true)).toBe('0'); + expect(validateEventAndLowerCaseConversion(ev, true, false)).toBe('0'); + }); + + it('should convert string types to lowercase', () => { + const ev = 'Abc'; + expect(validateEventAndLowerCaseConversion(ev, true, true)).toBe('abc'); + }); + + it('should throw error if event is object type', () => { + expect(() => { + validateEventAndLowerCaseConversion({}, true, true); + }).toThrow(InstrumentationError); + expect(() => { + validateEventAndLowerCaseConversion([1, 2], false, true); + }).toThrow(InstrumentationError); + expect(() => { + validateEventAndLowerCaseConversion({ a: 1 }, true, true); + }).toThrow(InstrumentationError); + }); + + it('should convert string to lowercase', () => { + expect(validateEventAndLowerCaseConversion('Abc', true, true)).toBe('abc'); + expect(validateEventAndLowerCaseConversion('ABC', true, false)).toBe('ABC'); + expect(validateEventAndLowerCaseConversion('abc55', false, true)).toBe('abc55'); + expect(validateEventAndLowerCaseConversion(123, false, true)).toBe('123'); + }); + + it('should throw error for null and undefined', () => { + expect(() => { + validateEventAndLowerCaseConversion(null, true, true); + }).toThrow(InstrumentationError); + expect(() => { + validateEventAndLowerCaseConversion(undefined, false, true); + }).toThrow(InstrumentationError); + }); + + it('should throw error for boolean values', () => { + expect(() => { + validateEventAndLowerCaseConversion(true, true, true); + }).toThrow(InstrumentationError); + expect(() => { + validateEventAndLowerCaseConversion(false, false, false); + }).toThrow(InstrumentationError); + }); +}); diff --git a/test/integrations/destinations/facebook_conversions/processor/data.ts b/test/integrations/destinations/facebook_conversions/processor/data.ts index beb7eb32aa..6eb90942a7 100644 --- a/test/integrations/destinations/facebook_conversions/processor/data.ts +++ b/test/integrations/destinations/facebook_conversions/processor/data.ts @@ -1434,4 +1434,104 @@ export const data = [ }, mockFns: defaultMockFns, }, + { + name: 'facebook_conversions', + description: 'Track event with standard event order completed with content_type in properties', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1', + channel: 'web', + context: { + traits: { + email: ' aBc@gmail.com ', + address: { + zip: 1234, + }, + anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1', + }, + }, + event: 'order completed', + integrations: { + All: true, + }, + message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8', + properties: { + content_type: 'product_group', + revenue: 400, + additional_bet_index: 0, + products: [ + { + product_id: 1234, + quantity: 5, + price: 55, + }, + ], + }, + timestamp: '2023-11-12T15:46:51.693229+05:30', + type: 'track', + }, + destination: { + Config: { + limitedDataUsage: true, + blacklistPiiProperties: [ + { + blacklistPiiProperties: '', + blacklistPiiHash: false, + }, + ], + accessToken: '09876', + datasetId: 'dummyID', + eventsToEvents: [ + { + from: '', + to: '', + }, + ], + removeExternalId: true, + actionSource: 'website', + }, + Enabled: true, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://graph.facebook.com/v18.0/dummyID/events?access_token=09876', + headers: {}, + params: {}, + body: { + JSON: {}, + XML: {}, + JSON_ARRAY: {}, + FORM: { + data: [ + '{"user_data":{"em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08","zp":"03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4"},"event_name":"Purchase","event_time":1699784211,"action_source":"website","custom_data":{"content_type":"product_group","revenue":400,"additional_bet_index":0,"products":[{"product_id":1234,"quantity":5,"price":55}],"content_ids":[1234],"contents":[{"id":1234,"quantity":5,"item_price":55}],"currency":"USD","value":400,"num_items":1}}', + ], + }, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + mockFns: defaultMockFns, + }, ]; diff --git a/test/integrations/destinations/facebook_pixel/processor/data.ts b/test/integrations/destinations/facebook_pixel/processor/data.ts index 557bc7066c..f6a5cd1e20 100644 --- a/test/integrations/destinations/facebook_pixel/processor/data.ts +++ b/test/integrations/destinations/facebook_pixel/processor/data.ts @@ -6460,4 +6460,111 @@ export const data = [ }, }, }, + { + name: 'facebook_pixel', + description: + 'Test 51: properties.content_type is given priority over populating it from categoryToContent mapping.', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + type: 'track', + messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be', + originalTimestamp: '2023-10-14T15:46:51.693229+05:30', + anonymousId: '00000000000000000000000000', + userId: '12345', + event: 'order completed', + properties: { + content_type: 'product_group', + category: ['clothing', 'fishing'], + order_id: 'rudderstackorder1', + revenue: 12.24, + currency: 'INR', + products: [ + { + quantity: 1, + price: 24.75, + name: 'my product', + sku: 'p-298', + }, + { + quantity: 3, + price: 24.75, + name: 'other product', + sku: 'p-299', + }, + ], + }, + integrations: { + All: true, + }, + sentAt: '2019-10-14T11:15:53.296Z', + }, + destination: { + Config: { + blacklistPiiProperties: [ + { + blacklistPiiProperties: '', + blacklistPiiHash: true, + }, + ], + categoryToContent: [ + { + from: 'clothing', + to: 'product', + }, + ], + accessToken: '09876', + pixelId: 'dummyPixelId', + eventsToEvents: [ + { + from: '', + to: '', + }, + ], + valueFieldIdentifier: 'properties.price', + advancedMapping: false, + }, + Enabled: true, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: `https://graph.facebook.com/${VERSION}/dummyPixelId/events?access_token=09876`, + headers: {}, + params: {}, + body: { + JSON: {}, + JSON_ARRAY: {}, + XML: {}, + FORM: { + data: [ + '{"user_data":{"external_id":"5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5"},"event_name":"Purchase","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"content_type":"product_group","category[0]":"clothing","category[1]":"fishing","order_id":"rudderstackorder1","revenue":12.24,"currency":"INR","products[0].quantity":1,"products[0].price":24.75,"products[0].name":"my product","products[0].sku":"p-298","products[1].quantity":3,"products[1].price":24.75,"products[1].name":"other product","products[1].sku":"p-299","content_category":"clothing,fishing","content_ids":["p-298","p-299"],"value":12.24,"contents":[{"id":"p-298","quantity":1,"item_price":24.75},{"id":"p-299","quantity":3,"item_price":24.75}],"num_items":2}}', + ], + }, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ].map((d) => ({ ...d, mockFns })); diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts b/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts index 414e46ea19..fe16ffef47 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts @@ -21,7 +21,7 @@ export const data = [ destination: 'google_adwords_remarketing_lists', listId: '709078448', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -101,7 +101,7 @@ export const data = [ listId: '709078448', customerId: '7693729833', destination: 'google_adwords_remarketing_lists', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -200,7 +200,7 @@ export const data = [ listId: '709078448', customerId: '7693729833', destination: 'google_adwords_remarketing_lists', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/network.ts b/test/integrations/destinations/google_adwords_remarketing_lists/network.ts index 8e1edd21aa..8e7c0acbcf 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/network.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/network.ts @@ -7,7 +7,10 @@ export const networkCallsData = [ data: { job: { type: 'CUSTOMER_MATCH_USER_LIST', - customerMatchUserListMetadata: { userList: 'customers/7693729833/userLists/709078448' }, + customerMatchUserListMetadata: { + userList: 'customers/7693729833/userLists/709078448', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, }, }, headers: { @@ -86,7 +89,10 @@ export const networkCallsData = [ data: { job: { type: 'CUSTOMER_MATCH_USER_LIST', - customerMatchUserListMetadata: { userList: 'customers/7693729833/userLists/709078448' }, + customerMatchUserListMetadata: { + userList: 'customers/7693729833/userLists/709078448', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, }, }, headers: { diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts b/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts index 804efec220..a846e0370d 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts @@ -79,7 +79,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -213,7 +213,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -332,7 +332,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -1434,7 +1434,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -2829,7 +2829,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -2909,7 +2909,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -4122,7 +4122,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -5422,7 +5422,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -6799,7 +6799,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -8109,7 +8109,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -9409,7 +9409,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -10804,7 +10804,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -10884,7 +10884,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11059,7 +11059,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11137,7 +11137,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11281,7 +11281,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11488,7 +11488,7 @@ export const data = [ params: { listId: 'aud1234', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11627,7 +11627,7 @@ export const data = [ params: { listId: '830441345', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11765,7 +11765,7 @@ export const data = [ params: { listId: '830441345', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11836,6 +11836,8 @@ export const data = [ userSchema: ['email', 'phone', 'addressInfo'], isHashRequired: true, typeOfList: 'General', + userDataConsent: 'UNSPECIFIED', + personalizationConsent: 'GRANTED', }, }, message: { @@ -11860,8 +11862,6 @@ export const data = [ event: 'Add_Audience', messageId: 'bd2d67ca-0c9a-4d3b-a2f8-35a3c3f75ba7', properties: { - userDataConsent: 'UNSPECIFIED', - personalizationConsent: 'GRANTED', listData: { add: [ { @@ -11979,6 +11979,8 @@ export const data = [ userSchema: ['email', 'phone', 'addressInfo'], isHashRequired: true, typeOfList: 'General', + userDataConsent: 'RANDOM', + personalizationConsent: 'RANDOM', }, }, message: { @@ -12003,8 +12005,6 @@ export const data = [ event: 'Add_Audience', messageId: 'bd2d67ca-0c9a-4d3b-a2f8-35a3c3f75ba7', properties: { - userDataConsent: 'RANDOM', - personalizationConsent: 'RANDOM', listData: { add: [ { @@ -12048,7 +12048,7 @@ export const data = [ params: { listId: '830441345', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNKNOWN', adUserData: 'UNKNOWN' }, }, body: { JSON: { diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts b/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts index 8c12225400..31d5c72694 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts @@ -228,7 +228,11 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, body: { JSON: { enablePartialFailure: true, @@ -305,7 +309,11 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, body: { JSON: { enablePartialFailure: true, @@ -359,7 +367,11 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, body: { JSON: { enablePartialFailure: true, @@ -436,7 +448,11 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, body: { JSON: { enablePartialFailure: true, @@ -484,7 +500,11 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, body: { JSON: { enablePartialFailure: true, diff --git a/test/integrations/destinations/mp/processor/data.ts b/test/integrations/destinations/mp/processor/data.ts index dfa94352c9..5b2d0fbfff 100644 --- a/test/integrations/destinations/mp/processor/data.ts +++ b/test/integrations/destinations/mp/processor/data.ts @@ -121,7 +121,10 @@ export const data = [ request: { body: [ { - destination: sampleDestination, + destination: overrideDestination(sampleDestination, { + useUserDefinedPageEventName: true, + userDefinedPageEventTemplate: 'Viewed a {{ name }} page', + }), message: { anonymousId: 'e6ab2c5e-2cda-44a9-a962-e2f67df78bca', channel: 'web', @@ -195,7 +198,7 @@ export const data = [ JSON: {}, JSON_ARRAY: { batch: - '[{"event":"Loaded a Page","properties":{"ip":"0.0.0.0","$user_id":"hjikl","$current_url":"https://docs.rudderstack.com/destinations/mixpanel","$screen_dpi":2,"mp_lib":"RudderLabs JavaScript SDK","$app_build_number":"1.0.0","$app_version_string":"1.0.5","$insert_id":"dd266c67-9199-4a52-ba32-f46ddde67312","token":"dummyApiKey","distinct_id":"hjikl","time":1579847342402,"name":"Contact Us","category":"Contact","$browser":"Chrome","$browser_version":"79.0.3945.117"}}]', + '[{"event":"Viewed a Contact Us page","properties":{"ip":"0.0.0.0","$user_id":"hjikl","$current_url":"https://docs.rudderstack.com/destinations/mixpanel","$screen_dpi":2,"mp_lib":"RudderLabs JavaScript SDK","$app_build_number":"1.0.0","$app_version_string":"1.0.5","$insert_id":"dd266c67-9199-4a52-ba32-f46ddde67312","token":"dummyApiKey","distinct_id":"hjikl","time":1579847342402,"name":"Contact Us","category":"Contact","$browser":"Chrome","$browser_version":"79.0.3945.117"}}]', }, XML: {}, FORM: {}, diff --git a/test/integrations/destinations/reddit/dataDelivery/business.ts b/test/integrations/destinations/reddit/dataDelivery/business.ts new file mode 100644 index 0000000000..2c4714ef13 --- /dev/null +++ b/test/integrations/destinations/reddit/dataDelivery/business.ts @@ -0,0 +1,131 @@ +import { + generateMetadata, + generateProxyV0Payload, + generateProxyV1Payload, +} from '../../../testUtils'; + +const validRequestPayload = { + events: [ + { + event_at: '2019-10-14T09:03:17.562Z', + event_type: { + tracking_type: 'Purchase', + }, + user: { + aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', + email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', + external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', + ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', + user_agent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + screen_dimensions: {}, + }, + event_metadata: { + item_count: 3, + products: [ + { + id: '123', + name: 'Monopoly', + category: 'Games', + }, + { + id: '345', + name: 'UNO', + category: 'Games', + }, + ], + }, + }, + ], +}; + +const commonHeaders = { + Authorization: 'Bearer dummyAccessToken', + 'Content-Type': 'application/json', +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: validRequestPayload, +}; + +export const testScenariosForV0API = [ + { + id: 'reddit_v0_scenario_1', + name: 'reddit', + description: + '[Proxy v0 API] :: Test for a valid request with a successful 200 response from the destination', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_fsddXXXfsfd', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + destResp: { + response: { + message: 'Successfully processed 1 conversion events.', + }, + status: 200, + }, + message: 'Request Processed Successfully', + status: 200, + }, + }, + }, + }, + }, +]; + +export const testScenariosForV1API = [ + { + id: 'reddit_v1_scenario_1', + name: 'reddit', + description: + '[Proxy v1 API] :: Test for a valid request with a successful 200 response from the destination', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://dfareporting.googleapis.com/test_url_for_valid_request', + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + metadata: generateMetadata(1), + statusCode: 500, + }, + ], + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/reddit/dataDelivery/data.ts b/test/integrations/destinations/reddit/dataDelivery/data.ts new file mode 100644 index 0000000000..54728ecb90 --- /dev/null +++ b/test/integrations/destinations/reddit/dataDelivery/data.ts @@ -0,0 +1,9 @@ +import { testScenariosForV0API, testScenariosForV1API } from './business'; +import { v0oauthScenarios, v1oauthScenarios } from './oauth'; + +export const data = [ + ...v0oauthScenarios, + ...v1oauthScenarios, + ...testScenariosForV0API, + ...testScenariosForV1API, +]; diff --git a/test/integrations/destinations/reddit/dataDelivery/oauth.ts b/test/integrations/destinations/reddit/dataDelivery/oauth.ts new file mode 100644 index 0000000000..90368cd60b --- /dev/null +++ b/test/integrations/destinations/reddit/dataDelivery/oauth.ts @@ -0,0 +1,147 @@ +import { + generateMetadata, + generateProxyV1Payload, + generateProxyV0Payload, +} from '../../../testUtils'; + +const authorizationRequiredRequestPayload = { + events: [ + { + event_at: '2019-10-14T09:03:17.562Z', + event_type: { + tracking_type: 'ViewContent', + }, + user: { + aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', + email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', + external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', + ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', + user_agent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + screen_dimensions: {}, + }, + event_metadata: { + item_count: 3, + products: [ + { + id: '123', + name: 'Monopoly', + category: 'Games', + }, + { + id: '345', + name: 'UNO', + category: 'Games', + }, + ], + }, + }, + ], +}; + +const commonHeaders = { + Authorization: 'Bearer dummyAccessToken', + 'Content-Type': 'application/json', +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: authorizationRequiredRequestPayload, +}; + +const expectedStatTags = { + destType: 'REDDIT', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'retryable', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', +}; + +export const v0oauthScenarios = [ + { + id: 'reddit_v0_oauth_scenario_1', + name: 'reddit', + description: '[Proxy v0 API] :: Oauth where Authorization Required response from destination', + successCriteria: 'Should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_gsddXXXfsfd', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + authErrorCategory: 'REFRESH_TOKEN', + destinationResponse: { + response: 'Authorization Required', + status: 401, + }, + message: + "Request failed due to Authorization Required 'during reddit response transformation'", + statTags: expectedStatTags, + status: 500, + }, + }, + }, + }, + }, +]; + +export const v1oauthScenarios = [ + { + id: 'reddit_v1_oauth_scenario_1', + name: 'reddit', + description: '[Proxy v1 API] :: Oauth where Authorization Required response from destination', + successCriteria: 'Should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_gsddXXXfsfd', + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + authErrorCategory: 'REFRESH_TOKEN', + message: + "Request failed due to Authorization Required 'during reddit response transformation'", + response: [ + { + error: '"Authorization Required"', + metadata: generateMetadata(1), + statusCode: 500, + }, + ], + statTags: expectedStatTags, + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/reddit/delivery/data.ts b/test/integrations/destinations/reddit/delivery/data.ts deleted file mode 100644 index 66c1e2863f..0000000000 --- a/test/integrations/destinations/reddit/delivery/data.ts +++ /dev/null @@ -1,174 +0,0 @@ -export const data = [ - { - name: 'reddit', - description: 'Test 0', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_fsddXXXfsfd', - headers: { - Authorization: 'Bearer dummyAccessToken', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - events: [ - { - event_at: '2019-10-14T09:03:17.562Z', - event_type: { - tracking_type: 'Purchase', - }, - user: { - aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', - email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', - external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', - ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', - user_agent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', - screen_dimensions: {}, - }, - event_metadata: { - item_count: 3, - products: [ - { - id: '123', - name: 'Monopoly', - category: 'Games', - }, - { - id: '345', - name: 'UNO', - category: 'Games', - }, - ], - }, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - userId: '', - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - destResp: { - response: { - message: 'Successfully processed 1 conversion events.', - }, - status: 200, - }, - message: 'Request Processed Successfully', - status: 200, - }, - }, - }, - }, - }, - { - name: 'reddit', - description: 'Test 1', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_gsddXXXfsfd', - headers: { - Authorization: 'Bearer dummyAccessToken', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - events: [ - { - event_at: '2019-10-14T09:03:17.562Z', - event_type: { - tracking_type: 'ViewContent', - }, - user: { - aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', - email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', - external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', - ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', - user_agent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', - screen_dimensions: {}, - }, - event_metadata: { - item_count: 3, - products: [ - { - id: '123', - name: 'Monopoly', - category: 'Games', - }, - { - id: '345', - name: 'UNO', - category: 'Games', - }, - ], - }, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - userId: '', - }, - method: 'POST', - }, - }, - output: { - response: { - status: 500, - body: { - output: { - authErrorCategory: 'REFRESH_TOKEN', - destinationResponse: { - response: 'Authorization Required', - status: 401, - }, - message: - "Request failed due to Authorization Required 'during reddit response transformation'", - statTags: { - destType: 'REDDIT', - destinationId: 'Non-determininable', - errorCategory: 'network', - errorType: 'retryable', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'Non-determininable', - }, - status: 500, - }, - }, - }, - }, - }, -];