diff --git a/.eslintrc.json b/.eslintrc.json index c2614ac5ef6..d2928e50fdc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -54,7 +54,7 @@ "@typescript-eslint/no-explicit-any": "off", "class-methods-use-this": "off", "@typescript-eslint/return-await": "error", - "import/prefer-default-export": "error", + "import/prefer-default-export": "off", "sonarjs/no-ignored-return": "error", "no-new": "error", "@typescript-eslint/no-shadow": "error", diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000000..b1edf4ef4f4 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,45 @@ +## What are the changes introduced in this PR? + +Write a brief explainer on your code changes. + +## Please explain the objectives of your changes below: + +Put down any required details on the broader aspect of your changes. If there are any dependent changes, **mandatorily** mention them here + +### Type of change + +If the pull request is a **bug-fix**, **enhancement** or a **refactor**, please fill in the details on the changes made. +| Current changes | New changes | +|--|--| +| | | + +If the pull request is a **new feature**, + +### Any technical or performance related pointers to consider with the change? + +### Any new dependencies introduced with this change? + +Any new generic utility introduced or modified. Please explain the changes. + +### If the PR has changes in more than 5 files, please mention why the changes were not split into multiple PRs. + +-- + +### If there are multiple linear items associated with the PR changes, please elaborate on the reason: + +-- + +
+ +### Developer checklist + +[ ] **Please confirm if there are no breaking changes being introduced.** +[ ] Are all related docs linked with the PR? +[ ] Are all changes tested? +[ ] Does this change require any RudderStack documentation changes? +[ ] Are relevant unit and component test-cases added? + +### Reviewer checklist + +[ ] Is the PR tagged correctly as per the changes? +[ ] Verified if there are no security credentials or confidential information in the changes. diff --git a/package-lock.json b/package-lock.json index 28d814c9258..4f83954e5b3 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.1.8", - "@rudderstack/workflow-engine": "^0.5.7", + "@rudderstack/workflow-engine": "^0.6.9", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", @@ -6657,13 +6657,45 @@ "tslib": "^2.4.0" } }, - "node_modules/@rudderstack/json-template-engine": { + "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/sha256-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-4.0.0.tgz", + "integrity": "sha512-MHGJyjE7TX9aaqXj7zk2ppnFUOhaDs5sP+HtNS0evOxn72c+5njUmyJmpGd7TfyoDznZlHMmdo/xGUdu2NIjNQ==", + "dependencies": { + "@aws-crypto/util": "^4.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/util": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-4.0.0.tgz", + "integrity": "sha512-2EnmPy2gsFZ6m8bwUQN4jq+IyXV3quHAcwPOS6ZA3k+geujiqI8aRokO2kFJe+idJ/P3v4qWI186rVMo0+zLDQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@rudderstack/integrations-lib/node_modules/@rudderstack/json-template-engine": { "version": "0.5.5", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.5.5.tgz", + "integrity": "sha512-p3HdTqgZiJjjZmjaHN2paa1e87ifGE5UjkA4zdvge4bBzJbKKMQNWqRg6I96SwoA+hsxNkW/f9R83SPLU9t7LA==" }, - "node_modules/@rudderstack/workflow-engine": { - "version": "0.5.7", - "license": "MIT", + "node_modules/@rudderstack/integrations-lib/node_modules/@rudderstack/workflow-engine": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.5.8.tgz", + "integrity": "sha512-H1aCowYqTnOoqJtL9cGDhdhoGNl+KzqmVbSjFmE7n75onZaIMs87+HQyW08jYxS9l1Uo4TL8SAvzFICqFqkBbw==", "dependencies": { "@aws-crypto/sha256-js": "^4.0.0", "@rudderstack/json-template-engine": "^0.5.5", @@ -6673,27 +6705,81 @@ "object-sizeof": "^2.6.3" } }, + "node_modules/@rudderstack/json-template-engine": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.8.2.tgz", + "integrity": "sha512-9oMBnqgNuwiXd7MUlNOAchCnJXQAy6w6XGmDqDM6iXdYDkvqYFiq7sbg5j4SdtpTTST293hahREr5PXfFVzVKg==" + }, + "node_modules/@rudderstack/workflow-engine": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.6.9.tgz", + "integrity": "sha512-b0ZHURJfCj2REIL/w7AJgJ+K5BGwIVX3sRDZQqN3F4YWcZX3ZYUXo7gtUeb99FLnZzm7KuThIWR02Fxwos+L4Q==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.0.0", + "@rudderstack/json-template-engine": "^0.8.1", + "jsonata": "^2.0.3", + "lodash": "^4.17.21", + "object-sizeof": "^2.6.3", + "yaml": "^2.3.2" + } + }, "node_modules/@rudderstack/workflow-engine/node_modules/@aws-crypto/sha256-js": { - "version": "4.0.0", - "license": "Apache-2.0", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dependencies": { - "@aws-crypto/util": "^4.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, "node_modules/@rudderstack/workflow-engine/node_modules/@aws-crypto/util": { - "version": "4.0.0", - "license": "Apache-2.0", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "dependencies": { "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@rudderstack/workflow-engine/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD" + "node_modules/@rudderstack/workflow-engine/node_modules/@smithy/is-array-buffer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", + "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rudderstack/workflow-engine/node_modules/@smithy/util-buffer-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", + "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", + "dependencies": { + "@smithy/is-array-buffer": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rudderstack/workflow-engine/node_modules/@smithy/util-utf8": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz", + "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/@sideway/address": { "version": "4.1.4", @@ -19379,8 +19465,9 @@ } }, "node_modules/tslib": { - "version": "2.5.3", - "license": "0BSD" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsscmp": { "version": "1.0.6", @@ -19918,9 +20005,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.3.1", - "dev": true, - "license": "ISC", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "engines": { "node": ">= 14" } diff --git a/package.json b/package.json index e4a4d9151c9..8a8b6177bdc 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "build:ci": "tsc -p tsconfig.json", "build:swagger": "npm run build && npm run setup:swagger", "build": "npm run build:ci && npm run copy", + "clean:build": "npm run clean && npm run build", "build:clean": "npm run clean && npm run build", "verify": "eslint . || exit 1; npm run test:js || exit 1", "test:testRouter": "jest testRouter --detectOpenHandles --coverage --notify --watchAll=false", @@ -64,7 +65,7 @@ "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", "@rudderstack/integrations-lib": "^0.1.8", - "@rudderstack/workflow-engine": "^0.5.7", + "@rudderstack/workflow-engine": "^0.6.9", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml new file mode 100644 index 00000000000..fe8697bc31a --- /dev/null +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -0,0 +1,88 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ./utils + exportAll: true + - name: defaultRequestConfig + path: ../../../../v0/util + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + - name: getDestinationExternalID + path: ../../../../v0/util + - name: httpGET + path: ../../../../adapters/network + - name: processAxiosResponse + path: ../../../../adapters/utils/networkUtils + + +steps: + - name: checkIfProcessed + condition: .message.statusCode + template: | + $.batchMode ? .message.body.JSON : .message + onComplete: return + + - name: messageType + template: | + .message.type.toLowerCase() + + - name: validateInput + template: | + let messageType = $.outputs.messageType + $.assert(messageType, "message Type is not present. Aborting") + $.assert(messageType in {{$.EventType.([.IDENTIFY])}}, "message type " + messageType + " is not supported") + $.assertConfig(.destination.Config.apiToken, "API Token is not present. Aborting") + $.assertConfig(.destination.Config.domain, "Gladly domain is not present. Aborting") + $.assertConfig(.destination.Config.userName, "User Name is not present. Aborting") + + - name: preparePayload + template: | + $.context.payload = { + name: .message.traits.name || .message.context.traits.name, + image: .message.traits.avatar || .message.context.traits.avatar, + address: .message.traits.address || .message.context.traits.address + } + $.context.payload.address && typeof $.context.payload.address === "object" ? $.context.payload.address = JSON.stringify($.context.payload.address) + $.context.payload.emails = $.formatField(.message, "email") + $.context.payload.phones = $.formatField(.message, "phone") + $.context.payload.customAttributes = $.getCustomAttributes(.message) + $.context.payload.externalCustomerId = $.getExternalCustomerId(.message) + $.context.payload.id = $.getCustomerId(.message) + $.context.payload = $.removeUndefinedAndNullValues($.context.payload) + + - name: validatePayload + template: | + $.validatePayload($.context.payload) + + - name: findCustomer + description: Find if customer is exist or not based on email, phone or externalCustomerId + condition: $.getQueryParams($.context.payload) !== undefined + template: | + const requestOptions = { + headers: $.getHeaders(.destination) + } + const endpoint = $.getEndpoint(.destination) + "?" + $.getQueryParams($.context.payload); + const rawResponse = await $.httpGET(endpoint,requestOptions) + const processedResponse = $.processAxiosResponse(rawResponse) + processedResponse + + - name: createCustomer + description: Build response for create customer + condition: $.outputs.findCustomer.status === 400 || ($.outputs.findCustomer.status === 200 && $.outputs.findCustomer.response.length === 0) || $.getQueryParams($.context.payload) === undefined + template: | + const response = $.defaultRequestConfig() + response.body.JSON = $.removeUndefinedAndNullValues($.context.payload) + response.endpoint = $.getEndpoint(.destination) + response.method = "POST" + response.headers = $.getHeaders(.destination) + response + else: + name: updateCustomer + description: Build response for update customer + template: | + const response = $.defaultRequestConfig() + response.body.JSON = $.removeUndefinedAndNullValues($.context.payload.{~["id"]}) + response.endpoint = $.getEndpoint(.destination) + "/" + $.outputs.findCustomer.response[0].id + response.method = "PATCH" + response.headers = $.getHeaders(.destination) + response diff --git a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml new file mode 100644 index 00000000000..341e5552c83 --- /dev/null +++ b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml @@ -0,0 +1,33 @@ +bindings: + - name: handleRtTfSingleEventError + path: ../../../../v0/util/index + - path: ./utils + exportAll: true + +steps: + - name: validateInput + template: | + $.assert(Array.isArray(^) && ^.length > 0, "Invalid event array") + + - name: transform + externalWorkflow: + path: ./procWorkflow.yaml + loopOverInput: true + + - name: successfulEvents + template: | + $.outputs.transform#idx.output.({ + "batchedRequest": ., + "batched": false, + "destination": ^[idx].destination, + "metadata": ^[idx].metadata[], + "statusCode": 200 + })[] + - name: failedEvents + template: | + $.outputs.transform#idx.error.( + $.handleRtTfSingleEventError(^[idx], .originalError ?? ., {}) + )[] + - name: finalPayload + template: | + [...$.outputs.successfulEvents, ...$.outputs.failedEvents] \ No newline at end of file diff --git a/src/cdk/v2/destinations/gladly/utils.js b/src/cdk/v2/destinations/gladly/utils.js new file mode 100644 index 00000000000..5abc9b6dd0f --- /dev/null +++ b/src/cdk/v2/destinations/gladly/utils.js @@ -0,0 +1,175 @@ +const get = require('get-value'); +const { InstrumentationError } = require('@rudderstack/integrations-lib'); +const { + base64Convertor, + getDestinationExternalID, +} = require('../../../../v0/util'); +const { MappedToDestinationKey } = require('../../../../constants'); + +const reservedCustomAttributes = [ + 'email', + 'phone', + 'address', + 'name', + 'avatar', + 'firstName', + 'lastName', + 'userId', +]; + +const externalIdKey = 'context.externalId.0.id'; +const identifierTypeKey = 'context.externalId.0.identifierType'; + +const getHeaders = (destination) => { + const { apiToken, userName } = destination.Config; + const credentials = `${userName}:${apiToken}`; + const base64Credentials = base64Convertor(credentials); + return { + 'Content-Type': 'application/json', + Authorization: `Basic ${base64Credentials}`, + }; +}; + +const getEndpoint = (destination) => { + const { domain } = destination.Config; + return `https://${domain}/api/v1/customer-profiles`; +}; + + +const getFieldValue = (field) => { + if (field) { + if (Array.isArray(field)) { + return field.map((item) => ({ original: item })); + } + return [{ original: field }]; + } + return undefined; +} + +const formatFieldForRETl = (message, fieldName) => { + const identifierType = get(message, identifierTypeKey); + if (identifierType && identifierType === fieldName) { + const field = get(message, externalIdKey); + if (field) { + return [{ original: field }]; + } + } + const key = fieldName === 'email' ? 'emails' : 'phones'; + const field = get(message, `traits.${key}`); + return getFieldValue(field); +}; + +const formatFieldForEventStream = (message, fieldName) => { + const field = get(message, `context.traits.${fieldName}`); + return getFieldValue(field); +}; + +const formatField = (message, fieldName) => { + const mappedToDestination = get(message, MappedToDestinationKey); + if (mappedToDestination) { + return formatFieldForRETl(message, fieldName); + } + return formatFieldForEventStream(message, fieldName); + +}; + +const getCustomAttributes = (message) => { + const mappedToDestination = get(message, MappedToDestinationKey); + // for rETL + if (mappedToDestination) { + if (message?.traits?.customAttributes && typeof message.traits.customAttributes === 'object') { + return Object.keys(message.traits.customAttributes).length > 0 ? message.traits.customAttributes : undefined; + } + return undefined; + } + + // for event stream + const customAttributes = message.context?.traits || {}; + reservedCustomAttributes.forEach((customAttribute) => { + if (customAttributes[customAttribute]) { + delete customAttributes[customAttribute]; + } + }); + return Object.keys(customAttributes).length > 0 ? customAttributes : undefined; +}; + +const getExternalCustomerId = (message) => { + const mappedToDestination = get(message, MappedToDestinationKey); + // for rETL + if (mappedToDestination) { + const identifierType = get(message, identifierTypeKey); + if (identifierType === 'externalCustomerId') { + return get(message, externalIdKey); + } + + if (message?.traits?.externalCustomerId) { + return message.traits.externalCustomerId; + } + + return undefined; + } + + // for event stream + return message.userId; +}; + +const getCustomerId = (message) => { + const mappedToDestination = get(message, MappedToDestinationKey); + // for rETL + if (mappedToDestination) { + const identifierType = get(message, identifierTypeKey); + if (identifierType === 'id') { + return get(message, externalIdKey); + } + + if (message?.traits?.id) { + return message.traits.id; + } + + return undefined; + } + + // for event stream + const customerId = getDestinationExternalID(message, 'GladlyCustomerId'); + if (customerId) { + return customerId; + } + + return undefined; +}; + +const validatePayload = (payload) => { + if (!(payload?.phones || payload?.emails || payload?.id || payload?.externalCustomerId)) { + throw new InstrumentationError('One of phone, email, userId or GladlyCustomerId is required for an identify call'); + } +}; + +const getQueryParams = (payload) => { + if (payload.emails && payload.emails.length > 0) { + return `email=${encodeURIComponent(payload.emails[0].original)}` + } + + if (payload.phones && payload.phones.length > 0) { + return `phoneNumber=${encodeURIComponent(payload.phones[0].original)}` + } + + if (payload.externalCustomerId) { + return `externalCustomerId=${encodeURIComponent(payload.externalCustomerId)}` + } + + return undefined; +} + +module.exports = { + getHeaders, + getEndpoint, + formatField, + getFieldValue, + getCustomerId, + getQueryParams, + validatePayload, + formatFieldForRETl, + getCustomAttributes, + getExternalCustomerId, + formatFieldForEventStream +}; diff --git a/src/cdk/v2/destinations/gladly/utils.test.js b/src/cdk/v2/destinations/gladly/utils.test.js new file mode 100644 index 00000000000..116f150448f --- /dev/null +++ b/src/cdk/v2/destinations/gladly/utils.test.js @@ -0,0 +1,503 @@ +const { + getHeaders, + getEndpoint, + formatField, + getCustomerId, + getFieldValue, + getQueryParams, + validatePayload, + formatFieldForRETl, + getCustomAttributes, + getExternalCustomerId, + formatFieldForEventStream, +} = require('./utils'); +const { base64Convertor } = require('../../../../v0/util'); + +describe('Unit test cases for getHeaders function', () => { + it('Should return headers', () => { + const destination = { + Config: { + apiToken: 'token', + userName: 'user', + }, + }; + const expectedHeaders = { + 'Content-Type': 'application/json', + Authorization: `Basic ${base64Convertor('user:token')}`, + }; + + const result = getHeaders(destination); + + expect(result).toEqual(expectedHeaders); + }); +}); + +describe('Unit test cases for getEndpoint function', () => { + it('Should return destination endpoint', () => { + const destination = { + Config: { + domain: 'rudderstack.us-uat.gladly.qa', + }, + }; + const expected = 'https://rudderstack.us-uat.gladly.qa/api/v1/customer-profiles'; + const result = getEndpoint(destination); + expect(result).toBe(expected); + }); +}); + +describe('Unit test cases for getFieldValue function', () => { + it('Should return an array with a single object containing the original value when the input field is a string', () => { + const field = 'rudderlabs1@gmail.com'; + const result = getFieldValue(field); + expect(result).toEqual([{ original: field }]); + }); + + it('should return an array with each element containing the original value when the input field is an array', () => { + const field = ['rudderlabs1@gmail.com', 'rudderlabs2@gmail.com', 'rudderlabs3@gmail.com']; + const result = getFieldValue(field); + expect(result).toEqual([ + { + original: 'rudderlabs1@gmail.com', + }, + { + original: 'rudderlabs2@gmail.com', + }, + { + original: 'rudderlabs3@gmail.com', + }, + ]); + }); + + it('Should return undefined when the input field is null', () => { + const field = null; + const result = getFieldValue(field); + expect(result).toBeUndefined(); + }); + + it('Should return undefined when the input field is undefined', () => { + const field = undefined; + const result = getFieldValue(field); + expect(result).toBeUndefined(); + }); +}); + +describe('Unit test cases for formatFieldForRETl function', () => { + it('should return the object containing the original value when identifierType matches fieldName', () => { + const message = { + context: { + externalId: [ + { + id: 'test@rudderlabs.com', + identifierType: 'email', + }, + ], + }, + traits: { + emails: ['test@rudderlabs.com', 'test@rudderlabshome.com'], + }, + }; + const fieldName = 'email'; + const expected = [{ original: 'test@rudderlabs.com' }]; + + const result = formatFieldForRETl(message, fieldName); + + expect(result).toEqual(expected); + }); + + it('Should retrieve the email value from traits when fieldName does not match with identifierType', () => { + const message = { + context: { + externalId: [ + { + id: '+91 9999999999', + identifierType: 'phone', + }, + ], + }, + traits: { + emails: ['test@rudderlabs.com', 'test@rudderlabshome.com'], + }, + }; + const fieldName = 'email'; + const expected = [{ original: 'test@rudderlabs.com' }, { original: 'test@rudderlabshome.com' }]; + + const result = formatFieldForRETl(message, fieldName); + + expect(result).toEqual(expected); + }); +}); + +describe('Unit test cases for formatFieldForEventStream function', () => { + it('Should return field value when fieldName exist in payload', () => { + const message = { + context: { + traits: { + phone: '+91 9999999999', + }, + }, + }; + const fieldName = 'phone'; + const expected = [{ original: '+91 9999999999' }]; + + const result = formatFieldForEventStream(message, fieldName); + expect(result).toEqual(expected); + }); + + it('Should return undefined when fieldName does not exist in payload', () => { + const message = { + context: { + traits: { + phone: '+91 9999999999', + }, + }, + }; + const fieldName = 'email'; + const result = formatFieldForEventStream(message, fieldName); + expect(result).toBeUndefined(); + }); +}); + +describe('Unit test cases for formatField function', () => { + describe('rETL tests', () => { + it('Should return field value from externalId when identifier type matches with fieldName', () => { + const message = { + context: { + externalId: [ + { + id: '+91 9999999999', + identifierType: 'phone', + }, + ], + mappedToDestination: true, + }, + traits: { + emails: ['test@rudderlabs.com', 'test@rudderlabshome.com'], + }, + }; + const result = formatField(message, 'phone'); + expect(result).toEqual([{ original: '+91 9999999999' }]); + }); + + it('Should return field value from traits when identifier type does not match with fieldName', () => { + const message = { + context: { + externalId: [ + { + id: 'user@1', + identifierType: 'externalCustomerId', + }, + ], + mappedToDestination: true, + }, + traits: { + phones: ['+91 9999999999'], + }, + }; + const result = formatField(message, 'phone'); + expect(result).toEqual([{ original: '+91 9999999999' }]); + }); + }); + + describe('Event stream tests', () => { + it('Should return field value from payload', () => { + const message = { + context: { + traits: { + phone: ['+91 9999999999'], + }, + }, + }; + const result = formatField(message, 'phone'); + expect(result).toEqual([{ original: '+91 9999999999' }]); + }); + }); +}); + +describe('Unit test cases for getCustomAttributes function', () => { + describe('rETL tests', () => { + it('Should return custom attributes from payload', () => { + const message = { + context: { + mappedToDestination: true, + }, + traits: { + customAttributes: { + attribute1: 'value1', + attribute2: 'value2', + }, + }, + }; + const result = getCustomAttributes(message); + expect(result).toEqual({ + attribute1: 'value1', + attribute2: 'value2', + }); + }); + + it('Should return undefined when empty custom attributes object is present in payload', () => { + const message = { + context: { + mappedToDestination: true, + }, + traits: { + customAttributes: {}, + }, + }; + const result = getCustomAttributes(message); + expect(result).toBeUndefined(); + }); + + it('Should return undefined when no custom attributes are present in payload', () => { + const message = { + context: { + mappedToDestination: true, + }, + traits: {}, + }; + const result = getCustomAttributes(message); + expect(result).toBeUndefined(); + }); + }); + + describe('Event stream tests', () => { + it('Should filter traits and return remaining custom attributes from payload', () => { + const message = { + context: { + traits: { + name: 'John Doe', + email: 'john@gmail.com', + age: 65, + source: 'rudderstack', + }, + }, + }; + const result = getCustomAttributes(message); + expect(result).toEqual({ + age: 65, + source: 'rudderstack', + }); + }); + + it('Should return undefined when empty traits object is present in payload', () => { + const message = { + context: { + traits: {}, + }, + }; + const result = getCustomAttributes(message); + expect(result).toBeUndefined(); + }); + + it('Should return undefined when no traits object is present in payload', () => { + const message = { + context: {}, + }; + const result = getCustomAttributes(message); + expect(result).toBeUndefined(); + }); + }); +}); + +describe('Unit test cases for getExternalCustomerId function', () => { + describe('rETL tests', () => { + it('Should return the external ID when the identifier type is "externalCustomerId"', () => { + const message = { + context: { + externalId: [ + { + id: 'externalCustomer@1', + identifierType: 'externalCustomerId', + }, + ], + mappedToDestination: true, + }, + }; + + const result = getExternalCustomerId(message); + expect(result).toBe('externalCustomer@1'); + }); + + it('Should return the external ID from traits when identifier type is not "externalCustomerId"', () => { + const message = { + context: { + externalId: [ + { + id: 'test@rudderlabs.com', + identifierType: 'email', + }, + ], + mappedToDestination: true, + }, + traits: { + externalCustomerId: 'externalCustomer@1', + }, + }; + const result = getExternalCustomerId(message); + expect(result).toBe('externalCustomer@1'); + }); + + it('Should return undefined when external customer id is not present in payload', () => { + const message = { + context: { + mappedToDestination: true, + }, + }; + + const result = getExternalCustomerId(message); + expect(result).toBeUndefined(); + }); + }); + + describe('Event stream tests', () => { + it('Should return the external ID as userId is present in payload', () => { + const message = { + userId: 'externalCustomer@1', + context: {}, + }; + + const result = getExternalCustomerId(message); + expect(result).toBe('externalCustomer@1'); + }); + + it('Should return undefined when userId is not present in payload', () => { + const message = { + context: {}, + }; + + const result = getExternalCustomerId(message); + expect(result).toBeUndefined(); + }); + }); +}); + +describe('Unit test cases for getCustomerId function', () => { + describe('rETL tests', () => { + it('Should return the customerId when the identifier type is "id"', () => { + const message = { + context: { + externalId: [ + { + id: 'user@1', + identifierType: 'id', + }, + ], + mappedToDestination: true, + }, + }; + + const result = getCustomerId(message); + expect(result).toBe('user@1'); + }); + + it('Should return the customerId from traits when identifier type is not "id"', () => { + const message = { + context: { + externalId: [ + { + id: 'test@rudderlabs.com', + identifierType: 'email', + }, + ], + mappedToDestination: true, + }, + traits: { + id: 'user@1', + }, + }; + const result = getCustomerId(message); + expect(result).toBe('user@1'); + }); + + it('Should return undefined when customerId is not present in payload', () => { + const message = { + context: { + mappedToDestination: true, + }, + }; + + const result = getCustomerId(message); + expect(result).toBeUndefined(); + }); + }); + + describe('Event stream tests', () => { + it('Should return the customerId as GladlyCustomerId is present in payload', () => { + const message = { + context: { + externalId: [ + { + id: 'user@1', + type: 'GladlyCustomerId', + }, + ], + }, + }; + const result = getCustomerId(message); + expect(result).toBe('user@1'); + }); + + it('Should return undefined when GladlyCustomerId is not present in payload', () => { + const message = { + context: {}, + }; + const result = getCustomerId(message); + expect(result).toBeUndefined(); + }); + }); +}); + +describe('Unit test cases for validatePayload function', () => { + it('Should throw an error when payload does not have all required fields', () => { + const payload = {}; + try { + validatePayload(payload); + } catch (err) { + expect(err.message).toEqual( + 'One of phone, email, userId or GladlyCustomerId is required for an identify call', + ); + } + }); + + it('Should throw an error when payload is undefined', () => { + const payload = undefined; + try { + validatePayload(payload); + } catch (err) { + expect(err.message).toEqual( + 'One of phone, email, userId or GladlyCustomerId is required for an identify call', + ); + } + }); +}); + +describe('Unit test cases for getQueryParams function', () => { + it('Should return email as query parameter if email is present in payload', () => { + const payload = { + emails: [{ original: 'test@example.com' }], + }; + const result = getQueryParams(payload); + expect(result).toBe('email=test%40example.com'); + }); + + it('Should return phone as query parameter if phone is present in payload', () => { + const payload = { + phones: [{ original: '+91 9999999999' }], + }; + const result = getQueryParams(payload); + expect(result).toBe('phoneNumber=%2B91%209999999999'); + }); + + it('Should return externalCustomerId as query parameter if externalCustomerId is present in payload', () => { + const payload = { + externalCustomerId: 'externalCustomer@1', + }; + const result = getQueryParams(payload); + expect(result).toBe('externalCustomerId=externalCustomer%401'); + }); + + it('should return undefined when no supported query params are present in payload', () => { + const payload = {}; + const result = getQueryParams(payload); + expect(result).toBeUndefined(); + }); +}); diff --git a/src/cdk/v2/handler.ts b/src/cdk/v2/handler.ts index 3058d62e51b..47d6d101797 100644 --- a/src/cdk/v2/handler.ts +++ b/src/cdk/v2/handler.ts @@ -64,8 +64,12 @@ export function getCachedWorkflowEngine( return workflowEnginePromiseMap[destName][feature]; } -export async function executeWorkflow(workflowEngine: WorkflowEngine, parsedEvent: FixMe) { - const result = await workflowEngine.execute(parsedEvent); +export async function executeWorkflow( + workflowEngine: WorkflowEngine, + parsedEvent: FixMe, + requestMetadata: NonNullable = {}, +) { + const result = await workflowEngine.execute(parsedEvent, { requestMetadata }); // TODO: Handle remaining output scenarios return result.output; } @@ -74,11 +78,12 @@ export async function processCdkV2Workflow( destType: string, parsedEvent: FixMe, feature: string, + requestMetadata: NonNullable = {}, bindings: Record = {}, ) { try { const workflowEngine = await getCachedWorkflowEngine(destType, feature, bindings); - return await executeWorkflow(workflowEngine, parsedEvent); + return await executeWorkflow(workflowEngine, parsedEvent, requestMetadata); } catch (error) { throw getErrorInfo(error, isCdkV2Destination(parsedEvent), defTags); } diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index d1e199c9e2d..48e7c6d8bb3 100644 --- a/src/constants/destinationCanonicalNames.js +++ b/src/constants/destinationCanonicalNames.js @@ -140,7 +140,7 @@ const DestCanonicalNames = { 'twitter_ads', 'TWITTER_ADS', ], - BRAZE: ['BRAZE', 'Braze', 'braze'], + BRAZE: ['BRAZE', 'Braze', 'braze'] }; module.exports = { DestHandlerMap, DestCanonicalNames }; diff --git a/src/controllers/delivery.ts b/src/controllers/delivery.ts index 5dee091c469..af94764d461 100644 --- a/src/controllers/delivery.ts +++ b/src/controllers/delivery.ts @@ -1,15 +1,15 @@ import { Context } from 'koa'; -import MiscService from '../services/misc'; +import { MiscService } from '../services/misc'; import { DeliveryResponse, ProcessorTransformationOutput } from '../types/index'; -import ServiceSelector from '../helpers/serviceSelector'; -import DeliveryTestService from '../services/delivertTest/deliveryTest'; -import ControllerUtility from './util'; +import { ServiceSelector } from '../helpers/serviceSelector'; +import { DeliveryTestService } from '../services/delivertTest/deliveryTest'; +import { ControllerUtility } from './util'; import logger from '../logger'; -import DestinationPostTransformationService from '../services/destination/postTransformation'; +import { DestinationPostTransformationService } from '../services/destination/postTransformation'; import tags from '../v0/util/tags'; import { FixMe } from '../util/types'; -export default class DeliveryController { +export class DeliveryController { public static async deliverToDestination(ctx: Context) { logger.debug('Native(Delivery):: Request to transformer::', JSON.stringify(ctx.request.body)); let deliveryResponse: DeliveryResponse; diff --git a/src/controllers/destination.ts b/src/controllers/destination.ts index 60eac8a56c4..71075d1b4ce 100644 --- a/src/controllers/destination.ts +++ b/src/controllers/destination.ts @@ -1,22 +1,22 @@ import { Context } from 'koa'; -import MiscService from '../services/misc'; -import PreTransformationDestinationService from '../services/destination/preTransformation'; -import PostTransformationDestinationService from '../services/destination/postTransformation'; +import { MiscService } from '../services/misc'; +import { DestinationPreTransformationService } from '../services/destination/preTransformation'; +import { DestinationPostTransformationService } from '../services/destination/postTransformation'; import { ProcessorTransformationRequest, RouterTransformationRequest, ProcessorTransformationResponse, RouterTransformationResponse, } from '../types/index'; -import ServiceSelector from '../helpers/serviceSelector'; -import ControllerUtility from './util'; +import { ServiceSelector } from '../helpers/serviceSelector'; +import { ControllerUtility } from './util'; import stats from '../util/stats'; import logger from '../logger'; import { getIntegrationVersion } from '../util/utils'; import tags from '../v0/util/tags'; -import DynamicConfigParser from '../util/dynamicConfigParser'; +import { DynamicConfigParser } from '../util/dynamicConfigParser'; -export default class DestinationController { +export class DestinationController { public static async destinationTransformAtProcessor(ctx: Context) { const startTime = new Date(); logger.debug( @@ -36,7 +36,7 @@ export default class DestinationController { const integrationService = ServiceSelector.getDestinationService(events); try { integrationService.init(); - events = PreTransformationDestinationService.preProcess( + events = DestinationPreTransformationService.preProcess( events, ctx, ) as ProcessorTransformationRequest[]; @@ -59,7 +59,7 @@ export default class DestinationController { tags.FEATURES.PROCESSOR, ); metaTO.metadata = ev.metadata; - const errResp = PostTransformationDestinationService.handleProcessorTransformFailureEvents( + const errResp = DestinationPostTransformationService.handleProcessorTransformFailureEvents( error, metaTO, ); @@ -110,7 +110,7 @@ export default class DestinationController { const integrationService = ServiceSelector.getDestinationService(events); let resplist: RouterTransformationResponse[]; try { - events = PreTransformationDestinationService.preProcess(events, ctx); + events = DestinationPreTransformationService.preProcess(events, ctx); const timestampCorrectEvents = ControllerUtility.handleTimestampInEvents(events); events = DynamicConfigParser.process(timestampCorrectEvents); resplist = await integrationService.doRouterTransformation( @@ -127,7 +127,7 @@ export default class DestinationController { tags.FEATURES.ROUTER, ); metaTO.metadatas = events.map((ev) => ev.metadata); - const errResp = PostTransformationDestinationService.handleRouterTransformFailureEvents( + const errResp = DestinationPostTransformationService.handleRouterTransformFailureEvents( error, metaTO, ); @@ -165,7 +165,7 @@ export default class DestinationController { let events = routerRequest.input; const integrationService = ServiceSelector.getDestinationService(events); try { - events = PreTransformationDestinationService.preProcess(events, ctx); + events = DestinationPreTransformationService.preProcess(events, ctx); const timestampCorrectEvents = ControllerUtility.handleTimestampInEvents(events); const resplist = integrationService.doBatchTransformation( timestampCorrectEvents, @@ -182,7 +182,7 @@ export default class DestinationController { tags.FEATURES.BATCH, ); metaTO.metadatas = events.map((ev) => ev.metadata); - const errResp = PostTransformationDestinationService.handleBatchTransformFailureEvents( + const errResp = DestinationPostTransformationService.handleBatchTransformFailureEvents( error, metaTO, ); diff --git a/src/controllers/eventTest.ts b/src/controllers/eventTest.ts index 8888b638075..d5507f65d29 100644 --- a/src/controllers/eventTest.ts +++ b/src/controllers/eventTest.ts @@ -1,9 +1,9 @@ import { Context } from 'koa'; -import EventTesterService from '../services/eventTest/eventTester'; +import { EventTesterService } from '../services/eventTest/eventTester'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { CatchErr, FixMe } from '../util/types'; -export default class EventTestController { +export class EventTestController { private static API_VERSION = '1'; public static async testEvent(ctx: Context) { diff --git a/src/controllers/misc.ts b/src/controllers/misc.ts index 92ec33f80f3..e2efdab5db7 100644 --- a/src/controllers/misc.ts +++ b/src/controllers/misc.ts @@ -1,7 +1,7 @@ import { Context } from 'koa'; -import MiscService from '../services/misc'; +import { MiscService } from '../services/misc'; -export default class MiscController { +export class MiscController { public static healthStats(ctx: Context) { ctx.body = MiscService.getHealthInfo(); ctx.status = 200; diff --git a/src/controllers/profile.ts b/src/controllers/profile.ts index 984f4ac6457..a0a62bf4792 100644 --- a/src/controllers/profile.ts +++ b/src/controllers/profile.ts @@ -1,8 +1,8 @@ import { Context } from 'koa'; -import ProfileService from '../services/profile'; -import ControllerUtility from './util'; +import { ProfileService } from '../services/profile'; +import { ControllerUtility } from './util'; -export default class ProfileController { +export class ProfileController { /** * Example usage of API * diff --git a/src/controllers/regulation.ts b/src/controllers/regulation.ts index cefe1f748ad..a50541780d6 100644 --- a/src/controllers/regulation.ts +++ b/src/controllers/regulation.ts @@ -1,15 +1,14 @@ import { Context } from 'koa'; import logger from '../logger'; import { UserDeletionRequest, UserDeletionResponse } from '../types'; -import ServiceSelector from '../helpers/serviceSelector'; +import { ServiceSelector } from '../helpers/serviceSelector'; import tags from '../v0/util/tags'; import stats from '../util/stats'; -import PostTransformationDestinationService from '../services/destination/postTransformation'; +import { DestinationPostTransformationService } from '../services/destination/postTransformation'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { CatchErr } from '../util/types'; -// TODO: refactor this class to new format -export default class RegulationController { +export class RegulationController { public static async deleteUsers(ctx: Context) { logger.debug( 'Native(Process-Transform):: Requst to transformer::', @@ -44,7 +43,7 @@ export default class RegulationController { tags.FEATURES.USER_DELETION, ); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const errResp = PostTransformationDestinationService.handleUserDeletionFailureEvents( + const errResp = DestinationPostTransformationService.handleUserDeletionFailureEvents( error, metaTO, ); diff --git a/src/controllers/source.ts b/src/controllers/source.ts index b1e7e8fe129..ef5483a756f 100644 --- a/src/controllers/source.ts +++ b/src/controllers/source.ts @@ -1,11 +1,11 @@ import { Context } from 'koa'; -import MiscService from '../services/misc'; -import ServiceSelector from '../helpers/serviceSelector'; -import ControllerUtility from './util'; +import { MiscService } from '../services/misc'; +import { ServiceSelector } from '../helpers/serviceSelector'; +import { ControllerUtility } from './util'; import logger from '../logger'; -import PostTransformationServiceSource from '../services/source/postTransformation'; +import { SourcePostTransformationService } from '../services/source/postTransformation'; -export default class SourceController { +export class SourceController { public static async sourceTransform(ctx: Context) { logger.debug( 'Native(Source-Transform):: Request to transformer::', @@ -30,7 +30,7 @@ export default class SourceController { ctx.body = resplist; } catch (err: any) { const metaTO = integrationService.getTags(); - const resp = PostTransformationServiceSource.handleFailureEventsSource(err, metaTO); + const resp = SourcePostTransformationService.handleFailureEventsSource(err, metaTO); ctx.body = [resp]; } ControllerUtility.postProcess(ctx); diff --git a/src/controllers/trackingPlan.ts b/src/controllers/trackingPlan.ts index 66bf3607003..74e47e0ec92 100644 --- a/src/controllers/trackingPlan.ts +++ b/src/controllers/trackingPlan.ts @@ -1,8 +1,8 @@ import { Context } from 'koa'; -import TrackingPlanservice from '../services/trackingPlan'; -import ControllerUtility from './util'; +import { TrackingPlanservice } from '../services/trackingPlan'; +import { ControllerUtility } from './util'; -export default class TrackingPlanController { +export class TrackingPlanController { public static async validateTrackingPlan(ctx: Context) { const events = ctx.request.body; const requestSize = Number(ctx.request.get('content-length')); diff --git a/src/controllers/userTransform.ts b/src/controllers/userTransform.ts index 6cbf5780778..c344bd072ad 100644 --- a/src/controllers/userTransform.ts +++ b/src/controllers/userTransform.ts @@ -1,15 +1,15 @@ import { Context } from 'koa'; import { ProcessorTransformationRequest, UserTransformationServiceResponse } from '../types/index'; -import UserTransformService from '../services/userTransform'; +import { UserTransformService } from '../services/userTransform'; import logger from '../logger'; import { setupUserTransformHandler, extractLibraries, validateCode, } from '../util/customTransformer'; -import ControllerUtility from './util'; +import { ControllerUtility } from './util'; -export default class UserTransformController { +export class UserTransformController { public static async transform(ctx: Context) { logger.debug( '(User transform - router:/customTransform ):: Request to transformer', diff --git a/src/controllers/util/index.test.ts b/src/controllers/util/index.test.ts index 0bc5f174b07..e23d3f68324 100644 --- a/src/controllers/util/index.test.ts +++ b/src/controllers/util/index.test.ts @@ -1,4 +1,4 @@ -import ControllerUtility from './index'; +import { ControllerUtility } from './index'; describe('adaptInputToVersion', () => { it('should return the input unchanged when the implementation version is not found', () => { diff --git a/src/controllers/util/index.ts b/src/controllers/util/index.ts index c52eb6899e1..75d3d8ffa7c 100644 --- a/src/controllers/util/index.ts +++ b/src/controllers/util/index.ts @@ -15,7 +15,7 @@ import { getValueFromMessage } from '../../v0/util'; import genericFieldMap from '../../v0/util/data/GenericFieldMapping.json'; import { EventType, MappedToDestinationKey } from '../../constants'; -export default class ControllerUtility { +export class ControllerUtility { private static sourceVersionMap: Map = new Map(); public static timestampValsMap: Record = { diff --git a/src/features.json b/src/features.json index 27da6cfaf2c..224968c99b7 100644 --- a/src/features.json +++ b/src/features.json @@ -60,6 +60,7 @@ "TWITTER_ADS": true, "CLEVERTAP": true, "ORTTO": true, + "GLADLY": true, "ONE_SIGNAL": true, "TIKTOK_AUDIENCE": true }, diff --git a/src/helpers/fetchHandlers.ts b/src/helpers/fetchHandlers.ts index 96a74e528d9..ef7d9e66116 100644 --- a/src/helpers/fetchHandlers.ts +++ b/src/helpers/fetchHandlers.ts @@ -1,6 +1,6 @@ -import MiscService from '../services/misc'; +import { MiscService } from '../services/misc'; -export default class FetchHandler { +export class FetchHandler { private static sourceHandlerMap: Map = new Map(); private static destHandlerMap: Map = new Map(); diff --git a/src/helpers/serviceSelector.ts b/src/helpers/serviceSelector.ts index 891c21313da..89678e94074 100644 --- a/src/helpers/serviceSelector.ts +++ b/src/helpers/serviceSelector.ts @@ -1,16 +1,16 @@ import { PlatformError } from '@rudderstack/integrations-lib'; import { ProcessorTransformationRequest, RouterTransformationRequestData } from '../types/index'; import { INTEGRATION_SERVICE } from '../routes/utils/constants'; -import CDKV1DestinationService from '../services/destination/cdkV1Integration'; -import CDKV2DestinationService from '../services/destination/cdkV2Integration'; -import DestinationService from '../interfaces/DestinationService'; -import NativeIntegrationDestinationService from '../services/destination/nativeIntegration'; -import SourceService from '../interfaces/SourceService'; -import NativeIntegrationSourceService from '../services/source/nativeIntegration'; -import ComparatorService from '../services/comparator'; +import { CDKV1DestinationService } from '../services/destination/cdkV1Integration'; +import { CDKV2DestinationService } from '../services/destination/cdkV2Integration'; +import { DestinationService } from '../interfaces/DestinationService'; +import { NativeIntegrationDestinationService } from '../services/destination/nativeIntegration'; +import { SourceService } from '../interfaces/SourceService'; +import { NativeIntegrationSourceService } from '../services/source/nativeIntegration'; +import { ComparatorService } from '../services/comparator'; import { FixMe } from '../util/types'; -export default class ServiceSelector { +export class ServiceSelector { private static serviceMap: Map = new Map(); private static services = { diff --git a/src/index.ts b/src/index.ts index d1cc95cc360..36f32f1aed6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,8 +4,6 @@ import gracefulShutdown from 'http-graceful-shutdown'; import dotenv from 'dotenv'; import logger from './logger'; import cluster from './util/cluster'; -import { router } from './legacy/router'; -import { testRouter } from './testRouter'; import { metricsRouter } from './routes/metricsRouter'; import { addStatMiddleware, addRequestSizeMiddleware, initPyroscope } from './middleware'; import { logProcessInfo } from './util/utils'; @@ -14,7 +12,6 @@ import { RedisDB } from './util/redis/redisConnector'; dotenv.config(); const clusterEnabled = process.env.CLUSTER_ENABLED !== 'false'; -const useUpdatedRoutes = process.env.ENABLE_NEW_ROUTES !== 'false'; const port = parseInt(process.env.PORT ?? '9090', 10); const metricsPort = parseInt(process.env.METRICS_PORT || '9091', 10); @@ -35,15 +32,8 @@ app.use( addRequestSizeMiddleware(app); addSwaggerRoutes(app); -if (useUpdatedRoutes) { - logger.info('Using new routes'); - applicationRoutes(app); -} else { - // To be depricated - logger.info('Using old routes'); - app.use(router.routes()).use(router.allowedMethods()); - app.use(testRouter.routes()).use(testRouter.allowedMethods()); -} +logger.info('Using new routes'); +applicationRoutes(app); function finalFunction() { RedisDB.disconnect(); diff --git a/src/interfaces/DestinationService.ts b/src/interfaces/DestinationService.ts index 123da35292f..b27f98da2a0 100644 --- a/src/interfaces/DestinationService.ts +++ b/src/interfaces/DestinationService.ts @@ -10,7 +10,7 @@ import { UserDeletionResponse, } from '../types/index'; -export default interface DestinationService { +export interface DestinationService { getName(): string; init(): void; diff --git a/src/interfaces/SourceService.ts b/src/interfaces/SourceService.ts index 99b29f095bd..c7de8cfe8bc 100644 --- a/src/interfaces/SourceService.ts +++ b/src/interfaces/SourceService.ts @@ -1,6 +1,6 @@ import { MetaTransferObject, SourceTransformationResponse } from '../types/index'; -export default interface SourceService { +export interface SourceService { getTags(): MetaTransferObject; sourceTransformRoutine( diff --git a/src/middlewares/featureFlag.ts b/src/middlewares/featureFlag.ts index 146e57186c3..206fa7c5ea1 100644 --- a/src/middlewares/featureFlag.ts +++ b/src/middlewares/featureFlag.ts @@ -6,7 +6,7 @@ export interface FeatureFlags { export const FEATURE_FILTER_CODE = 'filter-code'; -export default class FeatureFlagMiddleware { +export class FeatureFlagMiddleware { public static async handle(ctx: Context, next: Next): Promise { // Initialize ctx.state.features if it doesn't exist ctx.state.features = (ctx.state.features || {}) as FeatureFlags; diff --git a/src/middlewares/routeActivation.ts b/src/middlewares/routeActivation.ts index dfdaef964ee..ffb1e15e80b 100644 --- a/src/middlewares/routeActivation.ts +++ b/src/middlewares/routeActivation.ts @@ -18,7 +18,7 @@ const destinationFilterList = process.env.DESTINATION_FILTER_LIST?.toLocaleLower const sourceFilteList = process.env.SOURCE_FILTER_LIST?.toLocaleLowerCase(); const deliveryFilterList = process.env.DESTINATION_DELIVERY_FILTER_LIST?.toLocaleLowerCase(); -export default class RouteActivationMiddleware { +export class RouteActivationMiddleware { private static executeActivationRule(ctx: Context, next: Next, shouldActivate: boolean) { if (shouldActivate) { return next(); diff --git a/src/routes/delivery.ts b/src/routes/delivery.ts index 141700fd9fb..0591dc8b9eb 100644 --- a/src/routes/delivery.ts +++ b/src/routes/delivery.ts @@ -1,6 +1,6 @@ import Router from '@koa/router'; -import DeliveryController from '../controllers/delivery'; -import RouteActivationMiddleware from '../middlewares/routeActivation'; +import { DeliveryController } from '../controllers/delivery'; +import { RouteActivationMiddleware } from '../middlewares/routeActivation'; const router = new Router(); diff --git a/src/routes/destination.ts b/src/routes/destination.ts index 3d4be42ff3c..1c478251455 100644 --- a/src/routes/destination.ts +++ b/src/routes/destination.ts @@ -1,30 +1,30 @@ import Router from '@koa/router'; -import DestinationController from '../controllers/destination'; -import RegulationController from '../controllers/regulation'; -import FeatureFlagController from '../middlewares/featureFlag'; -import RouteActivationController from '../middlewares/routeActivation'; +import { DestinationController } from '../controllers/destination'; +import { RegulationController } from '../controllers/regulation'; +import { FeatureFlagMiddleware } from '../middlewares/featureFlag'; +import { RouteActivationMiddleware } from '../middlewares/routeActivation'; const router = new Router(); router.post( '/:version/destinations/:destination', - RouteActivationController.isDestinationRouteActive, - RouteActivationController.destinationProcFilter, - FeatureFlagController.handle, + RouteActivationMiddleware.isDestinationRouteActive, + RouteActivationMiddleware.destinationProcFilter, + FeatureFlagMiddleware.handle, DestinationController.destinationTransformAtProcessor, ); router.post( '/routerTransform', - RouteActivationController.isDestinationRouteActive, - RouteActivationController.destinationRtFilter, - FeatureFlagController.handle, + RouteActivationMiddleware.isDestinationRouteActive, + RouteActivationMiddleware.destinationRtFilter, + FeatureFlagMiddleware.handle, DestinationController.destinationTransformAtRouter, ); router.post( '/batch', - RouteActivationController.isDestinationRouteActive, - RouteActivationController.destinationBatchFilter, - FeatureFlagController.handle, + RouteActivationMiddleware.isDestinationRouteActive, + RouteActivationMiddleware.destinationBatchFilter, + FeatureFlagMiddleware.handle, DestinationController.batchProcess, ); diff --git a/src/routes/misc.ts b/src/routes/misc.ts index 750c1194dd3..4d1c5d5fb54 100644 --- a/src/routes/misc.ts +++ b/src/routes/misc.ts @@ -1,6 +1,6 @@ import Router from '@koa/router'; -import ProfileController from '../controllers/profile'; -import MiscController from '../controllers/misc'; +import { ProfileController } from '../controllers/profile'; +import { MiscController } from '../controllers/misc'; const router = new Router(); diff --git a/src/routes/source.ts b/src/routes/source.ts index ade26c8700f..1abc46e2e4d 100644 --- a/src/routes/source.ts +++ b/src/routes/source.ts @@ -1,13 +1,13 @@ import Router from '@koa/router'; -import RouteActivationController from '../middlewares/routeActivation'; -import SourceController from '../controllers/source'; +import { RouteActivationMiddleware } from '../middlewares/routeActivation'; +import { SourceController } from '../controllers/source'; const router = new Router(); router.post( '/:version/sources/:source', - RouteActivationController.isSourceRouteActive, - RouteActivationController.sourceFilter, + RouteActivationMiddleware.isSourceRouteActive, + RouteActivationMiddleware.sourceFilter, SourceController.sourceTransform, ); diff --git a/src/routes/testEvents.ts b/src/routes/testEvents.ts index 556ec10198b..a35fe447ba7 100644 --- a/src/routes/testEvents.ts +++ b/src/routes/testEvents.ts @@ -1,5 +1,5 @@ import Router from '@koa/router'; -import EventTestController from '../controllers/eventTest'; +import { EventTestController } from '../controllers/eventTest'; const router = new Router({ prefix: '/test-router' }); diff --git a/src/routes/trackingPlan.ts b/src/routes/trackingPlan.ts index d177af7b2ce..3e62ba2a749 100644 --- a/src/routes/trackingPlan.ts +++ b/src/routes/trackingPlan.ts @@ -1,5 +1,5 @@ import Router from '@koa/router'; -import TrackingPlanController from '../controllers/trackingPlan'; +import { TrackingPlanController } from '../controllers/trackingPlan'; const router = new Router(); diff --git a/src/routes/userTransform.ts b/src/routes/userTransform.ts index 23870db3b4b..1fb8ad3a1cd 100644 --- a/src/routes/userTransform.ts +++ b/src/routes/userTransform.ts @@ -1,34 +1,34 @@ import Router from '@koa/router'; -import RouteActivationController from '../middlewares/routeActivation'; -import FeatureFlagController from '../middlewares/featureFlag'; -import UserTransformController from '../controllers/userTransform'; +import { RouteActivationMiddleware } from '../middlewares/routeActivation'; +import { FeatureFlagMiddleware } from '../middlewares/featureFlag'; +import { UserTransformController } from '../controllers/userTransform'; const router = new Router(); router.post( '/customTransform', - RouteActivationController.isUserTransformRouteActive, - FeatureFlagController.handle, + RouteActivationMiddleware.isUserTransformRouteActive, + FeatureFlagMiddleware.handle, UserTransformController.transform, ); router.post( '/transformation/test', - RouteActivationController.isUserTransformTestRouteActive, + RouteActivationMiddleware.isUserTransformTestRouteActive, UserTransformController.testTransform, ); router.post( '/transformationLibrary/test', - RouteActivationController.isUserTransformTestRouteActive, + RouteActivationMiddleware.isUserTransformTestRouteActive, UserTransformController.testTransformLibrary, ); router.post( '/transformation/sethandle', - RouteActivationController.isUserTransformTestRouteActive, + RouteActivationMiddleware.isUserTransformTestRouteActive, UserTransformController.testTransformSethandle, ); router.post( '/extractLibs', - RouteActivationController.isUserTransformRouteActive, + RouteActivationMiddleware.isUserTransformRouteActive, UserTransformController.extractLibhandle, ); diff --git a/src/services/comparator.ts b/src/services/comparator.ts index 3495b7bbfd0..7f63da94022 100644 --- a/src/services/comparator.ts +++ b/src/services/comparator.ts @@ -1,5 +1,5 @@ /* eslint-disable class-methods-use-this */ -import IntegrationDestinationService from '../interfaces/DestinationService'; +import { DestinationService } from '../interfaces/DestinationService'; import { DeliveryResponse, Destination, @@ -20,15 +20,12 @@ import { CommonUtils } from '../util/common'; const NS_PER_SEC = 1e9; -export default class ComparatorService implements IntegrationDestinationService { - secondaryService: IntegrationDestinationService; +export class ComparatorService implements DestinationService { + secondaryService: DestinationService; - primaryService: IntegrationDestinationService; + primaryService: DestinationService; - constructor( - primaryService: IntegrationDestinationService, - secondaryService: IntegrationDestinationService, - ) { + constructor(primaryService: DestinationService, secondaryService: DestinationService) { this.primaryService = primaryService; this.secondaryService = secondaryService; } diff --git a/src/services/delivertTest/deliveryTest.ts b/src/services/delivertTest/deliveryTest.ts index e5713073c15..0d960ade172 100644 --- a/src/services/delivertTest/deliveryTest.ts +++ b/src/services/delivertTest/deliveryTest.ts @@ -7,7 +7,7 @@ import stats from '../../util/stats'; import logger from '../../logger'; import tags from '../../v0/util/tags'; -export default class DeliveryTestService { +export class DeliveryTestService { public static async doTestDelivery( destination: string, routerDestReqPayload: any, diff --git a/src/services/destination/cdkV1Integration.ts b/src/services/destination/cdkV1Integration.ts index 2f655fca9b9..8ccd3341e59 100644 --- a/src/services/destination/cdkV1Integration.ts +++ b/src/services/destination/cdkV1Integration.ts @@ -2,7 +2,7 @@ import { ConfigFactory, Executor, RudderBaseConfig } from 'rudder-transformer-cdk'; import path from 'path'; import { TransformationError } from '@rudderstack/integrations-lib'; -import IntegrationDestinationService from '../../interfaces/DestinationService'; +import { DestinationService } from '../../interfaces/DestinationService'; import { DeliveryResponse, ErrorDetailer, @@ -15,12 +15,12 @@ import { UserDeletionRequest, UserDeletionResponse, } from '../../types/index'; -import DestinationPostTransformationService from './postTransformation'; +import { DestinationPostTransformationService } from './postTransformation'; import tags from '../../v0/util/tags'; import { getErrorInfo } from '../../cdk/v1/handler'; import { CatchErr } from '../../util/types'; -export default class CDKV1DestinationService implements IntegrationDestinationService { +export class CDKV1DestinationService implements DestinationService { public init() { ConfigFactory.init({ basePath: path.resolve(__dirname, '../../cdk/v1'), diff --git a/src/services/destination/cdkV2Integration.ts b/src/services/destination/cdkV2Integration.ts index bc5a1126a89..f3be2c01443 100644 --- a/src/services/destination/cdkV2Integration.ts +++ b/src/services/destination/cdkV2Integration.ts @@ -3,7 +3,7 @@ import groupBy from 'lodash/groupBy'; import { TransformationError } from '@rudderstack/integrations-lib'; import { processCdkV2Workflow } from '../../cdk/v2/handler'; -import IntegrationDestinationService from '../../interfaces/DestinationService'; +import { DestinationService } from '../../interfaces/DestinationService'; import { DeliveryResponse, ErrorDetailer, @@ -17,11 +17,11 @@ import { UserDeletionResponse, } from '../../types/index'; import tags from '../../v0/util/tags'; -import DestinationPostTransformationService from './postTransformation'; +import { DestinationPostTransformationService } from './postTransformation'; import stats from '../../util/stats'; import { CatchErr } from '../../util/types'; -export default class CDKV2DestinationService implements IntegrationDestinationService { +export class CDKV2DestinationService implements DestinationService { public init() {} public getName(): string { @@ -52,7 +52,7 @@ export default class CDKV2DestinationService implements IntegrationDestinationSe events: ProcessorTransformationRequest[], destinationType: string, _version: string, - _requestMetadata: NonNullable, + requestMetadata: NonNullable, ): Promise { // TODO: Change the promise type const respList: ProcessorTransformationResponse[][] = await Promise.all( @@ -64,6 +64,7 @@ export default class CDKV2DestinationService implements IntegrationDestinationSe destinationType, event, tags.FEATURES.PROCESSOR, + requestMetadata ); stats.increment('event_transform_success', { @@ -108,7 +109,7 @@ export default class CDKV2DestinationService implements IntegrationDestinationSe events: RouterTransformationRequestData[], destinationType: string, _version: string, - _requestMetadata: NonNullable, + requestMetadata: NonNullable, ): Promise { const allDestEvents: object = groupBy( events, @@ -126,7 +127,7 @@ export default class CDKV2DestinationService implements IntegrationDestinationSe metaTo.metadata = destInputArray[0].metadata; try { const doRouterTransformationResponse: RouterTransformationResponse[] = - await processCdkV2Workflow(destinationType, destInputArray, tags.FEATURES.ROUTER); + await processCdkV2Workflow(destinationType, destInputArray, tags.FEATURES.ROUTER, requestMetadata); return DestinationPostTransformationService.handleRouterTransformSuccessEvents( doRouterTransformationResponse, undefined, diff --git a/src/services/destination/nativeIntegration.ts b/src/services/destination/nativeIntegration.ts index a788b388e4b..6763f54c7e0 100644 --- a/src/services/destination/nativeIntegration.ts +++ b/src/services/destination/nativeIntegration.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import groupBy from 'lodash/groupBy'; import cloneDeep from 'lodash/cloneDeep'; -import IntegrationDestinationService from '../../interfaces/DestinationService'; +import { DestinationService } from '../../interfaces/DestinationService'; import { DeliveryResponse, ErrorDetailer, @@ -14,12 +14,12 @@ import { UserDeletionRequest, UserDeletionResponse, } from '../../types/index'; -import DestinationPostTransformationService from './postTransformation'; +import { DestinationPostTransformationService } from './postTransformation'; import networkHandlerFactory from '../../adapters/networkHandlerFactory'; -import FetchHandler from '../../helpers/fetchHandlers'; +import { FetchHandler } from '../../helpers/fetchHandlers'; import tags from '../../v0/util/tags'; -export default class NativeIntegrationDestinationService implements IntegrationDestinationService { +export class NativeIntegrationDestinationService implements DestinationService { public init() {} public getName(): string { diff --git a/src/services/destination/postTransformation.ts b/src/services/destination/postTransformation.ts index 090e39b0590..0b91eb7cc19 100644 --- a/src/services/destination/postTransformation.ts +++ b/src/services/destination/postTransformation.ts @@ -12,11 +12,11 @@ import { UserDeletionResponse, } from '../../types/index'; import { generateErrorObject } from '../../v0/util'; -import ErrorReportingService from '../errorReporting'; +import { ErrorReportingService } from '../errorReporting'; import tags from '../../v0/util/tags'; import stats from '../../util/stats'; -export default class DestinationPostTransformationService { +export class DestinationPostTransformationService { public static handleProcessorTransformSucessEvents( event: ProcessorTransformationRequest, transformedPayloads: ProcessorTransformationOutput | ProcessorTransformationOutput[], diff --git a/src/services/destination/preTransformation.ts b/src/services/destination/preTransformation.ts index dd5579b7b9f..db1d76d64ed 100644 --- a/src/services/destination/preTransformation.ts +++ b/src/services/destination/preTransformation.ts @@ -1,7 +1,7 @@ import { Context } from 'koa'; import { ProcessorTransformationRequest, RouterTransformationRequestData } from '../../types/index'; -export default class PreTransformationDestinationService { +export class DestinationPreTransformationService { public static preProcess( events: ProcessorTransformationRequest[] | RouterTransformationRequestData[], ctx: Context, diff --git a/src/services/errorReporting.ts b/src/services/errorReporting.ts index 2d3c84ff30b..3a4276f9780 100644 --- a/src/services/errorReporting.ts +++ b/src/services/errorReporting.ts @@ -1,6 +1,6 @@ import { client } from '../util/errorNotifier'; -export default class ErrorReportingService { +export class ErrorReportingService { public static reportError(error: NonNullable, context: string, errorResp: object) { client.notify(error, context, { ...errorResp, diff --git a/src/services/eventTest/eventTester.ts b/src/services/eventTest/eventTester.ts index a3755d3e803..75df29cc63c 100644 --- a/src/services/eventTest/eventTester.ts +++ b/src/services/eventTest/eventTester.ts @@ -1,7 +1,7 @@ import { sendToDestination, userTransformHandler } from '../../routerUtils'; import { FixMe } from '../../util/types'; -export default class EventTesterService { +export class EventTesterService { private static getDestHandler(version, destination) { // eslint-disable-next-line global-require, import/no-dynamic-require return require(`../../${version}/destinations/${destination}/transform`); diff --git a/src/services/misc.ts b/src/services/misc.ts index 4c3e2ae6dae..e0953d08bf1 100644 --- a/src/services/misc.ts +++ b/src/services/misc.ts @@ -6,7 +6,7 @@ import { DestHandlerMap } from '../constants/destinationCanonicalNames'; import { Metadata } from '../types'; import { getCPUProfile, getHeapProfile } from '../middleware'; -export default class MiscService { +export class MiscService { public static getDestHandler(dest: string, version: string) { if (DestHandlerMap.hasOwnProperty(dest)) { return require(`../${version}/destinations/${DestHandlerMap[dest]}/transform`); diff --git a/src/services/profile.ts b/src/services/profile.ts index 4d7a7104e7a..d71826e251e 100644 --- a/src/services/profile.ts +++ b/src/services/profile.ts @@ -22,7 +22,7 @@ logger.info(`Interval Bytes set: ${intervalBytes}`); heap.start(intervalBytes, stackDepth); -export default class ProfileService { +export class ProfileService { private static async promisifiedRead(readable: any) { // eslint-disable-next-line no-new new Promise((resolve, reject) => { diff --git a/src/services/source/nativeIntegration.ts b/src/services/source/nativeIntegration.ts index 22783db049c..6eaef2f835b 100644 --- a/src/services/source/nativeIntegration.ts +++ b/src/services/source/nativeIntegration.ts @@ -1,4 +1,4 @@ -import IntegrationSourceService from '../../interfaces/SourceService'; +import { SourceService } from '../../interfaces/SourceService'; import { ErrorDetailer, MetaTransferObject, @@ -6,12 +6,12 @@ import { SourceTransformationResponse, } from '../../types/index'; import { FixMe } from '../../util/types'; -import PostTransformationServiceSource from './postTransformation'; -import FetchHandler from '../../helpers/fetchHandlers'; +import { SourcePostTransformationService } from './postTransformation'; +import { FetchHandler } from '../../helpers/fetchHandlers'; import tags from '../../v0/util/tags'; import stats from '../../util/stats'; -export default class NativeIntegrationSourceService implements IntegrationSourceService { +export class NativeIntegrationSourceService implements SourceService { public getTags(): MetaTransferObject { const metaTO = { errorDetails: { @@ -38,14 +38,14 @@ export default class NativeIntegrationSourceService implements IntegrationSource try { const respEvents: RudderMessage | RudderMessage[] | SourceTransformationResponse = await sourceHandler.process(sourceEvent); - return PostTransformationServiceSource.handleSuccessEventsSource(respEvents); + return SourcePostTransformationService.handleSuccessEventsSource(respEvents); } catch (error: FixMe) { const metaTO = this.getTags(); stats.increment('source_transform_errors', { source: sourceType, version, }); - return PostTransformationServiceSource.handleFailureEventsSource(error, metaTO); + return SourcePostTransformationService.handleFailureEventsSource(error, metaTO); } }), ); diff --git a/src/services/source/postTransformation.ts b/src/services/source/postTransformation.ts index f732cac3a77..20c815171be 100644 --- a/src/services/source/postTransformation.ts +++ b/src/services/source/postTransformation.ts @@ -1,9 +1,9 @@ import { MetaTransferObject, RudderMessage, SourceTransformationResponse } from '../../types/index'; import { CatchErr } from '../../util/types'; import { generateErrorObject } from '../../v0/util'; -import ErrorReportingService from '../errorReporting'; +import { ErrorReportingService } from '../errorReporting'; -export default class PostTransformationSourceService { +export class SourcePostTransformationService { public static handleFailureEventsSource( error: CatchErr, metaTO: MetaTransferObject, diff --git a/src/services/trackingPlan.ts b/src/services/trackingPlan.ts index 35f21320a57..2e68df55e99 100644 --- a/src/services/trackingPlan.ts +++ b/src/services/trackingPlan.ts @@ -4,7 +4,7 @@ import { getMetadata } from '../v0/util'; import eventValidator from '../util/eventValidation'; import stats from '../util/stats'; -export default class TrackingPlanservice { +export class TrackingPlanservice { public static async validateTrackingPlan(events, requestSize, reqParams) { const requestStartTime = new Date(); const respList: any[] = []; diff --git a/src/services/userTransform.ts b/src/services/userTransform.ts index 4fe1ad0b52a..ddd5c82f67b 100644 --- a/src/services/userTransform.ts +++ b/src/services/userTransform.ts @@ -24,7 +24,7 @@ import { CatchErr, FixMe } from '../util/types'; import { FeatureFlags, FEATURE_FILTER_CODE } from '../middlewares/featureFlag'; import { HTTP_CUSTOM_STATUS_CODES } from '../constants'; -export default class UserTransformService { +export class UserTransformService { public static async transformRoutine( events: ProcessorTransformationRequest[], features: FeatureFlags = {}, diff --git a/src/util/dynamicConfigParser.ts b/src/util/dynamicConfigParser.ts index 73ad85a0d2c..6de2e38f945 100644 --- a/src/util/dynamicConfigParser.ts +++ b/src/util/dynamicConfigParser.ts @@ -6,7 +6,7 @@ import { FixMe } from './types'; const get = require('get-value'); const unset = require('unset-value'); -export default class DynamicConfigParser { +export class DynamicConfigParser { private static getDynamicConfigValue( event: ProcessorTransformationRequest | RouterTransformationRequestData, value: FixMe, diff --git a/src/util/openfaas/index.js b/src/util/openfaas/index.js index 47a69aeb186..2792003f4ac 100644 --- a/src/util/openfaas/index.js +++ b/src/util/openfaas/index.js @@ -277,7 +277,7 @@ const executeFaasFunction = async ( } if (error.statusCode === 504) { - throw new RespStatusError(`${name} timed out`); + throw new RespStatusError(`${name} timed out`, 504); } throw error; diff --git a/src/v0/destinations/hs/util.js b/src/v0/destinations/hs/util.js index 72025997d20..f83ce0b6de7 100644 --- a/src/v0/destinations/hs/util.js +++ b/src/v0/destinations/hs/util.js @@ -607,7 +607,8 @@ const splitEventsForCreateUpdate = async (inputs, destination) => { const { destinationExternalId } = getDestinationExternalIDInfoForRetl(message, DESTINATION); const filteredInfo = updateHubspotIds.filter( - (update) => update.property.toString().toLowerCase() === destinationExternalId.toString().toLowerCase(), + (update) => + update.property.toString().toLowerCase() === destinationExternalId.toString().toLowerCase(), ); if (filteredInfo.length > 0) { diff --git a/src/v0/destinations/mp/config.js b/src/v0/destinations/mp/config.js index 41a801e9da1..35b40294f55 100644 --- a/src/v0/destinations/mp/config.js +++ b/src/v0/destinations/mp/config.js @@ -11,6 +11,9 @@ const ConfigCategory = { IDENTIFY: { name: 'MPIdentifyConfig', }, + SET_ONCE: { + name: 'MPSetOnceConfig', + }, PROFILE_ANDROID: { name: 'MPProfilePropertiesAndroid', }, diff --git a/src/v0/destinations/mp/data/MPSetOnceConfig.json b/src/v0/destinations/mp/data/MPSetOnceConfig.json new file mode 100644 index 00000000000..e5aaf851a33 --- /dev/null +++ b/src/v0/destinations/mp/data/MPSetOnceConfig.json @@ -0,0 +1,122 @@ +[ + { + "destKey": "$created", + "sourceKeys": "createdAt", + "required": false + }, + { + "destKey": "$email", + "sourceKeys": "email", + "required": false + }, + { + "destKey": "$first_name", + "sourceKeys": ["firstName", "firstname", "first_name"], + "required": false + }, + { + "destKey": "$last_name", + "sourceKeys": ["lastName", "lastname", "last_name"], + "required": false + }, + { + "destKey": "$name", + "sourceKeys": "name", + "required": false + }, + { + "destKey": "$username", + "sourceKeys": ["username", "userName"], + "required": false + }, + { + "destKey": "$phone", + "sourceKeys": "phone", + "required": false + }, + { + "destKey": "$avatar", + "sourceKeys": "avatar", + "required": false + }, + { + "destKey": "$country_code", + "sourceKeys": ["country", "address.country"], + "required": false + }, + { + "destKey": "$city", + "sourceKeys": ["city", "address.city"], + "required": false + }, + { + "destKey": "$region", + "sourceKeys": ["state", "address.state", "location.region"], + "required": false + }, + { + "destKey": "$unsubscribed", + "sourceKeys": "unsubscribed", + "required": false + }, + { + "destKey": "$geo_source", + "sourceKeys": "location.geoSource", + "required": false + }, + { + "destKey": "$timezone", + "sourceKeys": "location.timezone", + "required": false + }, + { + "destKey": "$latitude", + "sourceKeys": "location.latitude", + "required": false + }, + { + "destKey": "$longitude", + "sourceKeys": "location.longitude", + "required": false + }, + { + "destKey": "$carrier", + "sourceKeys": "network.carrier", + "required": false + }, + { + "destKey": "$manufacturer", + "sourceKeys": "device.manufacturer", + "required": false + }, + { + "destKey": "$model", + "sourceKeys": "device.model", + "required": false + }, + { + "destKey": "$screen_height", + "sourceKeys": "screen.height", + "required": false + }, + { + "destKey": "$screen_width", + "sourceKeys": "screen.width", + "required": false + }, + { + "destKey": "$wifi", + "sourceKeys": "network.wifi", + "required": false + }, + { + "destKey": "$initial_referrer", + "sourceKeys": "page.initial_referrer", + "required": false + }, + { + "destKey": "$initial_referring_domain", + "sourceKeys": ["page.initial_referring_domain", "page.initialReferringDomain"], + "required": false + } +] diff --git a/src/v0/destinations/mp/transform.js b/src/v0/destinations/mp/transform.js index bb8d3e57566..3d0aaa7c4c6 100644 --- a/src/v0/destinations/mp/transform.js +++ b/src/v0/destinations/mp/transform.js @@ -17,6 +17,7 @@ const { checkInvalidRtTfEvents, handleRtTfSingleEventError, groupEventsByType, + parseConfigArray, } = require('../../util'); const { ConfigCategory, @@ -35,6 +36,7 @@ const { combineBatchRequestsWithSameJobIds, groupEventsByEndpoint, batchEvents, + trimTraits, } = require('./util'); const { CommonUtils } = require('../../../util/common'); @@ -226,17 +228,51 @@ const processTrack = (message, destination) => { return returnValue; }; +const createSetOnceResponse = (message, type, destination, setOnce) => { + const payload = { + $set_once: setOnce, + $token: destination.Config.token, + $distinct_id: message.userId || message.anonymousId, + }; + + if (destination?.Config.identityMergeApi === 'simplified') { + payload.$distinct_id = message.userId || `$device:${message.anonymousId}`; + } + + return responseBuilderSimple(payload, message, type, destination.Config); +}; + const processIdentifyEvents = async (message, type, destination) => { + const messageClone = { ...message }; + let seggregatedTraits = {}; const returnValue = []; + let setOnceProperties = []; + + // making payload for set_once properties + if (destination.Config.setOnceProperties && destination.Config.setOnceProperties.length > 0) { + setOnceProperties = parseConfigArray(destination.Config.setOnceProperties, 'property'); + seggregatedTraits = trimTraits( + messageClone.traits, + messageClone.context.traits, + setOnceProperties, + ); + messageClone.traits = seggregatedTraits.traits; + messageClone.context.traits = seggregatedTraits.contextTraits; + if (Object.keys(seggregatedTraits.setOnce).length > 0) { + returnValue.push( + createSetOnceResponse(messageClone, type, destination, seggregatedTraits.setOnce), + ); + } + } // Creating the user profile // https://developer.mixpanel.com/reference/profile-set - returnValue.push(createIdentifyResponse(message, type, destination, responseBuilderSimple)); + returnValue.push(createIdentifyResponse(messageClone, type, destination, responseBuilderSimple)); if ( destination.Config?.identityMergeApi !== 'simplified' && - message.userId && - message.anonymousId && + messageClone.userId && + messageClone.anonymousId && isImportAuthCredentialsAvailable(destination) ) { // If userId and anonymousId both are present and required credentials for /import @@ -245,13 +281,13 @@ const processIdentifyEvents = async (message, type, destination) => { const trackPayload = { event: '$merge', properties: { - $distinct_ids: [message.userId, message.anonymousId], + $distinct_ids: [messageClone.userId, messageClone.anonymousId], token: destination.Config.token, }, }; const identifyTrackResponse = responseBuilderSimple( trackPayload, - message, + messageClone, 'merge', destination.Config, ); @@ -440,7 +476,6 @@ const processRouterDest = async (inputs, reqMetadata) => { destination: event.destination, }; } - let processedEvents = await process(event); processedEvents = CommonUtils.toArray(processedEvents); return processedEvents.map((res) => ({ diff --git a/src/v0/destinations/mp/util.js b/src/v0/destinations/mp/util.js index bb4c23f1b44..bb8f36fdbe3 100644 --- a/src/v0/destinations/mp/util.js +++ b/src/v0/destinations/mp/util.js @@ -1,3 +1,4 @@ +const lodash = require('lodash'); const set = require('set-value'); const get = require('get-value'); const { InstrumentationError } = require('@rudderstack/integrations-lib'); @@ -14,6 +15,7 @@ const { defaultBatchRequestConfig, IsGzipSupported, isObject, + isDefinedAndNotNullAndNotEmpty, } = require('../../util'); const { ConfigCategory, @@ -26,6 +28,7 @@ const { CommonUtils } = require('../../../util/common'); const mPIdentifyConfigJson = mappingConfig[ConfigCategory.IDENTIFY.name]; const mPProfileAndroidConfigJson = mappingConfig[ConfigCategory.PROFILE_ANDROID.name]; const mPProfileIosConfigJson = mappingConfig[ConfigCategory.PROFILE_IOS.name]; +const mPSetOnceConfigJson = mappingConfig[ConfigCategory.SET_ONCE.name]; /** * this function has been used to create @@ -322,6 +325,72 @@ const combineBatchRequestsWithSameJobIds = (inputBatches) => { return combineBatches(combineBatches(inputBatches)); }; +/** + * Trims the traits and contextTraits objects based on the setOnceProperties array and returns an object containing the modified traits, contextTraits, and setOnce properties. + * + * @param {object} traits - An object representing the traits. + * @param {object} contextTraits - An object representing the context traits. + * @param {string[]} setOnceProperties - An array of property paths to be considered for the setOnce transformation. + * @returns {object} - An object containing the modified traits, contextTraits, and setOnce properties. + * + * @example + * const traits = { name: 'John', age: 30 }; + * const contextTraits = { country: 'USA', language: 'English', address: { city: 'New York', state: 'NY' }}}; + * const setOnceProperties = ['name', 'country', 'address.city']; + * + * const result = trimTraits(traits, contextTraits, setOnceProperties); + * // Output: { traits: { age: 30 }, contextTraits: { language: 'English' }, setOnce: { $name: 'John', $country_code: 'USA', city: 'New York'} } + */ +function trimTraits(traits, contextTraits, setOnceProperties) { + let sentOnceTransformedPayload; + // Create a copy of the original traits object + const traitsCopy = { ...traits }; + const contextTraitsCopy = { ...contextTraits }; + + // Initialize setOnce object + const setOnceEligible = {}; + + // Step 1: find the k-v pairs of setOnceProperties in traits and contextTraits + + setOnceProperties.forEach((propertyPath) => { + const propName = lodash.last(propertyPath.split('.')); + + const traitsValue = get(traitsCopy, propertyPath); + const contextTraitsValue = get(contextTraitsCopy, propertyPath); + + if (isDefinedAndNotNullAndNotEmpty(traitsValue)) { + setOnceEligible[propName] = traitsValue; + lodash.unset(traitsCopy, propertyPath); + } + if (isDefinedAndNotNullAndNotEmpty(contextTraitsValue)) { + if (!setOnceEligible.hasOwnProperty(propName)) { + setOnceEligible[propName] = contextTraitsValue; + } + lodash.unset(contextTraitsCopy, propertyPath); + } + }); + + if (setOnceEligible && Object.keys(setOnceEligible).length > 0) { + // Step 2: transform properties eligible as per rudderstack declared identify event mapping + // setOnce should have all traits from message.traits and message.context.traits by now + sentOnceTransformedPayload = constructPayload(setOnceEligible, mPSetOnceConfigJson); + + // Step 3: combine the transformed and custom setOnce traits + sentOnceTransformedPayload = extractCustomFields( + setOnceEligible, + sentOnceTransformedPayload, + 'root', + MP_IDENTIFY_EXCLUSION_LIST, + ); + } + + return { + traits: traitsCopy, + contextTraits: contextTraitsCopy, + setOnce: sentOnceTransformedPayload || {}, + }; +} + module.exports = { createIdentifyResponse, isImportAuthCredentialsAvailable, @@ -330,4 +399,5 @@ module.exports = { generateBatchedPayloadForArray, batchEvents, combineBatchRequestsWithSameJobIds, + trimTraits, }; diff --git a/src/v0/destinations/mp/util.test.js b/src/v0/destinations/mp/util.test.js index 6d5b24766df..fbaa6f9b9ff 100644 --- a/src/v0/destinations/mp/util.test.js +++ b/src/v0/destinations/mp/util.test.js @@ -4,6 +4,7 @@ const { batchEvents, generateBatchedPayloadForArray, buildUtmParams, + trimTraits, } = require('./util'); const { FEATURE_GZIP_SUPPORT } = require('../../util/constant'); @@ -602,4 +603,97 @@ describe('Mixpanel utils test', () => { }); }); }); + 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); + console.log(result); + + 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']; + + const result = trimTraits(traits, contextTraits, setOnceProperties); + + 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 = []; + + const result = trimTraits(traits, contextTraits, setOnceProperties); + + 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']; + + const result = trimTraits(traits, contextTraits, setOnceProperties); + + 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']; + + const result = trimTraits(traits, contextTraits, setOnceProperties); + + 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']; + + 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' }, + }); + }); + }); }); diff --git a/src/v0/sources/shopify/util.js b/src/v0/sources/shopify/util.js index 1da75cba3d4..61501bdab65 100644 --- a/src/v0/sources/shopify/util.js +++ b/src/v0/sources/shopify/util.js @@ -32,7 +32,10 @@ const getDataFromRedis = async (key, metricMetadata) => { ...metricMetadata, }); const redisData = await RedisDB.getVal(key); - if (redisData === null || (typeof redisData === "object" && Object.keys(redisData).length === 0)) { + if ( + redisData === null || + (typeof redisData === 'object' && Object.keys(redisData).length === 0) + ) { stats.increment('shopify_redis_no_val', { ...metricMetadata, }); diff --git a/src/v0/util/index.js b/src/v0/util/index.js index 02968956622..fee1d7a96d4 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -2083,6 +2083,31 @@ const IsGzipSupported = (reqMetadata = {}) => { return false; }; +/** + * Returns an array containing the values of the specified key from each object in the input array. + * If the input array is falsy (null, undefined, empty array), an empty array is returned. + * + * @param {Array} arr - The input array from which values will be extracted. + * @param {string} key - The key of the property whose values will be extracted from each object in the input array. + * @returns {Array} - A new array containing the values of the specified key from each object in the input array. + * + * @example + * const configArray = [ + * { name: 'John', age: 25 }, + * { name: 'Jane', age: 30 }, + * { name: 'Bob', age: 35 } + * ]; + * + * const result = parseConfigArray(configArray, 'name'); + * Output: ['John', 'Jane', 'Bob'] + */ +const parseConfigArray = (arr, key) => { + if (!arr) { + return []; + } + return arr.map((item) => item[key]); +}; + // ======================================================================== // EXPORTS // ======================================================================== @@ -2192,4 +2217,5 @@ module.exports = { isValidInteger, isNewStatusCodesAccepted, IsGzipSupported, + parseConfigArray, }; diff --git a/test/__tests__/data/sanity/active_campaign_output.json b/test/__tests__/data/sanity/active_campaign_output.json deleted file mode 100644 index 544f9b937f2..00000000000 --- a/test/__tests__/data/sanity/active_campaign_output.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://active.campaigns.rudder.com/api/3/contact/sync", - "headers": { - "Content-Type": "application/json", - "Api-Token": "dummyApiToken" - }, - "params": {}, - "body": { - "JSON": { - "contact": { - "email": "manashi@gmail.com", - "phone": 9090909000, - "firstName": null, - "lastName": null - }, - "apiKey": "dummyApiKey" - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {} - }, - { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://active.campaigns.rudder.com/api/3/contact/sync", - "headers": { - "Content-Type": "application/json", - "Api-Token": "dummyApiToken" - }, - "params": {}, - "body": { - "JSON": { - "contact": { - "email": "testkolkata@rudderlabs.com", - "phone": "570-690-4150", - "firstName": "Sajal", - "lastName": "Mohanta" - }, - "apiKey": "dummyApiKey" - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {} - } -] diff --git a/test/__tests__/data/sanity/destination_config.json b/test/__tests__/data/sanity/destination_config.json deleted file mode 100644 index 11c999aa9ce..00000000000 --- a/test/__tests__/data/sanity/destination_config.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "active_campaign": { - "config": { - "processor": { - "Config": { - "apiKey": "dummyApiKey", - "apiUrl": "https://active.campaigns.rudder.com", - "actid": "476550467", - "eventKey": "f8a866fddc721350fdc2fbbd2e5c43a6dddaaa03" - } - } - } - }, - "clevertap": { - "config": { - "processor": {}, - "router": {} - } - }, - "marketo": { - "config": { - "router": { - "Config": { - "accountId": "marketo_acct_id_success", - "clientId": "marketo_client_id_success", - "clientSecret": "marketo_client_secret_success", - "trackAnonymousEvents": true, - "customActivityEventMap": [{ "from": "Product Clicked", "to": "100001" }], - "customActivityPropertyMap": [{ "from": "name", "to": "productName" }], - "customActivityPrimaryKeyMap": [{ "from": "Product Clicked", "to": "name" }], - "leadTraitMapping": [{ "from": "leadScore", "to": "customLeadScore" }] - }, - "secretConfig": {}, - "ID": "1mMy5cqbtfuaKZv1IhVQKnBdVwe", - "name": "Marketo", - "enabled": true, - "workspaceId": "1TSN08muJTZwH8iCDmnnRt1pmLd", - "deleted": false, - "createdAt": "2020-12-30T08:39:32.005Z", - "updatedAt": "2021-02-03T16:22:31.374Z", - "destinationDefinition": { - "config": { - "destConfig": { - "defaultConfig": [ - "accountId", - "clientId", - "clientSecret", - "trackAnonymousEvents", - "customActivityEventMap", - "customActivityPropertyMap", - "customActivityPrimaryKeyMap", - "leadTraitMapping" - ] - }, - "secretKeys": ["clientSecret"], - "excludeKeys": [], - "includeKeys": [], - "routerTransform": true, - "supportedSourceTypes": [ - "android", - "ios", - "web", - "unity", - "amp", - "cloud", - "reactnative" - ] - }, - "responseRules": { - "responseType": "JSON", - "rules": { - "retryable": [ - { "success": "false", "errors.0.code": 600 }, - { "success": "false", "errors.0.code": 601 }, - { "success": "false", "errors.0.code": 602 }, - { "success": "false", "errors.0.code": 604 }, - { "success": "false", "errors.0.code": 606 }, - { "success": "false", "errors.0.code": 607 }, - { "success": "false", "errors.0.code": 608 }, - { "success": "false", "errors.0.code": 611 } - ], - "abortable": [ - { "success": "false", "errors.0.code": 603 }, - { "success": "false", "errors.0.code": 605 }, - { "success": "false", "errors.0.code": 609 }, - { "success": "false", "errors.0.code": 610 } - ] - } - }, - "id": "1aIXqM806xAVm92nx07YwKbRrO9", - "name": "MARKETO", - "displayName": "Marketo", - "createdAt": "2020-04-09T09:24:31.794Z", - "updatedAt": "2021-01-11T11:03:28.103Z" - }, - "transformations": [], - "isConnectionEnabled": true, - "isProcessorEnabled": true - } - } - } -} diff --git a/test/__tests__/data/sanity/marketo_router_output.json b/test/__tests__/data/sanity/marketo_router_output.json deleted file mode 100644 index 9f967074a38..00000000000 --- a/test/__tests__/data/sanity/marketo_router_output.json +++ /dev/null @@ -1,165 +0,0 @@ -[ - { - "batchedRequest": { - "version": "1", - "type": "REST", - "method": "POST", - "endpoint": "https://marketo_acct_id_success.mktorest.com/rest/v1/activities/external.json", - "headers": { - "Authorization": "Bearer access_token_success", - "Content-Type": "application/json" - }, - "params": {}, - "body": { - "JSON": { - "input": [ - { - "activityDate": "2020-12-17T21:00:59.176Z", - "activityTypeId": 100001, - "attributes": [], - "leadId": 4, - "primaryAttributeValue": "Test Product" - } - ] - }, - "XML": {}, - "JSON_ARRAY": {}, - "FORM": {} - }, - "files": {} - }, - "metadata": [ - { - "jobId": 1 - } - ], - "batched": false, - "statusCode": 200, - "destination": { - "Config": { - "accountId": "marketo_acct_id_success", - "clientId": "marketo_client_id_success", - "clientSecret": "marketo_client_secret_success", - "trackAnonymousEvents": true, - "customActivityEventMap": [ - { - "from": "Product Clicked", - "to": "100001" - } - ], - "customActivityPropertyMap": [ - { - "from": "name", - "to": "productName" - } - ], - "customActivityPrimaryKeyMap": [ - { - "from": "Product Clicked", - "to": "name" - } - ], - "leadTraitMapping": [ - { - "from": "leadScore", - "to": "customLeadScore" - } - ] - }, - "secretConfig": {}, - "ID": "1mMy5cqbtfuaKZv1IhVQKnBdVwe", - "name": "Marketo", - "enabled": true, - "workspaceId": "1TSN08muJTZwH8iCDmnnRt1pmLd", - "deleted": false, - "createdAt": "2020-12-30T08:39:32.005Z", - "updatedAt": "2021-02-03T16:22:31.374Z", - "destinationDefinition": { - "config": { - "destConfig": { - "defaultConfig": [ - "accountId", - "clientId", - "clientSecret", - "trackAnonymousEvents", - "customActivityEventMap", - "customActivityPropertyMap", - "customActivityPrimaryKeyMap", - "leadTraitMapping" - ] - }, - "secretKeys": ["clientSecret"], - "excludeKeys": [], - "includeKeys": [], - "routerTransform": true, - "supportedSourceTypes": ["android", "ios", "web", "unity", "amp", "cloud", "reactnative"] - }, - "responseRules": { - "responseType": "JSON", - "rules": { - "retryable": [ - { - "success": "false", - "errors.0.code": 600 - }, - { - "success": "false", - "errors.0.code": 601 - }, - { - "success": "false", - "errors.0.code": 602 - }, - { - "success": "false", - "errors.0.code": 604 - }, - { - "success": "false", - "errors.0.code": 606 - }, - { - "success": "false", - "errors.0.code": 607 - }, - { - "success": "false", - "errors.0.code": 608 - }, - { - "success": "false", - "errors.0.code": 611 - } - ], - "abortable": [ - { - "success": "false", - "errors.0.code": 603 - }, - { - "success": "false", - "errors.0.code": 605 - }, - { - "success": "false", - "errors.0.code": 609 - }, - { - "success": "false", - "errors.0.code": 610 - } - ] - } - }, - "id": "1aIXqM806xAVm92nx07YwKbRrO9", - "name": "MARKETO", - "displayName": "Marketo", - "createdAt": "2020-04-09T09:24:31.794Z", - "updatedAt": "2021-01-11T11:03:28.103Z" - }, - "transformations": [], - "isConnectionEnabled": true, - "isProcessorEnabled": true - } - } -] diff --git a/test/__tests__/data/sanity/sanity_input.json b/test/__tests__/data/sanity/sanity_input.json deleted file mode 100644 index 9dd99611603..00000000000 --- a/test/__tests__/data/sanity/sanity_input.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "messages": [ - { - "type": "identify", - "event": "identify", - "sentAt": "2021-02-08T12:45:42.760Z", - "userId": "User_111", - "channel": "mobile", - "context": { - "os": { - "name": "iOS", - "version": "13.0" - }, - "app": { - "name": "MyApp", - "build": "1", - "version": "1.0", - "namespace": "com.rudderlabs.MyApp" - }, - "device": { - "id": "e2b94e2d-8327-429c-9d91-792a80d189c8", - "name": "iPhone 11 Pro Max", - "type": "iOS", - "model": "iPhone", - "manufacturer": "Apple" - }, - "locale": "en-US", - "screen": { - "width": 896, - "height": 414, - "density": 3 - }, - "traits": { - "age": 24, - "city": "Bangalore", - "name": "Manashi Mazumder", - "email": "manashi@gmail.com", - "phone": 9090909000, - "userId": "User_111", - "anonymousId": "e2b94e2d-8327-429c-9d91-792a80d189c8" - }, - "library": { - "name": "rudder-ios-library", - "version": "1.0.11" - }, - "network": { - "wifi": true, - "carrier": "unavailable", - "cellular": false, - "bluetooth": false - }, - "timezone": "Asia/Kolkata", - "userAgent": "unknown" - }, - "rudderId": "139d65bb-8a40-48c7-854c-06bbf44f686e", - "messageId": "1612788330-7e1e60a8-fb7e-437d-81c1-5b000318d0cb", - "anonymousId": "e2b94e2d-8327-429c-9d91-792a80d189c8", - "integrations": { - "All": true - }, - "originalTimestamp": "2021-02-08T12:45:30.717Z" - }, - { - "type": "identify", - "sentAt": "2021-04-15T19:43:53.393Z", - "userId": "sajal1234", - "channel": "web", - "context": { - "id": "ID101", - "ip": "0.0.0.0", - "os": { - "name": "", - "version": "" - }, - "app": { - "name": "RudderLabs JavaScript SDK", - "build": "1.0.0", - "version": "1.1.17", - "namespace": "com.rudderlabs.javascript" - }, - "page": { - "url": "www.rudderstack.com", - "path": "/path5", - "title": "JS Test", - "search": "My Page", - "tab_url": "https://odd-rat-19.loca.lt/Rectified.html", - "referrer": "www.google.com", - "initial_referrer": "$direct", - "referring_domain": "", - "initial_referring_domain": "" - }, - "event": "Sample Identify call", - "locale": "en-US", - "screen": { - "density": 2.75 - }, - "traits": { - "id": "ID101", - "city": "east greenwich", - "plan": "Open source", - "tags": ["x0002x", "y0004y", "t0001t"], - "email": "testkolkata@rudderlabs.com", - "event": "Sample Identify call", - "lists": [ - { - "id": 2, - "status": "unsubscribed" - }, - { - "id": 3, - "status": "unsubscribe" - } - ], - "phone": "570-690-4150", - "state": "RI", - "title": "Mr", - "logins": 5, - "mobile": "123456786", - "rating": "Hot", - "street": "19123 forest lane", - "company": { - "id": 378763009439, - "name": "Rudderlabs", - "industry": "IT", - "employee_count": 1200 - }, - "country": "USA", - "sent_at": "20210109134567", - "birthDay": "2021/04/15", - "category": "SampleIdentify", - "industry": "ITES", - "lastName": "Mohanta", - "timezone": "Berlin", - "Homephone": 9836543283, - "createdAt": "2021/04/15", - "fieldInfo": { - "state": "California", - "Address": "kolkata", - "listBox": ["Option1", "Option2"], - "CheckBox": ["Option1", "Option2", "Option3"], - "TextArea": "This is a sample text area field value . it is tested for Active Campaign. It should have more than 100 words. Lorem ipsum is a dummy sentence to fill up any area. And also , it is used to fill up dummy website. So, it is wriiten by simply wasting time. ", - "DateField": "1988-12-05", - "HiddenField": "Hidden", - "RadioButton": "Option1", - "multiChoice": "Option 1", - "DateTimeField": "2020-05-19T02:45:00-05:00" - }, - "firstName": "Sajal", - "isEnabled": true, - "timestamp": "1403743443", - "event_text": "identify", - "leadSource": "WEB", - "postalCode": "94115", - "received_at": "202101091347654", - "organizationId": 378763009439, - "original_timestamp": "1403743443" - }, - "library": { - "name": "RudderLabs JavaScript SDK", - "version": "1.1.17" - }, - "sent_at": "20210109134567", - "campaign": {}, - "timestamp": "1403743443", - "userAgent": "Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86_arm Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36", - "event_text": "identify", - "externalId": [ - { - "id": "0035g000002XN5MAAW", - "type": "Salesforce-Contact" - } - ], - "received_at": "202101091347654", - "original_timestamp": "1403743443" - }, - "rudderId": "bc2402b7-6796-4fa5-9beb-bad3761fc961", - "messageId": "9d0df235-d354-4a81-8056-07dcb58081d7", - "anonymousId": "anonIDfromAndroid", - "integrations": { - "All": true - }, - "originalTimestamp": "2021-04-15T19:43:53.392Z" - } - ] -} diff --git a/test/__tests__/data/sanity/sanity_router_input.json b/test/__tests__/data/sanity/sanity_router_input.json deleted file mode 100644 index 16b8ccffadc..00000000000 --- a/test/__tests__/data/sanity/sanity_router_input.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "messages": [ - { - "anonymousId": "anon_id_success", - "channel": "mobile", - "context": { - "app": { - "build": "1", - "name": "TestAppName", - "namespace": "com.android.sample", - "version": "1.0" - }, - "device": { - "id": "anon_id_success", - "manufacturer": "Google", - "model": "Android SDK built for x86", - "name": "generic_x86", - "type": "android" - }, - "library": { - "name": "com.rudderstack.android.sdk.core", - "version": "1.0.1-beta.1" - }, - "locale": "en-US", - "network": { - "carrier": "Android", - "bluetooth": false, - "cellular": true, - "wifi": true - }, - "os": { - "name": "Android", - "version": "8.1.0" - }, - "screen": { - "density": 420, - "height": 1794, - "width": 1080 - }, - "timezone": "Asia/Kolkata", - "traits": { - "anonymousId": "anon_id_success" - }, - "userAgent": "Dalvik/2.1.0 (Linux; U; Android 8.1.0; Android SDK built for x86 Build/OSM1.180201.007)" - }, - "event": "Product Clicked", - "integrations": { - "All": true - }, - "messageId": "id1", - "properties": { - "name": "Test Product" - }, - "originalTimestamp": "2020-12-17T21:00:59.176Z", - "type": "track", - "sentAt": "2020-03-12T09:05:03.421Z" - } - ] -} diff --git a/test/__tests__/facebook_conversions.test.js b/test/__tests__/facebook_conversions.test.js index a450952efe9..9495a85913d 100644 --- a/test/__tests__/facebook_conversions.test.js +++ b/test/__tests__/facebook_conversions.test.js @@ -23,6 +23,8 @@ const outputRouterDataFile = fs.readFileSync( const inputRouterData = JSON.parse(inputRouterDataFile); const expectedRouterData = JSON.parse(outputRouterDataFile); +Date.now = jest.fn(() => new Date("2023-11-12T15:46:51.000Z")); // 2023-11-12T15:46:51.693229+05:30 + describe(`${name} Tests`, () => { describe("Processor", () => { testData.forEach((dataPoint, index) => { diff --git a/test/__tests__/legacyRouter.test.ts b/test/__tests__/legacyRouter.test.ts index 768db08ca82..926f6e76d4e 100644 --- a/test/__tests__/legacyRouter.test.ts +++ b/test/__tests__/legacyRouter.test.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import path from 'path'; -import DestinationController from '../../src/controllers/destination'; +import { DestinationController } from '../../src/controllers/destination'; const destArg = process.argv.filter((x) => x.startsWith('--destName='))[0]; // send arguments on which destination const typeArg = process.argv.filter((x) => x.startsWith('--type='))[0]; // send argument on which function diff --git a/test/__tests__/sanity.test.js b/test/__tests__/sanity.test.js deleted file mode 100644 index ae5faa917cb..00000000000 --- a/test/__tests__/sanity.test.js +++ /dev/null @@ -1,204 +0,0 @@ -jest.mock("axios"); -const name = "Sanity"; -const fs = require("fs"); -const path = require("path"); -const version = "v0"; -const { getDirectories } = require("./util"); -const { mockaxios } = require("../__mocks__/network"); - -// ******************************** -// Getting Started -// ******************************** -// sanity-folder-structure -// __tests__ -// | -// |--sanity.test.js -// |--data -// | | -// | |--sanity -// | | -// | {integration(s)}_output.json -// | {integration(s)}_router_output.json -// | sanity_input.json -// | sanity_router_input.json -// | destination_config.json - -// ------ destination_config.json ------ -// JSON to store the destination-config of all the destinations -// Each destination-config object has a config object storing the -// processor/ router (transform-at) configs -// ------------------------------------- -// If transformation is only done at processor -// only use the processor as key and store the -// destination definition as value. -// -// Format : -// { -// "clevertap": { -// "config": { -// "processor": { ...destination_definition }, -// "router": { ...destination_definition } -// } -// } -// .. -// .. -// } - -// ----- sanity_input.json ----- -// JSON to store the sanity input messages which will be -// used to test sanity for all the destination. -// -// Format: -// { -// "messages": [ -// { sanity_input_message_1}, -// { sanity_input_message_2}, -// { sanity_input_message_3} -// ... -// ] -// } - -// ----- sanity_router_input.json ----- -// JSON to store the sanity input messages which will be -// used to test sanity for all the destination. -// These inputs will be used for testing router -// transformation for all destination which support it. -// -// Format: -// { -// "messages": [ -// { router_sanity_input_message_1}, -// { router_sanity_input_message_2}, -// { router_sanity_input_message_3} -// ... -// ] -// } - -// ------ {integration(s)}_output.json ------ -// These are specific output for each of the destinations -// given the sanity input. We are using jest result matcher -// to check if the output is matching with the expected output. -// Example clevertap_output.json -// Format: -// [ -// {sanity_output_for_message_1}, -// {sanity_output_for_message_2}, -// .. -// ] - -// ------ {integration(s)}_router_output.json ------ -// These are specific output for each of the destinations -// given the router sanity input. -// ** CHECK IF THE PARTICULAR DESTINATION SUPPORTS ROUTER TRANSFORMATION ** -// Example clevertap_router_output.json -// Format: -// [ -// {router_sanity_output_for_message_1}, -// {router_sanity_output_for_message_2}, -// .. -// ] - -// Parsing all the destination names from /v0/destinations dir structure -// parsing it into an array of string. This keeping the destinations to test -// dynamic. -// const integrations = getDirectories( -// path.resolve(__dirname, `../${version}/destinations/`) -// ); -// For Testing Current: -// Uncomment this Line and comment the above 3 lines -const integrations = ["marketo"]; - -// Parsing the sanity input JSON which will be used for testing each destination -const processorSanityInput = JSON.parse( - fs.readFileSync(path.resolve(__dirname, `./data/sanity/sanity_input.json`)) -); - -// Parsing the sanity router input JSON which will be used for testing each destination -// which support it -const routerSanityInputRouter = JSON.parse( - fs.readFileSync( - path.resolve(__dirname, `./data/sanity/sanity_router_input.json`) - ) -); -// Parsing the destination config JSON from which we will get the destination definitions -// along with if the destination supports router-transformation -const destinationConfig = JSON.parse( - fs.readFileSync( - path.resolve(__dirname, `./data/sanity/destination_config.json`) - ) -); - -// Iterating each of the destinations -integrations.forEach(intg => { - // Getting the transformation object - const transformer = require(`../../src/${version}/destinations/${intg}/transform`); - // Getting the config for this particular destination - const { config } = destinationConfig[`${intg}`]; - // Where the ransformation is done at (processor, router ..) - Object.keys(config).forEach(processAt => { - // Depending on the case - switch (processAt) { - case "processor": - { - // Parsing the expected data for this particular destination - const expectedData = JSON.parse( - fs.readFileSync( - path.resolve(__dirname, `./data/sanity/${intg}_output.json`) - ) - ); - // For each of the messages we are processing using the transformer - processorSanityInput.messages.forEach((message, index) => { - // Building the event object with specified destination-definition - const event = { - message, - destination: config[`${processAt}`] - }; - // Sending the event to transformer and matching the result with expected output - it(`${name} - integration[Processor]: ${intg} payload:${index}`, async () => { - try { - const output = await transformer.process(event); - expect(output).toEqual(expectedData[index]); - } catch (error) { - expect(error.message).toEqual(expectedData[index].error); - } - }); - }); - } - break; - - case "router": - { - // Parsing the expected router output data for this particular destination - const expectedData = JSON.parse( - fs.readFileSync( - path.resolve( - __dirname, - `./data/sanity/${intg}_router_output.json` - ) - ) - ); - // For each of the messages we are processing using the router transformer - routerSanityInputRouter.messages.forEach((message, index) => { - // Building the event object with specified destination-definition - const events = [ - { - message, - metadata: { - jobId: 1 - }, - destination: config[`${processAt}`] - } - ]; - // Sending the event to router transformer of this destinationand matching the result with expected output - it(`${name} - integration(Router): ${intg} payload:${index}`, async () => { - const routerOutput = await transformer.processRouterDest(events); - expect(routerOutput[0]).toEqual(expectedData[index]); - }); - }); - } - break; - default: - throw new Error("Undefined Transform-At Config"); - } - }); -}); diff --git a/test/__tests__/user_transformation_ts.test.ts b/test/__tests__/user_transformation_ts.test.ts index 418c42fe334..971476e5135 100644 --- a/test/__tests__/user_transformation_ts.test.ts +++ b/test/__tests__/user_transformation_ts.test.ts @@ -1,5 +1,5 @@ import fetch from 'node-fetch'; -import UserTransformService from '../../src/services/userTransform'; +import { UserTransformService } from '../../src/services/userTransform'; import { FeatureFlags, FEATURE_FILTER_CODE } from '../../src/middlewares/featureFlag'; jest.mock('node-fetch', () => jest.fn()); diff --git a/test/apitests/data_scenarios/source/v1/pipedream.json b/test/apitests/data_scenarios/source/v1/pipedream.json index 4219f3f6b1d..1496471066e 100644 --- a/test/apitests/data_scenarios/source/v1/pipedream.json +++ b/test/apitests/data_scenarios/source/v1/pipedream.json @@ -46,4 +46,4 @@ } } ] -} \ No newline at end of file +} diff --git a/test/controllerUtility/ctrl-utility.test.ts b/test/controllerUtility/ctrl-utility.test.ts index c3a668d7e7b..bf9eef1846f 100644 --- a/test/controllerUtility/ctrl-utility.test.ts +++ b/test/controllerUtility/ctrl-utility.test.ts @@ -1,5 +1,5 @@ import { ProcessorTransformationRequest, RouterTransformationRequestData } from '../../src/types'; -import ControllerUtility from '../../src/controllers/util'; +import { ControllerUtility } from '../../src/controllers/util'; type timestampTestCases = { caseName: string; diff --git a/test/integrations/destinations/gladly/network.ts b/test/integrations/destinations/gladly/network.ts new file mode 100644 index 00000000000..8c1c2287387 --- /dev/null +++ b/test/integrations/destinations/gladly/network.ts @@ -0,0 +1,120 @@ +const deleteNwData = [ + { + httpReq: { + method: 'get', + url: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles?email=test%40rudderlabs.com', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + }, + }, + httpRes: { + data: [], + status: 200, + }, + }, + { + httpReq: { + method: 'get', + url: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles?email=test%2B2%40rudderlabs.com', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + }, + }, + httpRes: { + data: [ + { + emails: [ + { + normalized: 'test+2@rudderstack.com', + original: 'test+2@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@2', + name: 'Test Rudderstack', + phones: [], + id: 'user@2', + }, + ], + status: 200, + }, + }, + { + httpReq: { + method: 'get', + url: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles?phoneNumber=%2B91%209999999988', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + }, + }, + httpRes: { + data: [ + { + emails: [ + { + normalized: 'test+3@rudderstack.com', + original: 'test+3@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@3', + name: 'Test Rudderstack', + phones: [], + id: 'user@3', + }, + { + emails: [ + { + normalized: 'test+4@rudderstack.com', + original: 'test+4@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@4', + name: 'Test Rudderstack', + phones: [], + id: 'user@4', + }, + ], + status: 200, + }, + }, + { + httpReq: { + method: 'get', + url: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles?email=test6%40rudderlabs.com', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + }, + }, + httpRes: { + data: [], + status: 200, + }, + }, + { + httpReq: { + method: 'get', + url: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles?email=abc', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + }, + }, + httpRes: { + data: { + errors: [ + { + attr: 'email', + code: 'invalid', + detail: 'invalid email address', + }, + ], + }, + status: 400, + }, + }, +]; + +export const networkCallsData = [...deleteNwData]; diff --git a/test/integrations/destinations/gladly/processor/data.ts b/test/integrations/destinations/gladly/processor/data.ts new file mode 100644 index 00000000000..211fa781342 --- /dev/null +++ b/test/integrations/destinations/gladly/processor/data.ts @@ -0,0 +1,809 @@ +export const data = [ + { + name: 'gladly', + description: 'No message type', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'adc@test.com', + firstName: 'Test', + }, + }, + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 1, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 1, + }, + statusCode: 400, + error: + 'message Type is not present. Aborting: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message Type is not present. Aborting', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'GLADLY', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Unsupported message type', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'adc@test.com', + firstName: 'Test', + }, + }, + event: 'Product Viewed', + type: 'track', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 2, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 2, + }, + statusCode: 400, + error: + 'message type track is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type track is not supported', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'GLADLY', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Missing config', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'user@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'adc@test.com', + firstName: 'Test', + }, + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 3, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 3, + }, + statusCode: 400, + error: + 'User Name is not present. Aborting: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: User Name is not present. Aborting', + statTags: { + errorCategory: 'dataValidation', + errorType: 'configuration', + destType: 'GLADLY', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Create customer with email as lookup field', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'externalCustomer@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'test@rudderlabs.com', + phone: '+91 9999999999', + firstName: 'Test', + lastName: 'Rudderlabs', + address: 'california usa', + }, + externalId: [ + { + id: 'user@1', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 4, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'california usa', + customAttributes: { age: 23 }, + emails: [{ original: 'test@rudderlabs.com' }], + externalCustomerId: 'externalCustomer@1', + id: 'user@1', + phones: [{ original: '+91 9999999999' }], + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 4 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Update customer with email as lookup field', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'externalCustomer@2', + channel: 'web', + context: { + traits: { + age: 23, + email: 'test+2@rudderlabs.com', + phone: '+91 9999999998', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + externalId: [ + { + id: 'user@2', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 5, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'New York, USA', + customAttributes: { age: 23 }, + emails: [{ original: 'test+2@rudderlabs.com' }], + externalCustomerId: 'externalCustomer@2', + phones: [{ original: '+91 9999999998' }], + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@2', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'PATCH', + files: {}, + params: {}, + }, + metadata: { jobId: 5 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Update customer with phone as lookup field', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'externalCustomer@3', + channel: 'web', + context: { + traits: { + phone: '+91 9999999988', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + externalId: [ + { + id: 'user@3', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 6, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'New York, USA', + externalCustomerId: 'externalCustomer@3', + phones: [{ original: '+91 9999999988' }], + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@3', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'PATCH', + files: {}, + params: {}, + }, + metadata: { jobId: 6 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Required values are not present in payload to create or update customer', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + traits: { + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + }, + type: 'identify', + anonymousId: '78c53c15-32a1-4b65-adac-bec2d7bb8fab', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 7, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + jobId: 7, + }, + statusCode: 400, + error: + 'One of phone, email, userId or GladlyCustomerId is required for an identify call: Workflow: procWorkflow, Step: validatePayload, ChildStep: undefined, OriginalError: One of phone, email, userId or GladlyCustomerId is required for an identify call', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'GLADLY', + module: 'destination', + implementation: 'cdkV2', + feature: 'processor', + }, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Multiple emails and phones are present in payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + userId: 'externalCustomer@6', + channel: 'web', + context: { + traits: { + age: 23, + email: [ + 'test6@rudderlabs.com', + 'test6home@rudderlabs.com', + 'test6office@rudderlabs.com', + ], + phone: ['+91 8888888888', '+91 8888888889'], + firstName: 'Test', + lastName: 'Rudderlabs', + address: 'Germany', + }, + externalId: [ + { + id: 'user@6', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 8, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'Germany', + customAttributes: { age: 23 }, + emails: [ + { original: 'test6@rudderlabs.com' }, + { original: 'test6home@rudderlabs.com' }, + { original: 'test6office@rudderlabs.com' }, + ], + externalCustomerId: 'externalCustomer@6', + id: 'user@6', + phones: [{ original: '+91 8888888888' }, { original: '+91 8888888889' }], + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 8 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Create customer with only GladlyCustomerId', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + traits: { + firstName: 'Test', + lastName: 'Undefined', + address: 'India', + isProUser: true, + }, + externalId: [ + { + id: 'user@9', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 9, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'India', + customAttributes: { isProUser: true }, + id: 'user@9', + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 9 }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'gladly', + description: 'Create customer with invalid lookup field value', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + traits: { + firstName: 'Test', + lastName: 'Undefined', + address: 'Pakistan', + email: 'abc', + }, + externalId: [ + { + id: 'user@10', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.757+05:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 10, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + JSON: { + address: 'Pakistan', + emails: [{original: 'abc'}], + id: 'user@10', + }, + XML: {}, + FORM: {}, + JSON_ARRAY: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + userId: '', + version: '1', + type: 'REST', + method: 'POST', + files: {}, + params: {}, + }, + metadata: { jobId: 10 }, + statusCode: 200, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/gladly/router/data.ts b/test/integrations/destinations/gladly/router/data.ts new file mode 100644 index 00000000000..d3339d81083 --- /dev/null +++ b/test/integrations/destinations/gladly/router/data.ts @@ -0,0 +1,604 @@ +export const data = [ + { + name: 'gladly', + description: 'Gladly router tests', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + userId: 'externalCustomer@1', + channel: 'web', + context: { + traits: { + age: 23, + email: 'test@rudderlabs.com', + phone: '+91 9999999999', + firstName: 'Test', + lastName: 'Rudderlabs', + address: 'california usa', + }, + externalId: [ + { + id: 'user@1', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 1, + }, + }, + { + message: { + userId: 'externalCustomer@2', + channel: 'web', + context: { + traits: { + age: 23, + email: 'test+2@rudderlabs.com', + phone: '+91 9999999998', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + externalId: [ + { + id: 'user@2', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 2, + }, + }, + { + message: { + userId: 'externalCustomer@3', + channel: 'web', + context: { + traits: { + phone: '+91 9999999988', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + externalId: [ + { + id: 'user@3', + type: 'GladlyCustomerId', + }, + ], + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 3, + }, + }, + ], + destType: 'gladly', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'california usa', + customAttributes: { + age: 23, + }, + emails: [ + { + original: 'test@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@1', + id: 'user@1', + phones: [ + { + original: '+91 9999999999', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 1, + }, + ], + statusCode: 200, + }, + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'New York, USA', + customAttributes: { + age: 23, + }, + emails: [ + { + original: 'test+2@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@2', + phones: [ + { + original: '+91 9999999998', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@2', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'PATCH', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 2, + }, + ], + statusCode: 200, + }, + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'New York, USA', + externalCustomerId: 'externalCustomer@3', + phones: [ + { + original: '+91 9999999988', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@3', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'PATCH', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 3, + }, + ], + statusCode: 200, + }, + ], + }, + }, + }, + }, + { + name: 'gladly', + description: 'Gladly rETL tests', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + userId: 'externalCustomer@1', + channel: 'web', + context: { + externalId: [ + { + id: 'externalCustomer@1', + identifierType: 'externalCustomerId', + }, + ], + mappedToDestination: true + }, + traits: { + id: 'user@1', + emails: ['test@rudderlabs.com'], + phones: ['+91 9999999999'], + firstName: 'Test', + lastName: 'Rudderlabs', + address: 'california usa', + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 1, + }, + }, + { + message: { + userId: 'externalCustomer@2', + channel: 'web', + context: { + externalId: [ + { + id: 'externalCustomer@2', + identifierType: 'externalCustomerId', + }, + ], + mappedToDestination: true, + }, + traits: { + id: 'user@2', + emails: 'test+2@rudderlabs.com', + phones: '+91 9999999998', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 2, + }, + }, + { + message: { + userId: 'externalCustomer@3', + channel: 'web', + context: { + externalId: [ + { + id: 'externalCustomer@3', + identifierType: 'externalCustomerId', + }, + ], + mappedToDestination: true, + }, + traits: { + id: 'user@3', + phones: '+91 9999999988', + firstName: 'Test', + lastName: 'Rudderstack', + address: 'New York, USA', + }, + type: 'identify', + originalTimestamp: '2023-11-10T14:42:44.724Z', + timestamp: '2023-11-22T10:12:44.75705:30', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiToken: 'testApiToken', + userName: 'testUserName', + domain: 'rudderlabs.us-uat.gladly.qa', + }, + }, + metadata: { + jobId: 3, + }, + }, + ], + destType: 'gladly', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'california usa', + emails: [ + { + original: 'test@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@1', + id: 'user@1', + phones: [ + { + original: '+91 9999999999', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 1, + }, + ], + statusCode: 200, + }, + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'New York, USA', + emails: [ + { + original: 'test+2@rudderlabs.com', + }, + ], + externalCustomerId: 'externalCustomer@2', + phones: [ + { + original: '+91 9999999998', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@2', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'PATCH', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 2, + }, + ], + statusCode: 200, + }, + { + batched: false, + batchedRequest: { + body: { + FORM: {}, + JSON: { + address: 'New York, USA', + externalCustomerId: 'externalCustomer@3', + phones: [ + { + original: '+91 9999999988', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: 'https://rudderlabs.us-uat.gladly.qa/api/v1/customer-profiles/user@3', + files: {}, + headers: { + Authorization: 'Basic dGVzdFVzZXJOYW1lOnRlc3RBcGlUb2tlbg==', + 'Content-Type': 'application/json', + }, + method: 'PATCH', + params: {}, + type: 'REST', + version: '1', + }, + destination: { + Config: { + apiToken: 'testApiToken', + domain: 'rudderlabs.us-uat.gladly.qa', + userName: 'testUserName', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + metadata: [ + { + jobId: 3, + }, + ], + statusCode: 200, + }, + ], + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/mp/common.ts b/test/integrations/destinations/mp/common.ts index ad12566cc6e..76ed25a760d 100644 --- a/test/integrations/destinations/mp/common.ts +++ b/test/integrations/destinations/mp/common.ts @@ -20,4 +20,42 @@ const sampleDestination = { Transformations: [], }; -export { sampleDestination, defaultMockFns }; +const destinationWithSetOnceProperty = { + Config: { + apiSecret: 'dummySecret', + dataResidency: 'us', + identityMergeApi: 'simplified', + setOnceProperties: [ + { + property: 'nationality', + }, + { + property: 'firstName', + }, + { + property: 'address.city', + }, + ], + superProperties: [ + { + property: 'random', + }, + ], + token: 'dummyToken', + useNativeSDK: false, + useNewMapping: false, + userDeletionApi: 'engage', + whitelistedEvents: [], + }, + DestinationDefinition: { + DisplayName: 'Kiss Metrics', + ID: '1WhbSZ6uA3H5ChVifHpfL2H6sie', + Name: 'MIXPANEL', + }, + Enabled: true, + ID: '1WhcOCGgj9asZu850HvugU2C3Aq', + Name: 'Kiss Metrics', + Transformations: [], +}; + +export { sampleDestination, defaultMockFns, destinationWithSetOnceProperty }; diff --git a/test/integrations/destinations/mp/processor/data.ts b/test/integrations/destinations/mp/processor/data.ts index 2745c09ecc6..a6ba51ee781 100644 --- a/test/integrations/destinations/mp/processor/data.ts +++ b/test/integrations/destinations/mp/processor/data.ts @@ -1,5 +1,5 @@ import { overrideDestination } from '../../../testUtils'; -import { sampleDestination, defaultMockFns } from '../common'; +import { sampleDestination, defaultMockFns, destinationWithSetOnceProperty } from '../common'; export const data = [ { @@ -5852,4 +5852,273 @@ export const data = [ }, }, }, + { + name: 'mp', + description: 'Test Set Once Property', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + description: 'Alias: with property beyond and within exclusion list', + destination: destinationWithSetOnceProperty, + message: { + anonymousId: 'e6ab2c5e-2cda-44a9-a962-e2f67df78bca', + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.5', + }, + ip: '0.0.0.0', + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.5', + }, + locale: 'en-GB', + os: { + name: '', + version: '', + }, + screen: { + density: 2, + }, + traits: { + address: { + city: 'Disney', + }, + country: 'USA', + email: 'TestSanity@disney.com', + firstName: 'Mickey test', + lastName: 'VarChange', + createdAt: '2020-01-23T08:54:02.362Z', + nationality: 'USA', + random: 'superProp', + }, + page: { + path: '/destinations/mixpanel', + referrer: '', + search: '', + title: '', + url: 'https://docs.rudderstack.com/destinations/mixpanel', + category: 'destination', + initial_referrer: 'https://docs.rudderstack.com', + initial_referring_domain: 'docs.rudderstack.com', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36', + }, + integrations: { + All: true, + }, + page: { + path: '/destinations/mixpanel', + referrer: '', + search: '', + title: '', + url: 'https://docs.rudderstack.com/destinations/mixpanel', + category: 'destination', + initial_referrer: 'https://docs.rudderstack.com', + initial_referring_domain: 'docs.rudderstack.com', + }, + request_ip: '[::1]:53709', + type: 'identify', + userId: 'Santiy', + }, + }, + ], + method: 'POST', + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api.mixpanel.com/engage/', + headers: {}, + params: {}, + body: { + JSON: {}, + JSON_ARRAY: { + batch: + '[{"$set_once":{"$first_name":"Mickey test","$city":"Disney","nationality":"USA"},"$token":"dummyToken","$distinct_id":"Santiy"}]', + }, + XML: {}, + FORM: {}, + }, + files: {}, + userId: 'Santiy', + }, + statusCode: 200, + }, + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api.mixpanel.com/engage/', + headers: {}, + params: {}, + body: { + JSON: {}, + JSON_ARRAY: { + batch: + '[{"$set":{"$created":"2020-01-23T08:54:02.362Z","$email":"TestSanity@disney.com","$country_code":"USA","$initial_referrer":"https://docs.rudderstack.com","$initial_referring_domain":"docs.rudderstack.com","random":"superProp","$lastName":"VarChange","$browser":"Chrome","$browser_version":"79.0.3945.117"},"$token":"dummyToken","$distinct_id":"Santiy","$ip":"0.0.0.0","$time":null}]', + }, + XML: {}, + FORM: {}, + }, + files: {}, + userId: 'Santiy', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'mp', + description: 'Test Set Once Property with anonymousId', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + description: 'Alias: with property beyond and within exclusion list', + destination: destinationWithSetOnceProperty, + message: { + anonymousId: 'dummyAnnonymousId', + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.5', + }, + ip: '0.0.0.0', + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.5', + }, + locale: 'en-GB', + os: { + name: '', + version: '', + }, + screen: { + density: 2, + }, + traits: { + address: { + city: 'Disney', + }, + country: 'USA', + email: 'TestSanity@disney.com', + firstName: 'Mickey test', + lastName: 'VarChange', + createdAt: '2020-01-23T08:54:02.362Z', + nationality: 'USA', + random: 'superProp', + }, + page: { + path: '/destinations/mixpanel', + referrer: '', + search: '', + title: '', + url: 'https://docs.rudderstack.com/destinations/mixpanel', + category: 'destination', + initial_referrer: 'https://docs.rudderstack.com', + initial_referring_domain: 'docs.rudderstack.com', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36', + }, + integrations: { + All: true, + }, + page: { + path: '/destinations/mixpanel', + referrer: '', + search: '', + title: '', + url: 'https://docs.rudderstack.com/destinations/mixpanel', + category: 'destination', + initial_referrer: 'https://docs.rudderstack.com', + initial_referring_domain: 'docs.rudderstack.com', + }, + request_ip: '[::1]:53709', + type: 'identify', + }, + }, + ], + method: 'POST', + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api.mixpanel.com/engage/', + headers: {}, + params: {}, + body: { + JSON: {}, + JSON_ARRAY: { + batch: + '[{"$set_once":{"$first_name":"Mickey test","$city":"Disney","nationality":"USA"},"$token":"dummyToken","$distinct_id":"$device:dummyAnnonymousId"}]', + }, + XML: {}, + FORM: {}, + }, + files: {}, + userId: 'dummyAnnonymousId', + }, + statusCode: 200, + }, + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api.mixpanel.com/engage/', + headers: {}, + params: {}, + body: { + JSON: {}, + JSON_ARRAY: { + batch: + '[{"$set":{"$created":"2020-01-23T08:54:02.362Z","$email":"TestSanity@disney.com","$country_code":"USA","$initial_referrer":"https://docs.rudderstack.com","$initial_referring_domain":"docs.rudderstack.com","random":"superProp","$lastName":"VarChange","$browser":"Chrome","$browser_version":"79.0.3945.117"},"$token":"dummyToken","$distinct_id":"$device:dummyAnnonymousId","$ip":"0.0.0.0","$time":null}]', + }, + XML: {}, + FORM: {}, + }, + files: {}, + userId: 'dummyAnnonymousId', + }, + statusCode: 200, + }, + ], + }, + }, + }, ];