diff --git a/src/cdk/v2/destinations/algolia/procWorkflow.yaml b/src/cdk/v2/destinations/algolia/procWorkflow.yaml index 87da64aa45..402b48dabd 100644 --- a/src/cdk/v2/destinations/algolia/procWorkflow.yaml +++ b/src/cdk/v2/destinations/algolia/procWorkflow.yaml @@ -76,6 +76,7 @@ steps: const objectIDs = ~r products.objectId; $.context.payload.objectIDs = Array.isArray(objectIDs) ? objectIDs[:20]:$.context.payload.objectIDs; $.context.payload.objectData = $.outputs.prepareObjectDataBlock + $.validatePayload($.context.payload) - name: validateDestPayload template: | diff --git a/src/v0/destinations/algolia/util.js b/src/v0/destinations/algolia/util.js index 863c23aba7..b10097bbee 100644 --- a/src/v0/destinations/algolia/util.js +++ b/src/v0/destinations/algolia/util.js @@ -143,9 +143,28 @@ const clickPayloadValidator = (payload) => { return updatedPayload; }; +// ref : https://www.algolia.com/doc/rest-api/insights/#method-param-objectdata-2:~:text=currency-,%23,currency%20as%20ISO%2D4217%20currency%20code%2C%20such%20as%20USD%20or%20EUR.,-ObjectData +function validatePayload(payload) { + if (payload.objectData && Array.isArray(payload.objectData)) { + const hasRelevantFields = payload.objectData.some( + (obj) => + obj.hasOwnProperty('price') || + obj.hasOwnProperty('quantity') || + obj.hasOwnProperty('discount'), + ); + + if (hasRelevantFields && !payload.currency) { + throw new InstrumentationError( + 'Currency missing when objectData fields has price informations.', + ); + } + } +} + module.exports = { genericpayloadValidator, createObjectArray, eventTypeMapping, clickPayloadValidator, + validatePayload, }; diff --git a/src/v0/destinations/algolia/util.test.js b/src/v0/destinations/algolia/util.test.js new file mode 100644 index 0000000000..850d93f1c6 --- /dev/null +++ b/src/v0/destinations/algolia/util.test.js @@ -0,0 +1,58 @@ +const { InstrumentationError } = require('@rudderstack/integrations-lib'); +const { validatePayload } = require('./util'); + +describe('validatePayload', () => { + // When payload is valid and contains relevant fields and currency + it('should validate payload when it is valid and contains relevant fields and currency', () => { + const payload = { + objectData: [ + { price: 10, quantity: 2, discount: 0.1 }, + { price: 20, quantity: 1, discount: 0 }, + ], + currency: 'USD', + }; + + expect(() => validatePayload(payload)).not.toThrow(); + }); + + // When payload contains objects with missing relevant fields + it('should throw an error when payload contains objects with missing relevant fields', () => { + const payload = { + objectData: [ + { price: 10, quantity: 2 }, + { price: 20, discount: 0 }, + ], + }; + + expect(() => validatePayload(payload)).toThrow(InstrumentationError); + }); + + // When payload is valid and contains relevant fields but no currency + it('should throw an InstrumentationError when currency is missing', () => { + const payload = { + objectData: [ + { price: 10, quantity: 2, discount: 0.1 }, + { price: 20, quantity: 1, discount: 0 }, + ], + }; + + expect(() => validatePayload(payload)).toThrow(InstrumentationError); + }); + + // When payload is valid but does not contain relevant fields + it('should not throw an error when payload does not contain relevant fields', () => { + const payload = { + objectData: [{ position: 'Product A' }, { position: 'Product B' }], + currency: 'USD', + }; + + expect(() => validatePayload(payload)).not.toThrow(); + }); + + // When payload is empty + it('should not throw an error when payload is empty', () => { + const payload = {}; + + expect(() => validatePayload(payload)).not.toThrow(); + }); +}); diff --git a/test/integrations/destinations/algolia/processor/data.ts b/test/integrations/destinations/algolia/processor/data.ts index a8dd31b51a..d239c8de70 100644 --- a/test/integrations/destinations/algolia/processor/data.ts +++ b/test/integrations/destinations/algolia/processor/data.ts @@ -1688,6 +1688,7 @@ export const data = [ properties: { index: 'products', eventSubtype: 'addToCart', + currency: 'USD', products: [ { objectId: 'ecommerce-sample-data-919', @@ -1751,6 +1752,7 @@ export const data = [ events: [ { index: 'products', + currency: 'USD', queryID: '43b15df305339e827f0ac0bdc5ebcaa7', objectIDs: ['ecommerce-sample-data-919', '9780439784542'], userToken: 'testuserId1', @@ -1760,11 +1762,13 @@ export const data = [ objectData: [ { quantity: 2, + price: '10', queryID: '123', discount: '10', }, { quantity: 3, + price: '30', queryID: '123', discount: '10', }, @@ -1858,6 +1862,7 @@ export const data = [ userId: 'testuserId1', properties: { index: 'products', + currency: 'USD', eventSubtype: 'purchase', products: [ { @@ -1922,6 +1927,7 @@ export const data = [ events: [ { index: 'products', + currency: 'USD', queryID: '43b15df305339e827f0ac0bdc5ebcaa7', objectIDs: ['ecommerce-sample-data-919', '9780439784542'], userToken: 'testuserId1', @@ -1933,11 +1939,13 @@ export const data = [ quantity: 2, queryID: '123', discount: '10', + price: '10', }, { quantity: 3, queryID: '123', discount: '10', + price: '30', }, ], }, @@ -2287,4 +2295,138 @@ export const data = [ }, }, }, + { + name: 'algolia', + description: 'When price information is present in objectData, currency is mandatory', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-US', + ip: '0.0.0.0', + os: { + name: '', + version: '', + }, + screen: { + density: 2, + }, + page: { + path: '/destinations/ometria', + referrer: '', + search: '', + title: '', + url: 'https://docs.rudderstack.com/destinations/ometria', + category: 'destination', + initial_referrer: 'https://docs.rudderstack.com', + initial_referring_domain: 'docs.rudderstack.com', + }, + }, + type: 'track', + messageId: '84e26acc-56a5-4835-8233-591137fca468', + session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22', + originalTimestamp: '2019-10-14T09:03:17.562Z', + anonymousId: '123456', + event: 'product list viewed', + userId: 'testuserId1', + properties: { + index: 'products', + products: [ + { + objectId: 'ecommerce-sample-data-919', + position: 7, + quantity: '2', + price: 10, + }, + { + objectId: '9780439784542', + position: 8, + quantity: '3', + price: 30, + }, + ], + queryId: '43b15df305339e827f0ac0bdc5ebcaa7', + }, + integrations: { + All: true, + }, + sentAt: '2019-10-14T09:03:22.563Z', + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + excludeKeys: [], + includeKeys: [], + }, + }, + Config: { + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'product list viewed', + to: 'conversion', + }, + ], + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Currency missing when objectData fields has price informations.: Workflow: procWorkflow, Step: populateProductsData, ChildStep: populateForClickEvent, OriginalError: Currency missing when objectData fields has price informations.', + statTags: { + destType: 'ALGOLIA', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + destinationId: 'destId', + workspaceId: 'wspId', + }, + statusCode: 400, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + }, ];