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,
+ },
+ ],
+ },
+ },
+ },
];