From a8b8f23d30b11bfe50cf819a31c8fcf3e196d950 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Thu, 15 Feb 2024 11:38:41 +0530 Subject: [PATCH] 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 --- .../braze/dataDelivery/business.ts | 377 ++++++++++++++++++ .../destinations/braze/dataDelivery/data.ts | 6 +- .../destinations/braze/dataDelivery/other.ts | 204 ++++++++++ .../destinations/braze/network.ts | 102 ++++- test/integrations/testUtils.ts | 4 +- 5 files changed, 690 insertions(+), 3 deletions(-) create mode 100644 test/integrations/destinations/braze/dataDelivery/business.ts create mode 100644 test/integrations/destinations/braze/dataDelivery/other.ts diff --git a/test/integrations/destinations/braze/dataDelivery/business.ts b/test/integrations/destinations/braze/dataDelivery/business.ts new file mode 100644 index 0000000000..4997c5ffae --- /dev/null +++ b/test/integrations/destinations/braze/dataDelivery/business.ts @@ -0,0 +1,377 @@ +import { ProxyMetdata } from '../../../../../src/types'; +import { ProxyV1TestData } from '../../../testTypes'; +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; + +const BRAZE_USERS_TRACK_ENDPOINT = 'https://rest.iad-03.braze.com/users/track'; + +const partner = 'RudderStack'; + +const headers = { + Accept: 'application/json', + Authorization: 'Bearer api_key', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', +}; + +const BrazeEvent1 = { + name: 'Product List Viewed', + time: '2023-11-30T21:48:45.634Z', + properties: { + products: [ + { + sku: '23-04-52-62-01-18', + name: 'Broman Hoodie', + price: '97.99', + variant: [ + { + id: 39653520310368, + sku: '23-04-52-62-01-18', + grams: 0, + price: '97.99', + title: '(SM)', + weight: 0, + option1: '(SM)', + taxable: true, + position: 1, + tax_code: '', + created_at: '2023-05-18T12:56:22-06:00', + product_id: 6660780884064, + updated_at: '2023-11-30T15:48:43-06:00', + weight_unit: 'kg', + quantity_rule: { + min: 1, + increment: 1, + }, + compare_at_price: '139.99', + inventory_policy: 'deny', + requires_shipping: true, + inventory_quantity: 8, + fulfillment_service: 'manual', + inventory_management: 'shopify', + quantity_price_breaks: [], + old_inventory_quantity: 8, + }, + ], + category: '62 OTHER/RETRO', + currency: 'CAD', + product_id: 6660780884064, + }, + { + sku: '23-04-08-61-01-18', + name: 'Kipling Camo Hoodie', + price: '69.99', + variant: [ + { + id: 39672628740192, + sku: '23-04-08-61-01-18', + grams: 0, + price: '69.99', + title: '(SM)', + weight: 0, + option1: '(SM)', + taxable: true, + position: 1, + tax_code: '', + created_at: '2023-06-28T12:52:56-06:00', + product_id: 6666835853408, + updated_at: '2023-11-30T15:48:43-06:00', + weight_unit: 'kg', + quantity_rule: { + min: 1, + increment: 1, + }, + compare_at_price: '99.99', + inventory_policy: 'deny', + requires_shipping: true, + inventory_quantity: 8, + fulfillment_service: 'manual', + inventory_management: 'shopify', + quantity_price_breaks: [], + old_inventory_quantity: 8, + }, + ], + category: 'Misc', + currency: 'CAD', + product_id: 6666835853408, + }, + ], + }, + _update_existing_only: false, + user_alias: { + alias_name: 'ab7de609-9bec-8e1c-42cd-084a1cd93a4e', + alias_label: 'rudder_id', + }, +}; + +const BrazeEvent2 = { + name: 'Add to Cart', + time: '2020-01-24T11:59:02.403+05:30', + properties: { + revenue: 50, + }, + external_id: 'mickeyMouse', +}; + +const BrazePurchaseEvent = { + product_id: '507f1f77bcf86cd799439011', + price: 0, + currency: 'USD', + quantity: 1, + time: '2020-01-24T11:59:02.402+05:30', + _update_existing_only: false, + user_alias: { + alias_name: 'e6ab2c5e-2cda-44a9-a962-e2f67df78bca', + alias_label: 'rudder_id', + }, +}; + +const metadataArray = [generateMetadata(1), generateMetadata(2), generateMetadata(3)]; + +const errorMessages = { + message_1: '{"events_processed":2,"purchases_processed":1,"message":"success"}', + message_2: + '{"events_processed":1,"message":"success","errors":[{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":1},{"type":"\'quantity\' is not valid","input_array":"purchases","index":0}]}', + message_3: + '{"message":"Valid data must be provided in the \'attributes\', \'events\', or \'purchases\' fields.","errors":[{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":0},{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":1},{"type":"\'quantity\' is not valid","input_array":"purchases","index":0}]}', +}; + +const expectedStatTags = { + errorCategory: 'network', + errorType: 'aborted', + destType: 'BRAZE', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', +}; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'braze_v1_scenario_1', + name: 'braze', + description: + '[Proxy v1 API] :: Test for a valid request - 2 events and 1 purchase event are sent where the destination responds with 200 without any error', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [BrazeEvent1, BrazeEvent2], + purchases: [BrazePurchaseEvent], + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/valid_scenario1`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: errorMessages.message_1, + statusCode: 200, + metadata: generateMetadata(1), + }, + { + error: errorMessages.message_1, + statusCode: 200, + metadata: generateMetadata(2), + }, + { + error: errorMessages.message_1, + statusCode: 200, + metadata: generateMetadata(3), + }, + ], + status: 200, + message: 'Request for braze Processed Successfully', + }, + }, + }, + }, + }, + { + id: 'braze_v1_scenario_2', + name: 'braze', + description: + '[Proxy v1 API] :: Test for a invalid request - 2 events and 1 purchase event are sent where the destination responds with 200 with error for a one of the event and the purchase event', + successCriteria: 'Should return 200 with error for one of the event and the purchase event', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [{ ...BrazeEvent1, user_alias: undefined }, BrazeEvent2], // modifying first event to be invalid + purchases: [{ ...BrazePurchaseEvent, quantity: 'invalid quantity' }], // modifying purchase event to be invalid + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario1`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: errorMessages.message_2, + statusCode: 200, + metadata: generateMetadata(1), + }, + { + error: errorMessages.message_2, + statusCode: 200, + metadata: generateMetadata(2), + }, + { + error: errorMessages.message_2, + statusCode: 200, + metadata: generateMetadata(3), + }, + ], + status: 200, + message: 'Request for braze Processed Successfully', + }, + }, + }, + }, + }, + { + id: 'braze_v1_scenario_3', + name: 'braze', + description: '[Proxy v1 API] :: Test for an invalid request - all the payloads are invalid', + successCriteria: 'Should return 400 with error for all the payloads', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [ + { ...BrazeEvent1, user_alias: undefined }, + { ...BrazeEvent2, external_id: undefined }, + ], // modifying first event to be invalid + purchases: [{ ...BrazePurchaseEvent, quantity: 'invalid quantity' }], // modifying purchase event to be invalid + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario2`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: errorMessages.message_3, + statusCode: 400, + metadata: generateMetadata(1), + }, + { + error: errorMessages.message_3, + statusCode: 400, + metadata: generateMetadata(2), + }, + { + error: errorMessages.message_3, + statusCode: 400, + metadata: generateMetadata(3), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 400', + status: 400, + }, + }, + }, + }, + }, + { + id: 'braze_v1_scenario_4', + name: 'braze', + description: '[Proxy v1 API] :: Test for invalid auth scneario', + successCriteria: 'Should return 400 for all the payloads', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [BrazeEvent1, BrazeEvent2], + purchases: [BrazePurchaseEvent], + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario3`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '{"message":"Invalid API Key"}', + statusCode: 401, + metadata: generateMetadata(1), + }, + { + error: '{"message":"Invalid API Key"}', + statusCode: 401, + metadata: generateMetadata(2), + }, + { + error: '{"message":"Invalid API Key"}', + statusCode: 401, + metadata: generateMetadata(3), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 401', + status: 401, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/braze/dataDelivery/data.ts b/test/integrations/destinations/braze/dataDelivery/data.ts index 3c1a97811e..2596a4b959 100644 --- a/test/integrations/destinations/braze/dataDelivery/data.ts +++ b/test/integrations/destinations/braze/dataDelivery/data.ts @@ -1,6 +1,8 @@ import MockAdapter from 'axios-mock-adapter'; +import { testScenariosForV1API } from './business'; +import { otherScenariosV1 } from './other'; -export const data = [ +export const existingTestData = [ { name: 'braze', description: 'Test 0', @@ -846,3 +848,5 @@ export const data = [ }, }, ]; + +export const data = [...existingTestData, ...testScenariosForV1API, ...otherScenariosV1]; diff --git a/test/integrations/destinations/braze/dataDelivery/other.ts b/test/integrations/destinations/braze/dataDelivery/other.ts new file mode 100644 index 0000000000..9353899a65 --- /dev/null +++ b/test/integrations/destinations/braze/dataDelivery/other.ts @@ -0,0 +1,204 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; + +const expectedStatTags = { + errorCategory: 'network', + errorType: 'retryable', + destType: 'BRAZE', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', +}; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'braze_v1_other_scenario_1', + name: 'braze', + description: + '[Proxy v1 API] :: Scenario for testing Service Unavailable error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_service_not_available', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: + '{"error":{"message":"Service Unavailable","description":"The server is currently unable to handle the request due to temporary overloading or maintenance of the server. Please try again later."}}', + statusCode: 503, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 503', + status: 503, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_2', + name: 'braze', + description: '[Proxy v1 API] :: Scenario for testing Internal Server error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_internal_server_error', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Internal Server Error"', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 500', + status: 500, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_3', + name: 'braze', + description: '[Proxy v1 API] :: Scenario for testing Gateway Time Out error from destination', + successCriteria: 'Should return 504 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_gateway_time_out', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Gateway Timeout"', + statusCode: 504, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 504', + status: 504, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_4', + name: 'braze', + description: '[Proxy v1 API] :: Scenario for testing null response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_null_response', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 500', + status: 500, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_5', + name: 'braze', + description: + '[Proxy v1 API] :: Scenario for testing null and no status response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_null_and_no_status', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 500', + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/braze/network.ts b/test/integrations/destinations/braze/network.ts index 40d75c9d34..ae093ce1f4 100644 --- a/test/integrations/destinations/braze/network.ts +++ b/test/integrations/destinations/braze/network.ts @@ -524,4 +524,104 @@ const deleteNwData = [ }, }, ]; -export const networkCallsData = [...deleteNwData, ...dataDeliveryMocksData]; + +const BRAZE_USERS_TRACK_ENDPOINT = 'https://rest.iad-03.braze.com/users/track'; + +// New Mocks for Braze +const updatedDataDeliveryMocksData = [ + { + description: + 'Mock response from destination depicting a valid request for 2 valid events and 1 purchase event', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/valid_scenario1`, + method: 'POST', + }, + httpRes: { + data: { + events_processed: 2, + purchases_processed: 1, + message: 'success', + }, + status: 200, + }, + }, + + { + description: + 'Mock response from destination depicting a request with 1 valid and 1 invalid event and 1 invalid purchase event', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario1`, + method: 'POST', + }, + httpRes: { + data: { + events_processed: 1, + message: 'success', + errors: [ + { + type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required", + input_array: 'events', + index: 1, + }, + { + type: "'quantity' is not valid", + input_array: 'purchases', + index: 0, + }, + ], + }, + status: 200, + }, + }, + + { + description: + 'Mock response from destination depicting a request with all the payloads are invalid', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario2`, + method: 'POST', + }, + httpRes: { + data: { + message: + "Valid data must be provided in the 'attributes', 'events', or 'purchases' fields.", + errors: [ + { + type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required", + input_array: 'events', + index: 0, + }, + { + type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required", + input_array: 'events', + index: 1, + }, + { + type: "'quantity' is not valid", + input_array: 'purchases', + index: 0, + }, + ], + }, + status: 400, + }, + }, + { + description: 'Mock response from destination depicting a request with invalid credentials', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario3`, + method: 'POST', + }, + httpRes: { + data: { + message: 'Invalid API Key', + }, + status: 401, + }, + }, +]; +export const networkCallsData = [ + ...deleteNwData, + ...dataDeliveryMocksData, + ...updatedDataDeliveryMocksData, +]; diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index 8e26c404db..07d5e5eb83 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -527,7 +527,9 @@ export const generateMetadata = (jobId: number): any => { sourceId: 'default-sourceId', destinationId: 'default-destinationId', workspaceId: 'default-workspaceId', - secret: {}, + secret: { + accessToken: 'default-accessToken', + }, dontBatch: false, }; };