diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index fe308ce8b9..cfcb1fc0d8 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -26,6 +26,8 @@ N/A N/A +@coderabbitai review +
### Developer checklist diff --git a/.gitignore b/.gitignore index f96c3ac807..956605f139 100644 --- a/.gitignore +++ b/.gitignore @@ -132,7 +132,7 @@ dist # Others **/.DS_Store - +.dccache .idea diff --git a/README.md b/README.md index 2b3908dcb5..4ff1cd4b34 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +

+⚠️ Docker image for rudder-transformer has been moved to new org rudderstack/rudder-transformer +

+

+ [![codecov](https://codecov.io/gh/rudderlabs/rudder-transformer/branch/develop/graph/badge.svg?token=G24OON85SB)](https://codecov.io/gh/rudderlabs/rudder-transformer) # RudderStack Transformer diff --git a/package-lock.json b/package-lock.json index 8db5ed5262..1ba14ac62c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", "@rudderstack/integrations-lib": "^0.2.2", - "@rudderstack/workflow-engine": "^0.6.9", + "@rudderstack/workflow-engine": "^0.7.2", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", @@ -4566,12 +4566,12 @@ "integrity": "sha512-+iH40g+ZA2ANgwjOITdEdZJLZV+ljR28Akn/dRoDia591tMu7PptyvDaAvl+m1DijWXddpLQ8SX9xaEcIdmqlw==" }, "node_modules/@rudderstack/workflow-engine": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.6.10.tgz", - "integrity": "sha512-3GRdnbB0BuSPWiKf4JsSpG7QuGffAFWkT5T0JLR7Jxps25gt+PgtjQiAlwrRhO5A0WeTJMIKTI7ctz6dGmJosg==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.7.2.tgz", + "integrity": "sha512-aXQvoXMekvXxxDG6Yc5P5l3PJIwqVA+EmJ2w4SnQ94BUHhbsybPjgGvyzD17MUTAdWEOtqS38SuzLflBs/5T4g==", "dependencies": { "@aws-crypto/sha256-js": "^5.0.0", - "@rudderstack/json-template-engine": "^0.8.1", + "@rudderstack/json-template-engine": "^0.8.4", "jsonata": "^2.0.3", "lodash": "^4.17.21", "object-sizeof": "^2.6.3", diff --git a/package.json b/package.json index 93ac70aef1..7c461ccd3f 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", "@rudderstack/integrations-lib": "^0.2.2", - "@rudderstack/workflow-engine": "^0.6.9", + "@rudderstack/workflow-engine": "^0.7.2", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", diff --git a/src/cdk/v2/handler.ts b/src/cdk/v2/handler.ts index 47d6d10179..edd14e7298 100644 --- a/src/cdk/v2/handler.ts +++ b/src/cdk/v2/handler.ts @@ -50,16 +50,20 @@ export async function getWorkflowEngine( const workflowEnginePromiseMap = new Map(); -export function getCachedWorkflowEngine( +export async function getCachedWorkflowEngine( destName: string, feature: string, bindings: Record = {}, -): WorkflowEngine { +): Promise { // Create a new instance of the engine for the destination if needed // TODO: Use cache to avoid long living engine objects workflowEnginePromiseMap[destName] = workflowEnginePromiseMap[destName] || new Map(); if (!workflowEnginePromiseMap[destName][feature]) { - workflowEnginePromiseMap[destName][feature] = getWorkflowEngine(destName, feature, bindings); + workflowEnginePromiseMap[destName][feature] = await getWorkflowEngine( + destName, + feature, + bindings, + ); } return workflowEnginePromiseMap[destName][feature]; } @@ -97,5 +101,8 @@ export function executeStep( ): Promise { return workflowEngine .getStepExecutor(stepName) - .execute(input, Object.assign(workflowEngine.bindings, getEmptyExecutionBindings(), bindings)); + .execute( + input, + Object.assign(workflowEngine.getBindings(), getEmptyExecutionBindings(), bindings), + ); } diff --git a/src/v0/destinations/af/transform.js b/src/v0/destinations/af/transform.js index 4d7ed7e635..57629b9483 100644 --- a/src/v0/destinations/af/transform.js +++ b/src/v0/destinations/af/transform.js @@ -113,9 +113,15 @@ function getEventValueForUnIdentifiedTrackEvent(message) { return { eventValue }; } -function getEventValueMapFromMappingJson(message, mappingJson, isMultiSupport) { +function getEventValueMapFromMappingJson(message, mappingJson, isMultiSupport, addPropertiesAtRoot) { let eventValue = {}; - set(eventValue, 'properties', message.properties); + + if (addPropertiesAtRoot) { + eventValue = message.properties; + } else { + set(eventValue, 'properties', message.properties); + } + const sourceKeys = Object.keys(mappingJson); sourceKeys.forEach((sourceKey) => { set(eventValue, mappingJson[sourceKey], get(message, sourceKey)); @@ -160,7 +166,7 @@ function processNonTrackEvents(message, eventName) { return payload; } -function processEventTypeTrack(message) { +function processEventTypeTrack(message, addPropertiesAtRoot) { let isMultiSupport = true; const evType = message.event && message.event.toLowerCase(); let category = ConfigCategory.DEFAULT; @@ -184,6 +190,7 @@ function processEventTypeTrack(message) { message, mappingConfig[category.name], isMultiSupport, + addPropertiesAtRoot, ); payload.eventName = message.event; payload.eventCurrency = message?.properties?.currency; @@ -196,7 +203,7 @@ function processSingleMessage(message, destination) { let payload; switch (messageType) { case EventType.TRACK: { - payload = processEventTypeTrack(message); + payload = processEventTypeTrack(message, destination.Config.addPropertiesAtRoot); break; } case EventType.SCREEN: { diff --git a/src/v0/destinations/am/transform.js b/src/v0/destinations/am/transform.js index d78a5f727f..afd72b77e1 100644 --- a/src/v0/destinations/am/transform.js +++ b/src/v0/destinations/am/transform.js @@ -614,16 +614,16 @@ const processSingleMessage = (message, destination) => { case EventType.PAGE: if (useUserDefinedPageEventName) { const getMessagePath = userProvidedPageEventString - .substring( + ?.substring( userProvidedPageEventString.indexOf('{') + 2, userProvidedPageEventString.indexOf('}'), ) .trim(); evType = - userProvidedPageEventString.trim() === '' + userProvidedPageEventString?.trim() === '' ? name : userProvidedPageEventString - .trim() + ?.trim() .replaceAll(/{{([^{}]+)}}/g, get(message, getMessagePath)); } else { evType = `Viewed ${name || get(message, CATEGORY_KEY) || ''} Page`; @@ -701,6 +701,7 @@ const processSingleMessage = (message, destination) => { logger.debug('could not determine type'); throw new InstrumentationError('message type not supported'); } + AMUtils.validateEventType(evType); return responseBuilderSimple( groupInfo, payloadObjectName, diff --git a/src/v0/destinations/am/util.test.js b/src/v0/destinations/am/util.test.js index faaa9170f0..455f9117ef 100644 --- a/src/v0/destinations/am/util.test.js +++ b/src/v0/destinations/am/util.test.js @@ -1,4 +1,4 @@ -const { getUnsetObj } = require('./utils'); +const { getUnsetObj, validateEventType } = require('./utils'); describe('getUnsetObj', () => { it("should return undefined when 'message.integrations.Amplitude.fieldsToUnset' is not array", () => { @@ -64,3 +64,34 @@ describe('getUnsetObj', () => { expect(result).toBeUndefined(); }); }); + + +describe('validateEventType', () => { + + it('should validate event type when it is valid with only page name given', () => { + expect(() => { + validateEventType('Home Page'); + }).not.toThrow(); + }); + + it('should throw an error when event type is null', () => { + expect(() => { + validateEventType(null); + }).toThrow('Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`'); + }); + + it('should throw an error when event type is undefined', () => { + expect(() => { + validateEventType(undefined); + }).toThrow('Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`'); + }); + + // Function receives an empty string as event type + it('should throw an error when event type is an empty string', () => { + expect(() => { + validateEventType(''); + }).toThrow('Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`'); + }); + +}); + diff --git a/src/v0/destinations/am/utils.js b/src/v0/destinations/am/utils.js index 71fe0ab459..e51d09aaa7 100644 --- a/src/v0/destinations/am/utils.js +++ b/src/v0/destinations/am/utils.js @@ -10,6 +10,7 @@ // populate these dest keys const get = require('get-value'); const uaParser = require('@amplitude/ua-parser-js'); +const { InstrumentationError } = require('@rudderstack/integrations-lib'); const logger = require('../../../logger'); const { isDefinedAndNotNull } = require('../../util'); @@ -108,6 +109,19 @@ const getUnsetObj = (message) => { return unsetObject; }; + +/** + * Check for evType as in some cases, like when the page name is absent, + * either the template depends only on the event.name or there is no template provided by user + * @param {*} evType + */ +const validateEventType = (evType) => { + if (!isDefinedAndNotNull(evType) || (typeof evType === "string" && evType.length ===0)) { + throw new InstrumentationError( + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + ); + } +}; module.exports = { getOSName, getOSVersion, @@ -117,4 +131,5 @@ module.exports = { getBrand, getEventId, getUnsetObj, + validateEventType }; diff --git a/src/v0/destinations/custify/deleteUsers.js b/src/v0/destinations/custify/deleteUsers.js index 147fcc602c..921cf953bd 100644 --- a/src/v0/destinations/custify/deleteUsers.js +++ b/src/v0/destinations/custify/deleteUsers.js @@ -17,36 +17,41 @@ const userDeletionHandler = async (userAttributes, config) => { } const { apiKey } = config; - const { userId } = userAttributes; if (!apiKey) { throw new ConfigurationError('api key for deletion not present', 400); } - if (!userId) { - throw new InstrumentationError('User id for deletion not present', 400); - } - const requestUrl = `https://api.custify.com/people?user_id=${userId}`; - const requestOptions = { - headers: { - Authorization: `Bearer ${apiKey}`, - }, - }; + await Promise.all( + userAttributes.map(async (userAttr) => { + const { userId } = userAttr; + if (!userId) { + throw new InstrumentationError('User id for deletion not present', 400); + } + // Reference: https://docs.custify.com/#tag/People/paths/~1people/delete + const requestUrl = `https://api.custify.com/people?user_id=${userId}`; + const requestOptions = { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }; - const deletionResponse = await httpDELETE(requestUrl, requestOptions, { - destType: 'custify', - feature: 'deleteUsers', - }); - const processedDeletionRequest = processAxiosResponse(deletionResponse); - if (processedDeletionRequest.status !== 200 && processedDeletionRequest.status !== 404) { - throw new NetworkError( - JSON.stringify(processedDeletionRequest.response) || 'Error while deleting user', - processedDeletionRequest.status, - { - [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(processedDeletionRequest.status), - }, - deletionResponse, - ); - } + const deletionResponse = await httpDELETE(requestUrl, requestOptions, { + destType: 'custify', + feature: 'deleteUsers', + }); + const processedDeletionRequest = processAxiosResponse(deletionResponse); + if (processedDeletionRequest.status !== 200 && processedDeletionRequest.status !== 404) { + throw new NetworkError( + JSON.stringify(processedDeletionRequest.response) || 'Error while deleting user', + processedDeletionRequest.status, + { + [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(processedDeletionRequest.status), + }, + deletionResponse, + ); + } + }), + ); return { statusCode: 200, status: 'successful' }; }; diff --git a/src/v0/destinations/ga4/networkHandler.js b/src/v0/destinations/ga4/networkHandler.js index b62fcc8d3b..2cb98e1460 100644 --- a/src/v0/destinations/ga4/networkHandler.js +++ b/src/v0/destinations/ga4/networkHandler.js @@ -30,9 +30,9 @@ const responseHandler = (destinationResponse, dest) => { const { description, validationCode, fieldPath } = response.validationMessages[0]; throw new NetworkError( `Validation Server Response Handler:: Validation Error for ${dest} of field path :${fieldPath} | ${validationCode}-${description}`, - status, + 400, { - [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), + [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(400), }, response?.validationMessages[0]?.description, ); diff --git a/src/v0/destinations/tiktok_ads/transformV2.js b/src/v0/destinations/tiktok_ads/transformV2.js index 91078dfe65..98f7d61e1e 100644 --- a/src/v0/destinations/tiktok_ads/transformV2.js +++ b/src/v0/destinations/tiktok_ads/transformV2.js @@ -40,7 +40,7 @@ const getTrackResponsePayload = (message, destConfig, event) => { } // if contents is not present but we have properties.products present which has fields with superset of contents fields - if (payload.properties && !payload.properties.contents && message.properties.products) { + if (!payload.properties?.contents && message.properties?.products) { // retreiving data from products only when contents is not present payload.properties.contents = getContents(message, false); } diff --git a/src/v0/destinations/tiktok_ads_offline_events/config.js b/src/v0/destinations/tiktok_ads_offline_events/config.js index 5d5e80c716..3c58b42a44 100644 --- a/src/v0/destinations/tiktok_ads_offline_events/config.js +++ b/src/v0/destinations/tiktok_ads_offline_events/config.js @@ -19,12 +19,24 @@ const CONFIG_CATEGORIES = { const PARTNER_NAME = 'RudderStack'; const EVENT_NAME_MAPPING = { + 'addpaymentinfo': 'AddPaymentInfo', + 'addtocart': 'AddToCart', + 'addtowishlist': 'AddToWishlist', + 'checkout started': 'InitiateCheckout', 'checkout step completed': 'CompletePayment', - contact: 'Contact', - submitform: 'SubmitForm', - subscribe: 'Subscribe', + 'clickbutton': 'ClickButton', + 'completeregistration': 'CompleteRegistration', + 'contact': 'Contact', + 'download': 'Download', + 'order completed': 'PlaceAnOrder', + 'payment info entered': 'AddPaymentInfo', + 'product added': 'AddToCart', + 'product added to wishlist': 'AddToWishlist', + 'search': 'Search', + 'submitform': 'SubmitForm', + 'subscribe': 'Subscribe', + 'viewcontent': 'ViewContent', }; - const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); module.exports = { diff --git a/src/v0/util/facebookUtils/index.js b/src/v0/util/facebookUtils/index.js index 4c09518559..7fa1e898fe 100644 --- a/src/v0/util/facebookUtils/index.js +++ b/src/v0/util/facebookUtils/index.js @@ -147,7 +147,7 @@ const getContentType = (message, defaultValue, categoryToContent, destinationNam return integrationsObj.contentType; } - let { category } = properties; + let { category } = properties || {}; if (!category) { const { products } = properties; if (products && products.length > 0 && Array.isArray(products) && isObject(products[0])) { diff --git a/test/integrations/destinations/af/processor/data.ts b/test/integrations/destinations/af/processor/data.ts index 8b639f45c0..d0fd29b089 100644 --- a/test/integrations/destinations/af/processor/data.ts +++ b/test/integrations/destinations/af/processor/data.ts @@ -45,6 +45,7 @@ export const data = [ destination: { Config: { devKey: 'ef1d42390426e3f7c90ac78272e74344', androidAppId: 'appId' }, Enabled: true, + addPropertiesAtRoot: false, }, }, ], @@ -118,6 +119,7 @@ export const data = [ Config: { devKey: 'ef1d42390426e3f7c90ac78272e74344', androidAppId: 'com.rudderlabs.javascript', + addPropertiesAtRoot: false, }, Enabled: true, }, @@ -305,6 +307,7 @@ export const data = [ Config: { devKey: 'ef1d42390426e3f7c90ac78272e74344', androidAppId: 'com.rudderlabs.javascript', + addPropertiesAtRoot: false, }, Enabled: true, }, @@ -1532,4 +1535,306 @@ export const data = [ }, }, }, + { + name: 'af', + description: 'Place Properties at root level Page Call', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }], + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + email: 'testhubspot2@email.com', + name: 'Test Hubspot', + anonymousId: '12345', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-GB', + ip: '0.0.0.0', + os: { name: 'android', version: '' }, + screen: { density: 2 }, + }, + type: 'page', + messageId: 'e8585d9a-7137-4223-b295-68ab1b17dad7', + originalTimestamp: '2019-10-15T09:35:31.289Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { path: '', referrer: '', search: '', title: '', url: '' }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + integrations: { AF: { af_uid: 'afUid' } }, + }, + destination: { + Config: { + devKey: 'ef1d42390426e3f7c90ac78272e74344', + androidAppId: 'com.rudderlabs.javascript', + sharingFilter: 'all', + addPropertiesAtRoot: true, + }, + Enabled: true, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript', + headers: { + 'Content-Type': 'application/json', + authentication: 'ef1d42390426e3f7c90ac78272e74344', + }, + method: 'POST', + params: {}, + body: { + JSON: { + app_version_name: '1.0.0', + bundleIdentifier: 'com.rudderlabs.javascript', + customer_user_id: '12345', + eventValue: '{"path":"","referrer":"","search":"","title":"","url":""}', + eventName: 'page', + appsflyer_id: 'afUid', + os: '', + ip: '0.0.0.0', + sharing_filter: 'all', + }, + XML: {}, + JSON_ARRAY: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'af', + description: 'Place properties at root level track call', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }], + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { email: 'testhubspot2@email.com', name: 'Test Hubspot' }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-GB', + ip: '0.0.0.0', + os: { name: 'android', version: '' }, + screen: { density: 2 }, + }, + type: 'track', + messageId: '08829772-d991-427c-b976-b4c4f4430b4e', + originalTimestamp: '2019-10-15T09:35:31.291Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + event: 'test track event HS', + properties: { user_actual_role: 'system_admin, system_user', user_actual_id: 12345 }, + sentAt: '2019-10-14T11:15:53.296Z', + integrations: { AF: { af_uid: 'afUid' } }, + }, + destination: { + Config: { + devKey: 'ef1d42390426e3f7c90ac78272e74344', + androidAppId: 'com.rudderlabs.javascript', + addPropertiesAtRoot: true, + }, + Enabled: true, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript', + headers: { + 'Content-Type': 'application/json', + authentication: 'ef1d42390426e3f7c90ac78272e74344', + }, + params: {}, + body: { + JSON: { + eventValue: + '{"user_actual_role":"system_admin, system_user","user_actual_id":12345}', + eventName: 'test track event HS', + customer_user_id: '12345', + ip: '0.0.0.0', + os: '', + appsflyer_id: 'afUid', + app_version_name: '1.0.0', + bundleIdentifier: 'com.rudderlabs.javascript', + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'af', + description: 'Place properties at root track call with af data', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Order Completed', + sentAt: '2020-08-14T05:30:30.118Z', + context: { + externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }], + source: 'test', + app: { namespace: 'com.rudderlabs.javascript' }, + os: { name: 'android' }, + traits: { anonymousId: '50be5c78-6c3f-4b60-be84-97805a316fb1' }, + library: { name: 'rudder-sdk-ruby-sync', version: '1.0.6' }, + }, + messageId: '7208bbb6-2c4e-45bb-bf5b-ad426f3593e9', + timestamp: '2020-08-14T05:30:30.118Z', + properties: { + tax: 2, + total: 27.5, + coupon: 'hasbros', + revenue: 48, + price: 25, + quantity: 2, + currency: 'ZAR', + discount: 2.5, + order_id: '50314b8e9bcf000000000000', + products: [ + { + sku: '45790-32', + url: 'https://www.example.com/product/path', + name: 'Monopoly: 3rd Edition', + price: 19, + category: 'Games', + quantity: 1, + image_url: 'https:///www.example.com/product/path.jpg', + product_id: '507f1f77bcf86cd799439011', + }, + { + sku: '46493-32', + name: 'Uno Card Game', + price: 3, + category: 'Games', + quantity: 2, + product_id: '505bd76785ebb509fc183733', + }, + ], + shipping: 3, + subtotal: 22.5, + affiliation: 'Google Store', + checkout_id: 'fksdjfsdjfisjf9sdfjsd9f', + }, + anonymousId: '50be5c78-6c3f-4b60-be84-97805a316fb1', + integrations: { AF: { af_uid: 'afUid' } }, + }, + destination: { + Config: { + devKey: 'abcde', + androidAppId: 'com.rudderlabs.javascript', + groupTypeTrait: 'email', + groupValueTrait: 'age', + trackProductsOnce: false, + trackRevenuePerProduct: false, + addPropertiesAtRoot: true, + }, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript', + headers: { 'Content-Type': 'application/json', authentication: 'abcde' }, + params: {}, + body: { + JSON: { + bundleIdentifier: 'com.rudderlabs.javascript', + eventValue: + '{"tax":2,"total":27.5,"coupon":"hasbros","revenue":48,"price":25,"quantity":2,"currency":"ZAR","discount":2.5,"order_id":"50314b8e9bcf000000000000","products":[{"sku":"45790-32","url":"https://www.example.com/product/path","name":"Monopoly: 3rd Edition","price":19,"category":"Games","quantity":1,"image_url":"https:///www.example.com/product/path.jpg","product_id":"507f1f77bcf86cd799439011"},{"sku":"46493-32","name":"Uno Card Game","price":3,"category":"Games","quantity":2,"product_id":"505bd76785ebb509fc183733"}],"shipping":3,"subtotal":22.5,"affiliation":"Google Store","checkout_id":"fksdjfsdjfisjf9sdfjsd9f","af_revenue":48,"af_price":[19,3],"af_quantity":[1,2],"af_order_id":"50314b8e9bcf000000000000","af_content_id":["507f1f77bcf86cd799439011","505bd76785ebb509fc183733"]}', + eventName: 'Order Completed', + eventCurrency: 'ZAR', + eventTime: '2020-08-14T05:30:30.118Z', + appsflyer_id: 'afUid', + }, + XML: {}, + JSON_ARRAY: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/am/processor/data.ts b/test/integrations/destinations/am/processor/data.ts index f28606da0c..b645fb5ac7 100644 --- a/test/integrations/destinations/am/processor/data.ts +++ b/test/integrations/destinations/am/processor/data.ts @@ -11327,4 +11327,57 @@ export const data = [ }, }, }, + { + name: 'am', + description: + 'Test 78 -> Page call invalid event type as page name and template is not provided', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + request_ip: '1.1.1.1', + type: 'page', + userId: '12345', + properties: {}, + integrations: { + All: true, + }, + sentAt: '2019-10-14T11:15:53.296Z', + }, + destination: { + Config: { + apiKey: 'abcde', + useUserDefinedPageEventName: true, + userProvidedPageEventString: '', + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + statusCode: 400, + error: + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'AM', + module: 'destination', + implementation: 'native', + feature: 'processor', + }, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/custify/deleteUsers/data.ts b/test/integrations/destinations/custify/deleteUsers/data.ts new file mode 100644 index 0000000000..3c5a461f69 --- /dev/null +++ b/test/integrations/destinations/custify/deleteUsers/data.ts @@ -0,0 +1,160 @@ +const destType = 'custify'; +const commonData = { + name: destType, + feature: 'userDeletion', + module: 'destination', + version: 'v0', +}; + +export const data = [ + { + description: 'Test 0: should fail when config is not being sent', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + ], + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'Config for deletion not present', + }, + ], + }, + }, + }, + { + description: 'Test 1: should fail when apiKey is not present in config', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder2', + }, + ], + config: { + apiToken: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'api key for deletion not present', + }, + ], + }, + }, + }, + + { + description: 'Test 2: should pass when one of the users is not present in destination', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + { + userId: 'rudder2', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [{ statusCode: 200, status: 'successful' }], + }, + }, + }, + + { + description: + 'Test 3: should fail when one of the users is returning with 4xx(not 404) from destination', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + { + userId: 'rudder3', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [{ statusCode: 400, error: '{"error":"User: rudder3 has a problem"}' }], + }, + }, + }, + + { + description: + 'Test 4: should fail when one of the userAttributes does not contain `userId`', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + { + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [{ statusCode: 400, error: 'User id for deletion not present' }], + }, + }, + }, +].map((props) => ({ ...commonData, ...props })); diff --git a/test/integrations/destinations/custify/network.ts b/test/integrations/destinations/custify/network.ts index 4af6545f9f..242f54c97b 100644 --- a/test/integrations/destinations/custify/network.ts +++ b/test/integrations/destinations/custify/network.ts @@ -1,36 +1,85 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.custify.com/company', - method: 'POST', + { + httpReq: { + url: 'https://api.custify.com/company', + method: 'POST', + }, + httpRes: { + data: { + company_id: '6', + name: 'Pizzeria Presto', + signed_up_at: '2019-05-30T12:00:00.000Z', + size: 15, + website: 'www.pizzeriapresto.com', + industry: 'Restaurant', + plan: 'Platinum', + monthly_revenue: 1234567, + churned: false, + owners_csm: 'john.doe@mail.com', + owners_account: 'john.doe@mail.com', + parent_companies: [ + { + id: '5ec50c9829d3c17c7cf455f2', + }, + { + id: '5ec50c9829d3c17c7cf457f2', + }, + ], + custom_attributes: { + restaurants: 5, + custom: 'template', }, - httpRes: { - data: { - "company_id": "6", - "name": "Pizzeria Presto", - "signed_up_at": "2019-05-30T12:00:00.000Z", - "size": 15, - "website": "www.pizzeriapresto.com", - "industry": "Restaurant", - "plan": "Platinum", - "monthly_revenue": 1234567, - "churned": false, - "owners_csm": "john.doe@mail.com", - "owners_account": "john.doe@mail.com", - "parent_companies": [ - { - "id": "5ec50c9829d3c17c7cf455f2" - }, - { - "id": "5ec50c9829d3c17c7cf457f2" - } - ], - "custom_attributes": { - "restaurants": 5, - "custom": "template" - } - }, - status: 200 - }, - } + }, + status: 200, + }, + }, + + { + httpReq: { + method: 'delete', + url: 'https://api.custify.com/people?user_id=rudder1', + headers: { + Authorization: 'Bearer dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder1 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.custify.com/people?user_id=rudder2', + headers: { + Authorization: 'Bearer dummyApiKey', + }, + }, + httpRes: { + data: { + error: 'User: rudder2 not found', + }, + status: 404, + }, + }, + + { + httpReq: { + method: 'delete', + url: 'https://api.custify.com/people?user_id=rudder3', + headers: { + Authorization: 'Bearer dummyApiKey', + }, + }, + httpRes: { + data: { + error: 'User: rudder3 has a problem', + }, + status: 400, + }, + }, ]; diff --git a/test/integrations/destinations/tiktok_ads/processor/data.ts b/test/integrations/destinations/tiktok_ads/processor/data.ts index 9d7c3a8d10..3b68426fbf 100644 --- a/test/integrations/destinations/tiktok_ads/processor/data.ts +++ b/test/integrations/destinations/tiktok_ads/processor/data.ts @@ -6870,4 +6870,107 @@ export const data = [ }, }, }, + { + name: 'tiktok_ads', + description: 'Test 46 -> V2 -> Event with no properties', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + anonymousId: '21e13f4bc7ceddad', + channel: 'web', + context: { + traits: { + email: 'dd6ff77f54e2106661089bae4d40cdb600979bf7edc9eb65c0942ba55c7c2d7f', + }, + userAgent: + 'Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion', + ip: '13.57.97.131', + locale: 'en-US', + externalId: [ + { + type: 'tiktokExternalId', + id: 'f0e388f53921a51f0bb0fc8a2944109ec188b59172935d8f23020b1614cc44bc', + }, + ], + }, + messageId: '84e26acc-56a5-4835-8233-591137fca468', + session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22', + originalTimestamp: '2019-10-14T09:03:17.562Z', + timestamp: '2020-09-17T19:49:27Z', + type: 'track', + event: 'customEvent', + integrations: { + All: true, + }, + sentAt: '2019-10-14T09:03:22.563Z', + }, + destination: { + Config: { + version: 'v2', + accessToken: 'dummyAccessToken', + pixelCode: '{{PIXEL-CODE}}', + hashUserProperties: false, + sendCustomEvents: true, + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/event/track/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + event_source: 'web', + event_source_id: '{{PIXEL-CODE}}', + partner_name: 'RudderStack', + data: [ + { + event: 'customEvent', + event_id: '84e26acc-56a5-4835-8233-591137fca468', + event_time: 1600372167, + properties: { content_type: 'product' }, + user: { + locale: 'en-US', + email: 'dd6ff77f54e2106661089bae4d40cdb600979bf7edc9eb65c0942ba55c7c2d7f', + external_id: + 'f0e388f53921a51f0bb0fc8a2944109ec188b59172935d8f23020b1614cc44bc', + ip: '13.57.97.131', + user_agent: + 'Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion', + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts b/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts index 2b9341b851..81e125eaca 100644 --- a/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts +++ b/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts @@ -614,4 +614,248 @@ export const data = [ }, }, }, + { + name: 'tiktok_ads_offline_events', + description: 'Test 7 -> `search` standard tiktok Event through event mapping from UI', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + accessToken: 'dummyAccessToken', + hashUserProperties: true, + eventsToStandard: [ + { + from: 'custom event', + to: 'Search', + }, + ], + }, + }, + message: { + type: 'track', + event: 'custom event', + sentAt: '2023-03-22T00:02:33.802Z', + traits: { + email: [ + 'efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + phone: [ + 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + }, + userId: '60241286212', + channel: 'sources', + context: { + sources: { + job_id: '2N4WuoNQpGYmCPASUvnV86QyhY4/Syncher', + version: 'v1.20.0', + job_run_id: 'cgd4a063b2fn2e1j0q90', + task_run_id: 'cgd4a063b2fn2e1j0qa0', + }, + }, + recordId: '16322', + rudderId: '5b4ed73f-69aa-4198-88d1-3d4d509acbf1', + messageId: 'cgd4b663b2fn2e1j8th0', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + phone: 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + value: 32.839999999999996, + emails: + '["efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900","078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f","","","","","","","",""]', + eventId: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + currency: 'USD', + order_id: 60241286212, + eventSetId: '7211223771099742210', + event_name: 'CompletePayment', + }, + receivedAt: '2023-03-22T00:02:33.171Z', + request_ip: '10.7.78.187', + anonymousId: '60241286212', + originalTimestamp: '2023-03-22T00:02:33.802Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/offline/track/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + event_set_id: '7211223771099742210', + event_id: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + order_id: 60241286212, + currency: 'USD', + value: 32.839999999999996, + }, + event: 'Search', + partner_name: 'RudderStack', + context: { + user: { + emails: [ + '4dc75b075057df6f6b729e74a9feed1244dcf8ceb7903eaba13203f3268ae4b9', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + phone_numbers: [ + '28b7b205c2936d2ded022d2587fb2677a76e560e921b3ad615b739b0238baa5d', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + event_set_id: '7211223771099742210', + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'tiktok_ads_offline_events', + description: + 'Test 8 -> `PlaceAnOrder` standard tiktok Event through event `order completed` in payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + accessToken: 'dummyAccessToken', + hashUserProperties: true, + eventsToStandard: [], + }, + }, + message: { + type: 'track', + event: 'order completed', + sentAt: '2023-03-22T00:02:33.802Z', + traits: { + email: [ + 'efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + phone: [ + 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + }, + userId: '60241286212', + channel: 'sources', + context: { + sources: { + job_id: '2N4WuoNQpGYmCPASUvnV86QyhY4/Syncher', + version: 'v1.20.0', + job_run_id: 'cgd4a063b2fn2e1j0q90', + task_run_id: 'cgd4a063b2fn2e1j0qa0', + }, + }, + recordId: '16322', + rudderId: '5b4ed73f-69aa-4198-88d1-3d4d509acbf1', + messageId: 'cgd4b663b2fn2e1j8th0', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + phone: 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + value: 32.839999999999996, + emails: + '["efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900","078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f","","","","","","","",""]', + eventId: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + currency: 'USD', + order_id: 60241286212, + eventSetId: '7211223771099742210', + event_name: 'CompletePayment', + }, + receivedAt: '2023-03-22T00:02:33.171Z', + request_ip: '10.7.78.187', + anonymousId: '60241286212', + originalTimestamp: '2023-03-22T00:02:33.802Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/offline/track/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + event_set_id: '7211223771099742210', + event_id: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + order_id: 60241286212, + currency: 'USD', + value: 32.839999999999996, + }, + event: 'PlaceAnOrder', + partner_name: 'RudderStack', + context: { + user: { + emails: [ + '4dc75b075057df6f6b729e74a9feed1244dcf8ceb7903eaba13203f3268ae4b9', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + phone_numbers: [ + '28b7b205c2936d2ded022d2587fb2677a76e560e921b3ad615b739b0238baa5d', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + event_set_id: '7211223771099742210', + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ];