diff --git a/.github/workflows/dt-test-and-report-code-coverage.yml b/.github/workflows/dt-test-and-report-code-coverage.yml index 51a9f8c9ee..4375b3383e 100644 --- a/.github/workflows/dt-test-and-report-code-coverage.yml +++ b/.github/workflows/dt-test-and-report-code-coverage.yml @@ -41,6 +41,8 @@ jobs: - name: Upload Coverage Reports to Codecov uses: codecov/codecov-action@v4.0.1 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: directory: ./reports/coverage diff --git a/.github/workflows/prepare-for-prod-dt-deploy.yml b/.github/workflows/prepare-for-prod-dt-deploy.yml index 2af853f643..a5ca48e3f8 100644 --- a/.github/workflows/prepare-for-prod-dt-deploy.yml +++ b/.github/workflows/prepare-for-prod-dt-deploy.yml @@ -144,3 +144,39 @@ jobs: git push -u origin hosted-transformer-$TAG_NAME gh pr create --fill + + - name: Update helm charts and raise pull request for enterprise customers on dedicated transformers + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + run: | + cd rudder-devops + git checkout -b dedicated-transformer-$TAG_NAME + + cd customer-objects + + declare -a enabled_ut_customers=() + declare -a sub_directories=('enterprise-us' 'enterprise-eu') + + # identify the customers enabled in sub-directories + for directory in "${sub_directories[@]}"; do + for f in "./$directory"/*; do + [[ -f $f ]] || continue + + enabled="$(yq e '.spec.user_transformer.enabled' $f)" + if [ $enabled == "true" ]; then + enabled_ut_customers+=( $f ) + fi + done + done + + # bump up the customers version and repository information + for customer in "${enabled_ut_customers[@]}"; do + yq eval -i ".spec.user_transformer.image.version=\"$TAG_NAME\"" $customer + yq eval -i ".spec.user_transformer.image.repository=\"$TF_IMAGE_REPOSITORY\"" $customer + git add $customer + done + + git commit -m "chore: upgrade dedicated transformers to $TAG_NAME" + git push -u origin dedicated-transformer-$TAG_NAME + + gh pr create --fill diff --git a/.github/workflows/prepare-for-prod-rollback.yml b/.github/workflows/prepare-for-prod-rollback.yml index 9ac144a21e..825720efe1 100644 --- a/.github/workflows/prepare-for-prod-rollback.yml +++ b/.github/workflows/prepare-for-prod-rollback.yml @@ -27,11 +27,14 @@ jobs: git config --global user.name "GitHub Actions" git config --global user.email "noreply@github.com" + - name: Clone Devops Repo + run: | + git clone https://${{secrets.PAT}}@github.com/rudderlabs/rudder-devops.git + - name: Update Helm Charts and Raise Pull Request env: GITHUB_TOKEN: ${{ secrets.PAT }} run: | - git clone https://${{secrets.PAT}}@github.com/rudderlabs/rudder-devops.git cd rudder-devops git checkout -b shared-transformer-rollback-${{ steps.target-version.outputs.tag_name }} @@ -57,3 +60,38 @@ jobs: git push -u origin shared-transformer-rollback-${{ steps.target-version.outputs.tag_name }} gh pr create --fill + + - name: Update helm charts and raise pull request for enterprise customers on dedicated transformers + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + run: | + cd rudder-devops + git checkout -b dedicated-transformer-rollback-${{ steps.target-version.outputs.tag_name }} + + cd customer-objects + + declare -a enabled_ut_customers=() + declare -a sub_directories=('enterprise-us' 'enterprise-eu') + + # identify the customers enabled in sub-directories + for directory in "${sub_directories[@]}"; do + for f in "./$directory"/*; do + [[ -f $f ]] || continue + + enabled="$(yq e '.spec.user_transformer.enabled' $f)" + if [ $enabled == "true" ]; then + enabled_ut_customers+=( $f ) + fi + done + done + + # bump up the customers version and repository information + for customer in "${enabled_ut_customers[@]}"; do + yq eval -i ".spec.user_transformer.image.version=\"${{ steps.target-version.outputs.tag_name }}\"" $customer + git add $customer + done + + git commit -m "chore: rollback dedicated transformers to ${{ steps.target-version.outputs.tag_name }}" + git push -u origin dedicated-transformer-rollback-${{ steps.target-version.outputs.tag_name }} + + gh pr create --fill diff --git a/.nvmrc b/.nvmrc index a9d087399d..3c5535cf60 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.19.0 +18.19.1 diff --git a/Dockerfile b/Dockerfile index 6bd03c9515..8cd4005a7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1.4 -FROM node:18.19.0-alpine3.18 AS base +FROM node:18.19.1-alpine3.18 AS base ENV HUSKY 0 RUN apk update diff --git a/src/cdk/v2/destinations/algolia/rtWorkflow.yaml b/src/cdk/v2/destinations/algolia/rtWorkflow.yaml index 758a71bf5b..f5442f3209 100644 --- a/src/cdk/v2/destinations/algolia/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/algolia/rtWorkflow.yaml @@ -2,7 +2,6 @@ bindings: - path: ../../../../v0/destinations/algolia/config - name: handleRtTfSingleEventError path: ../../../../v0/util/index - steps: - name: validateInput template: | @@ -28,10 +27,14 @@ steps: $.outputs.transform#idx.error.( $.handleRtTfSingleEventError(^[idx], .originalError ?? ., {}) )[] + - name: batchSuccessfulEvents description: Batches the successfulEvents template: | - let batches = $.chunk($.outputs.successfulEvents, $.MAX_BATCH_SIZE); + const dontBatchTrueEvents = $.outputs.successfulEvents{.metadata.dontBatch}[]; + const dontBatchFalseEvents = $.outputs.successfulEvents{!.metadata.dontBatch}[]; + + let batches = [...$.chunk(dontBatchFalseEvents, $.MAX_BATCH_SIZE), ...$.chunk(dontBatchTrueEvents, 1)]; batches@batch.({ "batchedRequest": { "body": { @@ -56,6 +59,7 @@ steps: "statusCode": 200, "destination": batch[0].destination })[]; + - name: finalPayload template: | [...$.outputs.failedEvents, ...$.outputs.batchSuccessfulEvents] diff --git a/src/cdk/v2/destinations/koala/procWorkflow.yaml b/src/cdk/v2/destinations/koala/procWorkflow.yaml new file mode 100644 index 0000000000..9ec0202b13 --- /dev/null +++ b/src/cdk/v2/destinations/koala/procWorkflow.yaml @@ -0,0 +1,65 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ../../bindings/jsontemplate + - name: defaultRequestConfig + path: ../../../../v0/util + +steps: + - name: validateInput + template: | + $.assert(.message.type, "message Type is not present. Aborting message"); + $.assert(.message.type in {{$.EventType.([.IDENTIFY, .TRACK])}}, + "message type " + .message.type + " is not supported"); + $.assertConfig(.destination.Config.publicKey, "publicKey is not present. Aborting message"); + $.context.email = .message.().({{{{$.getGenericPaths("emailOnly")}}}}); + $.context.ko_profile_id = .message.traits.ko_profile_id ?? .message.context.traits.ko_profile_id ?? .message.properties.ko_profile_id; + $.assert($.context.email || $.context.ko_profile_id, "Neither email or ko_profile_id are present on traits. Aborting message"); + - name: setMessageType + template: | + $.context.messageType = .message.type.toLowerCase(); + - name: preparePayloadForIdentify + condition: $.context.messageType === {{$.EventType.IDENTIFY}} + template: | + const traits = .message.traits ?? .message.context.traits ?? {}; + const koTraits = traits{~['ko_profile_id']} + const basePayload = { + email: $.context.email, + profile_id: $.context.ko_profile_id, + identifies: [{ + type: $.context.messageType, + sent_at: .message.().({{{{$.getGenericPaths("timestamp")}}}}), + traits: koTraits + }] + }; + + $.context.payload = basePayload + - name: preparePayloadForTrack + condition: $.context.messageType === {{$.EventType.TRACK}} + template: | + const properties = .message.properties ?? {}; + const koProperties = properties{~['ko_profile_id']} + const basePayload = { + ip: .message.context.ip ?? .message.request_ip, + email: $.context.email, + profile_id: $.context.ko_profile_id, + events: [{ + type: $.context.messageType, + event: .message.event, + message_id: .message.messageId, + sent_at: .message.().({{{{$.getGenericPaths("timestamp")}}}}), + properties: koProperties, + context: .message.context + }] + }; + + $.context.payload = basePayload + - name: buildResponseForProcessTransformation + template: | + const response = $.defaultRequestConfig(); + response.body.JSON = $.context.payload; + response.endpoint = "https://api2.getkoala.com/web/projects/" + .destination.Config.publicKey + "/batch"; + response.headers = { + "content-type": "application/json" + }; + response diff --git a/src/cdk/v2/destinations/koala/rtWorkflow.yaml b/src/cdk/v2/destinations/koala/rtWorkflow.yaml new file mode 100644 index 0000000000..335293b6db --- /dev/null +++ b/src/cdk/v2/destinations/koala/rtWorkflow.yaml @@ -0,0 +1,31 @@ +bindings: + - name: handleRtTfSingleEventError + path: ../../../../v0/util/index + +steps: + - name: validateInput + template: | + $.assert(Array.isArray(^) && ^.length > 0, "Invalid event array") + + - name: transform + externalWorkflow: + path: ./procWorkflow.yaml + loopOverInput: true + + - name: successfulEvents + template: | + $.outputs.transform#idx.output.({ + "batchedRequest": ., + "batched": false, + "destination": ^[idx].destination, + "metadata": ^[idx].metadata[], + "statusCode": 200 + })[] + - name: failedEvents + template: | + $.outputs.transform#idx.error.( + $.handleRtTfSingleEventError(^[idx], .originalError ?? ., {}) + )[] + - name: finalPayload + template: | + [...$.outputs.failedEvents, ...$.outputs.successfulEvents] diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index d1b2b24de0..17848e6b94 100644 --- a/src/constants/destinationCanonicalNames.js +++ b/src/constants/destinationCanonicalNames.js @@ -152,6 +152,7 @@ const DestCanonicalNames = { 'the trade desk', ], INTERCOM: ['INTERCOM', 'intercom', 'Intercom'], + koala: ['Koala', 'koala', 'KOALA'], }; module.exports = { DestHandlerMap, DestCanonicalNames }; diff --git a/src/controllers/bulkUpload.ts b/src/controllers/bulkUpload.ts index babb8b6db1..dbd77dc07f 100644 --- a/src/controllers/bulkUpload.ts +++ b/src/controllers/bulkUpload.ts @@ -1,14 +1,14 @@ /* eslint-disable global-require, import/no-dynamic-require, @typescript-eslint/no-unused-vars */ import { client as errNotificationClient } from '../util/errorNotifier'; import logger from '../logger'; +import { + getJobStatusHandler, + getPollStatusHandler, + getDestFileUploadHandler, +} from '../util/fetchDestinationHandlers'; import { CatchErr, ContextBodySimple } from '../util/types'; // TODO: To be refactored and redisgned -const getDestFileUploadHandler = (version, dest) => - require(`../${version}/destinations/${dest}/fileUpload`); -const getPollStatusHandler = (version, dest) => require(`../${version}/destinations/${dest}/poll`); -const getJobStatusHandler = (version, dest) => - require(`../${version}/destinations/${dest}/fetchJobStatus`); const ERROR_MESSAGE_PROCESSOR_STRING = 'Error occurred while processing payload.'; const getCommonMetadata = (ctx) => diff --git a/src/controllers/trackingPlan.ts b/src/controllers/trackingPlan.ts index 74e47e0ec9..e4802cfc4d 100644 --- a/src/controllers/trackingPlan.ts +++ b/src/controllers/trackingPlan.ts @@ -7,7 +7,7 @@ export class TrackingPlanController { const events = ctx.request.body; const requestSize = Number(ctx.request.get('content-length')); const reqParams = ctx.request.query; - const response = await TrackingPlanservice.validateTrackingPlan(events, requestSize, reqParams); + const response = await TrackingPlanservice.validate(events, requestSize, reqParams); ctx.body = response.body; ControllerUtility.postProcess(ctx, response.status); return ctx; diff --git a/src/features.json b/src/features.json index 5460111a22..dc52044048 100644 --- a/src/features.json +++ b/src/features.json @@ -66,7 +66,8 @@ "REDDIT": true, "THE_TRADE_DESK": true, "INTERCOM": true, - "NINETAILED": true + "NINETAILED": true, + "KOALA": true }, "regulations": [ "BRAZE", diff --git a/src/services/trackingPlan.ts b/src/services/trackingPlan.ts index 2e68df55e9..93b6ee11ff 100644 --- a/src/services/trackingPlan.ts +++ b/src/services/trackingPlan.ts @@ -1,88 +1,87 @@ import logger from '../logger'; import { RetryRequestError, RespStatusError, constructValidationErrors } from '../util/utils'; -import { getMetadata } from '../v0/util'; +import { getMetadata, getTrackingPlanMetadata } from '../v0/util'; import eventValidator from '../util/eventValidation'; import stats from '../util/stats'; +import { HTTP_STATUS_CODES } from '../v0/util/constant'; export class TrackingPlanservice { - public static async validateTrackingPlan(events, requestSize, reqParams) { - const requestStartTime = new Date(); + public static async validate(events, requestSize, reqParams) { + const startTime = Date.now(); const respList: any[] = []; - const metaTags = events[0].metadata ? getMetadata(events[0].metadata) : {}; + const metaTags = events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}; + const tpTags: any = + events.length && events[0].metadata ? getTrackingPlanMetadata(events[0].metadata) : {}; let ctxStatusCode = 200; for (let i = 0; i < events.length; i++) { + let eventValidationResponse: any; + let exceptionOccured = false; + const eventStartTime = Date.now(); const event = events[i]; - const eventStartTime = new Date(); + try { - const parsedEvent = event; - parsedEvent.request = { query: reqParams }; - const hv = await eventValidator.handleValidation(parsedEvent); - if (hv['dropEvent']) { - respList.push({ - output: event.message, - metadata: event.metadata, - statusCode: 400, - validationErrors: hv['validationErrors'], - error: JSON.stringify(constructValidationErrors(hv['validationErrors'])), - }); - stats.counter('tp_violation_type', 1, { - violationType: hv['violationType'], - ...metaTags, - }); - } else { - respList.push({ - output: event.message, - metadata: event.metadata, - statusCode: 200, - validationErrors: hv['validationErrors'], - error: JSON.stringify(constructValidationErrors(hv['validationErrors'])), - }); - stats.counter('tp_propagated_events', 1, { - ...metaTags, - }); - } - } catch (error) { - const errMessage = `Error occurred while validating : ${error}`; - logger.error(errMessage); - let status = 200; + event.request = { query: reqParams }; + const validatedEvent = await eventValidator.handleValidation(event); + eventValidationResponse = { + output: event.message, + metadata: event.metadata, + statusCode: validatedEvent['dropEvent'] + ? HTTP_STATUS_CODES.BAD_REQUEST + : HTTP_STATUS_CODES.OK, + validationErrors: validatedEvent['validationErrors'], + error: JSON.stringify(constructValidationErrors(validatedEvent['validationErrors'])), + }; + } catch (error: any) { + logger.debug( + `Error occurred while validating event`, + 'event', + `${event.message?.event}::${event.message?.type}`, + 'trackingPlan', + `${tpTags?.trackingPlanId}`, + 'error', + error.message, + ); + + exceptionOccured = true; + // no need to process further if + // we have error of retry request error if (error instanceof RetryRequestError) { ctxStatusCode = error.statusCode; + break; } - if (error instanceof RespStatusError) { - status = error.statusCode; - } - respList.push({ + + eventValidationResponse = { output: event.message, metadata: event.metadata, - statusCode: status, + statusCode: error instanceof RespStatusError ? error.statusCode : HTTP_STATUS_CODES.OK, validationErrors: [], - error: errMessage, - }); - stats.counter('tp_errors', 1, { - ...metaTags, - workspaceId: event.metadata?.workspaceId, - trackingPlanId: event.metadata?.trackingPlanId, - }); + error: `Error occurred while validating: ${error}`, + }; } finally { - stats.timing('tp_event_latency', eventStartTime, { + // finally on every event, we need to + // capture the information related to the validates event + stats.timing('tp_event_validation_latency', eventStartTime, { ...metaTags, + ...tpTags, + status: eventValidationResponse.statusCode, + exception: exceptionOccured, }); } - } - stats.counter('tp_events_count', events.length, { - ...metaTags, - }); + respList.push(eventValidationResponse); + } - stats.histogram('tp_request_size', requestSize, { + stats.histogram('tp_batch_size', requestSize, { ...metaTags, + ...tpTags, }); - stats.timing('tp_request_latency', requestStartTime, { + // capture overall function latency + // with metadata tags + stats.histogram('tp_batch_validation_latency', (Date.now() - startTime) / 1000, { ...metaTags, - workspaceId: events[0]?.metadata?.workspaceId, - trackingPlanId: events[0]?.metadata?.trackingPlanId, + ...tpTags, }); return { body: respList, status: ctxStatusCode }; diff --git a/src/util/fetchDestinationHandlers.ts b/src/util/fetchDestinationHandlers.ts new file mode 100644 index 0000000000..2661ef2e68 --- /dev/null +++ b/src/util/fetchDestinationHandlers.ts @@ -0,0 +1,42 @@ +import * as V0MarketoBulkUploadFileUpload from '../v0/destinations/marketo_bulk_upload/fileUpload'; +import * as V0MarketoBulkUploadPollStatus from '../v0/destinations/marketo_bulk_upload/poll'; +import * as V0MarketoBulkUploadJobStatus from '../v0/destinations/marketo_bulk_upload/fetchJobStatus'; + +const fileUploadHandlers = { + v0: { + marketo_bulk_upload: V0MarketoBulkUploadFileUpload, + }, +}; + +const pollStatusHandlers = { + v0: { + marketo_bulk_upload: V0MarketoBulkUploadPollStatus, + }, +}; + +const jobStatusHandlers = { + v0: { + marketo_bulk_upload: V0MarketoBulkUploadJobStatus, + }, +}; + +export const getDestFileUploadHandler = (version, dest) => { + if (fileUploadHandlers[version] && fileUploadHandlers[version][dest]) { + return fileUploadHandlers[version][dest]; + } + return undefined; +}; + +export const getPollStatusHandler = (version, dest) => { + if (pollStatusHandlers[version] && pollStatusHandlers[version][dest]) { + return pollStatusHandlers[version][dest]; + } + return undefined; +}; + +export const getJobStatusHandler = (version, dest) => { + if (jobStatusHandlers[version] && jobStatusHandlers[version][dest]) { + return jobStatusHandlers[version][dest]; + } + return undefined; +}; diff --git a/src/util/prometheus.js b/src/util/prometheus.js index 9a9ca9cfb6..9c26347208 100644 --- a/src/util/prometheus.js +++ b/src/util/prometheus.js @@ -590,18 +590,38 @@ class Prometheus { labelNames: ['method', 'route', 'code'], }, { - name: 'tp_request_size', - help: 'tp_request_size', + name: 'tp_batch_size', + help: 'Size of batch of events for tracking plan validation', type: 'histogram', - labelNames: ['sourceType', 'destinationType', 'k8_namespace'], buckets: [ 1024, 102400, 524288, 1048576, 10485760, 20971520, 52428800, 104857600, 209715200, 524288000, ], + labelNames: [ + 'sourceType', + 'destinationType', + 'k8_namespace', + 'workspaceId', + 'trackingPlanId', + ], + }, + { + name: 'tp_event_validation_latency', + help: 'Latency of validating tracking plan at event level', + type: 'histogram', + labelNames: [ + 'sourceType', + 'destinationType', + 'k8_namespace', + 'workspaceId', + 'trackingPlanId', + 'status', + 'exception', + ], }, { - name: 'tp_request_latency', - help: 'tp_request_latency', + name: 'tp_batch_validation_latency', + help: 'Latency of validating tracking plan at batch level', type: 'histogram', labelNames: [ 'sourceType', @@ -730,7 +750,7 @@ class Prometheus { name: 'get_libraries_code_time', help: 'get_libraries_code_time', type: 'histogram', - labelNames: ['libraryVersionId', 'versionId', 'type'], + labelNames: ['libraryVersionId', 'versionId', 'type', 'version'], }, { name: 'isolate_cpu_time', diff --git a/src/util/redis/redisConnector.test.js b/src/util/redis/redisConnector.test.js index 840f222e37..e0491132ff 100644 --- a/src/util/redis/redisConnector.test.js +++ b/src/util/redis/redisConnector.test.js @@ -70,7 +70,7 @@ describe(`Redis Class Get Tests`, () => { data.forEach((dataPoint, index) => { it(`${index}. Redis Get- ${dataPoint.description}`, async () => { try { - const output = await RedisDB.getVal(dataPoint.input.value, (isObjExpected = false)); + const output = await RedisDB.getVal(dataPoint.input.value, false); expect(output).toEqual(dataPoint.output); } catch (error) { expect(error.message).toEqual(dataPoint.output.error); diff --git a/src/util/redis/testData/shopify_source.json b/src/util/redis/testData/shopify_source.json index 53c6047298..04b80b8fc9 100644 --- a/src/util/redis/testData/shopify_source.json +++ b/src/util/redis/testData/shopify_source.json @@ -5,7 +5,8 @@ "user_id": "rudder01", "id": "shopify_test_get_items_fail", "query_parameters": { - "topic": ["carts_update"] + "topic": ["carts_update"], + "writeKey": ["wr"] }, "token": "shopify_test_get_items_fail", "email": "test@rudderstack.com", @@ -115,7 +116,8 @@ "input": { "cart_token": "shopifyGetAnonymousId", "query_parameters": { - "topic": ["checkouts_delete"] + "topic": ["checkouts_delete"], + "writeKey": ["wr"] }, "line_items": [], "note": null, @@ -154,7 +156,8 @@ "input": { "id": "shopify_test3", "query_parameters": { - "topic": ["carts_update"] + "topic": ["carts_update"], + "writeKey": ["wr"] }, "token": "shopify_test3", "line_items": [], @@ -252,7 +255,8 @@ "user_id": "rudder01", "id": "shopify_test_cart", "query_parameters": { - "topic": ["carts_update"] + "topic": ["carts_update"], + "writeKey": ["wr"] }, "token": "shopify_test_cart", "email": "test@rudderstack.com", @@ -1256,7 +1260,8 @@ "input": { "id": "shopify_test4", "query_parameters": { - "topic": ["carts_update"] + "topic": ["carts_update"], + "writeKey": ["wr"] }, "token": "shopify_test4", "line_items": [], diff --git a/src/v0/destinations/clevertap/data/CleverTapIdentify.json b/src/v0/destinations/clevertap/data/CleverTapIdentify.json index 577e13c339..cdc4b28d93 100644 --- a/src/v0/destinations/clevertap/data/CleverTapIdentify.json +++ b/src/v0/destinations/clevertap/data/CleverTapIdentify.json @@ -1,7 +1,7 @@ [ { "destKey": "Email", - "sourceKeys": "email", + "sourceKeys": "emailOnly", "required": false, "sourceFromGenericMap": true }, diff --git a/src/v0/sources/shopify/shopify_redis.util.test.js b/src/v0/sources/shopify/shopify_redis.util.test.js index db596e1dfb..fb99837932 100644 --- a/src/v0/sources/shopify/shopify_redis.util.test.js +++ b/src/v0/sources/shopify/shopify_redis.util.test.js @@ -1,5 +1,9 @@ const { getAnonymousIdAndSessionId, checkAndUpdateCartItems } = require('./util'); jest.mock('ioredis', () => require('../../../../test/__mocks__/redis')); +const metricMetadata = { + writeKey: 'dummyKey', + source: 'src', +}; describe('Shopify Utils Test', () => { describe('Check for valid cart update event test cases', () => { it('Event containing token and nothing is retreived from redis and less than req. time difference between created_at and uadated_at', async () => { @@ -14,7 +18,7 @@ describe('Shopify Utils Test', () => { created_at: '2023-02-10T12:05:04.402Z', }; const expectedOutput = false; - const output = await checkAndUpdateCartItems(input); + const output = await checkAndUpdateCartItems(input, null, metricMetadata); expect(output).toEqual(expectedOutput); }); it('Event containing token and nothing is retreived from redis', async () => { @@ -28,7 +32,7 @@ describe('Shopify Utils Test', () => { ], }; const expectedOutput = true; - const output = await checkAndUpdateCartItems(input); + const output = await checkAndUpdateCartItems(input, null, metricMetadata); expect(output).toEqual(expectedOutput); }); @@ -44,7 +48,7 @@ describe('Shopify Utils Test', () => { }; const expectedOutput = true; - const output = await checkAndUpdateCartItems(input); + const output = await checkAndUpdateCartItems(input, null, metricMetadata); expect(output).toEqual(expectedOutput); }); @@ -60,7 +64,7 @@ describe('Shopify Utils Test', () => { }; const expectedOutput = false; - const output = await checkAndUpdateCartItems(input); + const output = await checkAndUpdateCartItems(input, null, metricMetadata); expect(output).toEqual(expectedOutput); }); @@ -76,7 +80,7 @@ describe('Shopify Utils Test', () => { }; const expectedOutput = true; - const output = await checkAndUpdateCartItems(input); + const output = await checkAndUpdateCartItems(input, null, metricMetadata); expect(output).toEqual(expectedOutput); }); }); diff --git a/src/v0/sources/shopify/transform.js b/src/v0/sources/shopify/transform.js index 013580d7a3..4f09984054 100644 --- a/src/v0/sources/shopify/transform.js +++ b/src/v0/sources/shopify/transform.js @@ -143,7 +143,7 @@ const processEvent = async (inputEvent, metricMetadata) => { break; case 'carts_update': if (useRedisDatabase) { - redisData = await getDataFromRedis(event.id || event.token); + redisData = await getDataFromRedis(event.id || event.token, metricMetadata); const isValidEvent = await checkAndUpdateCartItems(inputEvent, redisData, metricMetadata); if (!isValidEvent) { return NO_OPERATION_SUCCESS; @@ -155,7 +155,8 @@ const processEvent = async (inputEvent, metricMetadata) => { if (!SUPPORTED_TRACK_EVENTS.includes(shopifyTopic)) { stats.increment('invalid_shopify_event', { event: shopifyTopic, - ...metricMetadata, + source: metricMetadata.source, + shopifyTopic: metricMetadata.shopifyTopic, }); return NO_OPERATION_SUCCESS; } @@ -215,7 +216,8 @@ const processIdentifierEvent = async (event, metricMetadata) => { stats.increment('shopify_redis_calls', { type: 'set', field: 'itemsHash', - ...metricMetadata, + source: metricMetadata.source, + writeKey: metricMetadata.writeKey, }); /* cart_token: { anonymousId: 'anon_id1', @@ -236,14 +238,16 @@ const processIdentifierEvent = async (event, metricMetadata) => { stats.increment('shopify_redis_calls', { type: 'set', field, - ...metricMetadata, + source: metricMetadata.source, + writeKey: metricMetadata.writeKey, }); await RedisDB.setVal(`${event.cartToken}`, value); } catch (e) { logger.debug(`{{SHOPIFY::}} cartToken map set call Failed due redis error ${e}`); stats.increment('shopify_redis_failures', { type: 'set', - ...metricMetadata, + source: metricMetadata.source, + writeKey: metricMetadata.writeKey, }); } } diff --git a/src/v0/sources/shopify/util.js b/src/v0/sources/shopify/util.js index 6f31ade4a7..c4bbb61b9c 100644 --- a/src/v0/sources/shopify/util.js +++ b/src/v0/sources/shopify/util.js @@ -29,7 +29,8 @@ const getDataFromRedis = async (key, metricMetadata) => { stats.increment('shopify_redis_calls', { type: 'get', field: 'all', - ...metricMetadata, + writeKey: metricMetadata.writeKey, + source: metricMetadata.source, }); const redisData = await RedisDB.getVal(key); if ( @@ -37,7 +38,8 @@ const getDataFromRedis = async (key, metricMetadata) => { (typeof redisData === 'object' && Object.keys(redisData).length === 0) ) { stats.increment('shopify_redis_no_val', { - ...metricMetadata, + writeKey: metricMetadata.writeKey, + source: metricMetadata.source, }); } return redisData; @@ -45,7 +47,8 @@ const getDataFromRedis = async (key, metricMetadata) => { logger.debug(`{{SHOPIFY::}} Get call Failed due redis error ${e}`); stats.increment('shopify_redis_failures', { type: 'get', - ...metricMetadata, + writeKey: metricMetadata.writeKey, + source: metricMetadata.source, }); } return null; @@ -166,7 +169,9 @@ const getAnonymousIdAndSessionId = async (message, metricMetadata, redisData = n if (isDefinedAndNotNull(anonymousId) && isDefinedAndNotNull(sessionId)) { stats.increment('shopify_anon_id_resolve', { method: 'note_attributes', - ...metricMetadata, + writeKey: metricMetadata.writeKey, + source: metricMetadata.source, + shopifyTopic: metricMetadata.shopifyTopic, }); return { anonymousId, sessionId }; } @@ -198,7 +203,9 @@ const getAnonymousIdAndSessionId = async (message, metricMetadata, redisData = n // and for how many stats.increment('shopify_anon_id_resolve', { method: 'database', - ...metricMetadata, + writeKey: metricMetadata.writeKey, + source: metricMetadata.source, + shopifyTopic: metricMetadata.shopifyTopic, }); } return { anonymousId, sessionId }; @@ -215,14 +222,16 @@ const updateCartItemsInRedis = async (cartToken, newCartItemsHash, metricMetadat stats.increment('shopify_redis_calls', { type: 'set', field: 'itemsHash', - ...metricMetadata, + writeKey: metricMetadata.writeKey, + source: metricMetadata.source, }); await RedisDB.setVal(`${cartToken}`, value); } catch (e) { logger.debug(`{{SHOPIFY::}} itemsHash set call Failed due redis error ${e}`); stats.increment('shopify_redis_failures', { type: 'set', - ...metricMetadata, + writeKey: metricMetadata.writeKey, + source: metricMetadata.source, }); } }; diff --git a/src/v0/util/index.js b/src/v0/util/index.js index c1debce088..32872cc5d9 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -1419,6 +1419,11 @@ function getStringValueOfJSON(json) { return output; } +const getTrackingPlanMetadata = (metadata) => ({ + trackingPlanId: metadata.trackingPlanId, + workspaceId: metadata.workspaceId, +}); + const getMetadata = (metadata) => ({ sourceType: metadata.sourceType, destinationType: metadata.destinationType, @@ -2267,6 +2272,7 @@ module.exports = { getMappingConfig, getMetadata, getTransformationMetadata, + getTrackingPlanMetadata, getParsedIP, getStringValueOfJSON, getSuccessRespEvents, diff --git a/src/v1/destinations/algolia/networkHandler.js b/src/v1/destinations/algolia/networkHandler.js new file mode 100644 index 0000000000..d953251050 --- /dev/null +++ b/src/v1/destinations/algolia/networkHandler.js @@ -0,0 +1,84 @@ +/* eslint-disable no-restricted-syntax */ +const { TransformerProxyError } = require('../../../v0/util/errorTypes'); +const { prepareProxyRequest, proxyRequest } = require('../../../adapters/network'); +const { isHttpStatusSuccess, getAuthErrCategoryFromStCode } = require('../../../v0/util/index'); + +const { + processAxiosResponse, + getDynamicErrorType, +} = require('../../../adapters/utils/networkUtils'); +const tags = require('../../../v0/util/tags'); + +const responseHandler = (responseParams) => { + const { destinationResponse, rudderJobMetadata } = responseParams; + const message = `[ALGOLIA Response V1 Handler] - Request Processed Successfully`; + const responseWithIndividualEvents = []; + // response: + // {status: 200, message: 'OK'} + // {response:'[ENOTFOUND] :: DNS lookup failed', status: 400} + // destinationResponse = { + // response: {"status": 422, "message": "EventType must be one of \"click\", \"conversion\" or \"view\""}, status: 422 + // } + const { response, status } = destinationResponse; + + if (isHttpStatusSuccess(status)) { + for (const mData of rudderJobMetadata) { + const proxyOutputObj = { + statusCode: 200, + metadata: mData, + error: 'success', + }; + responseWithIndividualEvents.push(proxyOutputObj); + } + + return { + status, + message, + destinationResponse, + response: responseWithIndividualEvents, + }; + } + + // in case of non 2xx status sending 500 for every event, populate response and update dontBatch to true + const errorMessage = response?.error?.message || response?.message || 'unknown error format'; + let serverStatus = 400; + for (const metadata of rudderJobMetadata) { + // handling case if dontBatch is true, and again we got invalid from destination + if (metadata.dontBatch && status === 422) { + responseWithIndividualEvents.push({ + statusCode: 400, + metadata, + error: errorMessage, + }); + } else { + serverStatus = 500; + metadata.dontBatch = true; + responseWithIndividualEvents.push({ + statusCode: 500, + metadata, + error: errorMessage, + }); + } + } + + // sending back 500 for retry + throw new TransformerProxyError( + `ALGOLIA: Error transformer proxy v1 during ALGOLIA response transformation`, + serverStatus, + { + [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), + }, + destinationResponse, + getAuthErrCategoryFromStCode(status), + responseWithIndividualEvents, + ); +}; + +function networkHandler() { + this.prepareProxy = prepareProxyRequest; + this.proxy = proxyRequest; + this.processAxiosResponse = processAxiosResponse; + this.responseHandler = responseHandler; +} + +module.exports = { networkHandler }; diff --git a/src/v1/destinations/campaign_manager/networkHandler.js b/src/v1/destinations/campaign_manager/networkHandler.js index 79f7e7f93b..300b5f9676 100644 --- a/src/v1/destinations/campaign_manager/networkHandler.js +++ b/src/v1/destinations/campaign_manager/networkHandler.js @@ -69,7 +69,7 @@ const responseHandler = (responseParams) => { const errorMessage = response.error?.message || 'unknown error format'; for (const metadata of rudderJobMetadata) { responseWithIndividualEvents.push({ - statusCode: 500, + statusCode: status, metadata, error: errorMessage, }); @@ -77,7 +77,7 @@ const responseHandler = (responseParams) => { throw new TransformerProxyError( `Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation`, - 500, + status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), }, diff --git a/test/integrations/common/network.ts b/test/integrations/common/network.ts index 8b0ed16c72..a6ab202a4e 100644 --- a/test/integrations/common/network.ts +++ b/test/integrations/common/network.ts @@ -81,4 +81,15 @@ export const networkCallsData = [ status: 429, }, }, + { + description: 'Mock response depicting DNS lookup failure error', + httpReq: { + method: 'post', + url: 'https://random_test_url/dns_lookup_failure', + }, + httpRes: { + data: {}, + status: 400, + }, + }, ]; diff --git a/test/integrations/destinations/algolia/router/data.ts b/test/integrations/destinations/algolia/router/data.ts index 65c74342dc..dca899693e 100644 --- a/test/integrations/destinations/algolia/router/data.ts +++ b/test/integrations/destinations/algolia/router/data.ts @@ -2220,4 +2220,2406 @@ export const data = [ }, }, }, + { + name: 'algolia', + description: 'dontBatch true for all', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 1, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + }, + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 2, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + }, + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 3, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + }, + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 4, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + }, + ], + destType: 'algolia', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: { + body: { + JSON: { + events: [ + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insights.algolia.io/1/events', + headers: { + 'X-Algolia-Application-Id': 'O2YARRI15I', + 'X-Algolia-API-Key': 'dummyApiKey', + }, + params: {}, + files: {}, + }, + metadata: [ + { + jobId: 1, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + ], + batched: true, + statusCode: 200, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + }, + { + batchedRequest: { + body: { + JSON: { + events: [ + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insights.algolia.io/1/events', + headers: { + 'X-Algolia-Application-Id': 'O2YARRI15I', + 'X-Algolia-API-Key': 'dummyApiKey', + }, + params: {}, + files: {}, + }, + metadata: [ + { + jobId: 2, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + ], + batched: true, + statusCode: 200, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + }, + { + batchedRequest: { + body: { + JSON: { + events: [ + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insights.algolia.io/1/events', + headers: { + 'X-Algolia-Application-Id': 'O2YARRI15I', + 'X-Algolia-API-Key': 'dummyApiKey', + }, + params: {}, + files: {}, + }, + metadata: [ + { + jobId: 3, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + ], + batched: true, + statusCode: 200, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + }, + { + batchedRequest: { + body: { + JSON: { + events: [ + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insights.algolia.io/1/events', + headers: { + 'X-Algolia-Application-Id': 'O2YARRI15I', + 'X-Algolia-API-Key': 'dummyApiKey', + }, + params: {}, + files: {}, + }, + metadata: [ + { + jobId: 4, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + ], + batched: true, + statusCode: 200, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + }, + ], + }, + }, + }, + }, + { + name: 'algolia', + description: 'dontBatch Partial true for events in a batch', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 1, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + }, + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 2, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + }, + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 3, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + }, + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 4, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + }, + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 5, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + }, + ], + destType: 'algolia', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: { + body: { + JSON: { + events: [ + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insights.algolia.io/1/events', + headers: { + 'X-Algolia-Application-Id': 'O2YARRI15I', + 'X-Algolia-API-Key': 'dummyApiKey', + }, + params: {}, + files: {}, + }, + metadata: [ + { + jobId: 1, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + { + jobId: 2, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + { + jobId: 3, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + ], + batched: true, + statusCode: 200, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + }, + { + batchedRequest: { + body: { + JSON: { + events: [ + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insights.algolia.io/1/events', + headers: { + 'X-Algolia-Application-Id': 'O2YARRI15I', + 'X-Algolia-API-Key': 'dummyApiKey', + }, + params: {}, + files: {}, + }, + metadata: [ + { + jobId: 4, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + ], + batched: true, + statusCode: 200, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + }, + { + batchedRequest: { + body: { + JSON: { + events: [ + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insights.algolia.io/1/events', + headers: { + 'X-Algolia-Application-Id': 'O2YARRI15I', + 'X-Algolia-API-Key': 'dummyApiKey', + }, + params: {}, + files: {}, + }, + metadata: [ + { + jobId: 5, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: true, + }, + ], + batched: true, + statusCode: 200, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + }, + ], + }, + }, + }, + }, + { + name: 'algolia', + description: 'dontBatch false for all events, all events are batched in 1', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 1, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + }, + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 2, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + }, + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 3, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + }, + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 4, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + }, + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + message: { + channel: 'web', + context: { + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + }, + type: 'track', + anonymousId: '345345', + event: 'product clicked', + userId: 'test', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + }, + metadata: { + jobId: 5, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + }, + ], + destType: 'algolia', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: { + body: { + JSON: { + events: [ + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + userToken: 'test', + eventName: 'product clicked', + eventType: 'click', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insights.algolia.io/1/events', + headers: { + 'X-Algolia-Application-Id': 'O2YARRI15I', + 'X-Algolia-API-Key': 'dummyApiKey', + }, + params: {}, + files: {}, + }, + metadata: [ + { + jobId: 1, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + { + jobId: 2, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + { + jobId: 3, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + { + jobId: 4, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + { + jobId: 5, + attemptNum: 0, + userId: '', + sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz', + destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: null, + dontBatch: false, + }, + ], + batched: true, + statusCode: 200, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + cdkV2Enabled: true, + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'Product List Filtered', + to: 'click', + }, + { + from: 'Product List Viewed', + to: 'view', + }, + { + from: 'Order Completed', + to: 'view', + }, + { + from: 'Product Added', + to: 'conversion', + }, + { + from: 'Product Viewed', + to: 'view', + }, + { + from: 'Product Clicked', + to: 'click', + }, + ], + pixelId: '123456789', + advertiserId: '429047995', + eventId: '429047995', + enhancedMatch: true, + enableDeduplication: true, + deduplicationKey: 'messageId', + sendingUnHashedData: true, + customProperties: [ + { + properties: 'presentclass', + }, + { + properties: 'presentgrade', + }, + ], + eventsMapping: [ + { + from: 'ABC Searched', + to: 'WatchVideo', + }, + ], + }, + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'ALGOLIA', + Enabled: true, + cdkV2Enabled: true, + Transformations: [], + }, + }, + ], + }, + }, + }, + }, ]; diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts index 929af485d8..288a06bfe6 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts @@ -326,14 +326,14 @@ export const v1oauthScenarios: ProxyV1TestData[] = [ }, output: { response: { - status: 500, + status: 401, body: { output: { response: [ { error: '{"error":{"code":401,"message":"Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.","errors":[{"message":"Login Required.","domain":"global","reason":"required","location":"Authorization","locationType":"header"}],"status":"UNAUTHENTICATED","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"CREDENTIALS_MISSING","domain":"googleapis.com","metadata":{"method":"google.ads.xfa.op.v4.DfareportingConversions.Batchinsert","service":"googleapis.com"}}]}}', - statusCode: 500, + statusCode: 401, metadata: { jobId: 1, attemptNum: 1, @@ -361,7 +361,7 @@ export const v1oauthScenarios: ProxyV1TestData[] = [ authErrorCategory: 'REFRESH_TOKEN', message: 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', - status: 500, + status: 401, }, }, }, @@ -389,14 +389,14 @@ export const v1oauthScenarios: ProxyV1TestData[] = [ }, output: { response: { - status: 500, + status: 403, body: { output: { response: [ { error: '{"error":{"code":403,"message":"Request had insufficient authentication scopes.","errors":[{"message":"Insufficient Permission","domain":"global","reason":"insufficientPermissions"}],"status":"PERMISSION_DENIED","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"ACCESS_TOKEN_SCOPE_INSUFFICIENT","domain":"googleapis.com","metadata":{"service":"gmail.googleapis.com","method":"caribou.api.proto.MailboxService.GetProfile"}}]}}', - statusCode: 500, + statusCode: 403, metadata: { jobId: 1, attemptNum: 1, @@ -424,7 +424,7 @@ export const v1oauthScenarios: ProxyV1TestData[] = [ authErrorCategory: 'AUTH_STATUS_INACTIVE', message: 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', - status: 500, + status: 403, }, }, }, @@ -452,14 +452,14 @@ export const v1oauthScenarios: ProxyV1TestData[] = [ }, output: { response: { - status: 500, + status: 403, body: { output: { response: [ { error: '{"error":{"code":403,"message":"invalid_grant","error_description":"Bad accesss"}}', - statusCode: 500, + statusCode: 403, metadata: { jobId: 1, attemptNum: 1, @@ -487,7 +487,7 @@ export const v1oauthScenarios: ProxyV1TestData[] = [ authErrorCategory: 'AUTH_STATUS_INACTIVE', message: 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', - status: 500, + status: 403, }, }, }, @@ -514,14 +514,14 @@ export const v1oauthScenarios: ProxyV1TestData[] = [ }, output: { response: { - status: 500, + status: 401, body: { output: { response: [ { error: '{"error":"unauthorized","error_description":"Access token expired: 2020-10-20T12:00:00.000Z"}', - statusCode: 500, + statusCode: 401, metadata: { jobId: 1, attemptNum: 1, @@ -549,7 +549,7 @@ export const v1oauthScenarios: ProxyV1TestData[] = [ authErrorCategory: 'REFRESH_TOKEN', message: 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', - status: 500, + status: 401, }, }, }, diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/other.ts b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts index 709f55a4c0..1c0c45728c 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/other.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts @@ -260,7 +260,7 @@ export const otherScenariosV1: ProxyV1TestData[] = [ { 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: 500, + statusCode: 503, metadata: { jobId: 1, attemptNum: 1, @@ -287,7 +287,7 @@ export const otherScenariosV1: ProxyV1TestData[] = [ }, message: 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', - status: 500, + status: 503, }, }, }, @@ -376,7 +376,7 @@ export const otherScenariosV1: ProxyV1TestData[] = [ response: [ { error: '"Gateway Timeout"', - statusCode: 500, + statusCode: 504, metadata: { jobId: 1, attemptNum: 1, @@ -403,7 +403,7 @@ export const otherScenariosV1: ProxyV1TestData[] = [ }, message: 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', - status: 500, + status: 504, }, }, }, diff --git a/test/integrations/destinations/clevertap/dataDelivery/business.ts b/test/integrations/destinations/clevertap/dataDelivery/business.ts new file mode 100644 index 0000000000..d9f83f52f3 --- /dev/null +++ b/test/integrations/destinations/clevertap/dataDelivery/business.ts @@ -0,0 +1,284 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; + +const params = { + destination: 'clevertap', +}; +const headers = { + 'X-CleverTap-Account-Id': '476550467', + 'X-CleverTap-Passcode': + 'fbee74a147828e2932c701d19dc1f2dcfa4ac0048be3aa3a88d427090a59dc1c0fa002f1', + 'Content-Type': 'application/json', +}; + +const statTags = { + destType: 'CLEVERTAP', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', +}; + +export const V1BusinessTestScenarion: ProxyV1TestData[] = [ + { + id: 'clevertap_business_0', + scenario: 'business', + successCriteria: 'should return 200 status code with success message', + name: 'clevertap', + description: '[business]:: create an user through identify call', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + params, + headers, + JSON: { + d: [ + { + type: 'profile', + profileData: { + Email: 'jamesDoe@gmail.com', + Name: 'James Doe', + Phone: '92374162212', + Gender: 'M', + Employed: true, + DOB: '1614775793', + Education: 'Science', + Married: 'Y', + 'Customer Type': 'Prime', + graduate: true, + msg_push: true, + msgSms: true, + msgemail: true, + msgwhatsapp: false, + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', + custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + address: + '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', + }, + identity: 'anon_id', + }, + ], + }, + endpoint: 'https://api.clevertap.com/1/upload/test1', + }, + [generateMetadata(123)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + metadata: generateMetadata(123), + error: '{"status":"success","processed":1,"unprocessed":[]}', + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'clevertap_business_1', + scenario: 'business', + successCriteria: 'should return 401 status code with error message', + name: 'clevertap', + description: '[business]:: event failed due to invalid credentials', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + params, + headers: { + 'X-CleverTap-Account-Id': 'fakeId123', + 'X-CleverTap-Passcode': 'fakePasscode123', + 'Content-Type': 'application/json', + }, + JSON: { + d: [ + { + identity: 'anon-id-new', + type: 'event', + evtName: 'Web Page Viewed: Rudder', + evtData: { + title: 'Home', + path: '/', + }, + }, + ], + }, + endpoint: 'https://api.clevertap.com/1/upload/test2', + }, + [generateMetadata(123)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 401, + message: 'Request failed with status: 401', + response: [ + { + metadata: generateMetadata(123), + error: '{"status":"fail","error":"Invalid Credentials","code":401}', + statusCode: 401, + }, + ], + statTags, + }, + }, + }, + }, + }, + { + id: 'clevertap_business_2', + scenario: 'business', + successCriteria: 'should return 401 status code with error message', + name: 'clevertap', + description: '[business]:: event failed due to invalid credentials', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + params, + headers: { + 'X-CleverTap-Account-Id': '476550467', + 'X-CleverTap-Passcode': + 'fbee74a147828e2932c701d19dc1f2dcfa4ac0048be3aa3a88d427090a59dc1c0fa002f1', + 'Content-Type': 'application/json', + }, + JSON: { + d: [ + { + identity: 'anon-id-new', + type: 'event', + evtData: { + title: 'Home', + path: '/', + }, + }, + ], + }, + endpoint: 'https://api.clevertap.com/1/upload/test3', + }, + [generateMetadata(123)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Request failed with status: 200', + response: [ + { + metadata: generateMetadata(123), + error: '{"status":"fail","processed":0,"unprocessed":[]}', + statusCode: 400, + }, + ], + statTags, + }, + }, + }, + }, + }, + { + id: 'clevertap_business_3', + scenario: 'business', + successCriteria: 'should return 200 status code with success message', + name: 'clevertap', + description: '[business]:: create an user through identify call', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + params, + headers, + JSON: { + d: [ + { + identity: 'testUser1', + type: 'profile', + profileData: { + Name: 'Test User1', + Email: 'test1@testMail.com', + }, + }, + { + evtData: { + name: 1234, + revenue: 4.99, + }, + type: 'event', + identity: 'user123', + }, + { + identity: 'testUser2', + type: 'profile', + profileData: { + Name: 'Test User2', + Email: 'test2@testMail.com', + }, + }, + ], + }, + endpoint: 'https://api.clevertap.com/1/upload/test4', + }, + [generateMetadata(123)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + statTags, + status: 400, + message: 'Request failed with status: 200', + response: [ + { + metadata: generateMetadata(123), + error: + '{"status":"partial","processed":2,"unprocessed":[{"status":"fail","code":509,"error":"Event Name is incorrect. ErrorCode: 509 - Event name is mandatory. Skipped record number : 2","record":{"evtData":{"name":1234,"revenue":4.99},"type":"event","identity":"user123"}}]}', + statusCode: 400, + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/clevertap/dataDelivery/data.ts b/test/integrations/destinations/clevertap/dataDelivery/data.ts index 8032dd50c8..57e0d0ceea 100644 --- a/test/integrations/destinations/clevertap/dataDelivery/data.ts +++ b/test/integrations/destinations/clevertap/dataDelivery/data.ts @@ -1,4 +1,5 @@ -export const data = [ +import { V1BusinessTestScenarion } from './business'; +const oldV0TestCases = [ { name: 'clevertap', description: 'Test 0', @@ -228,3 +229,5 @@ export const data = [ }, }, ]; + +export const data = [...oldV0TestCases, ...V1BusinessTestScenarion]; diff --git a/test/integrations/destinations/clevertap/network.ts b/test/integrations/destinations/clevertap/network.ts index 57a647e684..9122ba1129 100644 --- a/test/integrations/destinations/clevertap/network.ts +++ b/test/integrations/destinations/clevertap/network.ts @@ -87,6 +87,35 @@ const dataDeliveryMocksData = [ }, httpRes: { data: { status: 'fail', processed: 0, unprocessed: [] }, status: 200 }, }, + { + httpReq: { + url: 'https://api.clevertap.com/1/upload/test4', + method: 'POST', + }, + httpRes: { + data: { + status: 'partial', + processed: 2, + unprocessed: [ + { + status: 'fail', + code: 509, + error: + 'Event Name is incorrect. ErrorCode: 509 - Event name is mandatory. Skipped record number : 2', + record: { + evtData: { + name: 1234, + revenue: 4.99, + }, + type: 'event', + identity: 'user123', + }, + }, + ], + }, + status: 200, + }, + }, ]; const deleteNwData = [ { diff --git a/test/integrations/destinations/clevertap/processor/data.ts b/test/integrations/destinations/clevertap/processor/data.ts index 6309c5ec8a..1d7bdd7e78 100644 --- a/test/integrations/destinations/clevertap/processor/data.ts +++ b/test/integrations/destinations/clevertap/processor/data.ts @@ -122,6 +122,130 @@ export const data = [ }, }, }, + { + name: 'clevertap', + description: 'Should not load email from externalId', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + passcode: 'sample_passcode', + accountId: '476550467', + trackAnonymous: true, + enableObjectIdMapping: false, + }, + }, + message: { + channel: 'web', + messageId: '84e26acc-56a5-4835-8233-591137fca468', + session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22', + originalTimestamp: '2019-10-14T09:03:17.562Z', + anonymousId: 'anon_id', + type: 'identify', + traits: { + anonymousId: 'anon_id', + name: 'James Doe', + phone: '92374162212', + gender: 'M', + employed: true, + birthday: '1614775793', + education: 'Science', + graduate: true, + married: true, + customerType: 'Prime', + msg_push: true, + msgSms: true, + msgemail: true, + msgwhatsapp: false, + custom_tags: ['Test_User', 'Interested_User', 'DIY_Hobby'], + custom_mappings: { + Office: 'Trastkiv', + Country: 'Russia', + }, + address: { + city: 'kolkata', + country: 'India', + postalCode: 789223, + state: 'WB', + street: '', + }, + 'category-unsubscribe': { email: ['Marketing', 'Transactional'] }, + }, + context: { + externalId: [{ type: 'someId', id: 'someID' }], + }, + integrations: { + All: true, + }, + sentAt: '2019-10-14T09:03:22.563Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api.clevertap.com/1/upload', + headers: { + 'X-CleverTap-Account-Id': '476550467', + 'X-CleverTap-Passcode': 'sample_passcode', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + d: [ + { + type: 'profile', + profileData: { + Name: 'James Doe', + Phone: '92374162212', + Gender: 'M', + Employed: true, + DOB: '1614775793', + Education: 'Science', + Married: true, + 'Customer Type': 'Prime', + graduate: true, + msg_push: true, + msgSms: true, + msgemail: true, + msgwhatsapp: false, + custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', + address: + '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', + 'category-unsubscribe': { email: ['Marketing', 'Transactional'] }, + }, + identity: 'anon_id', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, { name: 'clevertap', description: 'Test 1', diff --git a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/business.ts b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/business.ts new file mode 100644 index 0000000000..fbeaf7f250 --- /dev/null +++ b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/business.ts @@ -0,0 +1,669 @@ +import { + generateMetadata, + generateProxyV0Payload, + generateProxyV1Payload, +} from '../../../testUtils'; + +const transactionAttribute = { + CUSTOM_KEY: 'CUSTOM_VALUE', + currency_code: 'INR', + order_id: 'order id', + store_attribute: { + store_code: 'store code', + }, + transaction_amount_micros: '100000000', + transaction_date_time: '2019-10-14 11:15:18+00:00', +}; + +const createJobPayload = { + job: { + storeSalesMetadata: { + custom_key: 'CUSTOM_KEY', + loyaltyFraction: 1, + transaction_upload_fraction: '1', + }, + type: 'STORE_SALES_UPLOAD_FIRST_PARTY', + }, +}; + +const products = [ + { + product_id: '507f1f77bcf86cd799439011', + quantity: '2', + price: '50', + sku: '45790-32', + name: 'Monopoly: 3rd Edition', + position: '1', + category: 'cars', + url: 'https://www.example.com/product/path', + image_url: 'https://www.example.com/product/path.jpg', + }, +]; + +const headers = { + header1: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + 'login-customer-id': 'logincustomerid', + }, + header2: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + }, +}; + +const params = { + param1: { + customerId: '1112223333', + event: 'Sign-up - click', + }, + param2: { + event: 'Sign-up - click', + customerId: '1234567891', + customVariables: [ + { + from: 'Value', + to: 'revenue', + }, + { + from: 'total', + to: 'cost', + }, + ], + properties: { + gbraid: 'gbraid', + wbraid: 'wbraid', + externalAttributionCredit: 10, + externalAttributionModel: 'externalAttributionModel', + conversionCustomVariable: 'conversionCustomVariable', + Value: 'value', + merchantId: '9876merchantId', + feedCountryCode: 'feedCountryCode', + feedLanguageCode: 'feedLanguageCode', + localTransactionCost: 20, + products, + userIdentifierSource: 'FIRST_PARTY', + conversionEnvironment: 'WEB', + gclid: 'gclid', + conversionDateTime: '2022-01-01 12:32:45-08:00', + conversionValue: '1', + currency: 'GBP', + orderId: 'PL-123QR', + }, + }, + param3: {}, + param4: {}, +}; + +params['param3'] = { ...params.param2, customVariables: [] }; + +params['param4'] = { ...params.param3, customerId: '1234567893', conversionEnvironment: 'APP' }; + +const validRequestPayload1 = { + addConversionPayload: { + enable_partial_failure: false, + enable_warnings: false, + operations: [ + { + create: { + transaction_attribute: transactionAttribute, + userIdentifiers: [ + { + hashedEmail: '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', + userIdentifierSource: 'UNSPECIFIED', + }, + ], + }, + }, + ], + validate_only: false, + }, + createJobPayload, + event: '1112223333', + executeJobPayload: { + validate_only: false, + }, + isStoreConversion: true, +}; + +const validRequestPayload2 = { + conversions: [ + { + gbraid: 'gbraid', + wbraid: 'wbraid', + externalAttributionData: { + externalAttributionCredit: 10, + externalAttributionModel: 'externalAttributionModel', + }, + cartData: { + merchantId: 9876, + feedCountryCode: 'feedCountryCode', + feedLanguageCode: 'feedLanguageCode', + localTransactionCost: 20, + items: [ + { + productId: '507f1f77bcf86cd799439011', + quantity: 2, + unitPrice: 50, + }, + ], + }, + userIdentifiers: [ + { + userIdentifierSource: 'FIRST_PARTY', + hashedPhoneNumber: '04e1dabb7c1348b72bfa87da179c9697c69af74827649266a5da8cdbb367abcd', + }, + ], + conversionEnvironment: 'WEB', + gclid: 'gclid', + conversionDateTime: '2022-01-01 12:32:45-08:00', + conversionValue: 1, + currencyCode: 'GBP', + orderId: 'PL-123QR', + }, + ], + partialFailure: true, +}; + +const invalidArgumentRequestPayload = { + addConversionPayload: { + enable_partial_failure: false, + enable_warnings: false, + operations: [ + { + create: { + transaction_attribute: transactionAttribute, + userIdentifiers: [ + { + hashedEmail: '6db61e6dcbcf2390e4a46af26f26a133a3bee45021422fc7ae86e9136f14110', + userIdentifierSource: 'UNSPECIFIED', + }, + ], + }, + }, + ], + validate_only: false, + }, + createJobPayload, + event: '1112223333', + executeJobPayload: { + validate_only: false, + }, + isStoreConversion: true, +}; + +const notAllowedToAccessFeatureRequestPayload = { + ...validRequestPayload2, + conversions: [ + { + ...validRequestPayload2.conversions[0], + conversionEnvironment: 'APP', + }, + ], +}; + +const metadataArray = [generateMetadata(1)]; + +const expectedStatTags = { + destType: 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', +}; + +export const testScenariosForV0API = [ + { + id: 'gaoc_v0_scenario_1', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v0 API] :: Test for invalid argument - where the destination responds with 400 with invalid argument error', + successCriteria: 'Should return 400 with error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + headers: headers.header1, + params: params.param1, + JSON: invalidArgumentRequestPayload, + endpoint: + 'https://googleads.googleapis.com/v14/customers/11122233331/offlineUserDataJobs', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 400, + body: { + output: { + status: 400, + message: + '[Google Ads Offline Conversions]:: Request contains an invalid argument. during google_ads_offline_store_conversions Add Conversion', + destinationResponse: { + error: { + code: 400, + details: [ + { + '@type': 'type.googleapis.com/google.ads.googleads.v14.errors.GoogleAdsFailure', + errors: [ + { + errorCode: { + offlineUserDataJobError: 'INVALID_SHA256_FORMAT', + }, + message: 'The SHA256 encoded value is malformed.', + location: { + fieldPathElements: [ + { + fieldName: 'operations', + index: 0, + }, + { + fieldName: 'create', + }, + { + fieldName: 'user_identifiers', + index: 0, + }, + { + fieldName: 'hashed_email', + }, + ], + }, + }, + ], + requestId: '68697987', + }, + ], + message: 'Request contains an invalid argument.', + status: 'INVALID_ARGUMENT', + }, + }, + statTags: expectedStatTags, + }, + }, + }, + }, + }, + { + id: 'gaoc_v0_scenario_2', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v0 API] :: Test for a valid operations request with a successful 200 response from the destination', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + headers: headers.header1, + params: params.param1, + JSON: validRequestPayload1, + endpoint: 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: + '[Google Ads Offline Conversions Response Handler] - Request processed successfully', + destinationResponse: { + response: { + name: 'customers/111-222-3333/operations/abcd=', + }, + status: 200, + }, + }, + }, + }, + }, + }, + { + id: 'gaoc_v0_scenario_3', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v0 API] :: Test for a valid request with a successful 200 response from the destination', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + headers: headers.header2, + params: params.param2, + JSON: validRequestPayload2, + endpoint: + 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: + '[Google Ads Offline Conversions Response Handler] - Request processed successfully', + destinationResponse: { + response: [ + { + adjustmentType: 'ENHANCEMENT', + conversionAction: 'customers/1234567891/conversionActions/874224905', + adjustmentDateTime: '2021-01-01 12:32:45-08:00', + gclidDateTimePair: { + gclid: '1234', + conversionDateTime: '2021-01-01 12:32:45-08:00', + }, + orderId: '12345', + }, + ], + status: 200, + }, + }, + }, + }, + }, + }, + { + id: 'gaoc_v0_scenario_4', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v0 API] :: Test for a valid conversion action request with a successful 200 response from the destination', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + headers: headers.header2, + params: params.param3, + JSON: validRequestPayload2, + endpoint: + 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + destinationResponse: { + response: [ + { + adjustmentDateTime: '2021-01-01 12:32:45-08:00', + adjustmentType: 'ENHANCEMENT', + conversionAction: 'customers/1234567891/conversionActions/874224905', + gclidDateTimePair: { + conversionDateTime: '2021-01-01 12:32:45-08:00', + gclid: '1234', + }, + orderId: '12345', + }, + ], + status: 200, + }, + message: + '[Google Ads Offline Conversions Response Handler] - Request processed successfully', + status: 200, + }, + }, + }, + }, + }, +]; + +export const testScenariosForV1API = [ + { + id: 'gaoc_v1_scenario_1', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v1 API] :: Test for invalid argument - where the destination responds with 400 with invalid argument error', + successCriteria: 'Should return 400 with error with destination response', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: headers.header1, + params: params.param1, + JSON: invalidArgumentRequestPayload, + endpoint: + 'https://googleads.googleapis.com/v14/customers/11122233331/offlineUserDataJobs', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + '[Google Ads Offline Conversions]:: Request contains an invalid argument. during google_ads_offline_store_conversions Add Conversion', + response: [ + { + error: + '[Google Ads Offline Conversions]:: Request contains an invalid argument. during google_ads_offline_store_conversions Add Conversion', + metadata: generateMetadata(1), + statusCode: 400, + }, + ], + statTags: expectedStatTags, + status: 400, + }, + }, + }, + }, + }, + { + id: 'gaoc_v1_scenario_2', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v1 API] :: Test for a valid operations request with a successful 200 response from the destination', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: headers.header1, + params: params.param1, + JSON: validRequestPayload1, + endpoint: + 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + '[Google Ads Offline Conversions Response Handler] - Request processed successfully', + response: [ + { + error: '{"name":"customers/111-222-3333/operations/abcd="}', + metadata: generateMetadata(1), + statusCode: 200, + }, + ], + status: 200, + }, + }, + }, + }, + }, + { + id: 'gaoc_v1_scenario_3', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v1 API] :: Test for a valid request with a successful 200 response from the destination', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: headers.header2, + params: params.param2, + JSON: validRequestPayload2, + endpoint: + 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + '[Google Ads Offline Conversions Response Handler] - Request processed successfully', + response: [ + { + error: + '[{"adjustmentType":"ENHANCEMENT","conversionAction":"customers/1234567891/conversionActions/874224905","adjustmentDateTime":"2021-01-01 12:32:45-08:00","gclidDateTimePair":{"gclid":"1234","conversionDateTime":"2021-01-01 12:32:45-08:00"},"orderId":"12345"}]', + metadata: generateMetadata(1), + statusCode: 200, + }, + ], + status: 200, + }, + }, + }, + }, + }, + { + id: 'gaoc_v1_scenario_4', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v1 API] :: Test for a valid conversion action request with a successful 200 response from the destination', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: headers.header2, + params: params.param3, + JSON: validRequestPayload2, + endpoint: + 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + '[Google Ads Offline Conversions Response Handler] - Request processed successfully', + response: [ + { + error: + '[{"adjustmentType":"ENHANCEMENT","conversionAction":"customers/1234567891/conversionActions/874224905","adjustmentDateTime":"2021-01-01 12:32:45-08:00","gclidDateTimePair":{"gclid":"1234","conversionDateTime":"2021-01-01 12:32:45-08:00"},"orderId":"12345"}]', + metadata: generateMetadata(1), + statusCode: 200, + }, + ], + status: 200, + }, + }, + }, + }, + }, + { + id: 'gaoc_v1_scenario_5', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v1 API] :: Test for customer is not allowed Test for a valid request with a successful 200 response from the destinationto access feature partial failure error', + successCriteria: 'Should return 400 with error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: headers.header2, + params: params.param4, + JSON: notAllowedToAccessFeatureRequestPayload, + endpoint: + 'https://googleads.googleapis.com/v14/customers/1234567893:uploadClickConversions', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + '[Google Ads Offline Conversions]:: partialFailureError - Customer is not allowlisted for accessing this feature., at conversions[0].conversion_environment', + response: [ + { + error: + '[Google Ads Offline Conversions]:: partialFailureError - Customer is not allowlisted for accessing this feature., at conversions[0].conversion_environment', + metadata: generateMetadata(1), + statusCode: 400, + }, + ], + statTags: expectedStatTags, + status: 400, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts index ae75273399..709ab6d2a8 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/data.ts @@ -1,768 +1,9 @@ +import { v0oauthScenarios, v1oauthScenarios } from './oauth'; +import { testScenariosForV0API, testScenariosForV1API } from './business'; + export const data = [ - { - name: 'google_adwords_offline_conversions', - description: 'Test 0', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://googleads.googleapis.com/v14/customers/11122233331/offlineUserDataJobs', - headers: { - Authorization: 'Bearer abcd1234', - 'Content-Type': 'application/json', - 'developer-token': 'ijkl91011', - 'login-customer-id': 'logincustomerid', - }, - params: { - customerId: '1112223333', - event: 'Sign-up - click', - }, - body: { - JSON: { - addConversionPayload: { - enable_partial_failure: false, - enable_warnings: false, - operations: [ - { - create: { - transaction_attribute: { - CUSTOM_KEY: 'CUSTOM_VALUE', - currency_code: 'INR', - order_id: 'order id', - store_attribute: { - store_code: 'store code', - }, - transaction_amount_micros: '100000000', - transaction_date_time: '2019-10-14 11:15:18+00:00', - }, - userIdentifiers: [ - { - hashedEmail: - '6db61e6dcbcf2390e4a46af26f26a133a3bee45021422fc7ae86e9136f14110', - userIdentifierSource: 'UNSPECIFIED', - }, - ], - }, - }, - ], - validate_only: false, - }, - createJobPayload: { - job: { - storeSalesMetadata: { - custom_key: 'CUSTOM_KEY', - loyaltyFraction: 1, - transaction_upload_fraction: '1', - }, - type: 'STORE_SALES_UPLOAD_FIRST_PARTY', - }, - }, - event: '1112223333', - executeJobPayload: { - validate_only: false, - }, - isStoreConversion: true, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - status: 400, - message: - '[Google Ads Offline Conversions]:: Request contains an invalid argument. during google_ads_offline_store_conversions Add Conversion', - destinationResponse: { - error: { - code: 400, - details: [ - { - '@type': 'type.googleapis.com/google.ads.googleads.v14.errors.GoogleAdsFailure', - errors: [ - { - errorCode: { - offlineUserDataJobError: 'INVALID_SHA256_FORMAT', - }, - message: 'The SHA256 encoded value is malformed.', - location: { - fieldPathElements: [ - { - fieldName: 'operations', - index: 0, - }, - { - fieldName: 'create', - }, - { - fieldName: 'user_identifiers', - index: 0, - }, - { - fieldName: 'hashed_email', - }, - ], - }, - }, - ], - requestId: '68697987', - }, - ], - message: 'Request contains an invalid argument.', - status: 'INVALID_ARGUMENT', - }, - }, - statTags: { - destType: 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS', - destinationId: 'Non-determininable', - errorCategory: 'network', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'Non-determininable', - }, - }, - }, - }, - }, - }, - { - name: 'google_adwords_offline_conversions', - description: 'Test 1', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://googleads.googleapis.com/v14/customers/1112223333/offlineUserDataJobs', - headers: { - Authorization: 'Bearer abcd1234', - 'Content-Type': 'application/json', - 'developer-token': 'ijkl91011', - 'login-customer-id': 'logincustomerid', - }, - params: { - customerId: '1112223333', - event: 'Sign-up - click', - }, - body: { - JSON: { - addConversionPayload: { - enable_partial_failure: false, - enable_warnings: false, - operations: [ - { - create: { - transaction_attribute: { - CUSTOM_KEY: 'CUSTOM_VALUE', - currency_code: 'INR', - order_id: 'order id', - store_attribute: { - store_code: 'store code', - }, - transaction_amount_micros: '100000000', - transaction_date_time: '2019-10-14 11:15:18+00:00', - }, - userIdentifiers: [ - { - hashedEmail: - '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', - userIdentifierSource: 'UNSPECIFIED', - }, - ], - }, - }, - ], - validate_only: false, - }, - createJobPayload: { - job: { - storeSalesMetadata: { - custom_key: 'CUSTOM_KEY', - loyaltyFraction: 1, - transaction_upload_fraction: '1', - }, - type: 'STORE_SALES_UPLOAD_FIRST_PARTY', - }, - }, - event: '1112223333', - executeJobPayload: { - validate_only: false, - }, - isStoreConversion: true, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: - '[Google Ads Offline Conversions Response Handler] - Request processed successfully', - destinationResponse: { - response: { - name: 'customers/111-222-3333/operations/abcd=', - }, - status: 200, - }, - }, - }, - }, - }, - }, - { - name: 'google_adwords_offline_conversions', - description: 'Test 2', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://googleads.googleapis.com/v14/customers/customerid/offlineUserDataJobs', - headers: { - Authorization: 'Bearer abcd1234', - 'Content-Type': 'application/json', - 'developer-token': 'ijkl91011', - 'login-customer-id': 'logincustomerid', - }, - params: { - customerId: '1112223333', - event: 'Sign-up - click', - }, - body: { - JSON: { - addConversionPayload: { - enable_partial_failure: false, - enable_warnings: false, - operations: [ - { - create: { - transaction_attribute: { - CUSTOM_KEY: 'CUSTOM_VALUE', - currency_code: 'INR', - order_id: 'order id', - store_attribute: { - store_code: 'store code', - }, - transaction_amount_micros: '100000000', - transaction_date_time: '2019-10-14 11:15:18+00:00', - }, - userIdentifiers: [ - { - hashedEmail: - '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', - userIdentifierSource: 'UNSPECIFIED', - }, - ], - }, - }, - ], - validate_only: false, - }, - createJobPayload: { - job: { - storeSalesMetadata: { - custom_key: 'CUSTOM_KEY', - loyaltyFraction: 1, - transaction_upload_fraction: '1', - }, - type: 'STORE_SALES_UPLOAD_FIRST_PARTY', - }, - }, - event: '1112223333', - executeJobPayload: { - validate_only: false, - }, - isStoreConversion: true, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - }, - }, - output: { - response: { - status: 401, - body: { - output: { - status: 401, - message: - '[Google Ads Offline Conversions]:: Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project. during google_ads_offline_store_conversions Job Creation', - authErrorCategory: 'REFRESH_TOKEN', - destinationResponse: { - error: { - code: 401, - message: - 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.', - status: 'UNAUTHENTICATED', - }, - }, - statTags: { - destType: 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS', - destinationId: 'Non-determininable', - errorCategory: 'network', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'Non-determininable', - }, - }, - }, - }, - }, - }, - { - name: 'google_adwords_offline_conversions', - description: 'Test 3', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://googleads.googleapis.com/v14/customers/1234567890:uploadClickConversions', - headers: { - Authorization: 'Bearer abcd1234', - 'Content-Type': 'application/json', - 'developer-token': 'ijkl91011', - }, - params: { - event: 'Sign-up - click', - customerId: '1234567890', - customVariables: [ - { - from: 'value', - to: 'revenue', - }, - { - from: 'total', - to: 'cost', - }, - ], - properties: { - gbraid: 'gbraid', - wbraid: 'wbraid', - externalAttributionCredit: 10, - externalAttributionModel: 'externalAttributionModel', - conversionCustomVariable: 'conversionCustomVariable', - value: 'value', - merchantId: '9876merchantId', - feedCountryCode: 'feedCountryCode', - feedLanguageCode: 'feedLanguageCode', - localTransactionCost: 20, - products: [ - { - product_id: '507f1f77bcf86cd799439011', - quantity: '2', - price: '50', - sku: '45790-32', - name: 'Monopoly: 3rd Edition', - position: '1', - category: 'cars', - url: 'https://www.example.com/product/path', - image_url: 'https://www.example.com/product/path.jpg', - }, - ], - userIdentifierSource: 'FIRST_PARTY', - conversionEnvironment: 'WEB', - gclid: 'gclid', - conversionDateTime: '2022-01-01 12:32:45-08:00', - conversionValue: '1', - currency: 'GBP', - orderId: 'PL-123QR', - }, - }, - body: { - JSON: { - conversions: [ - { - gbraid: 'gbraid', - wbraid: 'wbraid', - externalAttributionData: { - externalAttributionCredit: 10, - externalAttributionModel: 'externalAttributionModel', - }, - cartData: { - merchantId: 9876, - feedCountryCode: 'feedCountryCode', - feedLanguageCode: 'feedLanguageCode', - localTransactionCost: 20, - items: [ - { - productId: '507f1f77bcf86cd799439011', - quantity: 2, - unitPrice: 50, - }, - ], - }, - userIdentifiers: [ - { - userIdentifierSource: 'FIRST_PARTY', - hashedEmail: - '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', - }, - ], - conversionEnvironment: 'WEB', - gclid: 'gclid', - conversionDateTime: '2022-01-01 12:32:45-08:00', - conversionValue: 1, - currencyCode: 'GBP', - orderId: 'PL-123QR', - }, - ], - partialFailure: true, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - }, - }, - output: { - response: { - status: 401, - body: { - output: { - status: 401, - message: - '[Google Ads Offline Conversions]:: [{"error":{"code":401,"message":"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.","status":"UNAUTHENTICATED"}}] during google_ads_offline_conversions response transformation', - authErrorCategory: 'REFRESH_TOKEN', - destinationResponse: [ - { - error: { - code: 401, - message: - 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.', - status: 'UNAUTHENTICATED', - }, - }, - ], - statTags: { - destType: 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS', - destinationId: 'Non-determininable', - errorCategory: 'network', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'Non-determininable', - }, - }, - }, - }, - }, - }, - { - name: 'google_adwords_offline_conversions', - description: 'Test 4', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', - headers: { - Authorization: 'Bearer abcd1234', - 'Content-Type': 'application/json', - 'developer-token': 'ijkl91011', - }, - params: { - event: 'Sign-up - click', - customerId: '1234567891', - customVariables: [ - { - from: 'Value', - to: 'revenue', - }, - { - from: 'total', - to: 'cost', - }, - ], - properties: { - gbraid: 'gbraid', - wbraid: 'wbraid', - externalAttributionCredit: 10, - externalAttributionModel: 'externalAttributionModel', - conversionCustomVariable: 'conversionCustomVariable', - Value: 'value', - merchantId: '9876merchantId', - feedCountryCode: 'feedCountryCode', - feedLanguageCode: 'feedLanguageCode', - localTransactionCost: 20, - products: [ - { - product_id: '507f1f77bcf86cd799439011', - quantity: '2', - price: '50', - sku: '45790-32', - name: 'Monopoly: 3rd Edition', - position: '1', - category: 'cars', - url: 'https://www.example.com/product/path', - image_url: 'https://www.example.com/product/path.jpg', - }, - ], - userIdentifierSource: 'FIRST_PARTY', - conversionEnvironment: 'WEB', - gclid: 'gclid', - conversionDateTime: '2022-01-01 12:32:45-08:00', - conversionValue: '1', - currency: 'GBP', - orderId: 'PL-123QR', - }, - }, - body: { - JSON: { - conversions: [ - { - gbraid: 'gbraid', - wbraid: 'wbraid', - externalAttributionData: { - externalAttributionCredit: 10, - externalAttributionModel: 'externalAttributionModel', - }, - cartData: { - merchantId: 9876, - feedCountryCode: 'feedCountryCode', - feedLanguageCode: 'feedLanguageCode', - localTransactionCost: 20, - items: [ - { - productId: '507f1f77bcf86cd799439011', - quantity: 2, - unitPrice: 50, - }, - ], - }, - userIdentifiers: [ - { - userIdentifierSource: 'FIRST_PARTY', - hashedPhoneNumber: - '04e1dabb7c1348b72bfa87da179c9697c69af74827649266a5da8cdbb367abcd', - }, - ], - conversionEnvironment: 'WEB', - gclid: 'gclid', - conversionDateTime: '2022-01-01 12:32:45-08:00', - conversionValue: 1, - currencyCode: 'GBP', - orderId: 'PL-123QR', - }, - ], - partialFailure: true, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: - '[Google Ads Offline Conversions Response Handler] - Request processed successfully', - destinationResponse: { - response: [ - { - adjustmentType: 'ENHANCEMENT', - conversionAction: 'customers/1234567891/conversionActions/874224905', - adjustmentDateTime: '2021-01-01 12:32:45-08:00', - gclidDateTimePair: { - gclid: '1234', - conversionDateTime: '2021-01-01 12:32:45-08:00', - }, - orderId: '12345', - }, - ], - status: 200, - }, - }, - }, - }, - }, - }, - { - name: 'google_adwords_offline_conversions', - description: 'Test 5', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://googleads.googleapis.com/v14/customers/1234567891:uploadClickConversions', - headers: { - Authorization: 'Bearer abcd1234', - 'Content-Type': 'application/json', - 'developer-token': 'ijkl91011', - }, - params: { - event: 'Sign-up - click', - customerId: '1234567891', - customVariables: [], - properties: { - gbraid: 'gbraid', - wbraid: 'wbraid', - externalAttributionCredit: 10, - externalAttributionModel: 'externalAttributionModel', - conversionCustomVariable: 'conversionCustomVariable', - value: 'value', - merchantId: '9876merchantId', - feedCountryCode: 'feedCountryCode', - feedLanguageCode: 'feedLanguageCode', - localTransactionCost: 20, - products: [ - { - product_id: '507f1f77bcf86cd799439011', - quantity: '2', - price: '50', - sku: '45790-32', - name: 'Monopoly: 3rd Edition', - position: '1', - category: 'cars', - url: 'https://www.example.com/product/path', - image_url: 'https://www.example.com/product/path.jpg', - }, - ], - userIdentifierSource: 'FIRST_PARTY', - conversionEnvironment: 'WEB', - gclid: 'gclid', - conversionDateTime: '2022-01-01 12:32:45-08:00', - conversionValue: '1', - currency: 'GBP', - orderId: 'PL-123QR', - }, - }, - body: { - JSON: { - conversions: [ - { - gbraid: 'gbraid', - wbraid: 'wbraid', - externalAttributionData: { - externalAttributionCredit: 10, - externalAttributionModel: 'externalAttributionModel', - }, - cartData: { - merchantId: 9876, - feedCountryCode: 'feedCountryCode', - feedLanguageCode: 'feedLanguageCode', - localTransactionCost: 20, - items: [ - { - productId: '507f1f77bcf86cd799439011', - quantity: 2, - unitPrice: 50, - }, - ], - }, - userIdentifiers: [ - { - userIdentifierSource: 'FIRST_PARTY', - hashedPhoneNumber: - '04e1dabb7c1348b72bfa87da179c9697c69af74827649266a5da8cdbb367abcd', - }, - ], - conversionEnvironment: 'WEB', - gclid: 'gclid', - conversionDateTime: '2022-01-01 12:32:45-08:00', - conversionValue: 1, - currencyCode: 'GBP', - orderId: 'PL-123QR', - }, - ], - partialFailure: true, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - }, - }, - output: { - response: { - status: 200, - body: { - output: { - destinationResponse: { - response: [ - { - adjustmentDateTime: '2021-01-01 12:32:45-08:00', - adjustmentType: 'ENHANCEMENT', - conversionAction: 'customers/1234567891/conversionActions/874224905', - gclidDateTimePair: { - conversionDateTime: '2021-01-01 12:32:45-08:00', - gclid: '1234', - }, - orderId: '12345', - }, - ], - status: 200, - }, - message: - '[Google Ads Offline Conversions Response Handler] - Request processed successfully', - status: 200, - }, - }, - }, - }, - }, + ...v0oauthScenarios, + ...v1oauthScenarios, + ...testScenariosForV0API, + ...testScenariosForV1API, ]; diff --git a/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/oauth.ts b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/oauth.ts new file mode 100644 index 0000000000..15a150d0e5 --- /dev/null +++ b/test/integrations/destinations/google_adwords_offline_conversions/dataDelivery/oauth.ts @@ -0,0 +1,263 @@ +import { + generateMetadata, + generateProxyV1Payload, + generateProxyV0Payload, +} from '../../../testUtils'; + +const commonHeaders = { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + 'login-customer-id': 'logincustomerid', +}; + +const commonParams = { + customerId: '1112223333', + event: 'Sign-up - click', +}; + +const commonRequestPayload = { + addConversionPayload: { + enable_partial_failure: false, + enable_warnings: false, + operations: [ + { + create: { + transaction_attribute: { + CUSTOM_KEY: 'CUSTOM_VALUE', + currency_code: 'INR', + order_id: 'order id', + store_attribute: { + store_code: 'store code', + }, + transaction_amount_micros: '100000000', + transaction_date_time: '2019-10-14 11:15:18+00:00', + }, + userIdentifiers: [ + { + hashedEmail: '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', + userIdentifierSource: 'UNSPECIFIED', + }, + ], + }, + }, + ], + validate_only: false, + }, + createJobPayload: { + job: { + storeSalesMetadata: { + custom_key: 'CUSTOM_KEY', + loyaltyFraction: 1, + transaction_upload_fraction: '1', + }, + type: 'STORE_SALES_UPLOAD_FIRST_PARTY', + }, + }, + event: '1112223333', + executeJobPayload: { + validate_only: false, + }, + isStoreConversion: true, +}; + +const commonRequestParameters = { + headers: commonHeaders, + params: commonParams, + JSON: commonRequestPayload, +}; + +const metadataArray = [generateMetadata(1)]; + +const expectedStatTags = { + destType: 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', +}; + +export const v0oauthScenarios = [ + { + id: 'gaoc_v0_oauth_scenario_1', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v0 API] :: Oauth where valid credentials are missing as mock response from destination', + successCriteria: 'The proxy should return 401 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://googleads.googleapis.com/v14/customers/customerid/offlineUserDataJobs', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + status: 401, + message: + '[Google Ads Offline Conversions]:: Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project. during google_ads_offline_store_conversions Job Creation', + authErrorCategory: 'REFRESH_TOKEN', + destinationResponse: { + error: { + code: 401, + message: + 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.', + status: 'UNAUTHENTICATED', + }, + }, + statTags: expectedStatTags, + }, + }, + }, + }, + }, + { + id: 'gaoc_v0_oauth_scenario_2', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v0 API] :: Oauth where ACCESS_TOKEN_SCOPE_INSUFFICIENT error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 403 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://googleads.googleapis.com/v14/customers/1234/offlineUserDataJobs', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 403, + body: { + output: { + authErrorCategory: 'AUTH_STATUS_INACTIVE', + destinationResponse: { + error: { + code: 403, + message: 'Request had insufficient authentication scopes', + status: 'PERMISSION_DENIED', + }, + }, + message: + '[Google Ads Offline Conversions]:: Request had insufficient authentication scopes during google_ads_offline_store_conversions Job Creation', + statTags: expectedStatTags, + status: 403, + }, + }, + }, + }, + }, +]; + +export const v1oauthScenarios = [ + { + id: 'gaoc_v1_oauth_scenario_1', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v1 API] :: Oauth where valid credentials are missing as mock response from destination', + successCriteria: 'The proxy should return 401 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: + 'https://googleads.googleapis.com/v14/customers/customerid/offlineUserDataJobs', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + authErrorCategory: 'REFRESH_TOKEN', + message: + '[Google Ads Offline Conversions]:: Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project. during google_ads_offline_store_conversions Job Creation', + response: [ + { + error: + '[Google Ads Offline Conversions]:: Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project. during google_ads_offline_store_conversions Job Creation', + metadata: generateMetadata(1), + statusCode: 401, + }, + ], + statTags: expectedStatTags, + status: 401, + }, + }, + }, + }, + }, + { + id: 'gaoc_v1_oauth_scenario_2', + name: 'google_adwords_offline_conversions', + description: + '[Proxy v1 API] :: Oauth where ACCESS_TOKEN_SCOPE_INSUFFICIENT error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 403 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://googleads.googleapis.com/v14/customers/1234/offlineUserDataJobs', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 403, + body: { + output: { + authErrorCategory: 'AUTH_STATUS_INACTIVE', + message: + '[Google Ads Offline Conversions]:: Request had insufficient authentication scopes during google_ads_offline_store_conversions Job Creation', + response: [ + { + error: + '[Google Ads Offline Conversions]:: Request had insufficient authentication scopes during google_ads_offline_store_conversions Job Creation', + metadata: generateMetadata(1), + statusCode: 403, + }, + ], + statTags: expectedStatTags, + status: 403, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/google_adwords_offline_conversions/network.ts b/test/integrations/destinations/google_adwords_offline_conversions/network.ts index 375879727b..7dc7f97933 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/network.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/network.ts @@ -235,6 +235,8 @@ export const networkCallsData = [ }, }, { + description: + 'Mock response from destination depicting a request with invalid authentication credentials', httpReq: { url: 'https://googleads.googleapis.com/v14/customers/customerid/offlineUserDataJobs:create', data: { @@ -268,6 +270,41 @@ export const networkCallsData = [ }, }, }, + { + description: + 'Mock response from destination depicting a request with invalid authentication scopes', + httpReq: { + url: 'https://googleads.googleapis.com/v14/customers/1234/offlineUserDataJobs:create', + data: { + job: { + storeSalesMetadata: { + custom_key: 'CUSTOM_KEY', + loyaltyFraction: 1, + transaction_upload_fraction: '1', + }, + type: 'STORE_SALES_UPLOAD_FIRST_PARTY', + }, + }, + params: { destination: 'google_adwords_offline_conversion' }, + headers: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + 'login-customer-id': 'logincustomerid', + }, + method: 'POST', + }, + httpRes: { + status: 403, + data: { + error: { + code: 403, + message: 'Request had insufficient authentication scopes', + status: 'PERMISSION_DENIED', + }, + }, + }, + }, { httpReq: { url: 'https://googleads.googleapis.com/v14/customers/1234567890/googleAds:searchStream', @@ -491,4 +528,121 @@ export const networkCallsData = [ status: 200, }, }, + { + httpReq: { + url: 'https://googleads.googleapis.com/v14/customers/1234567893/googleAds:searchStream', + data: { + query: + "SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = 'Sign-up - click'", + }, + headers: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + }, + method: 'POST', + params: { destination: 'google_adwords_offline_conversion' }, + }, + httpRes: { + data: [ + { + results: [ + { + conversionAction: { + resourceName: 'customers/1234567893/conversionActions/848898417', + id: '848898417', + }, + }, + ], + fieldMask: 'conversionAction.id', + requestId: 'dummyRequestId', + }, + ], + status: 200, + }, + }, + { + httpReq: { + url: 'https://googleads.googleapis.com/v14/customers/1234567893:uploadClickConversions', + data: { + conversions: [ + { + gbraid: 'gbraid', + wbraid: 'wbraid', + externalAttributionData: { + externalAttributionCredit: 10, + externalAttributionModel: 'externalAttributionModel', + }, + cartData: { + merchantId: 9876, + feedCountryCode: 'feedCountryCode', + feedLanguageCode: 'feedLanguageCode', + localTransactionCost: 20, + items: [{ productId: '507f1f77bcf86cd799439011', quantity: 2, unitPrice: 50 }], + }, + userIdentifiers: [ + { + userIdentifierSource: 'FIRST_PARTY', + hashedPhoneNumber: + '04e1dabb7c1348b72bfa87da179c9697c69af74827649266a5da8cdbb367abcd', + }, + ], + conversionEnvironment: 'APP', + gclid: 'gclid', + conversionDateTime: '2022-01-01 12:32:45-08:00', + conversionValue: 1, + currencyCode: 'GBP', + orderId: 'PL-123QR', + conversionAction: 'customers/1234567893/conversionActions/848898417', + }, + ], + partialFailure: true, + }, + headers: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + }, + method: 'POST', + params: { destination: 'google_adwords_offline_conversion' }, + }, + httpRes: { + status: 200, + data: { + partialFailureError: { + code: 3, + message: + 'Customer is not allowlisted for accessing this feature., at conversions[0].conversion_environment', + details: [ + { + '@type': 'type.googleapis.com/google.ads.googleads.v14.errors.GoogleAdsFailure', + errors: [ + { + errorCode: { + notAllowlistedError: 'CUSTOMER_NOT_ALLOWLISTED_FOR_THIS_FEATURE', + }, + message: 'Customer is not allowlisted for accessing this feature.', + trigger: { + int64Value: '2', + }, + location: { + fieldPathElements: [ + { + fieldName: 'conversions', + index: 0, + }, + { + fieldName: 'conversion_environment', + }, + ], + }, + }, + ], + requestId: 'dummyRequestId', + }, + ], + }, + }, + }, + }, ]; diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/business.ts b/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/business.ts new file mode 100644 index 0000000000..2aefb18fdd --- /dev/null +++ b/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/business.ts @@ -0,0 +1,376 @@ +import { + generateMetadata, + generateProxyV0Payload, + generateProxyV1Payload, +} from '../../../testUtils'; + +const commonHeaders = { + Authorization: 'Bearer dummy-access', + 'Content-Type': 'application/json', + 'developer-token': 'dummy-dev-token', +}; + +const commonParams = { + destination: 'google_adwords_remarketing_lists', + listId: '709078448', + customerId: '7693729833', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, +}; + +const validRequestPayload1 = { + enablePartialFailure: true, + operations: [ + { + create: { + userIdentifiers: [ + { + hashedEmail: '85cc9fefa1eff1baab55d10df0cecff2acb25344867a5d0f96e1b1c5e2f10f05', + }, + { + hashedPhoneNumber: '8846dcb6ab2d73a0e67dbd569fa17cec2d9d391e5b05d1dd42919bc21ae82c45', + }, + { + hashedEmail: '85cc9fefa1eff1baab55d10df0cecff2acb25344867a5d0f96e1b1c5e2f10f05', + }, + { + hashedPhoneNumber: '8846dcb6ab2d73a0e67dbd569fa17cec2d9d391e5b05d1dd42919bc21ae82c45', + }, + { + addressInfo: { + hashedFirstName: 'e56d336922eaab3be8c1244dbaa713e134a8eba50ddbd4f50fd2fe18d72595cd', + }, + }, + ], + }, + }, + ], +}; + +const validRequestPayload2 = { + enablePartialFailure: true, + operations: [ + { + remove: { + userIdentifiers: [ + { + hashedEmail: '85cc9fefa1eff1baab55d10df0cecff2acb25344867a5d0f96e1b1c5e2f10f05', + }, + { + hashedPhoneNumber: '8846dcb6ab2d73a0e67dbd569fa17cec2d9d391e5b05d1dd42919bc21ae82c45', + }, + { + hashedEmail: '85cc9fefa1eff1baab55d10df0cecff2acb25344867a5d0f96e1b1c5e2f10f05', + }, + { + hashedPhoneNumber: '8846dcb6ab2d73a0e67dbd569fa17cec2d9d391e5b05d1dd42919bc21ae82c45', + }, + { + addressInfo: { + hashedFirstName: 'e56d336922eaab3be8c1244dbaa713e134a8eba50ddbd4f50fd2fe18d72595cd', + }, + }, + ], + }, + }, + ], +}; + +const invalidArgumentRequestPayload = { + enablePartialFailure: true, + operations: [ + { + create: { + userIdentifiers: [ + { + hashedEmail: 'abcd@testmail.com', + }, + ], + }, + }, + ], +}; + +const metadataArray = [generateMetadata(1)]; + +const expectedStatTags = { + destType: 'GOOGLE_ADWORDS_REMARKETING_LISTS', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', +}; + +export const testScenariosForV0API = [ + { + id: 'garl_v0_scenario_1', + name: 'google_adwords_remarketing_lists', + description: + '[Proxy v0 API] :: Test for a valid request with a successful 200 response from the destination', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + headers: commonHeaders, + params: commonParams, + JSON: validRequestPayload1, + endpoint: 'https://googleads.googleapis.com/v15/customers/7693729833/offlineUserDataJobs', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + destinationResponse: { response: '', status: 200 }, + }, + }, + }, + }, + }, + { + id: 'garl_v0_scenario_2', + name: 'google_adwords_remarketing_lists', + description: + '[Proxy v0 API] :: Test for a invalid argument request with a 400 response from the destination', + successCriteria: 'Should return 400 with invalid argument error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + headers: commonHeaders, + params: commonParams, + JSON: invalidArgumentRequestPayload, + endpoint: 'https://googleads.googleapis.com/v15/customers/7693729834/offlineUserDataJobs', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 400, + body: { + output: { + status: 400, + message: + 'Request contains an invalid argument. during ga_audience response transformation', + destinationResponse: { + error: { + code: 400, + details: [ + { + '@type': 'type.googleapis.com/google.ads.googleads.v9.errors.GoogleAdsFailure', + errors: [ + { + errorCode: { + offlineUserDataJobError: 'INVALID_SHA256_FORMAT', + }, + message: 'The SHA256 encoded value is malformed.', + location: { + fieldPathElements: [ + { fieldName: 'operations', index: 0 }, + { fieldName: 'remove' }, + { fieldName: 'user_identifiers', index: 0 }, + { fieldName: 'hashed_email' }, + ], + }, + }, + ], + }, + ], + message: 'Request contains an invalid argument.', + status: 'INVALID_ARGUMENT', + }, + }, + statTags: expectedStatTags, + }, + }, + }, + }, + }, + { + id: 'garl_v0_scenario_3', + name: 'google_adwords_remarketing_lists', + description: + '[Proxy v0 API] :: Test for a valid request with a successful 200 response from the destination', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + headers: commonHeaders, + params: commonParams, + JSON: validRequestPayload2, + endpoint: 'https://googleads.googleapis.com/v15/customers/7693729833/offlineUserDataJobs', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + destinationResponse: { response: '', status: 200 }, + }, + }, + }, + }, + }, +]; + +export const testScenariosForV1API = [ + { + id: 'garl_v1_scenario_1', + name: 'google_adwords_remarketing_lists', + description: + '[Proxy v1 API] :: Test for a valid request with a successful 200 response from the destination', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: commonHeaders, + params: commonParams, + JSON: validRequestPayload1, + endpoint: + 'https://googleads.googleapis.com/v15/customers/7693729833/offlineUserDataJobs', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: generateMetadata(1), + statusCode: 200, + }, + ], + status: 200, + }, + }, + }, + }, + }, + { + id: 'garl_v1_scenario_2', + name: 'google_adwords_remarketing_lists', + description: + '[Proxy v1 API] :: Test for a invalid argument request with a 400 response from the destination', + successCriteria: 'Should return 400 with invalid argument error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: commonHeaders, + params: commonParams, + JSON: invalidArgumentRequestPayload, + endpoint: + 'https://googleads.googleapis.com/v15/customers/7693729834/offlineUserDataJobs', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + 'Request contains an invalid argument. during ga_audience response transformation', + response: [ + { + error: + 'Request contains an invalid argument. during ga_audience response transformation', + metadata: generateMetadata(1), + statusCode: 400, + }, + ], + statTags: expectedStatTags, + status: 400, + }, + }, + }, + }, + }, + { + id: 'garl_v1_scenario_3', + name: 'google_adwords_remarketing_lists', + description: + '[Proxy v1 API] :: Test for a valid request with a successful 200 response from the destination', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: commonHeaders, + params: commonParams, + JSON: validRequestPayload2, + endpoint: + 'https://googleads.googleapis.com/v15/customers/7693729833/offlineUserDataJobs', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: generateMetadata(1), + statusCode: 200, + }, + ], + status: 200, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts b/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts index fe16ffef47..51827a38e2 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts @@ -1,261 +1,3 @@ -export const data = [ - { - name: 'google_adwords_remarketing_lists', - description: 'Test 0', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://googleads.googleapis.com/v15/customers/7693729833/offlineUserDataJobs', - headers: { - Authorization: 'Bearer dummy-access', - 'Content-Type': 'application/json', - 'developer-token': 'dummy-dev-token', - }, - params: { - destination: 'google_adwords_remarketing_lists', - listId: '709078448', - customerId: '7693729833', - consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, - }, - body: { - JSON: { - enablePartialFailure: true, - operations: [ - { - create: { - userIdentifiers: [ - { - hashedEmail: - '85cc9fefa1eff1baab55d10df0cecff2acb25344867a5d0f96e1b1c5e2f10f05', - }, - { - hashedPhoneNumber: - '8846dcb6ab2d73a0e67dbd569fa17cec2d9d391e5b05d1dd42919bc21ae82c45', - }, - { - hashedEmail: - '85cc9fefa1eff1baab55d10df0cecff2acb25344867a5d0f96e1b1c5e2f10f05', - }, - { - hashedPhoneNumber: - '8846dcb6ab2d73a0e67dbd569fa17cec2d9d391e5b05d1dd42919bc21ae82c45', - }, - { - addressInfo: { - hashedFirstName: - 'e56d336922eaab3be8c1244dbaa713e134a8eba50ddbd4f50fd2fe18d72595cd', - }, - }, - ], - }, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: 'Request Processed Successfully', - destinationResponse: { response: '', status: 200 }, - }, - }, - }, - }, - }, - { - name: 'google_adwords_remarketing_lists', - description: 'Test 1', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://googleads.googleapis.com/v15/customers/7693729834/offlineUserDataJobs', - headers: { - Authorization: 'Bearer dummy-access', - 'Content-Type': 'application/json', - 'developer-token': 'dummy-dev-token', - }, - params: { - listId: '709078448', - customerId: '7693729833', - destination: 'google_adwords_remarketing_lists', - consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, - }, - body: { - JSON: { - enablePartialFailure: true, - operations: [ - { - create: { - userIdentifiers: [ - { - hashedEmail: 'abcd@testmail.com', - }, - ], - }, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - status: 400, - message: - 'Request contains an invalid argument. during ga_audience response transformation', - destinationResponse: { - error: { - code: 400, - details: [ - { - '@type': 'type.googleapis.com/google.ads.googleads.v9.errors.GoogleAdsFailure', - errors: [ - { - errorCode: { - offlineUserDataJobError: 'INVALID_SHA256_FORMAT', - }, - message: 'The SHA256 encoded value is malformed.', - location: { - fieldPathElements: [ - { fieldName: 'operations', index: 0 }, - { fieldName: 'remove' }, - { fieldName: 'user_identifiers', index: 0 }, - { fieldName: 'hashed_email' }, - ], - }, - }, - ], - }, - ], - message: 'Request contains an invalid argument.', - status: 'INVALID_ARGUMENT', - }, - }, - statTags: { - destType: 'GOOGLE_ADWORDS_REMARKETING_LISTS', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, - }, - }, - }, - }, - }, - { - name: 'google_adwords_remarketing_lists', - description: 'Test 2', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://googleads.googleapis.com/v15/customers/7693729833/offlineUserDataJobs', - headers: { - Authorization: 'Bearer dummy-access', - 'Content-Type': 'application/json', - 'developer-token': 'dummy-dev-token', - }, - params: { - listId: '709078448', - customerId: '7693729833', - destination: 'google_adwords_remarketing_lists', - consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, - }, - body: { - JSON: { - enablePartialFailure: true, - operations: [ - { - remove: { - userIdentifiers: [ - { - hashedEmail: - '85cc9fefa1eff1baab55d10df0cecff2acb25344867a5d0f96e1b1c5e2f10f05', - }, - { - hashedPhoneNumber: - '8846dcb6ab2d73a0e67dbd569fa17cec2d9d391e5b05d1dd42919bc21ae82c45', - }, - { - hashedEmail: - '85cc9fefa1eff1baab55d10df0cecff2acb25344867a5d0f96e1b1c5e2f10f05', - }, - { - hashedPhoneNumber: - '8846dcb6ab2d73a0e67dbd569fa17cec2d9d391e5b05d1dd42919bc21ae82c45', - }, - { - addressInfo: { - hashedFirstName: - 'e56d336922eaab3be8c1244dbaa713e134a8eba50ddbd4f50fd2fe18d72595cd', - }, - }, - ], - }, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: 'Request Processed Successfully', - destinationResponse: { response: '', status: 200 }, - }, - }, - }, - }, - }, -]; +import { testScenariosForV0API, testScenariosForV1API } from './business'; + +export const data = [...testScenariosForV0API, ...testScenariosForV1API]; diff --git a/test/integrations/destinations/intercom/dataDelivery/business.ts b/test/integrations/destinations/intercom/dataDelivery/business.ts new file mode 100644 index 0000000000..2490041832 --- /dev/null +++ b/test/integrations/destinations/intercom/dataDelivery/business.ts @@ -0,0 +1,615 @@ +import { + generateMetadata, + generateProxyV0Payload, + generateProxyV1Payload, +} from '../../../testUtils'; + +const commonHeaders = { + 'Content-Type': 'application/json', + Authorization: 'Bearer testApiKey', + Accept: 'application/json', + 'Intercom-Version': '1.4', +}; + +const unauthorizedResponseHeaders = { + 'Content-Type': 'application/json', + Authorization: 'Bearer invalidApiKey', + Accept: 'application/json', + 'Intercom-Version': '1.4', +}; + +const createUserPayload = { + email: 'test_1@test.com', + phone: '9876543210', + name: 'Test Name', + signed_up_at: 1601493060, + last_seen_user_agent: 'unknown', + update_last_request_at: true, + user_id: 'test_user_id_1', + custom_attributes: { + 'address.city': 'Kolkata', + 'address.state': 'West Bengal', + }, +}; + +const conflictUserPayload = { + email: 'test_1@test.com', + name: 'Rudder Labs', + signed_up_at: 1601496060, + last_seen_user_agent: 'unknown', + update_last_request_at: true, + user_id: 'test_user_id_2', + custom_attributes: { + 'address.city': 'Kolkata', + 'address.state': 'West Bengal', + }, +}; + +const updateUserPayload = { + email: 'test_1@test.com', + phone: '9876543211', + name: 'Sample Name', + signed_up_at: 1601493060, + update_last_request_at: true, + user_id: 'test_user_id_1', + custom_attributes: { + 'address.city': 'Kolkata', + 'address.state': 'West Bengal', + }, +}; + +const createCompanyPayload = { + company_id: 'rudderlabs', + name: 'RudderStack', + website: 'www.rudderstack.com', + plan: 'enterprise', + size: 500, + industry: 'CDP', + custom_attributes: { isOpenSource: true }, +}; + +const sendMessagePayload = { + from: { + type: 'user', + id: 'id@1', + }, + body: 'heyy, how are you', + referer: 'https://twitter.com/bob', +}; + +const createUserRequestParameters = { + JSON: createUserPayload, + headers: commonHeaders, +}; + +const updateUserRequestParameters = { + JSON: updateUserPayload, + headers: commonHeaders, +}; + +const createCompanyRequestParameters = { + JSON: createCompanyPayload, + headers: commonHeaders, +}; + +const sendMessageRequestParameters = { + JSON: sendMessagePayload, + headers: commonHeaders, +}; + +const metadataArray = [generateMetadata(1)]; + +export const testScenariosForV0API = [ + { + id: 'intercom_v0_other_scenario_1', + name: 'intercom', + description: + '[Proxy v0 API] :: Scenario to test Invalid Credentials Handling during Destination Authentication', + successCriteria: 'Should return 401 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...createUserRequestParameters, + headers: unauthorizedResponseHeaders, + endpoint: 'https://api.intercom.io/users/test1', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + destinationResponse: { + errors: [ + { + code: 'unauthorized', + message: 'Access Token Invalid', + }, + ], + request_id: 'request123', + type: 'error.list', + }, + message: 'Request Processed Successfully', + status: 401, + }, + }, + }, + }, + }, + { + id: 'intercom_v0_other_scenario_2', + name: 'intercom', + description: + '[Proxy v0 API] :: Scenario to test Malformed Payload Response Handling from Destination', + successCriteria: 'Should return 401 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...createCompanyRequestParameters, + endpoint: 'https://api.eu.intercom.io/companies', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + destinationResponse: { + errors: [ + { + code: 'parameter_invalid', + message: "Custom attribute 'isOpenSource' does not exist", + }, + ], + request_id: 'request_1', + type: 'error.list', + }, + message: 'Request Processed Successfully', + status: 401, + }, + }, + }, + }, + }, + { + id: 'intercom_v0_other_scenario_3', + name: 'intercom', + description: + '[Proxy v0 API] :: Scenario to test Plan-Restricted Response Handling from Destination', + successCriteria: 'Should return 403 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...sendMessageRequestParameters, + endpoint: 'https://api.intercom.io/messages', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 403, + body: { + output: { + destinationResponse: { + errors: [ + { + code: 'api_plan_restricted', + message: 'Active subscription needed.', + }, + ], + request_id: 'request124', + type: 'error.list', + }, + message: 'Request Processed Successfully', + status: 403, + }, + }, + }, + }, + }, + { + id: 'intercom_v0_other_scenario_4', + name: 'intercom', + description: '[Proxy v0 API] :: Scenario to test Rate Limit Exceeded Handling from Destination', + successCriteria: 'Should return 429 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...updateUserRequestParameters, + endpoint: 'https://api.intercom.io/users/test1', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 429, + body: { + output: { + destinationResponse: { + errors: [ + { + code: 'rate_limit_exceeded', + message: 'The rate limit for the App has been exceeded', + }, + ], + request_id: 'request125', + type: 'error.list', + }, + message: 'Request Processed Successfully', + status: 429, + }, + }, + }, + }, + }, + { + id: 'intercom_v0_other_scenario_5', + name: 'intercom', + description: '[Proxy v0 API] :: Scenario to test Conflict User Handling from Destination', + successCriteria: 'Should return 409 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + JSON: conflictUserPayload, + headers: { + ...commonHeaders, + 'Intercom-Version': '2.10', + }, + endpoint: 'https://api.intercom.io/contacts', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 409, + body: { + output: { + destinationResponse: { + errors: [ + { + code: 'conflict', + message: 'A contact matching those details already exists with id=test1', + }, + ], + request_id: 'request126', + type: 'error.list', + }, + message: 'Request Processed Successfully', + status: 409, + }, + }, + }, + }, + }, + { + id: 'intercom_v0_other_scenario_6', + name: 'intercom', + description: '[Proxy v0 API] :: Scenario to test Unsupported Media Handling from Destination', + successCriteria: 'Should return 406 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + JSON: createUserPayload, + headers: { + ...commonHeaders, + 'Intercom-Version': '2.10', + }, + endpoint: 'https://api.intercom.io/users', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 406, + body: { + output: { + destinationResponse: { + errors: [ + { + code: 'media_type_not_acceptable', + message: 'The Accept header should send a media type of application/json', + }, + ], + type: 'error.list', + }, + message: 'Request Processed Successfully', + status: 406, + }, + }, + }, + }, + }, +]; + +export const testScenariosForV1API = [ + { + id: 'intercom_v1_other_scenario_1', + name: 'intercom', + description: + '[Proxy v1 API] :: Scenario to test Invalid Credentials Handling during Destination Authentication', + successCriteria: 'Should return 401 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...createUserRequestParameters, + headers: unauthorizedResponseHeaders, + endpoint: 'https://api.intercom.io/users/test1', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + error: + '{"type":"error.list","request_id":"request123","errors":[{"code":"unauthorized","message":"Access Token Invalid"}]}', + metadata: generateMetadata(1), + statusCode: 401, + }, + ], + status: 401, + }, + }, + }, + }, + }, + { + id: 'intercom_v1_other_scenario_2', + name: 'intercom', + description: + '[Proxy v1 API] :: Scenario to test Malformed Payload Response Handling from Destination', + successCriteria: 'Should return 401 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...createCompanyRequestParameters, + endpoint: 'https://api.eu.intercom.io/companies', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + error: + '{"type":"error.list","request_id":"request_1","errors":[{"code":"parameter_invalid","message":"Custom attribute \'isOpenSource\' does not exist"}]}', + metadata: generateMetadata(1), + statusCode: 401, + }, + ], + status: 401, + }, + }, + }, + }, + }, + { + id: 'intercom_v1_other_scenario_3', + name: 'intercom', + description: + '[Proxy v1 API] :: Scenario to test Plan-Restricted Response Handling from Destination', + successCriteria: 'Should return 403 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...sendMessageRequestParameters, + endpoint: 'https://api.intercom.io/messages', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + error: + '{"type":"error.list","request_id":"request124","errors":[{"code":"api_plan_restricted","message":"Active subscription needed."}]}', + metadata: generateMetadata(1), + statusCode: 403, + }, + ], + status: 403, + }, + }, + }, + }, + }, + { + id: 'intercom_v1_other_scenario_4', + name: 'intercom', + description: '[Proxy v1 API] :: Scenario to test Rate Limit Exceeded Handling from Destination', + successCriteria: 'Should return 429 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...updateUserRequestParameters, + endpoint: 'https://api.intercom.io/users/test1', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + error: + '{"type":"error.list","request_id":"request125","errors":[{"code":"rate_limit_exceeded","message":"The rate limit for the App has been exceeded"}]}', + metadata: generateMetadata(1), + statusCode: 429, + }, + ], + status: 429, + }, + }, + }, + }, + }, + { + id: 'intercom_v1_other_scenario_5', + name: 'intercom', + description: '[Proxy v1 API] :: Scenario to test Conflict User Handling from Destination', + successCriteria: 'Should return 409 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: conflictUserPayload, + headers: { + ...commonHeaders, + 'Intercom-Version': '2.10', + }, + endpoint: 'https://api.intercom.io/contacts', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + error: + '{"type":"error.list","request_id":"request126","errors":[{"code":"conflict","message":"A contact matching those details already exists with id=test1"}]}', + metadata: generateMetadata(1), + statusCode: 409, + }, + ], + status: 409, + }, + }, + }, + }, + }, + { + id: 'intercom_v1_other_scenario_6', + name: 'intercom', + description: '[Proxy v1 API] :: Scenario to test Unsupported Media Handling from Destination', + successCriteria: 'Should return 406 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: createUserPayload, + headers: { + ...commonHeaders, + 'Intercom-Version': '2.10', + }, + endpoint: 'https://api.intercom.io/users', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + error: + '{"errors":[{"code":"media_type_not_acceptable","message":"The Accept header should send a media type of application/json"}],"type":"error.list"}', + metadata: generateMetadata(1), + statusCode: 406, + }, + ], + status: 406, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/intercom/dataDelivery/data.ts b/test/integrations/destinations/intercom/dataDelivery/data.ts index db7aafc963..e5d2f4696b 100644 --- a/test/integrations/destinations/intercom/dataDelivery/data.ts +++ b/test/integrations/destinations/intercom/dataDelivery/data.ts @@ -1,91 +1,9 @@ +import { otherScenariosV0, otherScenariosV1 } from './other'; +import { testScenariosForV0API, testScenariosForV1API } from './business'; + export const data = [ - { - name: 'intercom', - description: 'Test 0', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://api.intercom.io/users/test1', - headers: { - 'Content-Type': 'application/json', - Authorization: 'Bearer intercomApiKey', - Accept: 'application/json', - 'Intercom-Version': '1.4', - }, - params: {}, - body: { - JSON: { - email: 'test_1@test.com', - phone: '9876543210', - name: 'Test Name', - signed_up_at: 1601493060, - last_seen_user_agent: 'unknown', - update_last_request_at: true, - user_id: 'test_user_id_1', - custom_attributes: { - anonymousId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', - key1: 'value1', - 'address.city': 'Kolkata', - 'address.state': 'West Bengal', - 'originalArray[0].nested_field': 'nested value', - 'originalArray[0].tags[0]': 'tag_1', - 'originalArray[0].tags[1]': 'tag_2', - 'originalArray[0].tags[2]': 'tag_3', - 'originalArray[1].nested_field': 'nested value', - 'originalArray[1].tags[0]': 'tag_1', - 'originalArray[2].nested_field': 'nested value', - }, - }, - XML: {}, - JSON_ARRAY: {}, - FORM: {}, - }, - files: {}, - userId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', - }, - method: 'POST', - }, - }, - output: { - response: { - status: 500, - body: { - output: { - status: 500, - message: - '[Intercom Response Handler] Request failed for destination intercom with status: 408', - destinationResponse: { - response: { - type: 'error.list', - request_id: '000on04msi4jpk7d3u60', - errors: [ - { - code: 'Request Timeout', - message: 'The server would not wait any longer for the client', - }, - ], - }, - status: 408, - }, - statTags: { - destType: 'INTERCOM', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'retryable', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, - }, - }, - }, - }, - }, + ...otherScenariosV0, + ...otherScenariosV1, + ...testScenariosForV0API, + ...testScenariosForV1API, ]; diff --git a/test/integrations/destinations/intercom/dataDelivery/other.ts b/test/integrations/destinations/intercom/dataDelivery/other.ts new file mode 100644 index 0000000000..46cdcac468 --- /dev/null +++ b/test/integrations/destinations/intercom/dataDelivery/other.ts @@ -0,0 +1,384 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { + generateMetadata, + generateProxyV0Payload, + generateProxyV1Payload, +} from '../../../testUtils'; + +const commonHeaders = { + 'Content-Type': 'application/json', + Authorization: 'Bearer testApiKey', + Accept: 'application/json', + 'Intercom-Version': '1.4', +}; + +const createUserPayload = { + email: 'test_1@test.com', + phone: '9876543210', + name: 'Test Name', + signed_up_at: 1601493060, + last_seen_user_agent: 'unknown', + update_last_request_at: true, + user_id: 'test_user_id_1', + custom_attributes: { + 'address.city': 'Kolkata', + 'address.state': 'West Bengal', + }, +}; + +const commonRequestParameters = { + JSON: createUserPayload, + headers: commonHeaders, +}; + +const expectedStatTags = { + destType: 'INTERCOM', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'retryable', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', +}; + +const metadataArray = [generateMetadata(1)]; + +export const otherScenariosV0 = [ + { + id: 'intercom_v0_other_scenario_1', + name: 'intercom', + description: '[Proxy v0 API] :: Request Timeout Error Handling from Destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://api.intercom.io/users/test1', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + '[Intercom Response Handler] Request failed for destination intercom with status: 408', + destinationResponse: { + response: { + type: 'error.list', + request_id: '000on04msi4jpk7d3u60', + errors: [ + { + code: 'Request Timeout', + message: 'The server would not wait any longer for the client', + }, + ], + }, + status: 408, + }, + statTags: expectedStatTags, + }, + }, + }, + }, + }, + { + id: 'intercom_v0_other_scenario_2', + name: 'intercom', + description: + '[Proxy v0 API] :: Scenario for testing Service Unavailable error from destination', + successCriteria: 'Should return 503 status code with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://api.intercom.io/users/test2', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 503, + body: { + output: { + status: 503, + message: 'Request Processed Successfully', + destinationResponse: { + type: 'error.list', + request_id: 'request127', + errors: [ + { + code: 'service_unavailable', + message: 'Sorry, the API service is temporarily unavailable', + }, + ], + }, + }, + }, + }, + }, + }, + { + id: 'intercom_v0_other_scenario_3', + name: 'intercom', + description: '[Proxy v0 API] :: Scenario for testing Internal Server error from destination', + successCriteria: 'Should return 500 status code with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://api.intercom.io/users/test3', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + destinationResponse: { + errors: [ + { + code: 'client_error', + message: 'Unknown server error', + }, + ], + request_id: 'request128', + type: 'error.list', + }, + message: 'Request Processed Successfully', + status: 500, + }, + }, + }, + }, + }, + { + id: 'intercom_v0_other_scenario_4', + name: 'intercom', + description: '[Proxy v0 API] :: Scenario for testing Gateway Time Out error from destination', + successCriteria: 'Should return 504 status code with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://api.intercom.io/users/test4', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 504, + body: { + output: { + destinationResponse: { + errors: [ + { + code: 'server_timeout', + message: 'Server timed out when making request', + }, + ], + request_id: 'request129', + type: 'error.list', + }, + message: 'Request Processed Successfully', + status: 504, + }, + }, + }, + }, + }, +]; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'intercom_v1_other_scenario_1', + name: 'intercom', + description: '[Proxy v1 API] :: Request Timeout Error Handling from Destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://api.intercom.io/users/test1', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + '[Intercom Response Handler] Request failed for destination intercom with status: 408', + response: [ + { + error: + '{"type":"error.list","request_id":"000on04msi4jpk7d3u60","errors":[{"code":"Request Timeout","message":"The server would not wait any longer for the client"}]}', + metadata: generateMetadata(1), + statusCode: 500, + }, + ], + statTags: expectedStatTags, + status: 500, + }, + }, + }, + }, + }, + { + id: 'intercom_v1_other_scenario_2', + name: 'intercom', + description: + '[Proxy v1 API] :: Scenario for testing Service Unavailable error from destination', + successCriteria: 'Should return 503 status code with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://api.intercom.io/users/test2', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + error: + '{"type":"error.list","request_id":"request127","errors":[{"code":"service_unavailable","message":"Sorry, the API service is temporarily unavailable"}]}', + metadata: generateMetadata(1), + statusCode: 503, + }, + ], + status: 503, + }, + }, + }, + }, + }, + { + id: 'intercom_v1_other_scenario_3', + name: 'intercom', + description: '[Proxy v1 API] :: Scenario for testing Internal Server error from destination', + successCriteria: 'Should return 500 status code with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://api.intercom.io/users/test3', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + error: + '{"type":"error.list","request_id":"request128","errors":[{"code":"client_error","message":"Unknown server error"}]}', + metadata: generateMetadata(1), + statusCode: 500, + }, + ], + status: 500, + }, + }, + }, + }, + }, + { + id: 'intercom_v1_other_scenario_4', + name: 'intercom', + description: '[Proxy v1 API] :: Scenario for testing Gateway Time Out error from destination', + successCriteria: 'Should return 504 status code with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://api.intercom.io/users/test4', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + error: + '{"type":"error.list","request_id":"request129","errors":[{"code":"server_timeout","message":"Server timed out when making request"}]}', + metadata: generateMetadata(1), + statusCode: 504, + }, + ], + status: 504, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/intercom/network.ts b/test/integrations/destinations/intercom/network.ts index 74c861259f..5fa8ac6e96 100644 --- a/test/integrations/destinations/intercom/network.ts +++ b/test/integrations/destinations/intercom/network.ts @@ -1,3 +1,48 @@ +const commonHeaders = { + Accept: 'application/json', + Authorization: 'Bearer testApiKey', + 'Content-Type': 'application/json', +}; + +const v0VersionHeaders = { + 'Content-Type': 'application/json', + Authorization: 'Bearer testApiKey', + Accept: 'application/json', + 'Intercom-Version': '1.4', + 'User-Agent': 'RudderLabs', +}; + +const v1VersionHeaders = { + 'Content-Type': 'application/json', + Authorization: 'Bearer testApiKey', + Accept: 'application/json', + 'Intercom-Version': '2.10', + 'User-Agent': 'RudderLabs', +}; + +const userPayload = { + email: 'test_1@test.com', + phone: '9876543210', + name: 'Test Name', + signed_up_at: 1601493060, + last_seen_user_agent: 'unknown', + update_last_request_at: true, + user_id: 'test_user_id_1', + custom_attributes: { + 'address.city': 'Kolkata', + 'address.state': 'West Bengal', + }, +}; + +const companyPayload = { + company_id: 'rudderlabs', + name: 'RudderStack', + website: 'www.rudderstack.com', + plan: 'enterprise', + size: 500, + industry: 'CDP', +}; + const deleteNwData = [ { httpReq: { @@ -6,11 +51,7 @@ const deleteNwData = [ data: { intercom_user_id: '1', }, - headers: { - Accept: 'application/json', - Authorization: 'Bearer testApiKey', - 'Content-Type': 'application/json', - }, + headers: commonHeaders, }, httpRes: { data: { @@ -33,11 +74,7 @@ const deleteNwData = [ data: { intercom_user_id: '12', }, - headers: { - Accept: 'application/json', - Authorization: 'Bearer testApiKey', - 'Content-Type': 'application/json', - }, + headers: commonHeaders, }, httpRes: { status: 200, @@ -54,11 +91,7 @@ const deleteNwData = [ data: { intercom_user_id: '7', }, - headers: { - Accept: 'application/json', - Authorization: 'Bearer testApiKey', - 'Content-Type': 'application/json', - }, + headers: commonHeaders, }, httpRes: { status: 200, @@ -75,11 +108,7 @@ const deleteNwData = [ data: { intercom_user_id: '9', }, - headers: { - Accept: 'application/json', - Authorization: 'Bearer testApiKey', - 'Content-Type': 'application/json', - }, + headers: commonHeaders, }, httpRes: { status: 200, @@ -89,6 +118,8 @@ const deleteNwData = [ }, }, }, +]; +const deliveryCallsData = [ { httpReq: { method: 'post', @@ -99,11 +130,7 @@ const deleteNwData = [ value: [{ field: 'email', operator: '=', value: 'test@rudderlabs.com' }], }, }, - headers: { - Accept: 'application/json', - Authorization: 'Bearer testApiKey', - 'Content-Type': 'application/json', - }, + headers: commonHeaders, }, httpRes: { status: 200, @@ -131,11 +158,7 @@ const deleteNwData = [ value: [{ field: 'email', operator: '=', value: 'test+2@rudderlabs.com' }], }, }, - headers: { - Accept: 'application/json', - Authorization: 'Bearer testApiKey', - 'Content-Type': 'application/json', - }, + headers: commonHeaders, }, httpRes: { status: 200, @@ -172,11 +195,7 @@ const deleteNwData = [ value: [{ field: 'email', operator: '=', value: 'test+5@rudderlabs.com' }], }, }, - headers: { - Accept: 'application/json', - Authorization: 'Bearer testApiKey', - 'Content-Type': 'application/json', - }, + headers: commonHeaders, }, httpRes: { status: 200, @@ -213,11 +232,7 @@ const deleteNwData = [ value: [{ field: 'phone', operator: '=', value: '+91 9299999999' }], }, }, - headers: { - Accept: 'application/json', - Authorization: 'Bearer testApiKey', - 'Content-Type': 'application/json', - }, + headers: commonHeaders, }, httpRes: { status: 200, @@ -254,11 +269,7 @@ const deleteNwData = [ value: [{ field: 'email', operator: '=', value: 'test+4@rudderlabs.com' }], }, }, - headers: { - Accept: 'application/json', - Authorization: 'Bearer testApiKey', - 'Content-Type': 'application/json', - }, + headers: commonHeaders, }, httpRes: { status: 200, @@ -310,19 +321,8 @@ const deleteNwData = [ httpReq: { method: 'post', url: 'https://api.eu.intercom.io/companies', - data: { - company_id: 'rudderlabs', - name: 'RudderStack', - website: 'www.rudderstack.com', - plan: 'enterprise', - size: 500, - industry: 'CDP', - }, - headers: { - Accept: 'application/json', - Authorization: 'Bearer testApiKey', - 'Content-Type': 'application/json', - }, + data: companyPayload, + headers: commonHeaders, }, httpRes: { status: 200, @@ -346,19 +346,10 @@ const deleteNwData = [ method: 'post', url: 'https://api.eu.intercom.io/companies', data: { - company_id: 'rudderlabs', - name: 'RudderStack', - website: 'www.rudderstack.com', - plan: 'enterprise', - size: 500, - industry: 'CDP', + ...companyPayload, custom_attributes: { isOpenSource: true }, }, - headers: { - Accept: 'application/json', - Authorization: 'Bearer testApiKey', - 'Content-Type': 'application/json', - }, + headers: commonHeaders, }, httpRes: { status: 401, @@ -374,55 +365,236 @@ const deleteNwData = [ }, }, }, -]; -const deliveryCallsData = [ + { + httpReq: { + url: 'https://api.intercom.io/users/test1', + data: userPayload, + params: {}, + headers: v0VersionHeaders, + method: 'POST', + }, + httpRes: { + data: { + type: 'error.list', + request_id: '000on04msi4jpk7d3u60', + errors: [ + { + code: 'Request Timeout', + message: 'The server would not wait any longer for the client', + }, + ], + }, + status: 408, + }, + }, + { + httpReq: { + url: 'https://api.intercom.io/users/test1', + data: userPayload, + params: {}, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer invalidApiKey', + Accept: 'application/json', + 'Intercom-Version': '1.4', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: { + type: 'error.list', + request_id: 'request123', + errors: [ + { + code: 'unauthorized', + message: 'Access Token Invalid', + }, + ], + }, + status: 401, + }, + }, + { + httpReq: { + url: 'https://api.intercom.io/messages', + data: { + from: { + type: 'user', + id: 'id@1', + }, + body: 'heyy, how are you', + referer: 'https://twitter.com/bob', + }, + params: {}, + headers: v0VersionHeaders, + method: 'POST', + }, + httpRes: { + data: { + type: 'error.list', + request_id: 'request124', + errors: [ + { + code: 'api_plan_restricted', + message: 'Active subscription needed.', + }, + ], + }, + status: 403, + }, + }, { httpReq: { url: 'https://api.intercom.io/users/test1', data: { email: 'test_1@test.com', - phone: '9876543210', - name: 'Test Name', + phone: '9876543211', + name: 'Sample Name', signed_up_at: 1601493060, - last_seen_user_agent: 'unknown', update_last_request_at: true, user_id: 'test_user_id_1', custom_attributes: { - anonymousId: '58b21c2d-f8d5-4410-a2d0-b268a26b7e33', - key1: 'value1', 'address.city': 'Kolkata', 'address.state': 'West Bengal', - 'originalArray[0].nested_field': 'nested value', - 'originalArray[0].tags[0]': 'tag_1', - 'originalArray[0].tags[1]': 'tag_2', - 'originalArray[0].tags[2]': 'tag_3', - 'originalArray[1].nested_field': 'nested value', - 'originalArray[1].tags[0]': 'tag_1', - 'originalArray[2].nested_field': 'nested value', }, }, params: {}, - headers: { - 'Content-Type': 'application/json', - Authorization: 'Bearer intercomApiKey', - Accept: 'application/json', - 'Intercom-Version': '1.4', - 'User-Agent': 'RudderLabs', + headers: v0VersionHeaders, + method: 'POST', + }, + httpRes: { + data: { + type: 'error.list', + request_id: 'request125', + errors: [ + { + code: 'rate_limit_exceeded', + message: 'The rate limit for the App has been exceeded', + }, + ], }, + status: 429, + }, + }, + { + httpReq: { + url: 'https://api.intercom.io/contacts', + data: { + email: 'test_1@test.com', + name: 'Rudder Labs', + signed_up_at: 1601496060, + last_seen_user_agent: 'unknown', + update_last_request_at: true, + user_id: 'test_user_id_2', + custom_attributes: { + 'address.city': 'Kolkata', + 'address.state': 'West Bengal', + }, + }, + params: {}, + headers: v1VersionHeaders, method: 'POST', }, httpRes: { data: { type: 'error.list', - request_id: '000on04msi4jpk7d3u60', + request_id: 'request126', errors: [ { - code: 'Request Timeout', - message: 'The server would not wait any longer for the client', + code: 'conflict', + message: 'A contact matching those details already exists with id=test1', }, ], }, - status: 408, + status: 409, + }, + }, + { + httpReq: { + url: 'https://api.intercom.io/users', + data: userPayload, + params: {}, + headers: v1VersionHeaders, + method: 'POST', + }, + httpRes: { + data: { + errors: [ + { + code: 'media_type_not_acceptable', + message: 'The Accept header should send a media type of application/json', + }, + ], + type: 'error.list', + }, + status: 406, + }, + }, + { + httpReq: { + url: 'https://api.intercom.io/users/test2', + data: userPayload, + params: {}, + headers: v0VersionHeaders, + method: 'POST', + }, + httpRes: { + data: { + type: 'error.list', + request_id: 'request127', + errors: [ + { + code: 'service_unavailable', + message: 'Sorry, the API service is temporarily unavailable', + }, + ], + }, + status: 503, + }, + }, + { + httpReq: { + url: 'https://api.intercom.io/users/test3', + data: userPayload, + params: {}, + headers: v0VersionHeaders, + method: 'POST', + }, + httpRes: { + data: { + type: 'error.list', + request_id: 'request128', + errors: [ + { + code: 'client_error', + message: 'Unknown server error', + }, + ], + }, + status: 500, + }, + }, + { + httpReq: { + url: 'https://api.intercom.io/users/test4', + data: userPayload, + params: {}, + headers: v0VersionHeaders, + method: 'POST', + }, + httpRes: { + data: { + type: 'error.list', + request_id: 'request129', + errors: [ + { + code: 'server_timeout', + message: 'Server timed out when making request', + }, + ], + }, + status: 504, }, }, ]; diff --git a/test/integrations/destinations/koala/processor/data.ts b/test/integrations/destinations/koala/processor/data.ts new file mode 100644 index 0000000000..b866353066 --- /dev/null +++ b/test/integrations/destinations/koala/processor/data.ts @@ -0,0 +1,323 @@ +export const data = [ + { + name: 'koala', + description: 'Sucessful track event', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + publicKey: 'kkooaallaa321', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + message: { + userId: 'user-uuid', + annonymousId: 'annonymous-uuid', + event: 'User Signed Up', + type: 'track', + messageId: '84e26acc-56a5-4835-8233-591137fca468', + properties: { + email: 'johndoe@somemail.com', + label: 'test', + value: 10, + }, + context: { + network: 'wifi', + }, + originalTimestamp: '2024-01-23T08:35:17.562Z', + sentAt: '2024-01-23T08:35:17.562Z', + request_ip: '192.11.22.33', + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + FORM: {}, + JSON_ARRAY: {}, + XML: {}, + JSON: { + ip: '192.11.22.33', + email: 'johndoe@somemail.com', + events: [ + { + type: 'track', + event: 'User Signed Up', + sent_at: '2024-01-23T08:35:17.562Z', + message_id: '84e26acc-56a5-4835-8233-591137fca468', + properties: { + email: 'johndoe@somemail.com', + label: 'test', + value: 10, + }, + context: { + network: 'wifi', + }, + }, + ], + }, + }, + endpoint: 'https://api2.getkoala.com/web/projects/kkooaallaa321/batch', + files: {}, + params: {}, + type: 'REST', + version: '1', + method: 'POST', + userId: '', + headers: { + 'content-type': 'application/json', + }, + }, + statusCode: 200, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + }, + { + name: 'koala', + description: 'Successful identify event', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + publicKey: 'kkooaallaa321', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + message: { + userId: 'user-uuid', + type: 'identify', + traits: { + FirstName: 'John', + LastName: 'Doe', + address: { + city: 'San Francisco', + state: 'CA', + postalCode: '94107', + }, + email: 'johndoe@somemail.com', + ko_profile_id: 'xxxx-2222-xxxx-xxxx', + }, + originalTimestamp: '2024-01-23T08:35:17.342Z', + sentAt: '2024-01-23T08:35:35.234Z', + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + FORM: {}, + JSON_ARRAY: {}, + XML: {}, + JSON: { + email: 'johndoe@somemail.com', + profile_id: 'xxxx-2222-xxxx-xxxx', + identifies: [ + { + type: 'identify', + sent_at: '2024-01-23T08:35:17.342Z', + traits: { + FirstName: 'John', + LastName: 'Doe', + address: { + city: 'San Francisco', + state: 'CA', + postalCode: '94107', + }, + email: 'johndoe@somemail.com', + }, + }, + ], + }, + }, + endpoint: 'https://api2.getkoala.com/web/projects/kkooaallaa321/batch', + files: {}, + params: {}, + type: 'REST', + version: '1', + method: 'POST', + userId: '', + headers: { + 'content-type': 'application/json', + }, + }, + statusCode: 200, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + }, + { + name: 'koala', + description: 'Missing required email or ko_profile_id fields in traits', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + publicKey: 'kkooaallaa321', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + message: { + userId: 'user-uuid', + type: 'track', + traits: { + name: 'John Doe', + }, + originalTimestamp: '2024-01-23T08:35:17.342Z', + sentAt: '2024-01-23T08:35:35.234Z', + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + statusCode: 400, + error: + 'Neither email or ko_profile_id are present on traits. Aborting message: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: Neither email or ko_profile_id are present on traits. Aborting message', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'KOALA', + module: 'destination', + implementation: 'cdkV2', + destinationId: 'destId', + workspaceId: 'wspId', + feature: 'processor', + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + }, + { + name: 'koala', + description: 'Invalid message type page', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + publicKey: 'kkooaallaa321', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + message: { + userId: 'user-uuid', + type: 'page', + groupId: 'group-uuid', + originalTimestamp: '2024-01-23T08:35:17.342Z', + sentAt: '2024-01-23T08:35:35.234Z', + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + statusCode: 400, + error: + 'message type page is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type page is not supported', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'KOALA', + module: 'destination', + implementation: 'cdkV2', + destinationId: 'destId', + workspaceId: 'wspId', + feature: 'processor', + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/koala/router/data.ts b/test/integrations/destinations/koala/router/data.ts new file mode 100644 index 0000000000..f998f48dc4 --- /dev/null +++ b/test/integrations/destinations/koala/router/data.ts @@ -0,0 +1,204 @@ +export const data = [ + { + name: 'koala', + description: 'Router batch request', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + destination: { + Config: { + publicKey: 'kkooaallaa321', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + message: { + userId: 'user-uuid', + annonymousId: 'annonymous-uuid', + event: 'User Signed Up', + type: 'track', + messageId: '84e26acc-56a5-4835-8233-591137fca468', + traits: { + email: 'johndoe@somemail.com', + }, + properties: { + label: 'test', + value: 10, + }, + context: { + network: 'wifi', + }, + originalTimestamp: '2024-01-23T08:35:17.562Z', + sentAt: '2024-01-23T08:35:17.562Z', + request_ip: '192.11.22.33', + }, + metadata: { + jobId: 1, + userId: 'u1', + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + { + destination: { + Config: { + publicKey: 'kkooaallaa321', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + message: { + userId: 'user-uuid', + annonymousId: 'annonymous-uuid', + event: 'User Deleted account', + type: 'track', + messageId: '8bc79b03-2a5c-4615-b2da-54c0aaaaaae8', + traits: { + ko_profile_id: '123456', + }, + properties: { + attr1: 'foo', + attr2: 'bar', + }, + context: { + network: 'wifi', + }, + originalTimestamp: '2024-01-23T08:35:17.562Z', + sentAt: '2024-01-23T08:35:17.562Z', + request_ip: '192.11.55.1', + }, + metadata: { + jobId: 2, + userId: 'u1', + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + destType: 'koala', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: { + body: { + FORM: {}, + JSON_ARRAY: {}, + XML: {}, + JSON: { + ip: '192.11.22.33', + email: 'johndoe@somemail.com', + events: [ + { + type: 'track', + event: 'User Signed Up', + sent_at: '2024-01-23T08:35:17.562Z', + message_id: '84e26acc-56a5-4835-8233-591137fca468', + properties: { + label: 'test', + value: 10, + }, + context: { + network: 'wifi', + }, + }, + ], + }, + }, + endpoint: 'https://api2.getkoala.com/web/projects/kkooaallaa321/batch', + files: {}, + params: {}, + type: 'REST', + version: '1', + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + }, + batched: false, + metadata: [{ jobId: 1, userId: 'u1', workspaceId: 'wspId', destinationId: 'destId' }], + statusCode: 200, + destination: { + Config: { + publicKey: 'kkooaallaa321', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + }, + { + batchedRequest: { + body: { + FORM: {}, + JSON_ARRAY: {}, + XML: {}, + JSON: { + ip: '192.11.55.1', + profile_id: '123456', + events: [ + { + type: 'track', + event: 'User Deleted account', + sent_at: '2024-01-23T08:35:17.562Z', + message_id: '8bc79b03-2a5c-4615-b2da-54c0aaaaaae8', + properties: { + attr1: 'foo', + attr2: 'bar', + }, + context: { + network: 'wifi', + }, + }, + ], + }, + }, + endpoint: 'https://api2.getkoala.com/web/projects/kkooaallaa321/batch', + files: {}, + params: {}, + type: 'REST', + version: '1', + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + }, + batched: false, + metadata: [{ jobId: 2, userId: 'u1', workspaceId: 'wspId', destinationId: 'destId' }], + statusCode: 200, + destination: { + Config: { + publicKey: 'kkooaallaa321', + }, + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + }, + ], + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/marketo/dataDelivery/business.ts b/test/integrations/destinations/marketo/dataDelivery/business.ts new file mode 100644 index 0000000000..ca4e05afa9 --- /dev/null +++ b/test/integrations/destinations/marketo/dataDelivery/business.ts @@ -0,0 +1,352 @@ +import { ProxyMetdata } from '../../../../../src/types'; +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload } from '../../../testUtils'; + +const statTags = { + aborted: { + destType: 'MARKETO', + destinationId: 'dummyDestinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'dummyWorkspaceId', + }, + retryable: { + destType: 'MARKETO', + destinationId: 'dummyDestinationId', + errorCategory: 'network', + errorType: 'retryable', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'dummyWorkspaceId', + }, + throttled: { + destType: 'MARKETO', + destinationId: 'dummyDestinationId', + errorCategory: 'network', + errorType: 'throttled', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'dummyWorkspaceId', + }, +}; + +export const proxyMetdata: ProxyMetdata = { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, +}; + +export const reqMetadataArray = [proxyMetdata]; +const params = { + destination: 'marketo', +}; + +const commonRequestParameters = { + JSON: { + action: 'createOrUpdate', + input: [ + { + City: 'Tokyo', + Country: 'JP', + Email: 'gabi29@gmail.com', + PostalCode: '100-0001', + Title: 'Owner', + id: 1328328, + userId: 'gabi_userId_45', + }, + ], + lookupField: 'id', + }, + params, +}; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'marketo_v1_scenario_1', + name: 'marketo', + description: '[Proxy v1 API] :: Test for a successful update request', + successCriteria: 'Should return a 200 status code with status updated and record id', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + headers: { + Authorization: 'Bearer test_token_1', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + endpoint: 'https://mktId.mktorest.com/rest/v1/leads.json/test1', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: + '{"requestId":"664#17dae8c3d48","result":[{"id":1328328,"status":"updated"}],"success":true}', + metadata: proxyMetdata, + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'marketo_v1_scenario_2', + name: 'marketo', + description: '[Proxy v1 API] :: Test for Access token invalid scenario', + successCriteria: 'Should return a 500 status code with message Access token invalid', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + headers: { + Authorization: 'Bearer test_token_2', + 'Content-Type': 'application/json', + }, + endpoint: 'https://mktId.mktorest.com/rest/v1/leads.json/test2', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 500, + statTags: statTags.retryable, + message: + 'Request Failed for marketo, Access token invalid (Retryable).during Marketo Response Handling', + response: [ + { + error: + '{"requestId":"a61c#17daea5968a","success":false,"errors":[{"code":"601","message":"Access token invalid"}]}', + metadata: proxyMetdata, + statusCode: 500, + }, + ], + }, + }, + }, + }, + }, + { + id: 'marketo_v1_scenario_3', + name: 'marketo', + description: '[Proxy v1 API] :: Test for Requested resource not found scenario', + successCriteria: 'Should return a 400 status code with message Requested resource not found', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + headers: { + Authorization: 'Bearer test_token_3', + 'Content-Type': 'application/json', + }, + endpoint: 'https://mktId.mktorest.com/rest/v1/leads.json/test3', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + statTags: statTags.aborted, + message: + 'Request Failed for marketo, Requested resource not found (Aborted).during Marketo Response Handling', + response: [ + { + error: + '{"requestId":"a61c#17daea5968a","success":false,"errors":[{"code":"610","message":"Requested resource not found"}]}', + metadata: proxyMetdata, + statusCode: 400, + }, + ], + }, + }, + }, + }, + }, + { + id: 'marketo_v1_scenario_4', + name: 'marketo', + description: '[Proxy v1 API] :: Test for Unknown error with empty response', + successCriteria: 'Should return a 500 status code with empty response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + headers: { + Authorization: 'Bearer test_token_4', + 'Content-Type': 'application/json', + }, + endpoint: 'https://mktId.mktorest.com/rest/v1/leads.json/test4', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 500, + statTags: statTags.retryable, + message: 'Request failed with status: 500', + response: [ + { + error: '""', + metadata: proxyMetdata, + statusCode: 500, + }, + ], + }, + }, + }, + }, + }, + { + id: 'marketo_v1_scenario_5', + name: 'marketo', + description: '[Proxy v1 API] :: Test for missing content type header scenario', + successCriteria: 'Should return a 612 status code with Invalid Content Type ', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: { + Authorization: 'Bearer test_token_6', + 'Content-Type': 'invalid', + 'User-Agent': 'RudderLabs', + }, + endpoint: 'https://mktId.mktorest.com/rest/v1/leads.json/test_invalid_header', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + statTags: statTags.aborted, + message: + 'Request Failed for marketo, Invalid Content Type (Aborted).during Marketo Response Handling', + response: [ + { + error: + '{"success":false,"errors":[{"code":"612","message":"Invalid Content Type"}]}', + metadata: proxyMetdata, + statusCode: 400, + }, + ], + }, + }, + }, + }, + }, + { + id: 'marketo_v1_scenario_6', + name: 'marketo', + description: '[Proxy v1 API] :: Test for a passed field exceeding max length', + successCriteria: 'Should return a 1077 status code with Value for field exceeds max length', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: { + Authorization: 'Bearer test_token_6', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + endpoint: 'https://mktId.mktorest.com/rest/v1/leads.json/test_exceeded_length', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + statTags: statTags.aborted, + message: 'Request failed with status: 400', + response: [ + { + error: + '{"success":false,"errors":[{"code":"1077","message":"Value for field exceeds max length"}]}', + metadata: proxyMetdata, + statusCode: 400, + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/marketo/dataDelivery/data.ts b/test/integrations/destinations/marketo/dataDelivery/data.ts index 47dd8e9236..db379c9e95 100644 --- a/test/integrations/destinations/marketo/dataDelivery/data.ts +++ b/test/integrations/destinations/marketo/dataDelivery/data.ts @@ -1,4 +1,7 @@ -export const data = [ +import { testScenariosForV1API } from './business'; +import { otheMarketoScenariosV1 } from './other'; + +const legacyTests = [ { name: 'marketo', description: 'Test 0', @@ -488,3 +491,5 @@ export const data = [ }, }, ]; + +export const data = [...legacyTests, ...testScenariosForV1API, ...otheMarketoScenariosV1]; diff --git a/test/integrations/destinations/marketo/dataDelivery/other.ts b/test/integrations/destinations/marketo/dataDelivery/other.ts new file mode 100644 index 0000000000..5d4e3b1f17 --- /dev/null +++ b/test/integrations/destinations/marketo/dataDelivery/other.ts @@ -0,0 +1,266 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload } from '../../../testUtils'; + +const statTags = { + aborted: { + destType: 'MARKETO', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', + }, + retryable: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'MARKETO', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, +}; + +const metadata = { + jobId: 1, + secret: { + accessToken: 'default-accessToken', + }, + attemptNum: 1, + userId: 'default-userId', + sourceId: 'default-sourceId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + dontBatch: false, +}; + +export const otheMarketoScenariosV1: ProxyV1TestData[] = [ + { + id: 'marketo_v1_other_scenario_1', + name: 'marketo', + description: + '[Proxy v1 API] :: Scenario for testing Service Unavailable error from destination', + successCriteria: 'Should return 503 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, + }, + ], + statTags: statTags.retryable, + message: 'Request failed with status: 503', + status: 503, + }, + }, + }, + }, + }, + { + id: 'marketo_v1_other_scenario_2', + name: 'marketo', + 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, + }, + ], + statTags: statTags.retryable, + message: 'Request failed with status: 500', + status: 500, + }, + }, + }, + }, + }, + { + id: 'marketo_v1_other_scenario_3', + name: 'marketo', + 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, + }, + ], + statTags: statTags.retryable, + message: 'Request failed with status: 504', + status: 504, + }, + }, + }, + }, + }, + { + id: 'marketo_v1_other_scenario_4', + name: 'marketo', + description: '[Proxy v1 API] :: Scenario for testing null response from destination', + successCriteria: 'Should return 500 status code with empty 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, + }, + ], + statTags: statTags.retryable, + message: 'Request failed with status: 500', + status: 500, + }, + }, + }, + }, + }, + { + id: 'marketo_v1_other_scenario_5', + name: 'marketo', + description: + '[Proxy v1 API] :: Scenario for testing null and no status response from destination', + successCriteria: 'Should return 500 status code with empty 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, + }, + ], + statTags: statTags.retryable, + message: 'Request failed with status: 500', + status: 500, + }, + }, + }, + }, + }, + { + id: 'marketo_v1_scenario_6', + name: 'marketo', + description: '[Proxy v1 API] :: Test for DNS lookup failed scenario', + successCriteria: 'Should return a 400 status code with empty response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/dns_lookup_failure', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + statTags: statTags.aborted, + message: 'Request failed with status: 400', + response: [ + { + error: '{}', + metadata, + statusCode: 400, + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/marketo/network.ts b/test/integrations/destinations/marketo/network.ts index 9c28a9aef1..1606e78c51 100644 --- a/test/integrations/destinations/marketo/network.ts +++ b/test/integrations/destinations/marketo/network.ts @@ -1,20 +1,26 @@ -export const networkCallsData = [ +const userObject = { + City: 'Tokyo', + Country: 'JP', + Email: 'gabi29@gmail.com', + PostalCode: '100-0001', + Title: 'Owner', + id: 1328328, + userId: 'gabi_userId_45', +}; + +const headerObject = { + Authorization: 'Bearer test_token_2', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', +}; + +const tfProxyMocksData = [ { httpReq: { url: 'https://mktId.mktorest.com/rest/v1/leads.json/test1', data: { action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - ], + input: [userObject], lookupField: 'id', }, headers: { @@ -44,24 +50,10 @@ export const networkCallsData = [ url: 'https://mktId.mktorest.com/rest/v1/leads.json/test2', data: { action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - ], + input: [userObject], lookupField: 'id', }, - headers: { - Authorization: 'Bearer test_token_2', - 'Content-Type': 'application/json', - 'User-Agent': 'RudderLabs', - }, + headers: headerObject, method: 'POST', }, httpRes: { @@ -84,17 +76,7 @@ export const networkCallsData = [ url: 'https://mktId.mktorest.com/rest/v1/leads.json/test3', data: { action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - ], + input: [userObject], lookupField: 'id', }, headers: { @@ -124,17 +106,7 @@ export const networkCallsData = [ url: 'https://mktId.mktorest.com/rest/v1/leads.json/test4', data: { action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - ], + input: [userObject], lookupField: 'id', }, headers: { @@ -151,17 +123,7 @@ export const networkCallsData = [ url: 'https://mktId.mktorest.com/rest/v1/leads.json/test5', data: { action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - ], + input: [userObject], lookupField: 'id', }, headers: { @@ -178,17 +140,7 @@ export const networkCallsData = [ url: 'https://mktId.mktorest.com/rest/v1/leads.json/test6', data: { action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - ], + input: [userObject], lookupField: 'id', }, headers: { @@ -970,3 +922,1007 @@ export const networkCallsData = [ }, }, ]; + +const businessMockData = [ + { + description: 'Mock response for a successful update request', + httpReq: { + url: 'https://mktId.mktorest.com/rest/v1/leads.json/test1', + data: { + action: 'createOrUpdate', + input: [userObject], + lookupField: 'id', + }, + headers: { + Authorization: 'Bearer test_token_1', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: { + requestId: '664#17dae8c3d48', + result: [ + { + id: 1328328, + status: 'updated', + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed update request due to invalid access token', + httpReq: { + url: 'https://mktId.mktorest.com/rest/v1/leads.json/test2', + data: { + action: 'createOrUpdate', + input: [userObject], + lookupField: 'id', + }, + headers: headerObject, + method: 'POST', + }, + httpRes: { + data: { + requestId: 'a61c#17daea5968a', + success: false, + errors: [ + { + code: '601', + message: 'Access token invalid', + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed update request due to requested resource not found', + httpReq: { + url: 'https://mktId.mktorest.com/rest/v1/leads.json/test3', + data: { + action: 'createOrUpdate', + input: [userObject], + lookupField: 'id', + }, + headers: { + Authorization: 'Bearer test_token_3', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: { + requestId: 'a61c#17daea5968a', + success: false, + errors: [ + { + code: '610', + message: 'Requested resource not found', + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful create/update request', + httpReq: { + url: 'https://mktId.mktorest.com/rest/v1/leads.json/test4', + data: { + action: 'createOrUpdate', + input: [userObject], + lookupField: 'id', + }, + headers: { + Authorization: 'Bearer test_token_4', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: {}, + }, + { + description: 'Mock response for a successful create/update request', + httpReq: { + url: 'https://mktId.mktorest.com/rest/v1/leads.json/test5', + data: { + action: 'createOrUpdate', + input: [userObject], + lookupField: 'id', + }, + headers: { + Authorization: 'Bearer test_token_5', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: '', + }, + { + description: 'Mock response for a failed create/update request due to DNS lookup failure', + httpReq: { + url: 'https://mktId.mktorest.com/rest/v1/leads.json/test6', + data: { + action: 'createOrUpdate', + input: [userObject], + lookupField: 'id', + }, + headers: { + Authorization: 'Bearer test_token_6', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + code: '[ENOTFOUND] :: DNS lookup failed', + status: 400, + }, + }, + { + description: + 'Mock response for a failed create/update request due to unhandled exception in proxy request', + httpReq: { + url: 'https://unhandled_exception_in_proxy_req.mktorest.com/rest/v1/leads.json', + data: { + action: 'createOrUpdate', + input: [userObject], + lookupField: 'id', + }, + headers: { + Authorization: 'Bearer access_token_success', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: { + requestId: '142e4#1835b117b76', + success: false, + errors: [ + { + code: 'random_marketo_code', + message: 'problem', + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful access token request', + httpReq: { + url: 'https://marketo_acct_id_success.mktorest.com/identity/oauth/token', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'access_token_success', + expires_in: 3599, + scope: 'integrations@rudderstack.com', + token_type: 'bearer', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed access token request due to expired token', + httpReq: { + url: 'https://marketo_acct_id_token_failure.mktorest.com/identity/oauth/token', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'access_token_expired', + expires_in: 0, + scope: 'integrations@rudderstack.com', + token_type: 'bearer', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful get request', + httpReq: { + url: 'https://marketo_acct_id_success.mktorest.com/rest/v1/leads.json', + method: 'get', + }, + httpRes: { + data: { + requestId: '7ab2#17672a46a99', + result: [ + { + id: 4, + status: 'created', + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful get request with filterType=email with no results', + httpReq: { + url: 'https://marketo_acct_id_success.mktorest.com/rest/v1/leads.json?filterType=email&filterValues=arnab.compsc%40gmail.com', + method: 'GET', + }, + httpRes: { + data: { + requestId: '107#17672aeadba', + result: [], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful get request with filterType=userId with results', + httpReq: { + url: 'https://marketo_acct_id_success.mktorest.com/rest/v1/leads.json?filterType=userId&filterValues=test-user-6j55yr', + method: 'GET', + }, + httpRes: { + data: { + requestId: '12093#17672aeaee6', + result: [ + { + createdAt: '2020-12-17T21:39:07Z', + email: null, + firstName: null, + id: 4, + lastName: null, + updatedAt: '2020-12-17T21:39:07Z', + userId: 'test-user-6j55yr', + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed access token request due to expired token', + httpReq: { + url: 'https://marketo_acct_id_failed.mktorest.com/identity/oauth/token', + method: 'GET', + }, + httpRes: { + data: { + success: false, + errors: [ + { + code: '601', + message: 'Access Token Expired', + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful access token request', + httpReq: { + url: 'https://munchkinId.mktorest.com/identity/oauth/token?client_id=b&client_secret=clientSecret&grant_type=client_credentials', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'test_acess', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed access token request due to invalid client id', + httpReq: { + url: 'https://munchkinId.mktorest.com/identity/oauth/token?client_id=wrongClientId&client_secret=clientSecret&grant_type=client_credentials', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'test_access_token', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful get request to get list of fields', + httpReq: { + url: 'https://munchkinId.mktorest.com/rest/v1/leads/describe2.json', + method: 'GET', + }, + httpRes: { + data: { + requestId: '7fa1#17fd1da66fe', + result: [ + { + name: 'API Lead', + searchableFields: [['email']], + fields: [ + { + name: 'email', + displayName: 'Email Address', + dataType: 'email', + length: 255, + updateable: true, + crmManaged: false, + }, + ], + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful bulk request with queued status', + httpReq: { + url: 'https://munchkinId.mktorest.com/bulk/v1/leads.json', + method: 'GET', + }, + httpRes: { + data: { + requestId: '5bdd#17fd1ff88cd', + result: [ + { + batchId: 2977, + importId: '2977', + status: 'Queued', + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for bulk request', + httpReq: { + url: 'https://a.mktorest.com/identity/oauth/token?client_id=b&client_secret=c&grant_type=client_credentials', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'test_access_token', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for bulk request for throttling error', + httpReq: { + url: 'https://a.mktorest.com/identity/oauth/token?client_id=b&client_secret=forThrottle&grant_type=client_credentials', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'test_access_token', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful get request to get list of fields', + httpReq: { + url: 'https://a.mktorest.com/rest/v1/leads/describe2.json', + method: 'GET', + }, + httpRes: { + data: { + requestId: '7fa1#17fd1da66fe', + result: [ + { + name: 'API Lead', + searchableFields: [['email']], + fields: [ + { + name: 'email', + displayName: 'Email Address', + dataType: 'email', + length: 255, + updateable: true, + crmManaged: false, + }, + ], + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a succesful oauth token request', + httpReq: { + url: 'https://testMunchkin4.mktorest.com/identity/oauth/token?client_id=b&client_secret=c&grant_type=client_credentials', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'test_access_token', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed bulk request with 400 error', + httpReq: { + url: 'https://testMunchkin4.mktorest.com/bulk/v1/leads/batch/1234.json', + method: 'GET', + }, + httpRes: { + data: { + errors: [ + { + message: 'Any 400 error', + code: 1000, + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful oauth token request', + httpReq: { + url: 'https://testMunchkin3.mktorest.com/identity/oauth/token?client_id=b&client_secret=c&grant_type=client_credentials', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'test_access_token', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful oauth token request', + httpReq: { + url: 'https://testMunchkin500.mktorest.com/identity/oauth/token?client_id=b&client_secret=c&grant_type=client_credentials', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'test_access_token', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed bulk request with 500 error', + httpReq: { + url: 'https://testMunchkin500.mktorest.com/bulk/v1/leads/batch/1234.json', + method: 'GET', + }, + httpRes: { + data: { + errors: [ + { + message: 'Any 500 error', + code: 502, + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful bulk request with warnings', + httpReq: { + url: 'https://a.mktorest.com/bulk/v1/leads/batch/12345/warnings.json', + method: 'GET', + }, + httpRes: { + data: 'data \n data', + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful bulk request with failures', + httpReq: { + url: 'https://a.mktorest.com/bulk/v1/leads/batch/12345/failures.json', + method: 'GET', + }, + httpRes: { + data: 'data \n data', + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful bearer token request', + httpReq: { + url: 'https://testMunchkin1.mktorest.com/identity/oauth/token?client_id=b&client_secret=c&grant_type=client_credentials', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'access_token_success', + expires_in: 3599, + scope: 'integrations@rudderstack.com', + token_type: 'bearer', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful get request to get list of fields', + httpReq: { + url: 'https://testMunchkin1.mktorest.com/rest/v1/leads/describe2.json', + method: 'GET', + }, + httpRes: { + data: { + requestId: '7fa1#17fd1da66fe', + result: [ + { + name: 'API Lead', + searchableFields: [['email']], + fields: [ + { + name: 'email', + displayName: 'Email Address', + dataType: 'email', + length: 255, + updateable: true, + crmManaged: false, + }, + ], + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed bulk request with 603 error code', + httpReq: { + url: 'https://testMunchkin1.mktorest.com/bulk/v1/leads.json', + method: 'GET', + }, + httpRes: { + data: { + success: false, + errors: [ + { + code: 603, + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful bearer token request', + httpReq: { + url: 'https://testMunchkin2.mktorest.com/identity/oauth/token?client_id=b&client_secret=c&grant_type=client_credentials', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'access_token_success', + expires_in: 3599, + scope: 'integrations@rudderstack.com', + token_type: 'bearer', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful get request to get list of fields', + httpReq: { + url: 'https://testMunchkin2.mktorest.com/rest/v1/leads/describe2.json', + method: 'GET', + }, + httpRes: { + data: { + requestId: '7fa1#17fd1da66fe', + result: [ + { + name: 'API Lead', + searchableFields: [['email']], + fields: [ + { + name: 'Email', + displayName: 'Email Address', + dataType: 'email', + length: 255, + updateable: true, + crmManaged: false, + }, + ], + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed leads query request with pending import error', + httpReq: { + url: 'https://testMunchkin2.mktorest.com/bulk/v1/leads.json', + method: 'GET', + }, + httpRes: { + data: { + success: false, + errors: [ + { + message: 'There are 10 imports currently being processed. Please try again later', + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a succesful leads query request', + httpReq: { + url: 'https://testMunchkin3.mktorest.com/rest/v1/leads/describe2.json', + method: 'GET', + }, + httpRes: { + data: { + requestId: '7fa1#17fd1da66fe', + result: [ + { + name: 'API Lead', + searchableFields: [['email']], + fields: [ + { + name: 'Email', + displayName: 'Email Address', + dataType: 'email', + length: 255, + updateable: true, + crmManaged: false, + }, + ], + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed leads query request with empty file error', + httpReq: { + url: 'https://testMunchkin3.mktorest.com/bulk/v1/leads.json', + method: 'GET', + }, + httpRes: { + data: { + success: false, + errors: [ + { + message: 'Empty file', + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful leads query request', + httpReq: { + url: 'https://testMunchkin4.mktorest.com/rest/v1/leads/describe2.json', + method: 'GET', + }, + httpRes: { + data: { + requestId: '7fa1#17fd1da66fe', + result: [ + { + name: 'API Lead', + searchableFields: [['email']], + fields: [ + { + name: 'Email', + displayName: 'Email Address', + dataType: 'email', + length: 255, + updateable: true, + crmManaged: false, + }, + ], + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed leads query request with any other error', + httpReq: { + url: 'https://testMunchkin4.mktorest.com/bulk/v1/leads.json', + method: 'GET', + }, + httpRes: { + data: { + success: false, + errors: [ + { + message: 'Any other error', + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a succesful bearer token request', + httpReq: { + url: 'https://valid_account_broken_event.mktorest.com/identity/oauth/token', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'access_token_success', + expires_in: 3599, + scope: 'integrations@rudderstack.com', + token_type: 'bearer', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: + 'Mock response for a successful get request with filterType=email and filterValues specified with no results', + httpReq: { + url: 'https://valid_account_broken_event.mktorest.com/rest/v1/leads.json?filterType=email&filterValues=0c7b8b80-9c43-4f8e-b2d2-5e2448a25040@j.mail', + method: 'GET', + }, + httpRes: { + data: { + requestId: '12093#17672aeaee6', + result: [], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed get request due to missing lookup field', + httpReq: { + url: 'https://valid_account_broken_event.mktorest.com/rest/v1/leads.json', + method: 'GET', + }, + httpRes: { + data: { + requestId: '142e4#1835b117b76', + success: false, + errors: [ + { + code: '1006', + message: "Lookup field 'userId' not found", + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful bearer token request', + httpReq: { + url: 'https://unhandled_status_code.mktorest.com/identity/oauth/token', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'access_token_success', + expires_in: 3599, + scope: 'integrations@rudderstack.com', + token_type: 'bearer', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: + 'Mock response for a successful get request with filterType=email and filterValues specified with no results', + httpReq: { + url: 'https://unhandled_status_code.mktorest.com/rest/v1/leads.json?filterType=email&filterValues=0c7b8b80-9c43-4f8e-b2d2-5e2448a25040@j.mail', + method: 'GET', + }, + httpRes: { + data: { + requestId: '12093#17672aeaee6', + result: [], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed get request due to random marketo error code', + httpReq: { + url: 'https://unhandled_status_code.mktorest.com/rest/v1/leads.json', + method: 'GET', + }, + httpRes: { + data: { + requestId: '142e4#1835b117b76', + success: false, + errors: [ + { + code: 'random_marketo_code', + message: 'some other problem', + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a successful bearer token request', + httpReq: { + url: 'https://successful_identify_transformation.mktorest.com/identity/oauth/token', + method: 'GET', + }, + httpRes: { + data: { + access_token: 'access_token_success', + expires_in: 3599, + scope: 'integrations@rudderstack.com', + token_type: 'bearer', + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: + 'Mock response for a successful get request with no filterType and filterValues specified', + httpReq: { + url: 'https://successful_identify_transformation.mktorest.com/rest/v1/leads.json', + method: 'GET', + }, + httpRes: { + data: { + requestId: '7ab2#17672a46a99', + result: [ + { + id: 4, + status: 'created', + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: + 'Mock response for a successful get request with filterType=email and filterValues specified with results', + httpReq: { + url: 'https://successful_identify_transformation.mktorest.com/rest/v1/leads.json?filterType=email&filterValues=0c7b8b80-9c43-4f8e-b2d2-5e2448a25040@j.mail', + method: 'GET', + }, + httpRes: { + data: { + requestId: '12093#17672aeaee6', + result: [ + { + createdAt: '2022-09-17T21:39:07Z', + email: '0c7b8b80-9c43-4f8e-b2d2-5e2448a25040@j.mail', + firstName: 'random_first', + id: 4, + lastName: 'random_last', + updatedAt: '2022-09-20T21:48:07Z', + userId: 'test-user-957ue', + }, + ], + success: true, + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed lead request due to invalid header', + httpReq: { + url: 'https://mktId.mktorest.com/rest/v1/leads.json/test_invalid_header', + headers: { + Authorization: 'Bearer test_token_6', + 'Content-Type': 'invalid', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: { + success: false, + errors: [ + { + code: '612', + message: 'Invalid Content Type', + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response for a failed lead request due to length exceeded', + httpReq: { + url: 'https://mktId.mktorest.com/rest/v1/leads.json/test_exceeded_length', + headers: { + Authorization: 'Bearer test_token_6', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: { + success: false, + errors: [ + { + code: '1077', + message: 'Value for field exceeds max length', + }, + ], + }, + status: 400, + statusText: 'OK', + }, + }, +]; + +export const networkCallsData = [...businessMockData, ...tfProxyMocksData]; diff --git a/test/integrations/destinations/marketo_static_list/dataDelivery/business.ts b/test/integrations/destinations/marketo_static_list/dataDelivery/business.ts new file mode 100644 index 0000000000..08be877ba8 --- /dev/null +++ b/test/integrations/destinations/marketo_static_list/dataDelivery/business.ts @@ -0,0 +1,255 @@ +import { ProxyMetdata } from '../../../../../src/types'; +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload } from '../../../testUtils'; + +export const statTags = { + aborted: { + destType: 'MARKETO_STATIC_LIST', + destinationId: 'dummyDestinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'dummyWorkspaceId', + }, + retryable: { + destType: 'MARKETO_STATIC_LIST', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'retryable', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', + }, + throttled: { + destType: 'MARKETO_STATIC_LIST', + destinationId: 'dummyDestinationId', + errorCategory: 'network', + errorType: 'throttled', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'dummyWorkspaceId', + }, +}; + +export const proxyMetdata: ProxyMetdata = { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + secret: {}, + dontBatch: false, +}; + +export const reqMetadataArray = [proxyMetdata]; +const params = { + destination: 'marketo_static_list', +}; + +const commonRequestParameters = { + params, + userId: '', + body: { + FORM: {}, + JSON: {}, + JSON_ARRAY: {}, + XML: {}, + }, + files: {}, +}; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'msl_v1_scenario_1', + name: 'marketo_static_list', + description: '[Proxy v1 API] :: Test for a partial successful request with multiple ids', + successCriteria: 'Should return a 200 status code with respective status for each record id', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + headers: { + Authorization: 'Bearer Incorrect_token', + 'Content-Type': 'application/json', + }, + endpoint: + 'https://marketo_acct_id_success.mktorest.com/rest/v1/lists/1234/leads.json?id=110&id=111&id=112', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: + '{"requestId":"b6d1#18a8d2c10e7","result":[{"id":110,"status":"skipped","reasons":[{"code":"1015","message":"Lead not in list"}]},{"id":111,"status":"removed"},{"id":112,"status":"removed"}],"success":true}', + metadata: proxyMetdata, + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'msl_v1_scenario_2', + name: 'marketo_static_list', + description: '[Proxy v1 API] :: Test for Access token invalid scenario', + successCriteria: 'Should return a 500 status code with message Access token invalid', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + headers: { + Authorization: 'Bearer Incorrect_token', + 'Content-Type': 'application/json', + }, + endpoint: + 'https://marketo_acct_id_success.mktorest.com/rest/v1/lists/1234/leads.json?id=1&id=2&id=3', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 500, + statTags: statTags.retryable, + message: + 'Request Failed for Marketo Static List, Access token invalid (Retryable).during Marketo Static List Response Handling', + response: [ + { + error: + '{"requestId":"68d8#1846058ee27","success":false,"errors":[{"code":"601","message":"Access token invalid"}]}', + metadata: proxyMetdata, + statusCode: 500, + }, + ], + }, + }, + }, + }, + }, + { + id: 'msl_v1_scenario_3', + name: 'marketo_static_list', + description: '[Proxy v1 API] :: Test for a complete successful request with multiple ids', + successCriteria: + 'Should return a 200 status code with respective added status for each record id', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + headers: { + Authorization: 'Bearer token', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + endpoint: + 'https://marketo_acct_id_success.mktorest.com/rest/v1/lists/1234/leads.json?id=1&id=2', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: + '{"requestId":"12d3c#1846057dce2","result":[{"id":1,"status":"added"},{"id":2,"status":"added"}],"success":true}', + metadata: proxyMetdata, + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'msl_v1_scenario_4', + name: 'marketo_static_list', + description: '[Proxy v1 API] :: Test for DNS lookup failed scenario', + successCriteria: 'Should return a 400 status code with empty response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + headers: { + Authorization: 'Bearer test_token_6', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + endpoint: 'https://mktId.mktorest.com/rest/v1/leads.json/test6', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 500, + statTags: statTags.retryable, + message: 'Request failed with status: 500', + response: [ + { + error: '""', + metadata: proxyMetdata, + statusCode: 500, + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/marketo_static_list/dataDelivery/data.ts b/test/integrations/destinations/marketo_static_list/dataDelivery/data.ts index f0275e329e..c0398f6d2b 100644 --- a/test/integrations/destinations/marketo_static_list/dataDelivery/data.ts +++ b/test/integrations/destinations/marketo_static_list/dataDelivery/data.ts @@ -1,4 +1,7 @@ -export const data = [ +import { testScenariosForV1API } from './business'; +import { otherScenariosV1 } from './other'; + +const legacyTests = [ { name: 'marketo_static_list', description: 'Test 0', @@ -158,21 +161,7 @@ export const data = [ }, body: { FORM: {}, - JSON: { - action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - ], - lookupField: 'id', - }, + JSON: {}, JSON_ARRAY: {}, XML: {}, }, @@ -234,30 +223,7 @@ export const data = [ params: {}, body: { FORM: {}, - JSON: { - action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - { - City: 'Tokyo', - Country: 'JP', - Email: 'b@s.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328329, - userId: 'ben_userId_45', - }, - ], - lookupField: 'id', - }, + JSON: {}, JSON_ARRAY: {}, XML: {}, }, @@ -311,30 +277,7 @@ export const data = [ params: {}, body: { FORM: {}, - JSON: { - action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - { - City: 'Tokyo', - Country: 'JP', - Email: 'b@s.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328329, - userId: 'ben_userId_45', - }, - ], - lookupField: 'id', - }, + JSON: {}, JSON_ARRAY: {}, XML: {}, }, @@ -373,3 +316,5 @@ export const data = [ }, }, ]; + +export const data = [...legacyTests, ...testScenariosForV1API, ...otherScenariosV1]; diff --git a/test/integrations/destinations/marketo_static_list/dataDelivery/other.ts b/test/integrations/destinations/marketo_static_list/dataDelivery/other.ts new file mode 100644 index 0000000000..b1f3403fa6 --- /dev/null +++ b/test/integrations/destinations/marketo_static_list/dataDelivery/other.ts @@ -0,0 +1,194 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; +import { reqMetadataArray, statTags } from './business'; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'marketo_static_list_v1_other_scenario_1', + name: 'marketo_static_list', + 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: statTags.retryable, + message: 'Request failed with status: 503', + status: 503, + }, + }, + }, + }, + }, + { + id: 'marketo_static_list_v1_other_scenario_2', + name: 'marketo_static_list', + 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: statTags.retryable, + message: 'Request failed with status: 500', + status: 500, + }, + }, + }, + }, + }, + { + id: 'marketo_static_list_v1_other_scenario_3', + name: 'marketo_static_list', + description: '[Proxy v1 API] :: Scenario for testing Gateway Time Out 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_gateway_time_out', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Gateway Timeout"', + statusCode: 504, + metadata: generateMetadata(1), + }, + ], + statTags: statTags.retryable, + message: 'Request failed with status: 504', + status: 504, + }, + }, + }, + }, + }, + { + id: 'marketo_static_list_v1_other_scenario_4', + name: 'marketo_static_list', + 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: statTags.retryable, + message: 'Request failed with status: 500', + status: 500, + }, + }, + }, + }, + }, + { + id: 'marketo_static_list_v1_other_scenario_5', + name: 'marketo_static_list', + 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: statTags.retryable, + message: 'Request failed with status: 500', + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/marketo_static_list/network.ts b/test/integrations/destinations/marketo_static_list/network.ts index 5c13273859..f165291c15 100644 --- a/test/integrations/destinations/marketo_static_list/network.ts +++ b/test/integrations/destinations/marketo_static_list/network.ts @@ -46,21 +46,6 @@ const deliveryCallsData = [ { httpReq: { url: 'https://marketo_acct_id_success.mktorest.com/rest/v1/lists/1234/leads.json?id=1&id=2', - data: { - action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - ], - lookupField: 'id', - }, params: { destination: 'marketo_static_list' }, headers: { Authorization: 'Bearer token', @@ -84,30 +69,6 @@ const deliveryCallsData = [ { httpReq: { url: 'https://marketo_acct_id_success.mktorest.com/rest/v1/lists/1234/leads.json?id=3&id=4', - data: { - action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - { - City: 'Tokyo', - Country: 'JP', - Email: 'b@s.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328329, - userId: 'ben_userId_45', - }, - ], - lookupField: 'id', - }, params: {}, headers: { Authorization: 'Bearer token', @@ -131,30 +92,6 @@ const deliveryCallsData = [ { httpReq: { url: 'https://marketo_acct_id_success.mktorest.com/rest/v1/lists/1234/leads.json?id=5&id=6', - data: { - action: 'createOrUpdate', - input: [ - { - City: 'Tokyo', - Country: 'JP', - Email: 'gabi29@gmail.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328328, - userId: 'gabi_userId_45', - }, - { - City: 'Tokyo', - Country: 'JP', - Email: 'b@s.com', - PostalCode: '100-0001', - Title: 'Owner', - id: 1328329, - userId: 'ben_userId_45', - }, - ], - lookupField: 'id', - }, params: {}, headers: { Authorization: 'Bearer token', diff --git a/test/integrations/destinations/salesforce_oauth/dataDelivery/data.ts b/test/integrations/destinations/salesforce_oauth/dataDelivery/data.ts new file mode 100644 index 0000000000..bed8eec8db --- /dev/null +++ b/test/integrations/destinations/salesforce_oauth/dataDelivery/data.ts @@ -0,0 +1,3 @@ +import { testScenariosForV1API } from './oauth'; + +export const data = [...testScenariosForV1API]; diff --git a/test/integrations/destinations/salesforce_oauth/dataDelivery/oauth.ts b/test/integrations/destinations/salesforce_oauth/dataDelivery/oauth.ts new file mode 100644 index 0000000000..55eaa9cca1 --- /dev/null +++ b/test/integrations/destinations/salesforce_oauth/dataDelivery/oauth.ts @@ -0,0 +1,174 @@ +import { ProxyMetdata } from '../../../../../src/types'; +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload } from '../../../testUtils'; + +const commonHeadersForWrongToken = { + Authorization: 'Bearer expiredAccessToken', + 'Content-Type': 'application/json', +}; + +const commonHeadersForRightToken = { + Authorization: 'Bearer correctAccessToken', + 'Content-Type': 'application/json', +}; +const params = { destination: 'salesforce_oauth' }; + +const users = [ + { + Email: 'danis.archurav@sbermarket.ru', + Company: 'itus.ru', + LastName: 'Danis', + FirstName: 'Archurav', + LeadSource: 'App Signup', + account_type__c: 'free_trial', + }, +]; + +const statTags = { + retryable: { + destType: 'SALESFORCE_OAUTH', + destinationId: 'dummyDestinationId', + errorCategory: 'network', + errorType: 'retryable', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'dummyWorkspaceId', + }, +}; + +const commonRequestParametersWithWrongToken = { + headers: commonHeadersForWrongToken, + JSON: users[0], + params, +}; + +const commonRequestParametersWithRightToken = { + headers: commonHeadersForRightToken, + JSON: users[0], + params, +}; + +export const proxyMetdataWithSecretWithWrongAccessToken: ProxyMetdata = { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: { + access_token: 'expiredAccessToken', + instanceUrl: 'https://rudderstack.my.salesforce_oauth.com', + }, + destInfo: { authKey: 'dummyDestinationId' }, + dontBatch: false, +}; + +export const proxyMetdataWithSecretWithRightAccessToken: ProxyMetdata = { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: { + access_token: 'expiredRightToken', + instanceUrl: 'https://rudderstack.my.salesforce_oauth.com', + }, + destInfo: { authKey: 'dummyDestinationId' }, + dontBatch: false, +}; + +export const reqMetadataArrayWithWrongSecret = [proxyMetdataWithSecretWithWrongAccessToken]; +export const reqMetadataArray = [proxyMetdataWithSecretWithRightAccessToken]; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'salesforce_v1_scenario_1', + name: 'salesforce_oauth', + description: '[Proxy v1 API] :: Test with expired access token scenario', + successCriteria: + 'Should return 5XX with error category REFRESH_TOKEN and Session expired or invalid, INVALID_SESSION_ID', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParametersWithWrongToken, + endpoint: + 'https://rudderstack.my.salesforce_oauth.com/services/data/v50.0/sobjects/Lead/20', + }, + reqMetadataArrayWithWrongSecret, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + authErrorCategory: 'REFRESH_TOKEN', + message: + 'Salesforce Request Failed - due to "INVALID_SESSION_ID", (Retryable) during Salesforce Response Handling', + response: [ + { + error: + '[{"message":"Session expired or invalid","errorCode":"INVALID_SESSION_ID"}]', + metadata: proxyMetdataWithSecretWithWrongAccessToken, + statusCode: 500, + }, + ], + statTags: statTags.retryable, + }, + }, + }, + }, + }, + { + id: 'salesforce_v1_scenario_2', + name: 'salesforce', + description: + '[Proxy v1 API] :: Test for a valid request - Lead creation with existing unchanged leadId and unchanged data', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParametersWithRightToken, + endpoint: + 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/existing_unchanged_leadId', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request for destination: salesforce Processed Successfully', + response: [ + { + error: '{"statusText":"No Content"}', + metadata: proxyMetdataWithSecretWithRightAccessToken, + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/salesforce_oauth/network.ts b/test/integrations/destinations/salesforce_oauth/network.ts new file mode 100644 index 0000000000..ae5f9d3fe4 --- /dev/null +++ b/test/integrations/destinations/salesforce_oauth/network.ts @@ -0,0 +1,51 @@ +const headerWithWrongAccessToken = { + Authorization: 'Bearer expiredAccessToken', + 'Content-Type': 'application/json', +}; + +const headerWithRightAccessToken = { + Authorization: 'Bearer correctAccessToken', + 'Content-Type': 'application/json', +}; + +const dataValue = { + Email: 'danis.archurav@sbermarket.ru', + Company: 'itus.ru', + LastName: 'Danis', + FirstName: 'Archurav', + LeadSource: 'App Signup', + account_type__c: 'free_trial', +}; + +const businessMockData = [ + { + description: 'Mock response from destination depicting an expired access token', + httpReq: { + method: 'post', + url: 'https://rudderstack.my.salesforce_oauth.com/services/data/v50.0/sobjects/Lead/20', + headers: headerWithWrongAccessToken, + data: dataValue, + params: { destination: 'salesforce_oauth' }, + }, + httpRes: { + data: [{ message: 'Session expired or invalid', errorCode: 'INVALID_SESSION_ID' }], + status: 401, + }, + }, + { + description: + 'Mock response from destination depicting a valid lead request, with no changed data', + httpReq: { + method: 'post', + url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/existing_unchanged_leadId', + data: dataValue, + headers: headerWithRightAccessToken, + }, + httpRes: { + data: { statusText: 'No Content' }, + status: 204, + }, + }, +]; + +export const networkCallsData = [...businessMockData]; diff --git a/test/integrations/destinations/the_trade_desk/common.ts b/test/integrations/destinations/the_trade_desk/common.ts index 28f46df829..08ba0ee6ea 100644 --- a/test/integrations/destinations/the_trade_desk/common.ts +++ b/test/integrations/destinations/the_trade_desk/common.ts @@ -5,8 +5,7 @@ const destTypeInUpperCase = 'THE_TRADE_DESK'; const advertiserId = 'test-advertiser-id'; const dataProviderId = 'rudderstack'; const segmentName = 'test-segment'; - -const trackerId = 'test-trackerId'; +const firstPartyDataEndpoint = 'https://sin-data.adsrvr.org/data/advertiser'; const sampleDestination: Destination = { Config: { @@ -48,6 +47,22 @@ const sampleContext = { sources: sampleSource, }; +const proxyV1AbortableErrorStatTags = { + destType: destTypeInUpperCase, + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', +}; + +const { errorType: _, ...proxyV1PlatformErrorStatTags } = proxyV1AbortableErrorStatTags; +proxyV1PlatformErrorStatTags.errorCategory = 'platform'; + +const proxyV1RetryableErrorStatTags = { ...proxyV1AbortableErrorStatTags, errorType: 'retryable' }; + export { destType, destTypeInUpperCase, @@ -56,4 +71,8 @@ export { segmentName, sampleDestination, sampleContext, + proxyV1AbortableErrorStatTags, + proxyV1PlatformErrorStatTags, + proxyV1RetryableErrorStatTags, + firstPartyDataEndpoint, }; diff --git a/test/integrations/destinations/the_trade_desk/delivery/business.ts b/test/integrations/destinations/the_trade_desk/delivery/business.ts new file mode 100644 index 0000000000..0406a5f0bc --- /dev/null +++ b/test/integrations/destinations/the_trade_desk/delivery/business.ts @@ -0,0 +1,175 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; +import { + destType, + advertiserId, + dataProviderId, + segmentName, + sampleDestination, + proxyV1AbortableErrorStatTags, + firstPartyDataEndpoint, +} from '../common'; + +const validRequestPayload1 = { + AdvertiserId: advertiserId, + DataProviderId: dataProviderId, + Items: [ + { + DAID: 'test-daid-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + { + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + UID2: 'test-uid2-1', + }, + { + DAID: 'test-daid-2', + Data: [ + { + Name: segmentName, + TTLInMinutes: 0, + }, + ], + }, + { + Data: [ + { + Name: segmentName, + TTLInMinutes: 0, + }, + ], + UID2: 'test-uid2-2', + }, + ], +}; + +const invalidRequestPayload1 = { + AdvertiserId: advertiserId, + DataProviderId: dataProviderId, + Items: [ + { + DAID: 'test-daid', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + { + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + UID2: 'test-invalid-uid2', + }, + ], +}; + +const metadataArray = [generateMetadata(1)]; + +export const businessProxyV1: ProxyV1TestData[] = [ + { + id: 'ttd_v1_scenario_1', + name: destType, + description: + '[Proxy v1 API] :: Test for a valid request - Successful delivery of Add/Remove IDs to Trade Desk', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: {}, + params: {}, + JSON: validRequestPayload1, + endpoint: firstPartyDataEndpoint, + }, + metadataArray, + sampleDestination.Config, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + statusCode: 200, + metadata: generateMetadata(1), + error: '{}', + }, + ], + }, + }, + }, + }, + }, + { + id: 'ttd_v1_scenario_2', + name: destType, + description: + '[Proxy v1 API] :: Test for invalid ID - where the destination responds with 200 with invalid ID', + successCriteria: 'Should return 400 with error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: {}, + params: {}, + JSON: invalidRequestPayload1, + endpoint: firstPartyDataEndpoint, + }, + metadataArray, + sampleDestination.Config, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + 'Request failed with status: 200 due to {"FailedLines":[{"ErrorCode":"MissingUserId","Message":"Invalid UID2, item #2"}]}', + response: [ + { + error: + '{"FailedLines":[{"ErrorCode":"MissingUserId","Message":"Invalid UID2, item #2"}]}', + metadata: generateMetadata(1), + statusCode: 400, + }, + ], + statTags: proxyV1AbortableErrorStatTags, + status: 400, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/the_trade_desk/delivery/data.ts b/test/integrations/destinations/the_trade_desk/delivery/data.ts index 320eb6dcfe..5099eafce7 100644 --- a/test/integrations/destinations/the_trade_desk/delivery/data.ts +++ b/test/integrations/destinations/the_trade_desk/delivery/data.ts @@ -1,248 +1,3 @@ -import { - destType, - destTypeInUpperCase, - advertiserId, - dataProviderId, - segmentName, - sampleDestination, -} from '../common'; - -beforeAll(() => { - process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY = 'mockedDataProviderSecretKey'; -}); - -afterAll(() => { - delete process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY; -}); - -export const data = [ - { - name: destType, - description: 'Successful delivery of Add/Remove IDs to/from Trade Desk', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - destinationConfig: sampleDestination.Config, - body: { - JSON: { - AdvertiserId: advertiserId, - DataProviderId: dataProviderId, - Items: [ - { - DAID: 'test-daid-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - { - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - UID2: 'test-uid2-1', - }, - { - DAID: 'test-daid-2', - Data: [ - { - Name: segmentName, - TTLInMinutes: 0, - }, - ], - }, - { - Data: [ - { - Name: segmentName, - TTLInMinutes: 0, - }, - ], - UID2: 'test-uid2-2', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - userId: '', - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - destinationResponse: { - response: {}, - status: 200, - }, - message: 'Request Processed Successfully', - status: 200, - }, - }, - }, - }, - }, - { - name: destType, - description: 'Error response from The Trade Desk due to invalid IDs', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - destinationConfig: sampleDestination.Config, - body: { - JSON: { - AdvertiserId: advertiserId, - DataProviderId: dataProviderId, - Items: [ - { - DAID: 'test-daid', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - { - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - UID2: 'test-invalid-uid2', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - userId: '', - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - destinationResponse: { - response: { - FailedLines: [{ ErrorCode: 'MissingUserId', Message: 'Invalid UID2, item #2' }], - }, - status: 200, - }, - message: - 'Request failed with status: 200 due to {"FailedLines":[{"ErrorCode":"MissingUserId","Message":"Invalid UID2, item #2"}]}', - statTags: { - destType: destTypeInUpperCase, - destinationId: 'Non-determininable', - errorCategory: 'network', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'Non-determininable', - }, - status: 400, - }, - }, - }, - }, - }, - { - name: destType, - description: - 'Missing advertiser secret key in destination config from proxy request from server', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - body: { - JSON: { - AdvertiserId: advertiserId, - DataProviderId: dataProviderId, - Items: [ - { - DAID: 'test-daid-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - userId: '', - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - destinationResponse: '', - message: 'Advertiser secret key is missing in destination config. Aborting', - statTags: { - destType: destTypeInUpperCase, - destinationId: 'Non-determininable', - errorCategory: 'platform', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'Non-determininable', - }, - status: 400, - }, - }, - }, - }, - }, -]; +import { businessProxyV1 } from './business'; +import { otherProxyV1 } from './other'; +export const data = [...businessProxyV1, ...otherProxyV1]; diff --git a/test/integrations/destinations/the_trade_desk/delivery/other.ts b/test/integrations/destinations/the_trade_desk/delivery/other.ts new file mode 100644 index 0000000000..bed10e6ec5 --- /dev/null +++ b/test/integrations/destinations/the_trade_desk/delivery/other.ts @@ -0,0 +1,290 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; +import { + destType, + advertiserId, + dataProviderId, + segmentName, + proxyV1PlatformErrorStatTags, + proxyV1RetryableErrorStatTags, + firstPartyDataEndpoint, + sampleDestination, +} from '../common'; +import { envMock } from '../mocks'; + +envMock(); + +const validRequestPayload1 = { + AdvertiserId: advertiserId, + DataProviderId: dataProviderId, + Items: [ + { + DAID: 'test-daid-1', + Data: [ + { + Name: segmentName, + TTLInMinutes: 43200, + }, + ], + }, + ], +}; + +const metadataArray = [generateMetadata(1)]; + +// https://partner.thetradedesk.com/v3/portal/data/doc/post-data-advertiser-external#error-codes-messages +export const otherProxyV1: ProxyV1TestData[] = [ + { + id: 'ttd_v1_other_scenario_1', + name: destType, + description: + '[Proxy v1 API] :: Missing advertiser secret key in destination config from proxy request from server', + successCriteria: 'Should return 400 with platform error', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: {}, + params: {}, + JSON: validRequestPayload1, + endpoint: firstPartyDataEndpoint, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Advertiser secret key is missing in destination config. Aborting', + response: [ + { + error: 'Advertiser secret key is missing in destination config. Aborting', + metadata: generateMetadata(1), + statusCode: 400, + }, + ], + statTags: proxyV1PlatformErrorStatTags, + status: 400, + }, + }, + }, + }, + }, + { + id: 'ttd_v1_other_scenario_2', + name: destType, + 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', + }, + metadataArray, + sampleDestination.Config, + ), + 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: proxyV1RetryableErrorStatTags, + message: + 'Request failed with status: 503 due to {"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."}}', + status: 503, + }, + }, + }, + }, + }, + { + id: 'ttd_v1_other_scenario_3', + name: destType, + 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', + }, + metadataArray, + sampleDestination.Config, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Internal Server Error"', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: proxyV1RetryableErrorStatTags, + message: 'Request failed with status: 500 due to "Internal Server Error"', + status: 500, + }, + }, + }, + }, + }, + { + id: 'ttd_v1_other_scenario_4', + name: destType, + 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', + }, + metadataArray, + sampleDestination.Config, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Gateway Timeout"', + statusCode: 504, + metadata: generateMetadata(1), + }, + ], + statTags: proxyV1RetryableErrorStatTags, + message: 'Request failed with status: 504 due to "Gateway Timeout"', + status: 504, + }, + }, + }, + }, + }, + { + id: 'ttd_v1_other_scenario_5', + name: destType, + 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', + }, + metadataArray, + sampleDestination.Config, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: proxyV1RetryableErrorStatTags, + message: 'Request failed with status: 500 due to ""', + status: 500, + }, + }, + }, + }, + }, + { + id: 'ttd_v1_other_scenario_6', + name: destType, + 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', + }, + metadataArray, + sampleDestination.Config, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: proxyV1RetryableErrorStatTags, + message: 'Request failed with status: 500 due to ""', + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/the_trade_desk/mocks.ts b/test/integrations/destinations/the_trade_desk/mocks.ts index ddcbebae88..bf9d910d42 100644 --- a/test/integrations/destinations/the_trade_desk/mocks.ts +++ b/test/integrations/destinations/the_trade_desk/mocks.ts @@ -3,3 +3,12 @@ import config from '../../../../src/cdk/v2/destinations/the_trade_desk/config'; export const defaultMockFns = () => { jest.replaceProperty(config, 'MAX_REQUEST_SIZE_IN_BYTES', 250); }; + +export const envMock = () => { + process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY = 'mockedDataProviderSecretKey'; + + // Clean up after all tests are done + afterAll(() => { + delete process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY; + }); +};