From cf1c3bae131a8bae876a5f14b62293c2d4f4d680 Mon Sep 17 00:00:00 2001 From: Gauravudia Date: Fri, 15 Mar 2024 18:52:43 +0530 Subject: [PATCH] feat: onboard new destination bloomreach --- src/cdk/v2/destinations/bloomreach/config.ts | 28 +++ .../BloomreachCustomerPropertiesConfig.json | 36 +++ .../destinations/bloomreach/procWorkflow.yaml | 119 ++++++++++ .../destinations/bloomreach/rtWorkflow.yaml | 76 ++++++ src/cdk/v2/destinations/bloomreach/utils.ts | 30 +++ src/constants/destinationCanonicalNames.js | 1 + .../destinations/bloomreach/common.ts | 90 +++++++ .../destinations/bloomreach/mocks.ts | 5 + .../destinations/bloomreach/processor/data.ts | 5 + .../bloomreach/processor/identify.ts | 156 +++++++++++++ .../destinations/bloomreach/processor/page.ts | 72 ++++++ .../bloomreach/processor/track.ts | 173 ++++++++++++++ .../bloomreach/processor/validation.ts | 131 +++++++++++ .../destinations/bloomreach/router/data.ts | 220 ++++++++++++++++++ 14 files changed, 1142 insertions(+) create mode 100644 src/cdk/v2/destinations/bloomreach/config.ts create mode 100644 src/cdk/v2/destinations/bloomreach/data/BloomreachCustomerPropertiesConfig.json create mode 100644 src/cdk/v2/destinations/bloomreach/procWorkflow.yaml create mode 100644 src/cdk/v2/destinations/bloomreach/rtWorkflow.yaml create mode 100644 src/cdk/v2/destinations/bloomreach/utils.ts create mode 100644 test/integrations/destinations/bloomreach/common.ts create mode 100644 test/integrations/destinations/bloomreach/mocks.ts create mode 100644 test/integrations/destinations/bloomreach/processor/data.ts create mode 100644 test/integrations/destinations/bloomreach/processor/identify.ts create mode 100644 test/integrations/destinations/bloomreach/processor/page.ts create mode 100644 test/integrations/destinations/bloomreach/processor/track.ts create mode 100644 test/integrations/destinations/bloomreach/processor/validation.ts create mode 100644 test/integrations/destinations/bloomreach/router/data.ts diff --git a/src/cdk/v2/destinations/bloomreach/config.ts b/src/cdk/v2/destinations/bloomreach/config.ts new file mode 100644 index 0000000000..fcfdb0b6b1 --- /dev/null +++ b/src/cdk/v2/destinations/bloomreach/config.ts @@ -0,0 +1,28 @@ +import { getMappingConfig } from '../../../../v0/util'; + +export const CUSTOMER_COMMAND = 'customers'; +export const CUSTOMER_EVENT_COMMAND = 'customers/events'; +export const MAX_BATCH_SIZE = 50; +export const getBatchEndpoint = (apiBaseUrl: string, projectToken: string): string => + `${apiBaseUrl}/track/v2/projects/${projectToken}/batch`; + +const CONFIG_CATEGORIES = { + CUSTOMER_PROPERTIES_CONFIG: { name: 'BloomreachCustomerPropertiesConfig' }, +}; +const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); +export const EXCLUSION_FIELDS = [ + 'email', + 'firstName', + 'firstname', + 'first_name', + 'lastName', + 'lastname', + 'last_name', + 'name', + 'phone', + 'city', + 'birthday', + 'country', +]; +export const CUSTOMER_PROPERTIES_CONFIG = + MAPPING_CONFIG[CONFIG_CATEGORIES.CUSTOMER_PROPERTIES_CONFIG.name]; diff --git a/src/cdk/v2/destinations/bloomreach/data/BloomreachCustomerPropertiesConfig.json b/src/cdk/v2/destinations/bloomreach/data/BloomreachCustomerPropertiesConfig.json new file mode 100644 index 0000000000..cb4c2f7201 --- /dev/null +++ b/src/cdk/v2/destinations/bloomreach/data/BloomreachCustomerPropertiesConfig.json @@ -0,0 +1,36 @@ +[ + { + "destKey": "first_name", + "sourceKeys": "firstName", + "sourceFromGenericMap": true + }, + { + "destKey": "last_name", + "sourceKeys": "lastName", + "sourceFromGenericMap": true + }, + { + "destKey": "email", + "sourceKeys": "emailOnly", + "sourceFromGenericMap": true + }, + { + "destKey": "phone", + "sourceKeys": "phone", + "sourceFromGenericMap": true + }, + { + "destKey": "city", + "sourceKeys": "city", + "sourceFromGenericMap": true + }, + { + "destKey": "country", + "sourceKeys": ["traits.address.country", "context.traits.address.country"] + }, + { + "destKey": "birthday", + "sourceKeys": "birthday", + "sourceFromGenericMap": true + } +] diff --git a/src/cdk/v2/destinations/bloomreach/procWorkflow.yaml b/src/cdk/v2/destinations/bloomreach/procWorkflow.yaml new file mode 100644 index 0000000000..f092d90382 --- /dev/null +++ b/src/cdk/v2/destinations/bloomreach/procWorkflow.yaml @@ -0,0 +1,119 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ../../bindings/jsontemplate + - name: defaultRequestConfig + path: ../../../../v0/util + - name: toUnixTimestamp + path: ../../../../v0/util + - name: base64Convertor + path: ../../../../v0/util + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + - name: generateExclusionList + path: ../../../../v0/util + - name: extractCustomFields + path: ../../../../v0/util + - name: constructPayload + path: ../../../../v0/util + - path: ./utils + - path: ./config + +steps: + - name: messageType + template: | + $.context.messageType = .message.type.toLowerCase(); + + - name: validateInput + template: | + let messageType = $.context.messageType; + $.assert(messageType, "message Type is not present. Aborting"); + $.assert(messageType in {{$.EventType.([.IDENTIFY,.TRACK,.PAGE,.SCREEN])}}, "message type " + messageType + " is not supported"); + $.assertConfig(.destination.Config.apiBaseUrl, "API Base URL is not present. Aborting"); + $.assertConfig(.destination.Config.apiKey, "API Key is not present . Aborting"); + $.assertConfig(.destination.Config.apiSecret, "API Secret is not present. Aborting"); + $.assertConfig(.destination.Config.projectToken, "Project Token is not present. Aborting"); + $.assertConfig(.destination.Config.hardID, "Hard ID is not present. Aborting"); + $.assertConfig(.destination.Config.softID, "Soft ID is not present. Aborting"); + $.assert(.message.timestamp ?? .message.originalTimestamp, "Timestamp is not present. Aborting"); + const userId = .message.().( + {{{{$.getGenericPaths("userIdOnly")}}}}; + ); + $.assert(userId ?? .message.anonymousId, "Either one of userId or anonymousId is required. Aborting"); + + - name: prepareIdentifyPayload + condition: $.context.messageType === {{$.EventType.IDENTIFY}} + template: | + const customerIDs = $.prepareCustomerIDs(.message, .destination); + const customerProperties = $.constructPayload(.message, $.CUSTOMER_PROPERTIES_CONFIG); + const extraCustomerProperties = $.extractCustomFields(.message, {}, ['traits', 'context.traits'], $.EXCLUSION_FIELDS); + const properties = { + ...customerProperties, + ...extraCustomerProperties + } + const data = .message.().({ + "customer_ids": customerIDs, + "update_timestamp": $.toUnixTimestamp({{{{$.getGenericPaths("timestamp")}}}}), + properties + }); + + $.context.payload = $.removeUndefinedAndNullValues({name: $.CUSTOMER_COMMAND, data}) + + - name: prepareEventName + steps: + - name: pageEventName + condition: $.context.messageType === {{$.EventType.PAGE}} + template: | + const category = .message.category ?? .message.properties.category; + const name = .message.name || .message.properties.name; + const eventNameArray = ["Viewed"]; + category ? eventNameArray.push(category); + name ? eventNameArray.push(name); + eventNameArray.push("Page"); + $.context.event = eventNameArray.join(" "); + - name: screenEventName + condition: $.context.messageType === {{$.EventType.SCREEN}} + template: | + const category = .message.category ?? .message.properties.category; + const name = .message.name || .message.properties.name; + const eventNameArray = ["Viewed"]; + category ? eventNameArray.push(category); + name ? eventNameArray.push(name); + eventNameArray.push("Screen"); + $.context.event = eventNameArray.join(" "); + - name: trackEventName + condition: $.context.messageType === {{$.EventType.TRACK}} + template: | + $.assert(.message.event, "Event name is required. Aborting"); + $.context.event = .message.event + + - name: prepareTrackPageScreenPayload + condition: $.context.messageType !== {{$.EventType.IDENTIFY}} + template: | + const customerIDs = $.prepareCustomerIDs(.message, .destination); + const data = .message.().({ + "customer_ids": customerIDs, + "timestamp": $.toUnixTimestamp({{{{$.getGenericPaths("timestamp")}}}}), + "properties": .properties, + "event_type": $.context.event, + }); + + $.context.payload = $.removeUndefinedAndNullValues({name: $.CUSTOMER_EVENT_COMMAND, data}) + + - name: buildResponse + description: In batchMode we return payload directly + condition: $.batchMode + template: | + $.context.payload + else: + name: buildResponseForProcessTransformation + template: | + const response = $.defaultRequestConfig(); + response.body.JSON = $.context.payload; + response.endpoint = $.getBatchEndpoint(.destination.Config.apiBaseUrl, .destination.Config.projectToken); + response.method = "POST"; + response.headers = { + "Content-Type": "application/json", + "Authorization": "Basic " + $.base64Convertor(.destination.Config.apiKey + ":" + .destination.Config.apiSecret) + } + response; diff --git a/src/cdk/v2/destinations/bloomreach/rtWorkflow.yaml b/src/cdk/v2/destinations/bloomreach/rtWorkflow.yaml new file mode 100644 index 0000000000..b8b27ca02e --- /dev/null +++ b/src/cdk/v2/destinations/bloomreach/rtWorkflow.yaml @@ -0,0 +1,76 @@ +bindings: + - name: handleRtTfSingleEventError + path: ../../../../v0/util/index + - path: ./utils + exportAll: true + - name: base64Convertor + path: ../../../../v0/util + - name: toUnixTimestamp + path: ../../../../v0/util + - name: BatchUtils + path: '@rudderstack/workflow-engine' + - path: ./config + +steps: + - name: validateInput + template: | + $.assert(Array.isArray(^) && ^.length > 0, "Invalid event array") + + - name: transform + externalWorkflow: + path: ./procWorkflow.yaml + bindings: + - name: batchMode + value: true + 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: batchSuccessfulEvents + description: Batches the successfulEvents + template: | + let batches = $.BatchUtils.chunkArrayBySizeAndLength( + $.outputs.successfulEvents, {maxItems: $.MAX_BATCH_SIZE}).items; + + batches@batch.({ + "batchedRequest": { + "body": { + "JSON": {"commands": ~r batch.batchedRequest[]}, + "JSON_ARRAY": {}, + "XML": {}, + "FORM": {} + }, + "version": "1", + "type": "REST", + "method": "POST", + "endpoint": batch[0].destination.Config.().($.getBatchEndpoint(.apiBaseUrl, .projectToken)), + "headers": batch[0].destination.Config.().({ + "Content-Type": "application/json", + "Authorization": "Basic " + $.base64Convertor(.apiKey + ":" + .apiSecret) + }), + "params": {}, + "files": {} + }, + "metadata": ~r batch.metadata[], + "batched": true, + "statusCode": 200, + "destination": batch[0].destination + })[]; + + - name: finalPayload + template: | + [...$.outputs.batchSuccessfulEvents, ...$.outputs.failedEvents] diff --git a/src/cdk/v2/destinations/bloomreach/utils.ts b/src/cdk/v2/destinations/bloomreach/utils.ts new file mode 100644 index 0000000000..ecf154c916 --- /dev/null +++ b/src/cdk/v2/destinations/bloomreach/utils.ts @@ -0,0 +1,30 @@ +import { isObject, isEmptyObject, getIntegrationsObj } from '../../../../v0/util'; + +const getCustomerIDsFromIntegrationObject = (message) => { + const integrationObj = getIntegrationsObj(message, 'bloomreach' as any) || {}; + const { hardID, softID } = integrationObj; + const customerIDs = {}; + + if (isObject(hardID) && !isEmptyObject(hardID)) { + Object.keys(hardID).forEach((id) => { + customerIDs[id] = hardID[id]; + }); + } + + if (isObject(softID) && !isEmptyObject(softID)) { + Object.keys(softID).forEach((id) => { + customerIDs[id] = softID[id]; + }); + } + + return customerIDs; +}; + +export const prepareCustomerIDs = (message, destination) => { + const customerIDs = { + [destination.Config.hardID]: message.userId, + [destination.Config.softID]: message.anonymousId, + ...getCustomerIDsFromIntegrationObject(message), + }; + return customerIDs; +}; diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index 17848e6b94..98103808e5 100644 --- a/src/constants/destinationCanonicalNames.js +++ b/src/constants/destinationCanonicalNames.js @@ -153,6 +153,7 @@ const DestCanonicalNames = { ], INTERCOM: ['INTERCOM', 'intercom', 'Intercom'], koala: ['Koala', 'koala', 'KOALA'], + bloomreach: ['Bloomreach', 'bloomreach', 'BLOOMREACH'], }; module.exports = { DestHandlerMap, DestCanonicalNames }; diff --git a/test/integrations/destinations/bloomreach/common.ts b/test/integrations/destinations/bloomreach/common.ts new file mode 100644 index 0000000000..c73b8a6b8c --- /dev/null +++ b/test/integrations/destinations/bloomreach/common.ts @@ -0,0 +1,90 @@ +import { Destination } from '../../../../src/types'; + +const destType = 'bloomreach'; +const destTypeInUpperCase = 'BLOOMREACH'; +const displayName = 'bloomreach'; +const channel = 'web'; +const destination: Destination = { + Config: { + apiBaseUrl: 'https://demoapp-api.bloomreach.com', + apiKey: 'test-api-key', + apiSecret: 'test-api-secret', + projectToken: 'test-project-token', + hardID: 'registered', + softID: 'cookie', + }, + DestinationDefinition: { + DisplayName: displayName, + ID: '123', + Name: destTypeInUpperCase, + Config: { cdkV2Enabled: true }, + }, + Enabled: true, + ID: '123', + Name: destTypeInUpperCase, + Transformations: [], + WorkspaceID: 'test-workspace-id', +}; + +const traits = { + email: 'test@example.com', + firstName: 'John', + lastName: 'Doe', + phone: '1234567890', + address: { + city: 'New York', + country: 'USA', + pinCode: '123456', + }, +}; + +const properties = { + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + category: 'Games', + name: 'Cones of Dunshire', + brand: 'Wyatt Games', + variant: 'expansion pack', + price: 49.99, + quantity: 5, + coupon: 'PREORDER15', + currency: 'USD', + position: 1, + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.webp', + key1: 'value1', +}; +const endpoint = 'https://demoapp-api.bloomreach.com/track/v2/projects/test-project-token/batch'; + +const processorInstrumentationErrorStatTags = { + destType: destTypeInUpperCase, + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', +}; + +const RouterInstrumentationErrorStatTags = { + ...processorInstrumentationErrorStatTags, + feature: 'router', +}; + +const headers = { + 'Content-Type': 'application/json', + Authorization: 'Basic dGVzdC1hcGkta2V5OnRlc3QtYXBpLXNlY3JldA==', +}; + +export { + destType, + channel, + destination, + processorInstrumentationErrorStatTags, + RouterInstrumentationErrorStatTags, + traits, + headers, + properties, + endpoint, +}; diff --git a/test/integrations/destinations/bloomreach/mocks.ts b/test/integrations/destinations/bloomreach/mocks.ts new file mode 100644 index 0000000000..3730bd8ac0 --- /dev/null +++ b/test/integrations/destinations/bloomreach/mocks.ts @@ -0,0 +1,5 @@ +import * as config from '../../../../src/cdk/v2/destinations/bloomreach/config'; + +export const defaultMockFns = () => { + jest.replaceProperty(config, 'MAX_BATCH_SIZE', 3 as any); +}; diff --git a/test/integrations/destinations/bloomreach/processor/data.ts b/test/integrations/destinations/bloomreach/processor/data.ts new file mode 100644 index 0000000000..a3633ad0dd --- /dev/null +++ b/test/integrations/destinations/bloomreach/processor/data.ts @@ -0,0 +1,5 @@ +import { validation } from './validation'; +import { identify } from './identify'; +import { track } from './track'; +import { page } from './page'; +export const data = [...identify, ...track, ...page, ...validation]; diff --git a/test/integrations/destinations/bloomreach/processor/identify.ts b/test/integrations/destinations/bloomreach/processor/identify.ts new file mode 100644 index 0000000000..2a79cb57e3 --- /dev/null +++ b/test/integrations/destinations/bloomreach/processor/identify.ts @@ -0,0 +1,156 @@ +import { ProcessorTestData } from '../../../testTypes'; +import { generateMetadata, transformResultBuilder } from '../../../testUtils'; +import { destType, destination, traits, headers, endpoint } from '../common'; + +export const identify: ProcessorTestData[] = [ + { + id: 'bloomreach-identify-test-1', + name: destType, + description: 'Identify call to create/update customer properties', + scenario: 'Framework+Business', + successCriteria: 'Response should contain all the mapping and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'identify', + userId: 'userId123', + anonymousId: 'anonId123', + traits, + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + userId: '', + endpoint, + headers, + JSON: { + data: { + customer_ids: { registered: 'userId123', cookie: 'anonId123' }, + properties: { + email: 'test@example.com', + first_name: 'John', + last_name: 'Doe', + phone: '1234567890', + city: 'New York', + country: 'USA', + address: { + city: 'New York', + country: 'USA', + pinCode: '123456', + }, + }, + update_timestamp: 1709566376, + }, + name: 'customers', + }, + }), + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + { + id: 'bloomreach-identify-test-2', + name: destType, + description: 'Identify call with multiple hard and soft identifiers using integration object', + scenario: 'Framework+Business', + successCriteria: + 'Response should contain multiple hard and soft identifiers and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'identify', + userId: 'userId123', + anonymousId: 'anonId123', + traits, + integrations: { + All: true, + bloomreach: { + hardID: { + hardID1: 'value1', + }, + softID: { + google_analytics: 'gaId123', + softID2: 'value2', + }, + }, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + userId: '', + endpoint, + headers, + JSON: { + data: { + customer_ids: { + registered: 'userId123', + cookie: 'anonId123', + hardID1: 'value1', + google_analytics: 'gaId123', + softID2: 'value2', + }, + properties: { + email: 'test@example.com', + first_name: 'John', + last_name: 'Doe', + phone: '1234567890', + city: 'New York', + country: 'USA', + address: { + city: 'New York', + country: 'USA', + pinCode: '123456', + }, + }, + update_timestamp: 1709566376, + }, + name: 'customers', + }, + }), + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/bloomreach/processor/page.ts b/test/integrations/destinations/bloomreach/processor/page.ts new file mode 100644 index 0000000000..0c2d27989d --- /dev/null +++ b/test/integrations/destinations/bloomreach/processor/page.ts @@ -0,0 +1,72 @@ +import { ProcessorTestData } from '../../../testTypes'; +import { generateMetadata, transformResultBuilder } from '../../../testUtils'; +import { destType, destination, headers, endpoint } from '../common'; + +const properties = { + category: 'Docs', + path: '', + referrer: '', + search: '', + title: '', + url: '', +}; + +export const page: ProcessorTestData[] = [ + { + id: 'bloomreach-page-test-1', + name: destType, + description: 'Page call with category, name', + scenario: 'Framework+Business', + successCriteria: + 'Response should contain event_name = "Viewed {{ category }} {{ name }} Page" and properties and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'page', + anonymousId: 'anonId123', + name: 'Integration', + properties, + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + userId: '', + endpoint, + headers, + JSON: { + data: { + customer_ids: { cookie: 'anonId123' }, + properties, + timestamp: 1709566376, + event_type: 'Viewed Docs Integration Page', + }, + name: 'customers/events', + }, + }), + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/bloomreach/processor/track.ts b/test/integrations/destinations/bloomreach/processor/track.ts new file mode 100644 index 0000000000..a369f508b2 --- /dev/null +++ b/test/integrations/destinations/bloomreach/processor/track.ts @@ -0,0 +1,173 @@ +import { ProcessorTestData } from '../../../testTypes'; +import { generateMetadata, transformResultBuilder } from '../../../testUtils'; +import { destType, destination, headers, properties, endpoint } from '../common'; + +export const track: ProcessorTestData[] = [ + { + id: 'bloomreach-track-test-1', + name: destType, + description: 'Track call with anonymous user', + scenario: 'Framework+Business', + successCriteria: 'Response should contain all the mapping and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'track', + anonymousId: 'anonId123', + event: 'Product Viewed', + properties, + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + userId: '', + endpoint, + headers, + JSON: { + data: { + customer_ids: { cookie: 'anonId123' }, + properties, + timestamp: 1709566376, + event_type: 'Product Viewed', + }, + name: 'customers/events', + }, + }), + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + { + id: 'bloomreach-track-test-2', + name: destType, + description: 'Track call with known user', + scenario: 'Framework+Business', + successCriteria: 'Response should contain all the mapping and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'track', + userId: 'userId123', + anonymousId: 'anonId123', + event: 'Product Added', + properties, + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + userId: '', + endpoint, + headers, + JSON: { + data: { + customer_ids: { registered: 'userId123', cookie: 'anonId123' }, + properties, + timestamp: 1709566376, + event_type: 'Product Added', + }, + name: 'customers/events', + }, + }), + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + { + id: 'bloomreach-track-test-3', + name: destType, + description: 'Track call with no properties', + scenario: 'Framework+Business', + successCriteria: 'Response should contain all the mapping and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'track', + anonymousId: 'anonId123', + event: 'test_event', + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + userId: '', + endpoint, + headers, + JSON: { + data: { + customer_ids: { cookie: 'anonId123' }, + timestamp: 1709566376, + event_type: 'test_event', + }, + name: 'customers/events', + }, + }), + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/bloomreach/processor/validation.ts b/test/integrations/destinations/bloomreach/processor/validation.ts new file mode 100644 index 0000000000..ff959d74c6 --- /dev/null +++ b/test/integrations/destinations/bloomreach/processor/validation.ts @@ -0,0 +1,131 @@ +import { ProcessorTestData } from '../../../testTypes'; +import { generateMetadata } from '../../../testUtils'; +import { destType, destination, processorInstrumentationErrorStatTags } from '../common'; + +export const validation: ProcessorTestData[] = [ + { + id: 'bloomreach-validation-test-1', + name: destType, + description: 'Missing userId and anonymousId', + scenario: 'Framework', + successCriteria: 'Instrumentation Error', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'identify', + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Either one of userId or anonymousId is required. Aborting: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: Either one of userId or anonymousId is required. Aborting', + metadata: generateMetadata(1), + statTags: processorInstrumentationErrorStatTags, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bloomreach-validation-test-2', + name: destType, + description: 'Unsupported message type -> group', + scenario: 'Framework', + successCriteria: 'Instrumentation Error for Unsupported message type', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'group', + userId: 'userId123', + channel: 'mobile', + anonymousId: 'anon_123', + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'message type group is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type group is not supported', + metadata: generateMetadata(1), + statTags: processorInstrumentationErrorStatTags, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bloomreach-validation-test-3', + name: destType, + description: 'Missing required field -> timestamp', + scenario: 'Framework', + successCriteria: 'Instrumentation Error', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'identify', + integrations: { + All: true, + }, + }, + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Timestamp is not present. Aborting: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: Timestamp is not present. Aborting', + metadata: generateMetadata(1), + statTags: processorInstrumentationErrorStatTags, + statusCode: 400, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/bloomreach/router/data.ts b/test/integrations/destinations/bloomreach/router/data.ts new file mode 100644 index 0000000000..e99d0cc8cd --- /dev/null +++ b/test/integrations/destinations/bloomreach/router/data.ts @@ -0,0 +1,220 @@ +import { generateMetadata } from '../../../testUtils'; +import { defaultMockFns } from '../mocks'; +import { + destType, + destination, + traits, + properties, + headers, + endpoint, + RouterInstrumentationErrorStatTags, +} from '../common'; + +const routerRequest = { + input: [ + { + message: { + type: 'track', + anonymousId: 'anonId1', + event: 'test_event_1A', + properties, + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(1), + destination, + }, + { + message: { + type: 'identify', + anonymousId: 'anonId1', + userId: 'userId1', + traits, + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(2), + destination, + }, + { + message: { + type: 'track', + anonymousId: 'anonId2', + event: 'test_event_2A', + properties, + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(3), + destination, + }, + { + message: { + type: 'track', + anonymousId: 'anonId1', + userId: 'userId1', + event: 'test_event_1B', + properties, + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(4), + destination, + }, + { + message: { + type: 'identify', + traits, + integrations: { + All: true, + }, + originalTimestamp: '2024-03-04T15:32:56.409Z', + }, + metadata: generateMetadata(5), + destination, + }, + ], + destType, +}; +export const data = [ + { + id: 'bloomreach-router-test-1', + name: destType, + description: 'Basic Router Test to test multiple payloads', + scenario: 'Framework', + successCriteria: 'All events should be transformed successfully and status code should be 200', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: routerRequest, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: { + version: '1', + type: 'REST', + method: 'POST', + endpoint, + headers, + params: {}, + body: { + JSON: { + commands: [ + { + data: { + customer_ids: { cookie: 'anonId1' }, + properties, + timestamp: 1709566376, + event_type: 'test_event_1A', + }, + name: 'customers/events', + }, + { + data: { + customer_ids: { + registered: 'userId1', + cookie: 'anonId1', + }, + properties: { + email: 'test@example.com', + first_name: 'John', + last_name: 'Doe', + phone: '1234567890', + city: 'New York', + country: 'USA', + address: { + city: 'New York', + country: 'USA', + pinCode: '123456', + }, + }, + update_timestamp: 1709566376, + }, + name: 'customers', + }, + { + data: { + customer_ids: { cookie: 'anonId2' }, + properties, + timestamp: 1709566376, + event_type: 'test_event_2A', + }, + name: 'customers/events', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + metadata: [generateMetadata(1), generateMetadata(2), generateMetadata(3)], + batched: true, + statusCode: 200, + destination, + }, + { + batchedRequest: { + version: '1', + type: 'REST', + method: 'POST', + endpoint, + headers, + params: {}, + body: { + JSON: { + commands: [ + { + data: { + customer_ids: { registered: 'userId1', cookie: 'anonId1' }, + properties, + timestamp: 1709566376, + event_type: 'test_event_1B', + }, + name: 'customers/events', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + metadata: [generateMetadata(4)], + batched: true, + statusCode: 200, + destination, + }, + { + metadata: [generateMetadata(5)], + batched: false, + statusCode: 400, + error: 'Either one of userId or anonymousId is required. Aborting', + statTags: RouterInstrumentationErrorStatTags, + destination, + }, + ], + }, + }, + }, + mockFns: defaultMockFns, + }, +];