From cc94bba682f667877a721f63627adc6ff6a7386a Mon Sep 17 00:00:00 2001 From: Gauravudia <60897972+Gauravudia@users.noreply.github.com> Date: Thu, 29 Feb 2024 12:34:15 +0530 Subject: [PATCH 01/10] feat: add event mapping support for branch destination (#3135) * feat: add event mapping support for branch destination * refactor: address review comments --- src/v0/destinations/branch/transform.js | 16 +- src/v0/destinations/branch/utils.js | 24 +++ src/v0/destinations/branch/utils.test.js | 18 ++ .../destinations/branch/processor/data.ts | 156 ++++++++++++++++++ 4 files changed, 205 insertions(+), 9 deletions(-) create mode 100644 src/v0/destinations/branch/utils.js create mode 100644 src/v0/destinations/branch/utils.test.js diff --git a/src/v0/destinations/branch/transform.js b/src/v0/destinations/branch/transform.js index 23dcd6c8db..2626d8aa81 100644 --- a/src/v0/destinations/branch/transform.js +++ b/src/v0/destinations/branch/transform.js @@ -13,6 +13,7 @@ const { simpleProcessRouterDest, } = require('../../util'); const { JSON_MIME_TYPE } = require('../../util/constant'); +const { getMappedEventNameFromConfig } = require('./utils'); function responseBuilder(payload, message, destination, category) { const response = defaultRequestConfig(); @@ -213,24 +214,21 @@ function getCommonPayload(message, category, evName) { return rawPayload; } -// function getTrackPayload(message) { -// const rawPayload = {}; -// const { name, category } = getCategoryAndName(message.event); -// rawPayload.name = name; -// -// return commonPayload(message, rawPayload, category); -// } - function processMessage(message, destination) { let evName; let category; switch (message.type) { - case EventType.TRACK: + case EventType.TRACK: { if (!message.event) { throw new InstrumentationError('Event name is required'); } ({ evName, category } = getCategoryAndName(message.event)); + const eventNameFromConfig = getMappedEventNameFromConfig(message, destination); + if (eventNameFromConfig) { + evName = eventNameFromConfig; + } break; + } case EventType.IDENTIFY: ({ evName, category } = getCategoryAndName(message.userId)); break; diff --git a/src/v0/destinations/branch/utils.js b/src/v0/destinations/branch/utils.js new file mode 100644 index 0000000000..1415f0de39 --- /dev/null +++ b/src/v0/destinations/branch/utils.js @@ -0,0 +1,24 @@ +const { getHashFromArray } = require('../../util'); + +/** + * Retrieves the mapped event name from the given config. + * + * @param {object} message - The message object containing the event. + * @param {object} destination - The destination object containing the events mapping configuration. + * @returns {string} - The mapped event name, or undefined if not found. + */ +const getMappedEventNameFromConfig = (message, destination) => { + let eventName; + const { event } = message; + const { eventsMapping } = destination.Config; + + // if event is mapped on dashboard, use the mapped event name + if (Array.isArray(eventsMapping) && eventsMapping.length > 0) { + const keyMap = getHashFromArray(eventsMapping, 'from', 'to', false); + eventName = keyMap[event]; + } + + return eventName; +}; + +module.exports = { getMappedEventNameFromConfig }; diff --git a/src/v0/destinations/branch/utils.test.js b/src/v0/destinations/branch/utils.test.js new file mode 100644 index 0000000000..e7da007d89 --- /dev/null +++ b/src/v0/destinations/branch/utils.test.js @@ -0,0 +1,18 @@ +const { getMappedEventNameFromConfig } = require('./utils'); +describe('getMappedEventNameFromConfig', () => { + it('should return the mapped event name when it exists in the events mapping configuration', () => { + const message = { event: 'Order Completed' }; + const destination = { + Config: { eventsMapping: [{ from: 'Order Completed', to: 'PURCHASE' }] }, + }; + const result = getMappedEventNameFromConfig(message, destination); + expect(result).toBe('PURCHASE'); + }); + + it('should return undefined when the event mapping is not created', () => { + const message = { event: 'Order Completed' }; + const destination = { Config: {} }; + const result = getMappedEventNameFromConfig(message, destination); + expect(result).toBeUndefined(); + }); +}); diff --git a/test/integrations/destinations/branch/processor/data.ts b/test/integrations/destinations/branch/processor/data.ts index 9fed354235..dc1bdd33bc 100644 --- a/test/integrations/destinations/branch/processor/data.ts +++ b/test/integrations/destinations/branch/processor/data.ts @@ -1490,4 +1490,160 @@ export const data = [ }, }, }, + { + name: 'branch', + description: 'Map event name to branch standard event name in track call', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + branchKey: 'test_branch_key', + eventsMapping: [ + { + from: 'Order Completed', + to: 'PURCHASE', + }, + ], + useNativeSDK: false, + }, + DestinationDefinition: { + DisplayName: 'Branch Metrics', + ID: '1WTpBSTiL3iAUHUdW7rHT4sawgU', + Name: 'BRANCH', + }, + Enabled: true, + ID: '1WTpIHpH7NTBgjeiUPW1kCUgZGI', + Name: 'branch test', + Transformations: [], + }, + message: { + anonymousId: 'anonId123', + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + device: { + adTrackingEnabled: true, + advertisingId: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', + id: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', + manufacturer: 'Google', + model: 'AOSP on IA Emulator', + name: 'generic_x86_arm', + type: 'ios', + attTrackingStatus: 3, + }, + ip: '0.0.0.0', + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + locale: 'en-US', + os: { + name: 'iOS', + version: '14.4.1', + }, + screen: { + density: 2, + }, + traits: { + anonymousId: 'anonId123', + email: 'test_user@gmail.com', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36', + }, + event: 'Order Completed', + integrations: { + All: true, + }, + messageId: 'ea5cfab2-3961-4d8a-8187-3d1858c90a9f', + originalTimestamp: '2020-01-17T04:53:51.185Z', + properties: { + name: 't-shirt', + revenue: '10', + currency: 'USD', + key1: 'value1', + key2: 'value2', + order_id: 'order123', + }, + receivedAt: '2020-01-17T10:23:52.688+05:30', + request_ip: '[::1]:64059', + sentAt: '2020-01-17T04:53:52.667Z', + timestamp: '2020-01-17T10:23:51.206+05:30', + type: 'track', + userId: 'userId123', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api2.branch.io/v2/event/standard', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + params: {}, + body: { + JSON: { + branch_key: 'test_branch_key', + name: 'PURCHASE', + content_items: [ + { + $product_name: 't-shirt', + }, + ], + event_data: { + revenue: '10', + currency: 'USD', + }, + custom_data: { + key1: 'value1', + key2: 'value2', + order_id: 'order123', + }, + user_data: { + os: 'iOS', + os_version: '14.4.1', + app_version: '1.0.0', + screen_dpi: 2, + developer_identity: 'userId123', + idfa: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', + idfv: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', + limit_ad_tracking: false, + model: 'AOSP on IA Emulator', + user_agent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36', + }, + }, + XML: {}, + JSON_ARRAY: {}, + FORM: {}, + }, + files: {}, + userId: 'anonId123', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; From 4dcbf8fb189a39bb40b950742425a0b9da2d8d7c Mon Sep 17 00:00:00 2001 From: shrouti1507 <60211312+shrouti1507@users.noreply.github.com> Date: Thu, 29 Feb 2024 12:44:25 +0530 Subject: [PATCH 02/10] fix: marketo bulk upload zero and null value allowed (#3134) --- .../marketo_bulk_upload/transform.js | 4 +- .../marketo_bulk_upload/processor/data.ts | 93 +++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/v0/destinations/marketo_bulk_upload/transform.js b/src/v0/destinations/marketo_bulk_upload/transform.js index 5431e67d38..1943d817cd 100644 --- a/src/v0/destinations/marketo_bulk_upload/transform.js +++ b/src/v0/destinations/marketo_bulk_upload/transform.js @@ -1,4 +1,4 @@ -const { InstrumentationError } = require('@rudderstack/integrations-lib'); +const { InstrumentationError, isDefined } = require('@rudderstack/integrations-lib'); const { getHashFromArray, getFieldValueFromMessage, @@ -34,7 +34,7 @@ function responseBuilderSimple(message, destination) { // columnNames with trait's values from rudder payload Object.keys(fieldHashmap).forEach((key) => { const val = traits[fieldHashmap[key]]; - if (val) { + if (isDefined(val)) { payload[key] = val; } }); diff --git a/test/integrations/destinations/marketo_bulk_upload/processor/data.ts b/test/integrations/destinations/marketo_bulk_upload/processor/data.ts index f1b162ad28..90a3ca8584 100644 --- a/test/integrations/destinations/marketo_bulk_upload/processor/data.ts +++ b/test/integrations/destinations/marketo_bulk_upload/processor/data.ts @@ -502,4 +502,97 @@ export const data = [ }, }, }, + { + name: 'marketo_bulk_upload', + description: 'Test 5: Any null or zero value will be passed through transform payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'identify', + traits: { + name: 'Carlo Lombard', + plan: 0, + id: null, + }, + userId: 476335, + context: { + ip: '14.0.2.238', + page: { + url: 'enuffsaid.proposify.com', + path: '/settings', + method: 'POST', + referrer: 'https://enuffsaid.proposify.com/login', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36', + }, + rudderId: '786dfec9-jfh', + messageId: '5d9bc6e2-ekjh', + }, + destination: { + ID: '1mMy5cqbtfuaKZv1IhVQKnBdVwe', + Config: { + munchkinId: 'XXXX', + clientId: 'YYYY', + clientSecret: 'ZZZZ', + columnFieldsMapping: [ + { + to: 'name__c', + from: 'name', + }, + { + to: 'email__c', + from: 'email', + }, + { + to: 'plan__c', + from: 'plan', + }, + { + to: 'id', + from: 'id', + }, + ], + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: '/fileUpload', + headers: {}, + params: {}, + body: { + JSON: { + name__c: 'Carlo Lombard', + plan__c: 0, + id: null, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; From 2a68c4573d92274908b26082ec3ad2b4c7f7776e Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 29 Feb 2024 07:26:41 +0000 Subject: [PATCH 03/10] chore(release): 1.57.0 --- CHANGELOG.md | 12 ++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 902d797301..7c526b8051 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.57.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.56.1...v1.57.0) (2024-02-29) + + +### Features + +* add event mapping support for branch destination ([#3135](https://github.com/rudderlabs/rudder-transformer/issues/3135)) ([cc94bba](https://github.com/rudderlabs/rudder-transformer/commit/cc94bba682f667877a721f63627adc6ff6a7386a)) + + +### Bug Fixes + +* marketo bulk upload zero and null value allowed ([#3134](https://github.com/rudderlabs/rudder-transformer/issues/3134)) ([4dcbf8f](https://github.com/rudderlabs/rudder-transformer/commit/4dcbf8fb189a39bb40b950742425a0b9da2d8d7c)) + ### [1.56.1](https://github.com/rudderlabs/rudder-transformer/compare/v1.56.0...v1.56.1) (2024-02-21) diff --git a/package-lock.json b/package-lock.json index 2d4f32bd5a..dfed6e2397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rudder-transformer", - "version": "1.56.1", + "version": "1.57.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rudder-transformer", - "version": "1.56.1", + "version": "1.57.0", "license": "ISC", "dependencies": { "@amplitude/ua-parser-js": "0.7.24", diff --git a/package.json b/package.json index a1053c0496..f241515ffc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rudder-transformer", - "version": "1.56.1", + "version": "1.57.0", "description": "", "homepage": "https://github.com/rudderlabs/rudder-transformer#readme", "bugs": { From feb256b5309cf8eb8aa350a6fc8825f791e597a6 Mon Sep 17 00:00:00 2001 From: Mihir Bhalala <77438541+mihir-4116@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:03:25 +0530 Subject: [PATCH 04/10] chore: reddit proxy test refactor (#3096) * feat: update proxy data type for response handler input * feat: update proxy v1 test cases * feat: update proxy tests for cm360 Added new structure for proxy test scnearios for cm360 also added zod validations as part of tests * fix: typo * Update test/integrations/destinations/campaign_manager/dataDelivery/business.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update test/integrations/destinations/campaign_manager/dataDelivery/business.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: api contract for v1 proxy * chore: clean up zod type * chore: update testutils * chore: update V0 proxy request type and zod schema * feat: adding zod validations (#3066) * feat: add type definitions for test cases * fix: update networkHandler for rakuten --------- Co-authored-by: Utsab Chowdhury * chore: update delivery test cases for criteo audience * Revert "chore: update delivery test cases for criteo audience" This reverts commit 689b0cda0aeace910e82167375045e123e365300. * chore: add type def for proxy v1 test * chore: fix generateMetdata func * chore: criteo audience update proxy test (#3068) * chore: update delivery test cases for criteo audience * chore: enable batch response schema check (#3083) * chore: reddit proxy test refactor * chore: code review changes --------- Co-authored-by: Utsab Chowdhury Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Utsab Chowdhury Co-authored-by: ItsSudip Co-authored-by: Sudip Paul <67197965+ItsSudip@users.noreply.github.com> Co-authored-by: chandumlg <54652834+chandumlg@users.noreply.github.com> --- .../reddit/dataDelivery/business.ts | 131 +++++++++++++ .../destinations/reddit/dataDelivery/data.ts | 9 + .../destinations/reddit/dataDelivery/oauth.ts | 147 +++++++++++++++ .../destinations/reddit/delivery/data.ts | 174 ------------------ 4 files changed, 287 insertions(+), 174 deletions(-) create mode 100644 test/integrations/destinations/reddit/dataDelivery/business.ts create mode 100644 test/integrations/destinations/reddit/dataDelivery/data.ts create mode 100644 test/integrations/destinations/reddit/dataDelivery/oauth.ts delete mode 100644 test/integrations/destinations/reddit/delivery/data.ts diff --git a/test/integrations/destinations/reddit/dataDelivery/business.ts b/test/integrations/destinations/reddit/dataDelivery/business.ts new file mode 100644 index 0000000000..2c4714ef13 --- /dev/null +++ b/test/integrations/destinations/reddit/dataDelivery/business.ts @@ -0,0 +1,131 @@ +import { + generateMetadata, + generateProxyV0Payload, + generateProxyV1Payload, +} from '../../../testUtils'; + +const validRequestPayload = { + events: [ + { + event_at: '2019-10-14T09:03:17.562Z', + event_type: { + tracking_type: 'Purchase', + }, + user: { + aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', + email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', + external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', + ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', + user_agent: + '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', + screen_dimensions: {}, + }, + event_metadata: { + item_count: 3, + products: [ + { + id: '123', + name: 'Monopoly', + category: 'Games', + }, + { + id: '345', + name: 'UNO', + category: 'Games', + }, + ], + }, + }, + ], +}; + +const commonHeaders = { + Authorization: 'Bearer dummyAccessToken', + 'Content-Type': 'application/json', +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: validRequestPayload, +}; + +export const testScenariosForV0API = [ + { + id: 'reddit_v0_scenario_1', + name: 'reddit', + description: + '[Proxy v0 API] :: Test for a valid request with a successful 200 response from the destination', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_fsddXXXfsfd', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + destResp: { + response: { + message: 'Successfully processed 1 conversion events.', + }, + status: 200, + }, + message: 'Request Processed Successfully', + status: 200, + }, + }, + }, + }, + }, +]; + +export const testScenariosForV1API = [ + { + id: 'reddit_v1_scenario_1', + name: 'reddit', + description: + '[Proxy v1 API] :: Test for a valid request with a successful 200 response from the destination', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://dfareporting.googleapis.com/test_url_for_valid_request', + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + metadata: generateMetadata(1), + statusCode: 500, + }, + ], + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/reddit/dataDelivery/data.ts b/test/integrations/destinations/reddit/dataDelivery/data.ts new file mode 100644 index 0000000000..54728ecb90 --- /dev/null +++ b/test/integrations/destinations/reddit/dataDelivery/data.ts @@ -0,0 +1,9 @@ +import { testScenariosForV0API, testScenariosForV1API } from './business'; +import { v0oauthScenarios, v1oauthScenarios } from './oauth'; + +export const data = [ + ...v0oauthScenarios, + ...v1oauthScenarios, + ...testScenariosForV0API, + ...testScenariosForV1API, +]; diff --git a/test/integrations/destinations/reddit/dataDelivery/oauth.ts b/test/integrations/destinations/reddit/dataDelivery/oauth.ts new file mode 100644 index 0000000000..90368cd60b --- /dev/null +++ b/test/integrations/destinations/reddit/dataDelivery/oauth.ts @@ -0,0 +1,147 @@ +import { + generateMetadata, + generateProxyV1Payload, + generateProxyV0Payload, +} from '../../../testUtils'; + +const authorizationRequiredRequestPayload = { + events: [ + { + event_at: '2019-10-14T09:03:17.562Z', + event_type: { + tracking_type: 'ViewContent', + }, + user: { + aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', + email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', + external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', + ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', + user_agent: + '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', + screen_dimensions: {}, + }, + event_metadata: { + item_count: 3, + products: [ + { + id: '123', + name: 'Monopoly', + category: 'Games', + }, + { + id: '345', + name: 'UNO', + category: 'Games', + }, + ], + }, + }, + ], +}; + +const commonHeaders = { + Authorization: 'Bearer dummyAccessToken', + 'Content-Type': 'application/json', +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: authorizationRequiredRequestPayload, +}; + +const expectedStatTags = { + destType: 'REDDIT', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'retryable', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', +}; + +export const v0oauthScenarios = [ + { + id: 'reddit_v0_oauth_scenario_1', + name: 'reddit', + description: '[Proxy v0 API] :: Oauth where Authorization Required response from destination', + successCriteria: 'Should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_gsddXXXfsfd', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + authErrorCategory: 'REFRESH_TOKEN', + destinationResponse: { + response: 'Authorization Required', + status: 401, + }, + message: + "Request failed due to Authorization Required 'during reddit response transformation'", + statTags: expectedStatTags, + status: 500, + }, + }, + }, + }, + }, +]; + +export const v1oauthScenarios = [ + { + id: 'reddit_v1_oauth_scenario_1', + name: 'reddit', + description: '[Proxy v1 API] :: Oauth where Authorization Required response from destination', + successCriteria: 'Should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_gsddXXXfsfd', + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + authErrorCategory: 'REFRESH_TOKEN', + message: + "Request failed due to Authorization Required 'during reddit response transformation'", + response: [ + { + error: '"Authorization Required"', + metadata: generateMetadata(1), + statusCode: 500, + }, + ], + statTags: expectedStatTags, + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/reddit/delivery/data.ts b/test/integrations/destinations/reddit/delivery/data.ts deleted file mode 100644 index 66c1e2863f..0000000000 --- a/test/integrations/destinations/reddit/delivery/data.ts +++ /dev/null @@ -1,174 +0,0 @@ -export const data = [ - { - name: 'reddit', - description: 'Test 0', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_fsddXXXfsfd', - headers: { - Authorization: 'Bearer dummyAccessToken', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - events: [ - { - event_at: '2019-10-14T09:03:17.562Z', - event_type: { - tracking_type: 'Purchase', - }, - user: { - aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', - email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', - external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', - ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', - user_agent: - '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', - screen_dimensions: {}, - }, - event_metadata: { - item_count: 3, - products: [ - { - id: '123', - name: 'Monopoly', - category: 'Games', - }, - { - id: '345', - name: 'UNO', - category: 'Games', - }, - ], - }, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - userId: '', - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - destResp: { - response: { - message: 'Successfully processed 1 conversion events.', - }, - status: 200, - }, - message: 'Request Processed Successfully', - status: 200, - }, - }, - }, - }, - }, - { - name: 'reddit', - description: 'Test 1', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_gsddXXXfsfd', - headers: { - Authorization: 'Bearer dummyAccessToken', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - events: [ - { - event_at: '2019-10-14T09:03:17.562Z', - event_type: { - tracking_type: 'ViewContent', - }, - user: { - aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', - email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', - external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', - ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', - user_agent: - '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', - screen_dimensions: {}, - }, - event_metadata: { - item_count: 3, - products: [ - { - id: '123', - name: 'Monopoly', - category: 'Games', - }, - { - id: '345', - name: 'UNO', - category: 'Games', - }, - ], - }, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - userId: '', - }, - method: 'POST', - }, - }, - output: { - response: { - status: 500, - body: { - output: { - authErrorCategory: 'REFRESH_TOKEN', - destinationResponse: { - response: 'Authorization Required', - status: 401, - }, - message: - "Request failed due to Authorization Required 'during reddit response transformation'", - statTags: { - destType: 'REDDIT', - destinationId: 'Non-determininable', - errorCategory: 'network', - errorType: 'retryable', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'Non-determininable', - }, - status: 500, - }, - }, - }, - }, - }, -]; From aea417cd2691547399010c034cadbc5db6b0c6ee Mon Sep 17 00:00:00 2001 From: Sandeep Digumarty Date: Fri, 1 Mar 2024 13:14:24 +0530 Subject: [PATCH 05/10] feat(facebook): update content_type mapping logic for fb pixel and fb conversions (#3113) * feat(facebook): update content_type mapping logic for fb pixel and fb conversions * feat(facebook): update content_type mapping logic for fb conversions * chore: added tests * chore: updated tests --- .../facebook_conversions/utils.js | 48 ++++---- src/v0/destinations/facebook_pixel/utils.js | 34 ++++-- .../facebook_conversions/processor/data.ts | 100 ++++++++++++++++ .../facebook_pixel/processor/data.ts | 107 ++++++++++++++++++ 4 files changed, 254 insertions(+), 35 deletions(-) diff --git a/src/v0/destinations/facebook_conversions/utils.js b/src/v0/destinations/facebook_conversions/utils.js index 26204ec61a..c6e3993e33 100644 --- a/src/v0/destinations/facebook_conversions/utils.js +++ b/src/v0/destinations/facebook_conversions/utils.js @@ -93,28 +93,26 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego ); const contentCategory = eventTypeCustomData.content_category; - let contentType; + let defaultContentType; if (contentIds.length > 0) { - contentType = 'product'; + defaultContentType = 'product'; } else if (contentCategory) { contentIds.push(contentCategory); contents.push({ id: contentCategory, quantity: 1, }); - contentType = 'product_group'; + defaultContentType = 'product_group'; } + const contentType = + message.properties?.content_type || + getContentType(message, defaultContentType, categoryToContent, DESTINATION.toLowerCase()); eventTypeCustomData = { ...eventTypeCustomData, content_ids: contentIds, contents, - content_type: getContentType( - message, - contentType, - categoryToContent, - DESTINATION.toLowerCase(), - ), + content_type: contentType, content_category: getContentCategory(contentCategory), }; break; @@ -125,18 +123,20 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego case 'payment info entered': case 'product added to wishlist': { const contentCategory = eventTypeCustomData.content_category; - const contentType = eventTypeCustomData.content_type; + const contentType = + message.properties?.content_type || + getContentType( + message, + eventTypeCustomData.content_type, + categoryToContent, + DESTINATION.toLowerCase(), + ); const { contentIds, contents } = populateContentsAndContentIDs([message.properties]); eventTypeCustomData = { ...eventTypeCustomData, content_ids: contentIds, contents, - content_type: getContentType( - message, - contentType, - categoryToContent, - DESTINATION.toLowerCase(), - ), + content_type: contentType, content_category: getContentCategory(contentCategory), }; validateProductSearchedData(eventTypeCustomData); @@ -151,18 +151,20 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego ); const contentCategory = eventTypeCustomData.content_category; - const contentType = eventTypeCustomData.content_type; + const contentType = + message.properties?.content_type || + getContentType( + message, + eventTypeCustomData.content_type, + categoryToContent, + DESTINATION.toLowerCase(), + ); eventTypeCustomData = { ...eventTypeCustomData, content_ids: contentIds, contents, - content_type: getContentType( - message, - contentType, - categoryToContent, - DESTINATION.toLowerCase(), - ), + content_type: contentType, content_category: getContentCategory(contentCategory), num_items: contentIds.length, }; diff --git a/src/v0/destinations/facebook_pixel/utils.js b/src/v0/destinations/facebook_pixel/utils.js index 8a63a0b0fe..cfa625ee3d 100644 --- a/src/v0/destinations/facebook_pixel/utils.js +++ b/src/v0/destinations/facebook_pixel/utils.js @@ -53,13 +53,9 @@ const getActionSource = (payload, channel) => { * Handles order completed and checkout started types of specific events */ const handleOrder = (message, categoryToContent) => { - const { products, revenue } = message.properties; - const value = formatRevenue(revenue); - - const contentType = getContentType(message, 'product', categoryToContent); - const contentIds = []; - const contents = []; const { + products, + revenue, category, quantity, price, @@ -67,6 +63,12 @@ const handleOrder = (message, categoryToContent) => { contentName, delivery_category: deliveryCategory, } = message.properties; + const value = formatRevenue(revenue); + let { content_type: contentType } = message.properties; + contentType = contentType || getContentType(message, 'product', categoryToContent); + const contentIds = []; + const contents = []; + if (products) { if (products.length > 0 && Array.isArray(products)) { products.forEach((singleProduct) => { @@ -109,10 +111,17 @@ const handleOrder = (message, categoryToContent) => { * Handles product list viewed */ const handleProductListViewed = (message, categoryToContent) => { - let contentType; + let defaultContentType; const contentIds = []; const contents = []; - const { products, category, quantity, value, contentName } = message.properties; + const { + products, + category, + quantity, + value, + contentName, + content_type: contentType, + } = message.properties; if (products && products.length > 0 && Array.isArray(products)) { products.forEach((product, index) => { if (isObject(product)) { @@ -132,7 +141,7 @@ const handleProductListViewed = (message, categoryToContent) => { } if (contentIds.length > 0) { - contentType = 'product'; + defaultContentType = 'product'; // for viewContent event content_ids and content arrays are not mandatory } else if (category) { contentIds.push(category); @@ -140,12 +149,12 @@ const handleProductListViewed = (message, categoryToContent) => { id: category, quantity: 1, }); - contentType = 'product_group'; + defaultContentType = 'product_group'; } return { content_ids: contentIds, - content_type: getContentType(message, contentType, categoryToContent), + content_type: contentType || getContentType(message, defaultContentType, categoryToContent), contents, content_category: getContentCategory(category), content_name: contentName, @@ -165,7 +174,8 @@ const handleProduct = (message, categoryToContent, valueFieldIdentifier) => { const useValue = valueFieldIdentifier === 'properties.value'; const contentId = message.properties?.product_id || message.properties?.sku || message.properties?.id; - const contentType = getContentType(message, 'product', categoryToContent); + const contentType = + message.properties?.content_type || getContentType(message, 'product', categoryToContent); const contentName = message.properties.product_name || message.properties.name || ''; const contentCategory = message.properties.category || ''; const currency = message.properties.currency || 'USD'; diff --git a/test/integrations/destinations/facebook_conversions/processor/data.ts b/test/integrations/destinations/facebook_conversions/processor/data.ts index beb7eb32aa..6eb90942a7 100644 --- a/test/integrations/destinations/facebook_conversions/processor/data.ts +++ b/test/integrations/destinations/facebook_conversions/processor/data.ts @@ -1434,4 +1434,104 @@ export const data = [ }, mockFns: defaultMockFns, }, + { + name: 'facebook_conversions', + description: 'Track event with standard event order completed with content_type in properties', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1', + channel: 'web', + context: { + traits: { + email: ' aBc@gmail.com ', + address: { + zip: 1234, + }, + anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1', + }, + }, + event: 'order completed', + integrations: { + All: true, + }, + message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8', + properties: { + content_type: 'product_group', + revenue: 400, + additional_bet_index: 0, + products: [ + { + product_id: 1234, + quantity: 5, + price: 55, + }, + ], + }, + timestamp: '2023-11-12T15:46:51.693229+05:30', + type: 'track', + }, + destination: { + Config: { + limitedDataUsage: true, + blacklistPiiProperties: [ + { + blacklistPiiProperties: '', + blacklistPiiHash: false, + }, + ], + accessToken: '09876', + datasetId: 'dummyID', + eventsToEvents: [ + { + from: '', + to: '', + }, + ], + removeExternalId: true, + actionSource: 'website', + }, + Enabled: true, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://graph.facebook.com/v18.0/dummyID/events?access_token=09876', + headers: {}, + params: {}, + body: { + JSON: {}, + XML: {}, + JSON_ARRAY: {}, + FORM: { + data: [ + '{"user_data":{"em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08","zp":"03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4"},"event_name":"Purchase","event_time":1699784211,"action_source":"website","custom_data":{"content_type":"product_group","revenue":400,"additional_bet_index":0,"products":[{"product_id":1234,"quantity":5,"price":55}],"content_ids":[1234],"contents":[{"id":1234,"quantity":5,"item_price":55}],"currency":"USD","value":400,"num_items":1}}', + ], + }, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + mockFns: defaultMockFns, + }, ]; diff --git a/test/integrations/destinations/facebook_pixel/processor/data.ts b/test/integrations/destinations/facebook_pixel/processor/data.ts index 557bc7066c..f6a5cd1e20 100644 --- a/test/integrations/destinations/facebook_pixel/processor/data.ts +++ b/test/integrations/destinations/facebook_pixel/processor/data.ts @@ -6460,4 +6460,111 @@ export const data = [ }, }, }, + { + name: 'facebook_pixel', + description: + 'Test 51: properties.content_type is given priority over populating it from categoryToContent mapping.', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + type: 'track', + messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be', + originalTimestamp: '2023-10-14T15:46:51.693229+05:30', + anonymousId: '00000000000000000000000000', + userId: '12345', + event: 'order completed', + properties: { + content_type: 'product_group', + category: ['clothing', 'fishing'], + order_id: 'rudderstackorder1', + revenue: 12.24, + currency: 'INR', + products: [ + { + quantity: 1, + price: 24.75, + name: 'my product', + sku: 'p-298', + }, + { + quantity: 3, + price: 24.75, + name: 'other product', + sku: 'p-299', + }, + ], + }, + integrations: { + All: true, + }, + sentAt: '2019-10-14T11:15:53.296Z', + }, + destination: { + Config: { + blacklistPiiProperties: [ + { + blacklistPiiProperties: '', + blacklistPiiHash: true, + }, + ], + categoryToContent: [ + { + from: 'clothing', + to: 'product', + }, + ], + accessToken: '09876', + pixelId: 'dummyPixelId', + eventsToEvents: [ + { + from: '', + to: '', + }, + ], + valueFieldIdentifier: 'properties.price', + advancedMapping: false, + }, + Enabled: true, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: `https://graph.facebook.com/${VERSION}/dummyPixelId/events?access_token=09876`, + headers: {}, + params: {}, + body: { + JSON: {}, + JSON_ARRAY: {}, + XML: {}, + FORM: { + data: [ + '{"user_data":{"external_id":"5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5"},"event_name":"Purchase","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"content_type":"product_group","category[0]":"clothing","category[1]":"fishing","order_id":"rudderstackorder1","revenue":12.24,"currency":"INR","products[0].quantity":1,"products[0].price":24.75,"products[0].name":"my product","products[0].sku":"p-298","products[1].quantity":3,"products[1].price":24.75,"products[1].name":"other product","products[1].sku":"p-299","content_category":"clothing,fishing","content_ids":["p-298","p-299"],"value":12.24,"contents":[{"id":"p-298","quantity":1,"item_price":24.75},{"id":"p-299","quantity":3,"item_price":24.75}],"num_items":2}}', + ], + }, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ].map((d) => ({ ...d, mockFns })); From 0eb2393939fba2452ef7f07a1d149d87f18290c3 Mon Sep 17 00:00:00 2001 From: Gauravudia <60897972+Gauravudia@users.noreply.github.com> Date: Fri, 1 Mar 2024 14:10:54 +0530 Subject: [PATCH 06/10] feat: add support of custom page/screen event name in mixpanel (#3098) * feat: add support of custom page/screen event name in mixpanel * refactor: use handlebars * test: add testcase for multiple handlebars * refactor: replace handlebar with regex * refactor: update error message * refactor: generatePageOrScreenCustomEventName * fix: skip event trimming --- src/v0/destinations/mp/transform.js | 26 +- src/v0/destinations/mp/util.js | 44 +- src/v0/destinations/mp/util.test.js | 725 +++++++++--------- .../destinations/mp/processor/data.ts | 7 +- 4 files changed, 452 insertions(+), 350 deletions(-) diff --git a/src/v0/destinations/mp/transform.js b/src/v0/destinations/mp/transform.js index 493169cd4e..10271bebef 100644 --- a/src/v0/destinations/mp/transform.js +++ b/src/v0/destinations/mp/transform.js @@ -36,6 +36,7 @@ const { groupEventsByEndpoint, batchEvents, trimTraits, + generatePageOrScreenCustomEventName, } = require('./util'); const { CommonUtils } = require('../../../util/common'); @@ -297,17 +298,25 @@ const processIdentifyEvents = async (message, type, destination) => { }; const processPageOrScreenEvents = (message, type, destination) => { + const { + token, + identityMergeApi, + useUserDefinedPageEventName, + userDefinedPageEventTemplate, + useUserDefinedScreenEventName, + userDefinedScreenEventTemplate, + } = destination.Config; const mappedProperties = constructPayload(message, mPEventPropertiesConfigJson); let properties = { ...get(message, 'context.traits'), ...message.properties, ...mappedProperties, - token: destination.Config.token, + token, distinct_id: message.userId || message.anonymousId, time: toUnixTimestampInMS(message.timestamp || message.originalTimestamp), ...buildUtmParams(message.context?.campaign), }; - if (destination.Config?.identityMergeApi === 'simplified') { + if (identityMergeApi === 'simplified') { properties = { ...properties, distinct_id: message.userId || `$device:${message.anonymousId}`, @@ -326,7 +335,18 @@ const processPageOrScreenEvents = (message, type, destination) => { properties.$browser = browser.name; properties.$browser_version = browser.version; } - const eventName = type === 'page' ? 'Loaded a Page' : 'Loaded a Screen'; + + let eventName; + if (type === 'page') { + eventName = useUserDefinedPageEventName + ? generatePageOrScreenCustomEventName(message, userDefinedPageEventTemplate) + : 'Loaded a Page'; + } else { + eventName = useUserDefinedScreenEventName + ? generatePageOrScreenCustomEventName(message, userDefinedScreenEventTemplate) + : 'Loaded a Screen'; + } + const payload = { event: eventName, properties, diff --git a/src/v0/destinations/mp/util.js b/src/v0/destinations/mp/util.js index 8e943f41dd..f56242d88b 100644 --- a/src/v0/destinations/mp/util.js +++ b/src/v0/destinations/mp/util.js @@ -1,7 +1,7 @@ const lodash = require('lodash'); const set = require('set-value'); const get = require('get-value'); -const { InstrumentationError } = require('@rudderstack/integrations-lib'); +const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib'); const { isDefined, constructPayload, @@ -16,6 +16,7 @@ const { IsGzipSupported, isObject, isDefinedAndNotNullAndNotEmpty, + isDefinedAndNotNull, } = require('../../util'); const { ConfigCategory, @@ -301,6 +302,46 @@ function trimTraits(traits, contextTraits, setOnceProperties) { }; } +/** + * Generates a custom event name for a page or screen. + * + * @param {Object} message - The message object + * @param {string} userDefinedEventTemplate - The user-defined event template to be used for generating the event name. + * @throws {ConfigurationError} If the event template is missing. + * @returns {string} The generated custom event name. + * @example + * const userDefinedEventTemplate = "Viewed {{ category }} {{ name }} Page"; + * const message = {name: 'Home', properties: {category: 'Index'}}; + * output: "Viewed Index Home Page" + */ +const generatePageOrScreenCustomEventName = (message, userDefinedEventTemplate) => { + if (!userDefinedEventTemplate) { + throw new ConfigurationError( + 'Event name template is not configured. Please provide a valid value for the `Page/Screen Event Name Template` in the destination dashboard.', + ); + } + + let eventName = userDefinedEventTemplate; + + if (isDefinedAndNotNull(message.properties?.category)) { + // Replace {{ category }} with actual values + eventName = eventName.replace(/{{\s*category\s*}}/g, message.properties.category); + } else { + // find {{ category }} surrounded by whitespace characters and replace it with a single whitespace character + eventName = eventName.replace(/\s{{\s*category\s*}}\s/g, ' '); + } + + if (isDefinedAndNotNull(message.name)) { + // Replace {{ name }} with actual values + eventName = eventName.replace(/{{\s*name\s*}}/g, message.name); + } else { + // find {{ name }} surrounded by whitespace characters and replace it with a single whitespace character + eventName = eventName.replace(/\s{{\s*name\s*}}\s/g, ' '); + } + + return eventName; +}; + module.exports = { createIdentifyResponse, isImportAuthCredentialsAvailable, @@ -309,4 +350,5 @@ module.exports = { generateBatchedPayloadForArray, batchEvents, trimTraits, + generatePageOrScreenCustomEventName, }; diff --git a/src/v0/destinations/mp/util.test.js b/src/v0/destinations/mp/util.test.js index 866119a336..40cdb34649 100644 --- a/src/v0/destinations/mp/util.test.js +++ b/src/v0/destinations/mp/util.test.js @@ -4,45 +4,88 @@ const { generateBatchedPayloadForArray, buildUtmParams, trimTraits, + generatePageOrScreenCustomEventName, } = require('./util'); const { FEATURE_GZIP_SUPPORT } = require('../../util/constant'); - -const destinationMock = { - Config: { - token: 'test_api_token', - prefixProperties: true, - useNativeSDK: false, - useOldMapping: true, - }, - DestinationDefinition: { - DisplayName: 'Mixpanel', - ID: 'test_destination_definition_id', - Name: 'MP', - }, - Enabled: true, - ID: 'test_id', - Name: 'Mixpanel', - Transformations: [], -}; +const { ConfigurationError } = require('@rudderstack/integrations-lib'); const maxBatchSizeMock = 2; -describe('Mixpanel utils test', () => { - describe('Unit test cases for groupEventsByEndpoint', () => { - it('should return an object with empty arrays for all properties when the events array is empty', () => { - const events = []; - const result = groupEventsByEndpoint(events); - expect(result).toEqual({ - engageEvents: [], - groupsEvents: [], - trackEvents: [], - importEvents: [], - batchErrorRespList: [], - }); +describe('Unit test cases for groupEventsByEndpoint', () => { + it('should return an object with empty arrays for all properties when the events array is empty', () => { + const events = []; + const result = groupEventsByEndpoint(events); + expect(result).toEqual({ + engageEvents: [], + groupsEvents: [], + trackEvents: [], + importEvents: [], + batchErrorRespList: [], }); + }); - it('should return an object with all properties containing their respective events when the events array contains events of all types', () => { - const events = [ + it('should return an object with all properties containing their respective events when the events array contains events of all types', () => { + const events = [ + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{prop:1}]', + }, + }, + userId: 'user1', + }, + }, + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{prop:2}]', + }, + }, + userId: 'user2', + }, + }, + { + message: { + endpoint: '/groups', + body: { + JSON_ARRAY: { + batch: '[{prop:3}]', + }, + }, + userId: 'user1', + }, + }, + { + message: { + endpoint: '/track', + body: { + JSON_ARRAY: { + batch: '[{prop:4}]', + }, + }, + userId: 'user1', + }, + }, + { + message: { + endpoint: '/import', + body: { + JSON_ARRAY: { + batch: '[{prop:5}]', + }, + }, + userId: 'user2', + }, + }, + { error: 'Message type abc not supported' }, + ]; + const result = groupEventsByEndpoint(events); + expect(result).toEqual({ + engageEvents: [ { message: { endpoint: '/engage', @@ -65,6 +108,8 @@ describe('Mixpanel utils test', () => { userId: 'user2', }, }, + ], + groupsEvents: [ { message: { endpoint: '/groups', @@ -76,6 +121,8 @@ describe('Mixpanel utils test', () => { userId: 'user1', }, }, + ], + trackEvents: [ { message: { endpoint: '/track', @@ -87,6 +134,8 @@ describe('Mixpanel utils test', () => { userId: 'user1', }, }, + ], + importEvents: [ { message: { endpoint: '/import', @@ -98,371 +147,359 @@ describe('Mixpanel utils test', () => { userId: 'user2', }, }, - { error: 'Message type abc not supported' }, - ]; - const result = groupEventsByEndpoint(events); - expect(result).toEqual({ - engageEvents: [ - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{prop:1}]', - }, - }, - userId: 'user1', - }, - }, - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{prop:2}]', - }, - }, - userId: 'user2', - }, - }, - ], - groupsEvents: [ - { - message: { - endpoint: '/groups', - body: { - JSON_ARRAY: { - batch: '[{prop:3}]', - }, - }, - userId: 'user1', - }, - }, - ], - trackEvents: [ - { - message: { - endpoint: '/track', - body: { - JSON_ARRAY: { - batch: '[{prop:4}]', - }, - }, - userId: 'user1', - }, - }, - ], - importEvents: [ - { - message: { - endpoint: '/import', - body: { - JSON_ARRAY: { - batch: '[{prop:5}]', - }, - }, - userId: 'user2', - }, - }, - ], - batchErrorRespList: [{ error: 'Message type abc not supported' }], - }); + ], + batchErrorRespList: [{ error: 'Message type abc not supported' }], }); }); +}); - describe('Unit test cases for batchEvents', () => { - it('should return an array of batched events with correct payload and metadata', () => { - const successRespList = [ - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{"prop":1}]', - }, +describe('Unit test cases for batchEvents', () => { + it('should return an array of batched events with correct payload and metadata', () => { + const successRespList = [ + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{"prop":1}]', }, - headers: {}, - params: {}, - userId: 'user1', }, - metadata: { jobId: 3 }, + headers: {}, + params: {}, + userId: 'user1', }, - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{"prop":2}]', - }, + metadata: { jobId: 3 }, + }, + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{"prop":2}]', }, - headers: {}, - params: {}, - userId: 'user2', }, - metadata: { jobId: 4 }, + headers: {}, + params: {}, + userId: 'user2', }, - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{"prop":3}]', - }, + metadata: { jobId: 4 }, + }, + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{"prop":3}]', }, - headers: {}, - params: {}, - userId: 'user2', }, - metadata: { jobId: 6 }, + headers: {}, + params: {}, + userId: 'user2', }, - ]; - - const result = batchEvents(successRespList, maxBatchSizeMock); - - expect(result).toEqual([ - { - batched: true, - batchedRequest: { - body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":1},{"prop":2}]' }, XML: {} }, - endpoint: '/engage', - files: {}, - headers: {}, - method: 'POST', - params: {}, - type: 'REST', - version: '1', - }, - destination: undefined, - metadata: [{ jobId: 3 }, { jobId: 4 }], - statusCode: 200, + metadata: { jobId: 6 }, + }, + ]; + + const result = batchEvents(successRespList, maxBatchSizeMock); + + expect(result).toEqual([ + { + batched: true, + batchedRequest: { + body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":1},{"prop":2}]' }, XML: {} }, + endpoint: '/engage', + files: {}, + headers: {}, + method: 'POST', + params: {}, + type: 'REST', + version: '1', }, - { - batched: true, - batchedRequest: { - body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":3}]' }, XML: {} }, - endpoint: '/engage', - files: {}, - headers: {}, - method: 'POST', - params: {}, - type: 'REST', - version: '1', - }, - destination: undefined, - metadata: [{ jobId: 6 }], - statusCode: 200, + destination: undefined, + metadata: [{ jobId: 3 }, { jobId: 4 }], + statusCode: 200, + }, + { + batched: true, + batchedRequest: { + body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":3}]' }, XML: {} }, + endpoint: '/engage', + files: {}, + headers: {}, + method: 'POST', + params: {}, + type: 'REST', + version: '1', }, - ]); - }); + destination: undefined, + metadata: [{ jobId: 6 }], + statusCode: 200, + }, + ]); + }); - it('should return an empty array when successRespList is empty', () => { - const successRespList = []; - const result = batchEvents(successRespList, maxBatchSizeMock); - expect(result).toEqual([]); - }); + it('should return an empty array when successRespList is empty', () => { + const successRespList = []; + const result = batchEvents(successRespList, maxBatchSizeMock); + expect(result).toEqual([]); }); +}); - describe('Unit test cases for generateBatchedPayloadForArray', () => { - it('should generate a batched payload with GZIP payload for /import endpoint when given an array of events', () => { - const events = [ - { - body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, - endpoint: '/import', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - { - body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, - endpoint: '/import', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - ]; - const expectedBatchedRequest = { - body: { - FORM: {}, - JSON: {}, - JSON_ARRAY: {}, - XML: {}, - GZIP: { - payload: '[{"event":"event1"},{"event":"event2"}]', - }, - }, +describe('Unit test cases for generateBatchedPayloadForArray', () => { + it('should generate a batched payload with GZIP payload for /import endpoint when given an array of events', () => { + const events = [ + { + body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, endpoint: '/import', - files: {}, headers: { 'Content-Type': 'application/json' }, - method: 'POST', params: {}, - type: 'REST', - version: '1', - }; - - const result = generateBatchedPayloadForArray(events, { - features: { [FEATURE_GZIP_SUPPORT]: true }, - }); - - expect(result).toEqual(expectedBatchedRequest); + }, + { + body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, + endpoint: '/import', + headers: { 'Content-Type': 'application/json' }, + params: {}, + }, + ]; + const expectedBatchedRequest = { + body: { + FORM: {}, + JSON: {}, + JSON_ARRAY: {}, + XML: {}, + GZIP: { + payload: '[{"event":"event1"},{"event":"event2"}]', + }, + }, + endpoint: '/import', + files: {}, + headers: { 'Content-Type': 'application/json' }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }; + + const result = generateBatchedPayloadForArray(events, { + features: { [FEATURE_GZIP_SUPPORT]: true }, }); - it('should generate a batched payload with JSON_ARRAY body when given an array of events', () => { - const events = [ - { - body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, - endpoint: '/endpoint', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - { - body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, - endpoint: '/endpoint', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - ]; - const expectedBatchedRequest = { - body: { - FORM: {}, - JSON: {}, - JSON_ARRAY: { batch: '[{"event":"event1"},{"event":"event2"}]' }, - XML: {}, - }, + expect(result).toEqual(expectedBatchedRequest); + }); + + it('should generate a batched payload with JSON_ARRAY body when given an array of events', () => { + const events = [ + { + body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, endpoint: '/endpoint', - files: {}, headers: { 'Content-Type': 'application/json' }, - method: 'POST', params: {}, - type: 'REST', - version: '1', - }; - - const result = generateBatchedPayloadForArray(events, { - features: { [FEATURE_GZIP_SUPPORT]: true }, - }); - - expect(result).toEqual(expectedBatchedRequest); + }, + { + body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, + endpoint: '/endpoint', + headers: { 'Content-Type': 'application/json' }, + params: {}, + }, + ]; + const expectedBatchedRequest = { + body: { + FORM: {}, + JSON: {}, + JSON_ARRAY: { batch: '[{"event":"event1"},{"event":"event2"}]' }, + XML: {}, + }, + endpoint: '/endpoint', + files: {}, + headers: { 'Content-Type': 'application/json' }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }; + + const result = generateBatchedPayloadForArray(events, { + features: { [FEATURE_GZIP_SUPPORT]: true }, }); + + expect(result).toEqual(expectedBatchedRequest); }); +}); - describe('Unit test cases for buildUtmParams', () => { - it('should return an empty object when campaign is undefined', () => { - const campaign = undefined; - const result = buildUtmParams(campaign); - expect(result).toEqual({}); - }); +describe('Unit test cases for buildUtmParams', () => { + it('should return an empty object when campaign is undefined', () => { + const campaign = undefined; + const result = buildUtmParams(campaign); + expect(result).toEqual({}); + }); - it('should return an empty object when campaign is an empty object', () => { - const campaign = {}; - const result = buildUtmParams(campaign); - expect(result).toEqual({}); - }); + it('should return an empty object when campaign is an empty object', () => { + const campaign = {}; + const result = buildUtmParams(campaign); + expect(result).toEqual({}); + }); - it('should return an empty object when campaign is not an object', () => { - const campaign = [{ name: 'test' }]; - const result = buildUtmParams(campaign); - expect(result).toEqual({}); - }); + it('should return an empty object when campaign is not an object', () => { + const campaign = [{ name: 'test' }]; + const result = buildUtmParams(campaign); + expect(result).toEqual({}); + }); - it('should handle campaign object with null/undefined values', () => { - const campaign = { name: null, source: 'rudder', medium: 'rudder', test: undefined }; - const result = buildUtmParams(campaign); - expect(result).toEqual({ - utm_campaign: null, - utm_source: 'rudder', - utm_medium: 'rudder', - test: undefined, - }); + it('should handle campaign object with null/undefined values', () => { + const campaign = { name: null, source: 'rudder', medium: 'rudder', test: undefined }; + const result = buildUtmParams(campaign); + expect(result).toEqual({ + utm_campaign: null, + utm_source: 'rudder', + utm_medium: 'rudder', + test: undefined, }); }); - 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); - - expect(result).toEqual({ - traits: { - age: 30, - }, - contextTraits: {}, - setOnce: { $name: 'John', $email: 'john@example.com' }, - }); +}); +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); + + 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']; + // 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); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: {}, - contextTraits: {}, - setOnce: {}, - }); + 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 = []; + // 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); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: { name: 'John', age: 30 }, - contextTraits: { email: 'john@example.com' }, - setOnce: {}, - }); + 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']; + // 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); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: { age: 30 }, - contextTraits: {}, - setOnce: { $name: 'John', $email: 'john@example.com' }, - }); + 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']; + // 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); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: { age: 30, address: 'kolkata' }, - contextTraits: {}, - setOnce: { $name: 'John', $email: 'john@example.com' }, - }); + 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']; + 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); + 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' }, - }); + expect(result).toEqual({ + traits: { age: 30, address: {}, isAdult: false }, + contextTraits: {}, + setOnce: { $name: 'John', $email: 'john@example.com', $city: 'kolkata' }, }); }); }); + +describe('generatePageOrScreenCustomEventName', () => { + it('should throw a ConfigurationError when userDefinedEventTemplate is not provided', () => { + const message = { name: 'Home' }; + const userDefinedEventTemplate = undefined; + expect(() => { + generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + }).toThrow(ConfigurationError); + }); + + it('should generate a custom event name when userDefinedEventTemplate contains event template and message object is provided', () => { + let message = { name: 'Doc', properties: { category: 'Integration' } }; + const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page'; + let expected = 'Viewed Integration Doc page'; + let result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + + message = { name: true, properties: { category: 0 } }; + expected = 'Viewed 0 true page'; + result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should generate a custom event name when userDefinedEventTemplate contains event template and category or name is missing in message object', () => { + const message = { name: 'Doc', properties: { category: undefined } }; + const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page someKeyword'; + const expected = 'Viewed Doc page someKeyword'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should generate a custom event name when userDefinedEventTemplate contains only category or name placeholder and message object is provided', () => { + const message = { name: 'Doc', properties: { category: 'Integration' } }; + const userDefinedEventTemplate = 'Viewed {{ name }} page'; + const expected = 'Viewed Doc page'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should return the userDefinedEventTemplate when it does not contain placeholder {{}}', () => { + const message = { name: 'Index' }; + const userDefinedEventTemplate = 'Viewed a Home page'; + const expected = 'Viewed a Home page'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should return a event name when message object is not provided/empty', () => { + const message = {}; + const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page someKeyword'; + const expected = 'Viewed page someKeyword'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); +}); diff --git a/test/integrations/destinations/mp/processor/data.ts b/test/integrations/destinations/mp/processor/data.ts index dfa94352c9..5b2d0fbfff 100644 --- a/test/integrations/destinations/mp/processor/data.ts +++ b/test/integrations/destinations/mp/processor/data.ts @@ -121,7 +121,10 @@ export const data = [ request: { body: [ { - destination: sampleDestination, + destination: overrideDestination(sampleDestination, { + useUserDefinedPageEventName: true, + userDefinedPageEventTemplate: 'Viewed a {{ name }} page', + }), message: { anonymousId: 'e6ab2c5e-2cda-44a9-a962-e2f67df78bca', channel: 'web', @@ -195,7 +198,7 @@ export const data = [ JSON: {}, JSON_ARRAY: { batch: - '[{"event":"Loaded a Page","properties":{"ip":"0.0.0.0","$user_id":"hjikl","$current_url":"https://docs.rudderstack.com/destinations/mixpanel","$screen_dpi":2,"mp_lib":"RudderLabs JavaScript SDK","$app_build_number":"1.0.0","$app_version_string":"1.0.5","$insert_id":"dd266c67-9199-4a52-ba32-f46ddde67312","token":"dummyApiKey","distinct_id":"hjikl","time":1579847342402,"name":"Contact Us","category":"Contact","$browser":"Chrome","$browser_version":"79.0.3945.117"}}]', + '[{"event":"Viewed a Contact Us page","properties":{"ip":"0.0.0.0","$user_id":"hjikl","$current_url":"https://docs.rudderstack.com/destinations/mixpanel","$screen_dpi":2,"mp_lib":"RudderLabs JavaScript SDK","$app_build_number":"1.0.0","$app_version_string":"1.0.5","$insert_id":"dd266c67-9199-4a52-ba32-f46ddde67312","token":"dummyApiKey","distinct_id":"hjikl","time":1579847342402,"name":"Contact Us","category":"Contact","$browser":"Chrome","$browser_version":"79.0.3945.117"}}]', }, XML: {}, FORM: {}, From 54178f811dd2549ce6bc91ee565c60b4ab368f67 Mon Sep 17 00:00:00 2001 From: Sandeep Digumarty Date: Fri, 1 Mar 2024 21:09:29 +0530 Subject: [PATCH 07/10] chore: fb app events v1 tests (#3141) * chore: fb app events v1 tests * chore: fb app events v1 tests * chore: removed unused constants * chore: updated tests --- src/services/destination/nativeIntegration.ts | 6 +- .../destinations/fb/dataDelivery/business.ts | 226 ++++++++++++++++++ .../destinations/fb/dataDelivery/data.ts | 6 +- .../destinations/fb/dataDelivery/other.ts | 51 ++++ test/integrations/destinations/fb/network.ts | 6 +- 5 files changed, 290 insertions(+), 5 deletions(-) create mode 100644 test/integrations/destinations/fb/dataDelivery/business.ts create mode 100644 test/integrations/destinations/fb/dataDelivery/other.ts diff --git a/src/services/destination/nativeIntegration.ts b/src/services/destination/nativeIntegration.ts index c33772d01d..2bb82fc602 100644 --- a/src/services/destination/nativeIntegration.ts +++ b/src/services/destination/nativeIntegration.ts @@ -209,7 +209,11 @@ export class NativeIntegrationDestinationService implements DestinationService { const jobStates = (deliveryRequest as ProxyV1Request).metadata.map( (metadata) => ({ - error: JSON.stringify(v0Response.destinationResponse?.response), + error: JSON.stringify( + v0Response.destinationResponse?.response === undefined + ? v0Response.destinationResponse + : v0Response.destinationResponse?.response, + ), statusCode: v0Response.status, metadata, }) as DeliveryJobState, diff --git a/test/integrations/destinations/fb/dataDelivery/business.ts b/test/integrations/destinations/fb/dataDelivery/business.ts new file mode 100644 index 0000000000..156dc26572 --- /dev/null +++ b/test/integrations/destinations/fb/dataDelivery/business.ts @@ -0,0 +1,226 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { VERSION } from '../../../../../src/v0/destinations/fb/config'; + +export const testData1 = { + event: 'CUSTOM_APP_EVENTS', + advertiser_id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R', + 'ud[em]': '48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08', + 'ud[fn]': '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', + 'ud[ge]': '62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a', + 'ud[ln]': '3547cb112ac4489af2310c0626cdba6f3097a2ad5a3b42ddd3b59c76c7a079a3', + 'ud[ph]': '588211a01b10feacbf7988d97a06e86c18af5259a7f457fd8759b7f7409a7d1f', + extinfo: + '["a2","","","","8.1.0","Redmi 6","","","Banglalink",640,480,"1.23",0,0,0,"Europe/Berlin"]', + app_user_id: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1', + custom_events: + '[{"_logTime":1567333011693,"_eventName":"spin_result","_valueToSum":400,"fb_currency":"GBP","additional_bet_index":0,"battle_id":"N/A","bet_amount":9,"bet_level":1,"bet_multiplier":1,"coin_balance":9466052,"current_module_name":"CasinoGameModule","days_in_game":0,"extra_param":"N/A","fb_profile":"0","featureGameType":"N/A","game_fps":30,"game_id":"fireEagleBase","game_name":"FireEagleSlots","gem_balance":0,"graphicsQuality":"HD","idfa":"2bf99787-33d2-4ae2-a76a-c49672f97252","internetReachability":"ReachableViaLocalAreaNetwork","isLowEndDevice":"False","is_auto_spin":"False","is_turbo":"False","isf":"False","ishighroller":"False","jackpot_win_amount":90,"jackpot_win_type":"Silver","level":6,"lifetime_gem_balance":0,"no_of_spin":1,"player_total_battles":0,"player_total_shields":0,"start_date":"2019-08-01","total_payments":0,"tournament_id":"T1561970819","userId":"c82cbdff-e5be-4009-ac78-cdeea09ab4b1","versionSessionCount":2,"win_amount":0,"fb_content_id":["123","345","567"]}]', + advertiser_tracking_enabled: '0', + application_tracking_enabled: '0', +}; + +export const testData2 = { + extinfo: '["a2","","","","8.1.0","Redmi 6","","","Banglalink",0,100,"50.00",0,0,0,""]', + custom_events: + '[{"_logTime":1567333011693,"_eventName":"Viewed Screen","fb_description":"Main.1233"}]', + 'ud[em]': '48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08', + advertiser_tracking_enabled: '0', + application_tracking_enabled: '0', + event: 'CUSTOM_APP_EVENTS', +}; + +export const statTags = { + destType: 'FB', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', +}; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'fb_v1_scenario_1', + name: 'fb', + description: 'app event fails due to access token error', + successCriteria: 'Should return 400 with invalid access token error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/RudderFbApp/activities?access_token=invalid_access_token`, + headers: { + 'x-forwarded-for': '1.2.3.4', + }, + params: { + destination: 'fb', + }, + FORM: testData1, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Invalid OAuth 2.0 access token', + statTags: { + ...statTags, + errorCategory: 'dataValidation', + errorType: 'configuration', + meta: 'accessTokenExpired', + }, + response: [ + { + error: 'Invalid OAuth 2.0 access token', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fb_v1_scenario_2', + name: 'fb', + description: 'app event sent successfully', + successCriteria: 'Should return 200', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/RudderFbApp/activities?access_token=my_access_token`, + headers: { + 'x-forwarded-for': '1.2.3.4', + }, + params: { + destination: 'fb', + }, + FORM: testData1, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '{"events_received":1,"fbtrace_id":"facebook_trace_id"}', + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fb_v1_scenario_3', + name: 'fb', + description: 'app event fails due to invalid timestamp', + successCriteria: 'Should return 400 with invalid timestamp error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_timestamp_correct_access_token`, + headers: { + 'x-forwarded-for': '1.2.3.4', + }, + params: { + destination: 'fb', + }, + FORM: testData1, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Event Timestamp Too Old', + statTags, + response: [ + { + error: 'Event Timestamp Too Old', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fb_v1_scenario_4', + name: 'fb', + description: 'app event fails due to missing permissions', + successCriteria: 'Should return 400 with missing permissions error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_account_id_valid_access_token`, + headers: { + 'x-forwarded-for': '1.2.3.4', + }, + params: { + destination: 'fb', + }, + FORM: testData2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: + "Object with ID 'PIXEL_ID' / 'DATASET_ID' / 'AUDIENCE_ID' does not exist, cannot be loaded due to missing permissions, or does not support this operation", + statTags, + response: [ + { + error: + "Object with ID 'PIXEL_ID' / 'DATASET_ID' / 'AUDIENCE_ID' does not exist, cannot be loaded due to missing permissions, or does not support this operation", + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/fb/dataDelivery/data.ts b/test/integrations/destinations/fb/dataDelivery/data.ts index 3b37d03f46..9ee19af265 100644 --- a/test/integrations/destinations/fb/dataDelivery/data.ts +++ b/test/integrations/destinations/fb/dataDelivery/data.ts @@ -1,6 +1,8 @@ import { VERSION } from '../../../../../src/v0/destinations/fb/config'; +import { testScenariosForV1API } from './business'; +import { otherScenariosV1 } from './other'; -export const data = [ +export const existingTestData = [ { name: 'fb', description: 'Test 0', @@ -371,3 +373,5 @@ export const data = [ }, }, ]; + +export const data = [...existingTestData, ...testScenariosForV1API, ...otherScenariosV1]; diff --git a/test/integrations/destinations/fb/dataDelivery/other.ts b/test/integrations/destinations/fb/dataDelivery/other.ts new file mode 100644 index 0000000000..9ac3f14fb5 --- /dev/null +++ b/test/integrations/destinations/fb/dataDelivery/other.ts @@ -0,0 +1,51 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { VERSION } from '../../../../../src/v0/destinations/fb/config'; +import { testData2 as testData, statTags } from './business'; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'fb_v1_other_scenario_1', + name: 'fb', + description: 'user update request is throttled due to too many calls', + successCriteria: 'Should return 429 with message there have been too many calls', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=throttled_valid_access_token`, + params: { + destination: 'fb', + }, + FORM: testData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 429, + message: 'API User Too Many Calls', + statTags: { + ...statTags, + errorType: 'throttled', + }, + response: [ + { + error: 'API User Too Many Calls', + statusCode: 429, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/fb/network.ts b/test/integrations/destinations/fb/network.ts index 1a2f114d74..31bbaf0b6e 100644 --- a/test/integrations/destinations/fb/network.ts +++ b/test/integrations/destinations/fb/network.ts @@ -12,7 +12,7 @@ const fbPixelTcs = data return nw.httpReq.url === fbendpoint; })[0]; const clonedFbpTc = cloneDeep(fbpTc); - const clonedFormData = cloneDeep(d.input.request.body.body.FORM); + const clonedFormData = cloneDeep(d.input.request.body.body?.FORM); clonedFbpTc.httpReq.data = getFormData(clonedFormData).toString(); return clonedFbpTc; }); @@ -21,7 +21,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/RudderFbApp/activities?access_token=invalid_access_token`, - data: getFormData(data[0].input.request.body.body.FORM).toString(), + data: getFormData(data[0].input.request.body.body?.FORM).toString(), params: { destination: 'fb' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -41,7 +41,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/RudderFbApp/activities?access_token=my_access_token`, - data: getFormData(data[1].input.request.body.body.FORM).toString(), + data: getFormData(data[1].input.request.body.body?.FORM).toString(), params: { destination: 'fb' }, headers: { 'x-forwarded-for': '1.2.3.4', 'User-Agent': 'RudderLabs' }, method: 'POST', From cfdb22e6ed67f6e5a1f92ee349eccfce4496d6b9 Mon Sep 17 00:00:00 2001 From: Sandeep Digumarty Date: Fri, 1 Mar 2024 21:10:24 +0530 Subject: [PATCH 08/10] chore: fb_custom_audience v1 tests (#3095) * feat: update proxy data type for response handler input * feat: update proxy v1 test cases * feat: update proxy tests for cm360 Added new structure for proxy test scnearios for cm360 also added zod validations as part of tests * fix: typo * Update test/integrations/destinations/campaign_manager/dataDelivery/business.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update test/integrations/destinations/campaign_manager/dataDelivery/business.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: api contract for v1 proxy * chore: clean up zod type * chore: update testutils * chore: update V0 proxy request type and zod schema * feat: adding zod validations (#3066) * feat: add type definitions for test cases * fix: update networkHandler for rakuten --------- Co-authored-by: Utsab Chowdhury * chore: update delivery test cases for criteo audience * Revert "chore: update delivery test cases for criteo audience" This reverts commit 689b0cda0aeace910e82167375045e123e365300. * chore: add type def for proxy v1 test * chore: fix generateMetdata func * chore: criteo audience update proxy test (#3068) * chore: update delivery test cases for criteo audience * chore: enable batch response schema check (#3083) * chore: fb_custom_audience v1 tests * chore: fb_custom_audience v1 tests * chore: fb_custom_audience v1 tests * chore: braze proxy v1 test (#3087) * chore: refactor braze proxy v1 tests * chore: address review comments and cleanup * chore: cleanup of mock --------- Co-authored-by: Utsab Chowdhury * chore: fb_custom_audience v1 tests * chore: fb_custom_audience v1 tests * chore: fb_custom_audience v1 tests * chore: fb_custom_audience v1 tests * chore: updated tests --------- Co-authored-by: Utsab Chowdhury Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Utsab Chowdhury Co-authored-by: ItsSudip Co-authored-by: Sudip Paul <67197965+ItsSudip@users.noreply.github.com> Co-authored-by: chandumlg <54652834+chandumlg@users.noreply.github.com> --- .../dataDelivery/business.ts | 427 ++++++++++++++++++ .../fb_custom_audience/dataDelivery/data.ts | 6 +- .../fb_custom_audience/dataDelivery/other.ts | 53 +++ 3 files changed, 485 insertions(+), 1 deletion(-) create mode 100644 test/integrations/destinations/fb_custom_audience/dataDelivery/business.ts create mode 100644 test/integrations/destinations/fb_custom_audience/dataDelivery/other.ts diff --git a/test/integrations/destinations/fb_custom_audience/dataDelivery/business.ts b/test/integrations/destinations/fb_custom_audience/dataDelivery/business.ts new file mode 100644 index 0000000000..c48ad227ab --- /dev/null +++ b/test/integrations/destinations/fb_custom_audience/dataDelivery/business.ts @@ -0,0 +1,427 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { getEndPoint } from '../../../../../src/v0/destinations/fb_custom_audience/config'; + +export const statTags = { + destType: 'FB_CUSTOM_AUDIENCE', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', +}; + +const testParams1 = { + access_token: 'ABC', + payload: { + is_raw: true, + data_source: { + sub_type: 'ANYTHING', + }, + schema: [ + 'EMAIL', + 'DOBM', + 'DOBD', + 'DOBY', + 'PHONE', + 'GEN', + 'FI', + 'MADID', + 'ZIP', + 'ST', + 'COUNTRY', + ], + data: [ + [ + 'shrouti@abc.com', + '2', + '13', + '2013', + '@09432457768', + 'f', + 'Ms.', + 'ABC', + 'ZIP ', + '123abc ', + 'IN', + ], + ], + }, +}; + +export const testParams2 = { + access_token: 'ABC', + payload: { + is_raw: true, + data_source: { + sub_type: 'ANYTHING', + }, + schema: ['DOBY', 'PHONE', 'GEN', 'FI', 'MADID', 'ZIP', 'ST', 'COUNTRY'], + data: [['2013', '@09432457768', 'f', 'Ms.', 'ABC', 'ZIP ', '123abc ', 'IN']], + }, +}; + +const testParams3 = { + access_token: 'BCD', + payload: { + is_raw: true, + data_source: { + sub_type: 'ANYTHING', + }, + schema: ['DOBM', 'DOBD', 'DOBY', 'PHONE', 'GEN', 'FI', 'MADID', 'ZIP', 'ST', 'COUNTRY'], + data: [['2', '13', '2013', '@09432457768', 'f', 'Ms.', 'ABC', 'ZIP ', '123abc ', 'IN']], + }, +}; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'fbca_v1_scenario_1', + name: 'fb_custom_audience', + description: 'successfully delete users from audience', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'successResponse', + }, + params: testParams1, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: + '{"audience_id":"aud1","session_id":"123","num_received":4,"num_invalid_entries":0,"invalid_entry_samples":{}}', + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_2', + name: 'fb_custom_audience', + description: 'user addition failed due to missing permission', + successCriteria: 'Fail with status code 400 due to missing permissions', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'POST', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'permissionMissingError', + }, + params: testParams3, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: + 'Missing permission. Please make sure you have ads_management permission and the application is included in the allowlist', + statTags, + response: [ + { + error: + 'Missing permission. Please make sure you have ads_management permission and the application is included in the allowlist', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_3', + name: 'fb_custom_audience', + description: 'user deletion failed due to unavailable audience error', + successCriteria: 'Fail with status code 400', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'audienceUnavailableError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: + 'Custom Audience Unavailable: The custom audience you are trying to use has not been shared with your ad account', + statTags, + response: [ + { + error: + 'Custom Audience Unavailable: The custom audience you are trying to use has not been shared with your ad account', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_4', + name: 'fb_custom_audience', + description: 'user deletion failed because the custom audience has been deleted', + successCriteria: 'Fail with status code 400', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'audienceDeletedError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Custom Audience Has Been Deleted', + statTags, + response: [ + { + error: 'Custom Audience Has Been Deleted', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_5', + name: 'fb_custom_audience', + description: 'Failed to update the custom audience for unknown reason', + successCriteria: 'Fail with status code 400', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'failedToUpdateAudienceError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Failed to update the custom audience', + statTags, + response: [ + { + error: 'Failed to update the custom audience', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_6', + name: 'fb_custom_audience', + description: + 'Failed to update the custom audience as excessive number of parameters were passed in the request', + successCriteria: 'Fail with status code 400', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'parameterExceededError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'The number of parameters exceeded the maximum for this operation', + statTags, + response: [ + { + error: 'The number of parameters exceeded the maximum for this operation', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_7', + name: 'fb_custom_audience', + description: 'user having permission issue while updating audience', + successCriteria: 'Fail with status code 403', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'code200PermissionError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 403, + message: '(#200) The current user can not update audience 23861283180290489', + statTags, + response: [ + { + error: '(#200) The current user can not update audience 23861283180290489', + statusCode: 403, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_8', + name: 'fb_custom_audience', + description: 'user deletion failed due expired access token error', + successCriteria: 'Fail with status code 400', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'accessTokenInvalidError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: + 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', + statTags: { + ...statTags, + errorCategory: 'dataValidation', + errorType: 'configuration', + meta: 'accessTokenExpired', + }, + response: [ + { + error: + 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts b/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts index 5ce15e0ea0..b41c656d9f 100644 --- a/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts +++ b/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts @@ -1,6 +1,8 @@ import { getEndPoint } from '../../../../../src/v0/destinations/fb_custom_audience/config'; +import { testScenariosForV1API } from './business'; +import { otherScenariosV1 } from './other'; -export const data = [ +export const existingTestData = [ { name: 'fb_custom_audience', description: 'successfully adding users to audience', @@ -645,3 +647,5 @@ export const data = [ }, }, ]; + +export const data = [...existingTestData, ...testScenariosForV1API, ...otherScenariosV1]; diff --git a/test/integrations/destinations/fb_custom_audience/dataDelivery/other.ts b/test/integrations/destinations/fb_custom_audience/dataDelivery/other.ts new file mode 100644 index 0000000000..52138604b0 --- /dev/null +++ b/test/integrations/destinations/fb_custom_audience/dataDelivery/other.ts @@ -0,0 +1,53 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { getEndPoint } from '../../../../../src/v0/destinations/fb_custom_audience/config'; +import { statTags, testParams2 as testParams } from './business'; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'fbca_v1_other_scenario_1', + name: 'fb_custom_audience', + description: 'user update request is throttled due to too many calls to the ad account', + successCriteria: + 'Should return 429 with message there have been too many calls to this ad-account', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'tooManyCallsError', + }, + params: testParams, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'There have been too many calls to this ad-account.', + statTags: { + ...statTags, + errorType: 'throttled', + }, + status: 429, + response: [ + { + error: 'There have been too many calls to this ad-account.', + statusCode: 429, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; From e93e47f33e098104fb532916932fe38bbfeaa4a1 Mon Sep 17 00:00:00 2001 From: Sudip Paul <67197965+ItsSudip@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:11:22 +0530 Subject: [PATCH 09/10] fix: add error handling for tiktok ads (#3144) * fix: add error handling for tiktok ads * chore: address comment --- src/v0/destinations/tiktok_ads/transform.js | 8 ++-- .../destinations/tiktok_ads/processor/data.ts | 47 ++++++++++++++++++- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/v0/destinations/tiktok_ads/transform.js b/src/v0/destinations/tiktok_ads/transform.js index b8b10d4608..ba852b9a97 100644 --- a/src/v0/destinations/tiktok_ads/transform.js +++ b/src/v0/destinations/tiktok_ads/transform.js @@ -129,12 +129,10 @@ const getTrackResponse = (message, Config, event) => { const trackResponseBuilder = async (message, { Config }) => { const { eventsToStandard, sendCustomEvents } = Config; - - let event = message.event?.toLowerCase().trim(); - if (!event) { - throw new InstrumentationError('Event name is required'); + if (!message.event || typeof message.event !== 'string') { + throw new InstrumentationError('Either event name is not present or it is not a string'); } - + let event = message.event?.toLowerCase().trim(); const standardEventsMap = getHashFromArrayWithDuplicate(eventsToStandard); if (!sendCustomEvents && eventNameMapping[event] === undefined && !standardEventsMap[event]) { diff --git a/test/integrations/destinations/tiktok_ads/processor/data.ts b/test/integrations/destinations/tiktok_ads/processor/data.ts index 3b68426fbf..d0447da43c 100644 --- a/test/integrations/destinations/tiktok_ads/processor/data.ts +++ b/test/integrations/destinations/tiktok_ads/processor/data.ts @@ -1369,7 +1369,7 @@ export const data = [ body: [ { statusCode: 400, - error: 'Event name is required', + error: 'Either event name is not present or it is not a string', statTags: { errorCategory: 'dataValidation', errorType: 'instrumentation', @@ -6973,4 +6973,49 @@ export const data = [ }, }, }, + { + name: 'tiktok_ads', + description: 'Testing if the event name provided as a string or not', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 123, + }, + destination: { + Config: { + accessToken: 'dummyAccessToken', + pixelCode: '{{PIXEL-CODE}}', + hashUserProperties: false, + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + statusCode: 400, + error: 'Either event name is not present or it is not a string', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'TIKTOK_ADS', + module: 'destination', + implementation: 'native', + feature: 'processor', + }, + }, + ], + }, + }, + }, ]; From 3e416be8816f8f45e921db11bcb1dd3e4b61d57f Mon Sep 17 00:00:00 2001 From: Sandeep Digumarty Date: Mon, 4 Mar 2024 09:39:39 +0530 Subject: [PATCH 10/10] chore: facebook_pixel to v1 proxy tests (#3149) * chore: facebook_pixel to v1 proxy tests * chore: updated tests * chore: updated tests * chore: updated tests * chore: updated tests --- .../facebook_pixel/dataDelivery/business.ts | 258 ++++++++++++++++++ .../facebook_pixel/dataDelivery/data.ts | 150 +++------- .../facebook_pixel/dataDelivery/oauth.ts | 45 +++ .../facebook_pixel/dataDelivery/other.ts | 93 +++++++ .../destinations/facebook_pixel/network.ts | 20 +- 5 files changed, 440 insertions(+), 126 deletions(-) create mode 100644 test/integrations/destinations/facebook_pixel/dataDelivery/business.ts create mode 100644 test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts create mode 100644 test/integrations/destinations/facebook_pixel/dataDelivery/other.ts diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts new file mode 100644 index 0000000000..9ac709978d --- /dev/null +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts @@ -0,0 +1,258 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config'; + +export const testFormData = { + data: [ + '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', + ], +}; +export const statTags = { + destType: 'FACEBOOK_PIXEL', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', +}; +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'facebook_pixel_v1_scenario_1', + name: 'facebook_pixel', + description: 'app event fails due to access token error', + successCriteria: 'Should return 400 with invalid access token error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Invalid OAuth 2.0 access token', + statTags: { + ...statTags, + errorCategory: 'dataValidation', + errorType: 'configuration', + meta: 'accessTokenExpired', + }, + response: [ + { + error: 'Invalid OAuth 2.0 access token', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_scenario_2', + name: 'facebook_pixel', + description: 'app event sent successfully', + successCriteria: 'Should return 200', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=my_access_token`, + params: { + destination: 'facebook_pixel', + }, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '{"events_received":1,"fbtrace_id":"facebook_trace_id"}', + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_scenario_3', + name: 'facebook_pixel', + description: 'app event fails due to invalid timestamp', + successCriteria: 'Should return 400 with invalid timestamp error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_timestamp_correct_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Event Timestamp Too Old', + statTags, + response: [ + { + error: 'Event Timestamp Too Old', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_scenario_4', + name: 'facebook_pixel', + description: 'app event fails due to missing permissions', + successCriteria: 'Should return 400 with missing permissions error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_account_id_valid_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: + "Object with ID 'PIXEL_ID' / 'DATASET_ID' / 'AUDIENCE_ID' does not exist, cannot be loaded due to missing permissions, or does not support this operation", + statTags, + response: [ + { + error: + "Object with ID 'PIXEL_ID' / 'DATASET_ID' / 'AUDIENCE_ID' does not exist, cannot be loaded due to missing permissions, or does not support this operation", + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_scenario_5', + name: 'facebook_pixel', + description: 'app event fails due to invalid parameter', + successCriteria: 'Should return 400 with Invalid parameter error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=not_found_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Invalid Parameter', + statTags, + response: [ + { + error: 'Invalid Parameter', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_scenario_6', + name: 'facebook_pixel', + description: 'app event fails due to invalid parameter', + successCriteria: 'Should return 400 with Invalid parameter error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234570/events?access_token=valid_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Invalid Parameter', + statTags, + response: [ + { + error: 'Invalid Parameter', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts index 239fa93a6a..dcc633e1a8 100644 --- a/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts @@ -1,6 +1,15 @@ import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config'; +import { testScenariosForV1API, testFormData, statTags as baseStatTags } from './business'; +import { otherScenariosV1 } from './other'; +import { oauthScenariosV1 } from './oauth'; -export const data = [ +const statTags = { + ...baseStatTags, + destinationId: 'Non-determininable', + workspaceId: 'Non-determininable', +}; + +export const v0TestData = [ { name: 'facebook_pixel', description: 'Test 0', @@ -12,11 +21,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654773112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -51,15 +56,10 @@ export const data = [ status: 500, }, statTags: { - destType: 'FACEBOOK_PIXEL', + ...statTags, errorCategory: 'dataValidation', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', errorType: 'configuration', meta: 'accessTokenExpired', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', }, }, }, @@ -77,11 +77,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654773112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -126,11 +122,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -169,16 +161,7 @@ export const data = [ }, status: 400, }, - statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, + statTags, }, }, }, @@ -195,11 +178,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -234,14 +213,8 @@ export const data = [ status: 500, }, statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + ...statTags, errorType: 'throttled', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', }, }, }, @@ -259,11 +232,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -300,16 +269,7 @@ export const data = [ }, status: 400, }, - statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, + statTags, }, }, }, @@ -326,11 +286,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"d58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -365,16 +321,7 @@ export const data = [ }, status: 404, }, - statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, + statTags, }, }, }, @@ -391,11 +338,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -430,16 +373,7 @@ export const data = [ }, status: 400, }, - statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, + statTags, }, }, }, @@ -456,11 +390,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -495,16 +425,7 @@ export const data = [ }, status: 500, }, - statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, + statTags, }, }, }, @@ -521,11 +442,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -561,14 +478,8 @@ export const data = [ status: 412, }, statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + ...statTags, errorType: 'retryable', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', meta: 'unhandledStatusCode', }, }, @@ -577,3 +488,10 @@ export const data = [ }, }, ]; + +export const data = [ + ...v0TestData, + ...testScenariosForV1API, + ...otherScenariosV1, + ...oauthScenariosV1, +]; diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts new file mode 100644 index 0000000000..c6d938c627 --- /dev/null +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts @@ -0,0 +1,45 @@ +import { testFormData, statTags } from './business'; +import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config'; + +export const oauthScenariosV1: ProxyV1TestData[] = [ + { + id: 'facebook_pixel_v1_oauth_scenario_1', + name: 'facebook_pixel', + description: 'app event fails due to missing permissions', + successCriteria: 'Should return 400 with missing permissions error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234571/events?access_token=valid_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Capability or permissions issue.', + statTags, + response: [ + { + error: 'Capability or permissions issue.', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/other.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/other.ts new file mode 100644 index 0000000000..e25cc8e07c --- /dev/null +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/other.ts @@ -0,0 +1,93 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config'; +import { testFormData, statTags } from './business'; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'facebook_pixel_v1_other_scenario_1', + name: 'facebook_pixel', + description: 'user update request is throttled due to too many calls', + successCriteria: 'Should return 429 with message there have been too many calls', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=throttled_valid_access_token`, + params: { + destination: 'facebook_pixel', + }, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 429, + message: 'API User Too Many Calls', + statTags: { + ...statTags, + errorType: 'throttled', + }, + response: [ + { + error: 'API User Too Many Calls', + statusCode: 429, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_other_scenario_2', + name: 'facebook_pixel', + description: 'app event fails due to Unhandled random error', + successCriteria: 'Should return 500 with Unhandled random error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234572/events?access_token=valid_access_token_unhandled_response`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 500, + message: 'Unhandled random error', + statTags: { + ...statTags, + errorType: 'retryable', + meta: 'unhandledStatusCode', + }, + response: [ + { + error: 'Unhandled random error', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/facebook_pixel/network.ts b/test/integrations/destinations/facebook_pixel/network.ts index 05b3a05fd0..a61fa44eab 100644 --- a/test/integrations/destinations/facebook_pixel/network.ts +++ b/test/integrations/destinations/facebook_pixel/network.ts @@ -1,4 +1,4 @@ -import { data } from './dataDelivery/data'; +import { testFormData } from './dataDelivery/business'; import { getFormData } from '../../../../src/adapters/network'; import { VERSION } from '../../../../src/v0/destinations/facebook_pixel/config'; @@ -6,7 +6,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_access_token`, - data: getFormData(data[0].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -26,7 +26,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_timestamp_correct_access_token`, - data: getFormData(data[2].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -51,7 +51,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=throttled_valid_access_token`, - data: getFormData(data[3].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -71,7 +71,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_account_id_valid_access_token`, - data: getFormData(data[4].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -93,7 +93,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=not_found_access_token`, - data: getFormData(data[5].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -114,7 +114,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234570/events?access_token=valid_access_token`, - data: getFormData(data[6].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -135,7 +135,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234571/events?access_token=valid_access_token`, - data: getFormData(data[7].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -156,7 +156,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234572/events?access_token=valid_access_token_unhandled_response`, - data: getFormData(data[8].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -177,7 +177,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=my_access_token`, - data: getFormData(data[1].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST',