From 055fe21d62f035199c616d937705637d5b789d68 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Wed, 29 Nov 2023 12:06:38 +0530 Subject: [PATCH] fix: sfmc bug fix for track event validations (#2852) --- src/v0/destinations/sfmc/transform.js | 23 +++- src/v0/destinations/sfmc/transform.test.js | 125 ++++++++++++++++++++ test/__tests__/data/sfmc_output.json | 2 +- test/__tests__/data/sfmc_router_output.json | 2 +- 4 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 src/v0/destinations/sfmc/transform.test.js diff --git a/src/v0/destinations/sfmc/transform.js b/src/v0/destinations/sfmc/transform.js index 879ca1989aa..7623d751f1a 100644 --- a/src/v0/destinations/sfmc/transform.js +++ b/src/v0/destinations/sfmc/transform.js @@ -3,6 +3,8 @@ const { NetworkError, ConfigurationError, InstrumentationError, + isDefinedAndNotNull, + isEmpty, } = require('@rudderstack/integrations-lib'); const myAxios = require('../../../util/myAxios'); const { EventType } = require('../../../constants'); @@ -17,7 +19,6 @@ const { flattenJson, toTitleCase, getHashFromArray, - isEmpty, simpleProcessRouterDest, } = require('../../util'); const { @@ -221,10 +222,22 @@ const responseBuilderSimple = async (message, category, destination) => { } if (category.type === 'identify' && createOrUpdateContacts) { - throw new ConfigurationError('Creating or updating contacts is disabled'); + throw new ConfigurationError( + 'Creating or updating contacts is disabled. To enable this feature set "Do Not Create or Update Contacts" to false', + ); } - if (category.type === 'track' && hashMapExternalKey[message.event.toLowerCase()]) { + if (category.type === 'track') { + if (isEmpty(message.event)) { + throw new ConfigurationError('Event name is required for track events'); + } + if (typeof message.event !== 'string') { + throw new ConfigurationError('Event name must be a string'); + } + if (!isDefinedAndNotNull(hashMapExternalKey[message.event.toLowerCase()])) { + throw new ConfigurationError('Event not mapped for this track call'); + } + return responseBuilderForInsertData( message, hashMapExternalKey[message.event.toLowerCase()], @@ -237,7 +250,7 @@ const responseBuilderSimple = async (message, category, destination) => { ); } - throw new ConfigurationError('Event not mapped for this track call'); + throw new ConfigurationError(`Event type '${category.type}' not supported`); }; const processEvent = async (message, destination) => { @@ -274,4 +287,4 @@ const processRouterDest = async (inputs, reqMetadata) => { return respList; }; -module.exports = { process, processRouterDest }; +module.exports = { process, processRouterDest, responseBuilderSimple }; diff --git a/src/v0/destinations/sfmc/transform.test.js b/src/v0/destinations/sfmc/transform.test.js new file mode 100644 index 00000000000..c49c49017c6 --- /dev/null +++ b/src/v0/destinations/sfmc/transform.test.js @@ -0,0 +1,125 @@ +const { ConfigurationError } = require('@rudderstack/integrations-lib'); +const axios = require('axios'); +const MockAxiosAdapter = require('axios-mock-adapter'); +const { responseBuilderSimple } = require('./transform'); +beforeAll(() => { + const mock = new MockAxiosAdapter(axios); + mock + .onPost('https://yourSubDomain.auth.marketingcloudapis.com/v2/token') + .reply(200, '{"access_token":"yourAuthToken"}'); +}); + +describe('responseBuilderSimple', () => { + const destination = { + Config: { + clientId: 'yourClientId', + clientSecret: 'yourClientSecret', + subDomain: 'yourSubDomain', + createOrUpdateContacts: false, + externalKey: 'yourExternalKey', + eventToExternalKey: [{ from: 'purchase', to: 'purchaseKey' }], + eventToPrimaryKey: [{ from: 'purchase', to: 'primaryKey' }], + eventToUUID: [{ event: 'purchase', uuid: true }], + }, + }; + it('should return an array of two payloads for identify calls when createOrUpdateContacts is false', async () => { + const message = { + type: 'identify', + userId: '12345', + }; + + const category = { + type: 'identify', + name: 'Identify', + }; + + const response = await responseBuilderSimple(message, category, destination); + + expect(response).toHaveLength(2); + expect(response[0]).toHaveProperty('endpoint'); + expect(response[0]).toHaveProperty('method'); + expect(response[0]).toHaveProperty('body.JSON'); + expect(response[0]).toHaveProperty('headers'); + expect(response[1]).toHaveProperty('endpoint'); + expect(response[1]).toHaveProperty('method'); + expect(response[1]).toHaveProperty('body.JSON'); + expect(response[1]).toHaveProperty('headers'); + }); + + // Throws an error when event name is not provided for track calls + it('should throw an error when event name is not provided for track calls', async () => { + const message = { + type: 'track', + }; + + const category = { + type: 'track', + name: 'Track', + }; + + try { + await responseBuilderSimple(message, category, destination); + } catch (e) { + expect(e).toBeInstanceOf(ConfigurationError); + expect(e.message).toBe('Event name is required for track events'); + } + }); + + // Throws an error when event is not mapped for track calls + it('should throw an error when event is not mapped for track calls', async () => { + const message = { + type: 'track', + event: 'unmappedEvent', + }; + + const category = { + type: 'track', + name: 'Track', + }; + try { + await responseBuilderSimple(message, category, destination); + } catch (e) { + expect(e).toBeInstanceOf(ConfigurationError); + expect(e.message).toBe('Event not mapped for this track call'); + } + }); + + // Throws an error when event type is not supported + it('should throw an error when event type is not supported', async () => { + const message = { + type: 'unsupported', + }; + + const category = { + type: 'unsupported', + name: 'Unsupported', + }; + + try { + await responseBuilderSimple(message, category, destination); + } catch (e) { + expect(e).toBeInstanceOf(ConfigurationError); + expect(e.message).toBe("Event type 'unsupported' not supported"); + } + }); + + // Returns a payload for track calls when event is mapped and event name is a string + it('should return a payload for track calls when event is mapped and event name is a string', async () => { + const message = { + type: 'track', + event: 'purchase', + userId: '12345', + }; + + const category = { + type: 'track', + name: 'Track', + }; + + const response = await responseBuilderSimple(message, category, destination); + expect(response).toHaveProperty('endpoint'); + expect(response).toHaveProperty('method'); + expect(response).toHaveProperty('body.JSON'); + expect(response).toHaveProperty('headers'); + }); +}); diff --git a/test/__tests__/data/sfmc_output.json b/test/__tests__/data/sfmc_output.json index 0271475e4ae..aaaf23aea85 100644 --- a/test/__tests__/data/sfmc_output.json +++ b/test/__tests__/data/sfmc_output.json @@ -1,6 +1,6 @@ [ { - "error": "Creating or updating contacts is disabled" + "error": "Creating or updating contacts is disabled. To enable this feature set \"Do Not Create or Update Contacts\" to false" }, [ { diff --git a/test/__tests__/data/sfmc_router_output.json b/test/__tests__/data/sfmc_router_output.json index d207b792ee6..beb90b5e13b 100644 --- a/test/__tests__/data/sfmc_router_output.json +++ b/test/__tests__/data/sfmc_router_output.json @@ -37,7 +37,7 @@ }, "batched": false, "statusCode": 400, - "error": "Creating or updating contacts is disabled", + "error": "Creating or updating contacts is disabled. To enable this feature set \"Do Not Create or Update Contacts\" to false", "statTags": { "errorCategory": "dataValidation", "errorType": "configuration"