diff --git a/src/cdk/v2/destinations/bluecore/procWorkflow.yaml b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml index f8a0aebc17b..b16e0da0a5a 100644 --- a/src/cdk/v2/destinations/bluecore/procWorkflow.yaml +++ b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml @@ -48,7 +48,7 @@ steps: const newPayload = $.cloneDeep(payload); newPayload.properties.distinct_id = $.populateAccurateDistinctId(newPayload, ^.message); const temporaryProductArray = newPayload.properties.products ?? $.createProductForStandardEcommEvent(^.message, eventName); - newPayload.properties.products = $.addProductArray(temporaryProductArray); + newPayload.properties.products = $.normalizeProductArray(temporaryProductArray); newPayload.event = eventName; newPayload.token = ^.destination.Config.bluecoreNamespace; $.verifyPayload(newPayload, ^.message); diff --git a/src/cdk/v2/destinations/bluecore/utils.js b/src/cdk/v2/destinations/bluecore/utils.js index cf0f11eb862..cc26a39f394 100644 --- a/src/cdk/v2/destinations/bluecore/utils.js +++ b/src/cdk/v2/destinations/bluecore/utils.js @@ -37,23 +37,31 @@ const verifyPayload = (payload, message) => { } switch (payload.event) { case 'search': - if (!payload.properties.search_term) { + if (!payload?.properties?.search_term) { throw new InstrumentationError( '[Bluecore] property:: search_query is required for search event', ); } break; case 'purchase': - if (!payload.properties.order_id) { + if (!payload?.properties?.order_id) { throw new InstrumentationError( '[Bluecore] property:: order_id is required for purchase event', ); } - if (!payload.properties.total) { + if (!payload?.properties?.total) { throw new InstrumentationError( '[Bluecore] property:: total is required for purchase event', ); } + if ( + !isDefinedAndNotNull(payload?.properties?.customer) || + Object.keys(payload.properties.customer).length === 0 + ) { + throw new InstrumentationError( + `[Bluecore] property:: No relevant trait to populate customer information, which is required for ${payload.event} event`, + ); + } break; case 'identify': case 'optin': @@ -63,6 +71,14 @@ const verifyPayload = (payload, message) => { `[Bluecore] property:: email is required for ${payload.event} action`, ); } + if ( + !isDefinedAndNotNull(payload?.properties?.customer) || + Object.keys(payload.properties.customer).length === 0 + ) { + throw new InstrumentationError( + `[Bluecore] property:: No relevant trait to populate customer information, which is required for ${payload.event} action`, + ); + } break; default: break; @@ -133,7 +149,7 @@ const isStandardBluecoreEvent = (eventName) => { * @throws {InstrumentationError} - If the products array is not defined or null. * @returns {array} - The updated product array. */ -const addProductArray = (products) => { +const normalizeProductArray = (products) => { let finalProductArray = null; if (isDefinedAndNotNull(products)) { const productArray = Array.isArray(products) ? products : [products]; @@ -145,6 +161,7 @@ const addProductArray = (products) => { ); finalProductArray = mappedProductArray; } + // if any custom event is not sent with product array, then it should be null return finalProductArray; }; @@ -171,7 +188,10 @@ const constructProperties = (message) => { * @returns {Array|null} - An array containing the properties if the event is a standard Bluecore event and not 'search', otherwise null. */ const createProductForStandardEcommEvent = (message, eventName) => { - const { properties } = message; + const { event, properties } = message; + if (event.toLowerCase() === 'order completed' && eventName === 'purchase') { + throw new InstrumentationError('[Bluecore]:: products array is required for purchase event'); + } if (isStandardBluecoreEvent(eventName) && eventName !== 'search') { return [properties]; } @@ -220,7 +240,7 @@ const populateAccurateDistinctId = (payload, message) => { module.exports = { verifyPayload, deduceTrackEventName, - addProductArray, + normalizeProductArray, isStandardBluecoreEvent, constructProperties, createProductForStandardEcommEvent, diff --git a/src/cdk/v2/destinations/bluecore/utils.test.js b/src/cdk/v2/destinations/bluecore/utils.test.js index 488651d526f..829073bbccb 100644 --- a/src/cdk/v2/destinations/bluecore/utils.test.js +++ b/src/cdk/v2/destinations/bluecore/utils.test.js @@ -1,13 +1,14 @@ const { - addProductArray, + normalizeProductArray, verifyPayload, isStandardBluecoreEvent, deduceTrackEventName, populateAccurateDistinctId, + createProductForStandardEcommEvent, } = require('./utils'); const { InstrumentationError } = require('@rudderstack/integrations-lib'); -describe('addProductArray', () => { +describe('normalizeProductArray', () => { // Adds an array of products to a message when products array is defined and not null. it('should add an array of products to a message when products array is defined and not null', () => { const products = [ @@ -16,7 +17,7 @@ describe('addProductArray', () => { ]; const eventName = 'purchase'; - const result = addProductArray(products, eventName); + const result = normalizeProductArray(products, eventName); expect(result).toEqual([ { id: 1, name: 'Product 1' }, @@ -29,7 +30,7 @@ describe('addProductArray', () => { const product = { product_id: 1, name: 'Product 1' }; const eventName = 'add_to_cart'; - const result = addProductArray(product, eventName); + const result = normalizeProductArray(product, eventName); expect(result).toEqual([{ id: 1, name: 'Product 1' }]); }); @@ -39,7 +40,7 @@ describe('addProductArray', () => { const eventName = 'custom'; expect(() => { - addProductArray(message, products, eventName); + normalizeProductArray(message, products, eventName); }).toBeNull; }); }); @@ -57,7 +58,7 @@ describe('verifyPayload', () => { }); // Verify payload for purchase event with order_id and total properties. - it('should verify payload for purchase event with order_id and total properties', () => { + it('should verify payload for purchase event with order_id and total and customer properties', () => { const payload = { event: 'purchase', properties: { @@ -65,14 +66,18 @@ describe('verifyPayload', () => { total: 100, }, }; - expect(() => verifyPayload(payload, {})).not.toThrow(); + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); }); // Verify payload for identify event with email property. it('should verify payload for identify event with email property', () => { const payload = { event: 'identify', - properties: {}, + properties: { + customer: { + first_name: 'John', + }, + }, }; const message = { traits: { @@ -329,3 +334,70 @@ describe('populateAccurateDistinctId', () => { expect(distinctId).toBe('54321'); }); }); + +describe('createProductForStandardEcommEvent', () => { + // Returns an array containing the properties if the event is a standard Bluecore event and not 'search'. + it("should return an array containing the properties when the event is a standard Bluecore event and not 'search'", () => { + const message = { + event: 'some event', + properties: { name: 'product 1' }, + }; + const eventName = 'some event'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toEqual(null); + }); + + // Returns null if the event is 'search'. + it("should return null when the event is 'search'", () => { + const message = { + event: 'search', + properties: { name: 'product 1' }, + }; + const eventName = 'search'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toBeNull(); + }); + + // Throws an InstrumentationError if the event is 'order completed' and the eventName is 'purchase'. + it("should throw an InstrumentationError when the event is 'order completed' and the eventName is 'purchase'", () => { + const message = { + event: 'order completed', + properties: { name: 'product 1' }, + }; + const eventName = 'purchase'; + expect(() => { + createProductForStandardEcommEvent(message, eventName); + }).toThrow(InstrumentationError); + }); + + // Returns null if the eventName is not a standard Bluecore event. + it('should return null when the eventName is not a standard Bluecore event', () => { + const message = { + event: 'some event', + properties: { name: 'product 1', products: [{ product_id: 1, name: 'prod1' }] }, + }; + const eventName = 'non-standard'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toBeNull(); + }); + + // Returns null if the eventName is not provided. + it('should return null when the eventName is not provided', () => { + const message = { + event: 'some event', + properties: { name: 'product 1' }, + }; + const result = createProductForStandardEcommEvent(message); + expect(result).toBeNull(); + }); + + // Returns null if the properties are not provided. + it('should return null when the properties are not provided', () => { + const message = { + event: 'some event', + }; + const eventName = 'some event'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toBeNull(); + }); +}); diff --git a/test/integrations/destinations/bluecore/ecommTestData.ts b/test/integrations/destinations/bluecore/ecommTestData.ts index 4f8e87bd8e3..297d720bf9a 100644 --- a/test/integrations/destinations/bluecore/ecommTestData.ts +++ b/test/integrations/destinations/bluecore/ecommTestData.ts @@ -429,4 +429,61 @@ export const ecomTestData = [ }, }, }, + { + id: 'bluecore-track-test-6', + name: 'bluecore', + description: + 'Track event call with Order Completed event without product array and not mapped in destination config. This will be sent with purchase name. This event without properties.products will generate error as products array is required for purchase event and ordered completed is a standard ecomm event', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'Order Completed', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithoutProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + '[Bluecore]:: products array is required for purchase event: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: [Bluecore]:: products array is required for purchase event', + metadata, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, ];